Autoloading Symfony using Doctrine with Zend Framework

Photo by Dayne Topkin on Unsplash.

On some projects I use the Doctrine Object Relational Mapper, a fantastic library for modeling database objects. To combine it with Zend Framework 1, I use Guilherme Blanco's Bisna application resource. I recently upgraded a project to the latest version of Doctrine and suddenly couldn't autoload the Symphony classes. It took far too long for me to figure out the problem, so I hope I can spare someone else the same trouble.

Doctrine borrows some classes from the Symfony framework, namely for the Doctrine console and YAML parser. These classes retain the Symfony namespace, but reside in /Doctrine/Symfony. This adds a wrinkle to the autoloading process. If you use the Bisna application resource, you may have something like this in your application INI:

resources.doctrine.classLoader.loaderClass = "Doctrine\Common\ClassLoader"
resources.doctrine.classLoader.loaderFile = "Doctrine/Common/ClassLoader.php"

resources.doctrine.classLoader.loaders.symfony_console.namespace = "Symfony"
resources.doctrine.classLoader.loaders.symfony_console.includePath = "Doctrine"

However you choose to autoload, you need to specify that the root of the Symfony library is in the /Doctrine directory. Once the Bisna resource initializes during your application's bootstrap phase, any references to Symfony will autoload correctly.

The problem occurs when you use the Doctrine command line tool. This tool resides in Doctrine's /bin directory and includes the doctrine.php script. Depending on how you obtained Doctrine (PEAR, Composer, or a downloadable archive), a doctrine.php script may already be in the /bin directory. This doctrine.php script used to properly autoload Symfony, until a recent update. As you can see in the commit changelog, the call to \Doctrine\Common\ClassLoader was updated and no longer specifies /Doctrine as the base directory of the Symfony library. This code runs before your cli-config.php, and once Symfony's path is defined in the autoloading stack it's difficult to change. We need our own version of the doctrine.php script.

Luckily the Doctrine command line loads the script with a simple include('doctrine.php');. Because this path is relative, we can override the default with our own doctrine.php if we place it higher in the include_path. Our doctrine.php can mirror the default one, minus the autoloader settings.

<?php
// doctrine.php
$configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php';

$helperSet = null;
if (file_exists($configFile)) {
    if ( ! is_readable($configFile)) {
        trigger_error(
            'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR
        );
    }

    require $configFile;

    foreach ($GLOBALS as $helperSetCandidate) {
        if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
            $helperSet = $helperSetCandidate;
            break;
        }
    }
}

$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();

\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);

This version will still kick off the cli-config.php script, which will bootstrap the application and configure the autoloader.

So in the end, the problem was complex but the solution is simple. Copy the /bin/doctrine.php file from the Doctrine library into your project and remove the \Doctrine\Common\ClassLoader setup code.

Drew

Drew

Hi! I'm Drew, the Wimpy Programmer. I'm a software developer and formerly a Windows server administrator. I use this blog to share my mistakes and ideas.