Search code examples
phpsymfonydoctrine-ormpsr-0symfony-console

Workaround for doctrine generator in PSR-4 codebase


With Symfony 2 & Doctrine on a Windows machine I'm trying to

  1. generate entities from an existing schema:

    php app/console doctrine:mapping:import --force CoreBundle annotation

  2. generate getters/setters on them:

    php app/console doctrine:generate:entities --path=/path/to/codebase/src/MyProject/CoreBundle/Entities CoreBundle

  3. generate REST CRUD controllers on them using Voryx:

    php app/console voryx:generate:rest --entity="CoreBundle:User"

The first steps works fine and I can find the entities in my CoreBundle/Entity folder with the correct namespace:

MyVendor\MyProject\CoreBundle\Entity

Good so far. However, running the other 2 commands will fail:

[RuntimeException]
Can't find base path for "CoreBundle" (path: 
"\path\to\codebase\src\MyProject\CoreBundle", destination: 
"/path/to/codebase/src/MyProject/CoreBundle").  

The autoload in my composer.json looks like this:

"autoload": {
    "psr-4": {
        "MyVendor\\": "src/"
    }
},

I found out that Doctrine can't deal with PSR-4 namespaces, that's probably what makes it fail.

I would really like the entities to live in the PSR-4 CoreBundle though - is there a workaround for it?

I tried this, but it doesn't work, either:

"autoload": {
    "psr-0": {
        "MyVendor\\MyProject\\CoreBundle\\Entity": "src/MyProject/CoreBundle/Entity/"
    },
    "psr-4": {
        "MyVendor\\": "src/"
    }
},

Thank you.


Solution

  • User janvennemann on GitHub fixed Doctrine for PSR-4. You can find the patch on Gist, or linked here below

    Step to fix it

    1. mkdir -p app/VendorOverride;
    2. cp vendor/doctrine/doctrine-bundle/Mapping/DisconnectedMetadataFactory.php app/VendorOverride/DisconnectedMetadataFactory.php;
    3. apply the DisconnectedMetadataFactory patch;
    4. add app/VendorOverride to the classmap section in composer.json;
    5. run composer dump-autoload.

    Then almost all scaffolding command works.

    DisconnectedMetadataFactory PSR-4 patch

    /**
     * Get a base path for a class
     *
     * @param string $name      class name
     * @param string $namespace class namespace
     * @param string $path      class path
     *
     * @return string
     * @throws \RuntimeException When base path not found
     */
    private function getBasePathForClass($name, $namespace, $path)
    {
        $composerClassLoader = $this->getComposerClassLoader();
        if ($composerClassLoader !== NULL) {
            $psr4Paths = $this->findPathsByPsr4Prefix($namespace, $composerClassLoader);
            if ($psr4Paths !== array()) {
                // We just use the first path for now
                return $psr4Paths[0];
            }
        }
    
        $namespace = str_replace('\\', '/', $namespace);
        $search = str_replace('\\', '/', $path);
        $destination = str_replace('/'.$namespace, '', $search, $c);
    
        if ($c != 1) {
            throw new \RuntimeException(sprintf('Can\'t find base path for "%s" (path: "%s", destination: "%s").', $name, $path, $destination));
        }
    
        return $destination;
    }
    
    /**
     * Gets the composer class loader from the list of registered autoloaders
     *
     * @return \Composer\Autoload\ClassLoader
     */
    private function getComposerClassLoader() {
        $activeAutloaders = spl_autoload_functions();
        foreach($activeAutloaders as $autoloaderFunction) {
            if (!is_array($autoloaderFunction)) {
                continue;
            }
    
            $classLoader = $autoloaderFunction[0];
            if ($classLoader instanceof \Symfony\Component\Debug\DebugClassLoader) {
                $classLoader = $classLoader->getClassLoader()[0];
            }
    
            if (!is_object($classLoader)) {
                continue;
            }
    
            if ($classLoader instanceof \Composer\Autoload\ClassLoader) {
                return $classLoader;
            }
        }
    
        return NULL;
    }
    
    /**
     * Matches the namespace against all registered psr4 prefixes and
     * returns their mapped paths if found
     *
     * @param string $namespace The full namespace to search for
     * @param \Composer\Autoload\ClassLoader $composerClassLoader A composer class loader instance to get the list of psr4 preixes from
     * @return array The found paths for the namespace or an empty array if none matched
     */
    private function findPathsByPsr4Prefix($namespace, $composerClassLoader) {
        foreach ($composerClassLoader->getPrefixesPsr4() as $prefix => $paths) {
            if (strpos($namespace, $prefix) === 0) {
                return $paths;
            }
        }
    
        return array();
    }