如何获取特定命名空间内的所有类名?

2022-08-30 08:24:59

我想获取命名空间内的所有类。我有这样的东西:

#File: MyClass1.php
namespace MyNamespace;

class MyClass1() { ... }

#File: MyClass2.php
namespace MyNamespace;

class MyClass2() { ... }

#Any number of files and classes with MyNamespace may be specified.

#File: ClassHandler.php
namespace SomethingElse;
use MyNamespace as Classes;

class ClassHandler {
    public function getAllClasses() {
        // Here I want every classes declared inside MyNamespace.
    }
}

我在里面试过,但不在列表中。get_declared_classes()getAllClasses()MyClass1MyClass2

我该怎么做?


答案 1

更新:由于这个答案变得有些流行,我创建了一个packagist包来简化事情。它基本上包含我在这里描述的内容,而无需自己添加类或手动配置。它最终可能支持不仅仅是PSR-4。$appRoot

该软件包可以在这里找到:haydenpierce/class-finder

$ composer require haydenpierce/class-finder

有关详细信息,请参阅自述文件中的信息。


我对这里的任何解决方案都不满意,所以我最终构建了我的类来处理这个问题。此解决方案要求您:

  • 使用作曲家
  • 使用 PSR-4

简而言之,此类尝试根据您在 中定义的命名空间来确定类在文件系统上的实际位置。例如,命名空间中定义的类可在 中找到。这是可以信任的,因为 PSR-4 需要将目录结构映射到命名空间:composer.jsonBackup\Test/home/hpierce/BackupApplicationRoot/src/Test

“命名空间前缀”后面的连续子命名空间名称对应于“基目录”中的子目录,其中命名空间分隔符表示目录分隔符。子目录名称必须与子命名空间名称的大小写匹配。

您可能需要调整以指向包含 的目录。appRootcomposer.json

<?php    
namespace Backup\Util;

class ClassFinder
{
    //This value should be the directory that contains composer.json
    const appRoot = __DIR__ . "/../../";

    public static function getClassesInNamespace($namespace)
    {
        $files = scandir(self::getNamespaceDirectory($namespace));

        $classes = array_map(function($file) use ($namespace){
            return $namespace . '\\' . str_replace('.php', '', $file);
        }, $files);

        return array_filter($classes, function($possibleClass){
            return class_exists($possibleClass);
        });
    }

    private static function getDefinedNamespaces()
    {
        $composerJsonPath = self::appRoot . 'composer.json';
        $composerConfig = json_decode(file_get_contents($composerJsonPath));

        return (array) $composerConfig->autoload->{'psr-4'};
    }

    private static function getNamespaceDirectory($namespace)
    {
        $composerNamespaces = self::getDefinedNamespaces();

        $namespaceFragments = explode('\\', $namespace);
        $undefinedNamespaceFragments = [];

        while($namespaceFragments) {
            $possibleNamespace = implode('\\', $namespaceFragments) . '\\';

            if(array_key_exists($possibleNamespace, $composerNamespaces)){
                return realpath(self::appRoot . $composerNamespaces[$possibleNamespace] . implode('/', $undefinedNamespaceFragments));
            }

            array_unshift($undefinedNamespaceFragments, array_pop($namespaceFragments));            
        }

        return false;
    }
}

答案 2

通用方法是获取项目中的所有完全限定类名(具有完整命名空间的类),然后按所需的命名空间进行筛选。

PHP提供了一些本机函数来获取这些类(get_declared_classes等),但它们将无法找到尚未加载的类(包括/需要),因此它不会像预期的那样与自动加载器一起工作(例如Composer)。这是一个主要问题,因为自动加载机的使用非常普遍。

所以你的最后手段是自己找到所有的PHP文件,并解析它们以提取它们的命名空间和类:

$path = __DIR__;
$fqcns = array();

$allFiles = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
$phpFiles = new RegexIterator($allFiles, '/\.php$/');
foreach ($phpFiles as $phpFile) {
    $content = file_get_contents($phpFile->getRealPath());
    $tokens = token_get_all($content);
    $namespace = '';
    for ($index = 0; isset($tokens[$index]); $index++) {
        if (!isset($tokens[$index][0])) {
            continue;
        }
        if (
            T_NAMESPACE === $tokens[$index][0]
            && T_WHITESPACE === $tokens[$index + 1][0]
            && T_STRING === $tokens[$index + 2][0]
        ) {
            $namespace = $tokens[$index + 2][1];
            // Skip "namespace" keyword, whitespaces, and actual namespace
            $index += 2;
        }
        if (
            T_CLASS === $tokens[$index][0]
            && T_WHITESPACE === $tokens[$index + 1][0]
            && T_STRING === $tokens[$index + 2][0]
        ) {
            $fqcns[] = $namespace.'\\'.$tokens[$index + 2][1];
            // Skip "class" keyword, whitespaces, and actual classname
            $index += 2;

            # break if you have one class per file (psr-4 compliant)
            # otherwise you'll need to handle class constants (Foo::class)
            break;
        }
    }
}

如果您遵循 PSR 0 或 PSR 4 标准(您的目录树反映了您的命名空间),则无需筛选任何内容:只需提供与所需命名空间相对应的路径即可。

如果您不喜欢复制/粘贴上述代码片段,则只需安装此库:https://github.com/gnugat/nomo-spaco 。如果使用 PHP >= 5.5,则还可以使用以下库:https://github.com/hanneskod/classtools


推荐