get real path to a PHP namespace (not class)

67 Views Asked by At

I'm working on a code generator, and would like to get the path to a namespace.

That is, given namespace 'App\Controller', I'd like to get "src/Controller".

I see how to get that information using ReflectionClass if I already had the class, but in this case I don't have it yet.

My solution at the application level is to simply read composer.json and look for the autload section. But that won't work with bundles (e.g. namespace Acme\ApiBundle -> vendor/acme/api-bundle'). I imagine there's an autoload map somewhere, but I can't find how to access it.

FWIW, I'm using Symfony, but I don't think that's going to matter here. More importantly, I'm using composer and psr-4 autloaded namespaces/classes.

Thanks.

2

There are 2 best solutions below

5
Sammitch On

The "path to a namespace" does not necessarily exist as the mapping between class name, namespace and all, can point literally anywhere. Only in the specific case of a PSR4-compliant autoloader does a namespace have any significance in that the there is a "root" directory that represents an arbitrary prefix of the namespace, and subdirectories/files representative of the rest of the class name. Any other type of autoloader [eg: classmap] will not necessarily have any relation between class name and file path.

Further to that, even in the case of a PSR4-compliant autoloader the specific function of a given autoloader may well prevent any dynamically-generated code from being picked up, eg: calling composer with -o / --optimize-autloader will cause Composer to scan its autoload directories for classes and generate a static classmap, so anything that you were to add there would not be picked up until you re-generated the classmap and restarted execution. While not the default, -o is a strongly-recommended and widely-used optimization.

A better solution would be to define your own custom autoloader and use spl_autoload_register() to append it to the autoloader queue after the composer autoloader. If you want your class to override something coming from composer you would want to use the $prepend argument.

Other related caveats:

  • Once a class is loaded it can never be un-loaded or re-loaded.
  • Modifying anything within the vendor/ directory is a generally not a good idea as that directory should be considered ephemeral and composer operations can and will erase any changes.
0
Tac Tacelosky On

In the end, the mapping file I was looking for is found in vendor/composer/autoload_psr4.php, and I was able to do the mapping by reading it and traversing the map.

    public static function namespaceToPath(string $namespace): ?string
    {
        $x = include "vendor/composer/autoload_psr4.php";
        $result = null;
        $parts = explode('\\', $namespace);
        $paths = [];
        do {
            $namespace = join('\\', $parts);
            $query = $namespace . '\\';
            if (array_key_exists($query, $x)) {
                array_unshift($paths, $x[$query][0]);
                return join('/', $paths);
            } else {
                array_unshift($paths, array_pop($parts));
            }
        } while (!$result && count($parts));
        return null;
    }