PHP - 获取特定命名空间内的所有类名

Ped*_*ozi 50 php namespaces

我想在命名空间中获取所有类.我有这样的事情:

#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.
    }
}
Run Code Online (Sandbox Code Playgroud)

我想get_declared_classes()里面getAllClasses(),但MyClass1MyClass2没有在列表中.

我怎么能这样做?

Loï*_*ron 33

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

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

因此,您最后的办法是自己查找所有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]) {
            $index += 2; // Skip namespace keyword and whitespace
            while (isset($tokens[$index]) && is_array($tokens[$index])) {
                $namespace .= $tokens[$index++][1];
            }
        }
        if (T_CLASS === $tokens[$index][0] && T_WHITESPACE === $tokens[$index + 1][0] && T_STRING === $tokens[$index + 2][0]) {
            $index += 2; // Skip class keyword and whitespace
            $fqcns[] = $namespace.'\\'.$tokens[$index][1];

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

如果您遵循PSR 0或PSR 4标准(您的目录树反映了您的命名空间),您不必过滤任何内容:只需提供与您想要的命名空间相对应的路径.

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


HPi*_*rce 23

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

这个包可以在这里找到:haydenpierce/class-finder.

$ composer require haydenpierce/class-finder
Run Code Online (Sandbox Code Playgroud)

请参阅自述文件中的更多信息.


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

  • 使用Composer
  • 使用PSR-4

简而言之,这个类试图根据你定义的命名空间找出类实际存在于文件系统中的位置composer.json.例如,在命名空间Backup\Test中定义的类可以在中找到/home/hpierce/BackupApplicationRoot/src/Test.这可以信任,因为PSR-4需要将目录结构映射到命名空间:

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

您可能需要调整appRoot以指向包含的目录composer.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));

        //Apparently PHP doesn't like hyphens, so we use variable variables instead.
        $psr4 = "psr-4";
        return (array) $composerConfig->autoload->$psr4;
    }

    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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @PaulBasenko,你能提出一个问题吗?我宁愿你在 Gitlab 上报告问题,而不是在这里。 (2认同)
  • 这可能是一种通用方法,但速度非常慢。我尝试在 ZF3 中使用您的 [package](https://gitlab.com/hpierce1102/ClassFinder) 并具有大量依赖项。结果是——生产力受到影响。 (2认同)

Jul*_*chi 7

上面有很多有趣的答案,其中一些实际上对于提议的任务来说特别复杂。

为了给可能性添加不同的风味,这里有一个快速简单的非优化函数,可以使用我能想到的最基本的技术和常用语句来完成您的要求:

function classes_in_namespace($namespace) {
      $namespace .= '\\';
      $myClasses  = array_filter(get_declared_classes(), function($item) use ($namespace) { return substr($item, 0, strlen($namespace)) === $namespace; });
      $theClasses = [];
      foreach ($myClasses AS $class):
            $theParts = explode('\\', $class);
            $theClasses[] = end($theParts);
      endforeach;
      return $theClasses;
}
Run Code Online (Sandbox Code Playgroud)

简单地使用:

$MyClasses = classes_in_namespace('namespace\sub\deep');

var_dump($MyClasses);
Run Code Online (Sandbox Code Playgroud)

我编写此函数是为了假设您没有在命名空间中添加最后一个“尾部斜杠” ( \),因此您不必将其加倍以对其进行转义。;)

请注意这个函数只是一个例子,有很多缺陷。根据上面的示例,如果您使用 ' namespace\sub' 和 ' namespace\sub\deep' 存在,该函数将返回在两个命名空间中找到的所有类(表现得好像它是递归的)。但是,调整和扩展此功能远不止于此,主要需要在foreach块中进行一些调整。

它可能不是code-art-nouveau的顶峰,但至少它做了提议的事情,并且应该足够简单,不言自明。

我希望它有助于为您实现所需的目标铺平道路。

注意:PHP 5、7 和 8 友好。

  • 由于这是基于“get_declared_class()”,因此它只会查找已加载的类。因此,在我看来,这个解决方案是不完整的,因为它错过了问题的要点。 (8认同)

thp*_*hpl 6

非常有趣的是,似乎没有任何反射方法可以为您做到这一点.但是我提出了一个能够读取命名空间信息的小类.

为此,您必须遍历所有已定义的类.然后我们获取该类的名称空间并将其与类名本身一起存储到数组中.

<?php

// ClassOne namespaces -> ClassOne
include 'ClassOne/ClassOne.php';

// ClassOne namespaces -> ClassTwo
include 'ClassTwo/ClassTwo.php';
include 'ClassTwo/ClassTwoNew.php';

// So now we have two namespaces defined 
// by ourselves (ClassOne -> contains 1 class, ClassTwo -> contains 2 classes)

class NameSpaceFinder {

    private $namespaceMap = [];
    private $defaultNamespace = 'global';

    public function __construct()
    {
        $this->traverseClasses();
    }

    private function getNameSpaceFromClass($class)
    {
        // Get the namespace of the given class via reflection.
        // The global namespace (for example PHP's predefined ones)
        // will be returned as a string defined as a property ($defaultNamespace)
        // own namespaces will be returned as the namespace itself

        $reflection = new \ReflectionClass($class);
        return $reflection->getNameSpaceName() === '' 
                ? $this->defaultNamespace
                : $reflection->getNameSpaceName();
    }

    public function traverseClasses()
    {
        // Get all declared classes
        $classes = get_declared_classes();

        foreach($classes AS $class)
        {
            // Store the namespace of each class in the namespace map
            $namespace = $this->getNameSpaceFromClass($class);
            $this->namespaceMap[$namespace][] = $class;
        }
    }

    public function getNameSpaces()
    {
        return array_keys($this->namespaceMap);
    }

    public function getClassesOfNameSpace($namespace)
    {
        if(!isset($this->namespaceMap[$namespace]))
            throw new \InvalidArgumentException('The Namespace '. $namespace . ' does not exist');

        return $this->namespaceMap[$namespace];
    }

}

$finder = new NameSpaceFinder();
var_dump($finder->getClassesOfNameSpace('ClassTwo'));
Run Code Online (Sandbox Code Playgroud)

输出将是:

array(2) { [0]=> string(17) "ClassTwo\ClassTwo" [1]=> string(20) "ClassTwo\ClassTwoNew" }

当然除了NameSpaceFinder类本身之外的所有东西如果组装得快而又脏.因此,请随意include使用自动加载来清理混乱.

  • 实际上自动加载会破坏你的解决方案,因为`get_declared_classes()`只包含已经自动加载的类.这将需要使用MyClass1和MyClass2(比如说'自动加载')才能找到它们. (9认同)

Eri*_*tos 5

我想很多人可能都会遇到这样的问题,所以我依靠@hpierce和@lo\xc3\xafc-faugeron的答案来解决这个问题。

\n\n

使用下面描述的类,您可以将所有类包含在命名空间中,或者它们遵循某个术语。

\n\n
<?php\n\nnamespace Backup\\Util;\n\nfinal class ClassFinder\n{\n    private static $composer = null;\n    private static $classes  = [];\n\n    public function __construct()\n    {\n        self::$composer = null;\n        self::$classes  = [];\n\n        self::$composer = require APP_PATH . '/vendor/autoload.php';\n\n        if (false === empty(self::$composer)) {\n            self::$classes  = array_keys(self::$composer->getClassMap());\n        }\n    }\n\n    public function getClasses()\n    {\n        $allClasses = [];\n\n        if (false === empty(self::$classes)) {\n            foreach (self::$classes as $class) {\n                $allClasses[] = '\\\\' . $class;\n            }\n        }\n\n        return $allClasses;\n    }\n\n    public function getClassesByNamespace($namespace)\n    {\n        if (0 !== strpos($namespace, '\\\\')) {\n            $namespace = '\\\\' . $namespace;\n        }\n\n        $termUpper = strtoupper($namespace);\n        return array_filter($this->getClasses(), function($class) use ($termUpper) {\n            $className = strtoupper($class);\n            if (\n                0 === strpos($className, $termUpper) and\n                false === strpos($className, strtoupper('Abstract')) and\n                false === strpos($className, strtoupper('Interface'))\n            ){\n                return $class;\n            }\n            return false;\n        });\n    }\n\n    public function getClassesWithTerm($term)\n    {\n        $termUpper = strtoupper($term);\n        return array_filter($this->getClasses(), function($class) use ($termUpper) {\n            $className = strtoupper($class);\n            if (\n                false !== strpos($className, $termUpper) and\n                false === strpos($className, strtoupper('Abstract')) and\n                false === strpos($className, strtoupper('Interface'))\n            ){\n                return $class;\n            }\n            return false;\n        });\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这种情况下,您必须使用 Composer 来执行类自动加载。使用其上可用的 ClassMap,解决方案得到简化。

\n

  • 请注意,您必须使用“-o”选项生成优化的自动加载器,才能使其适用于所有类。`composer dump-autoload -o` (3认同)

Tah*_*ksu 5

注意:该解决方案似乎可以直接与 Laravel 一起使用。对于 Laravel 外部,您可能需要从给定源复制并修改 ComposerClassMap 类。我没有尝试。

如果您已经使用 Composer 进行 PSR-4 兼容的自动加载,则可以使用此方法来获取所有自动加载的类并过滤它们(这是我的模块系统中的示例,直接从那里复制并粘贴):

function get_available_widgets()
{
    $namespaces = array_keys((new ComposerClassMap)->listClasses());
    return array_filter($namespaces, function($item){
        return Str::startsWith($item, "App\\Modules\\Widgets\\") && Str::endsWith($item, "Controller");
    });
}
Run Code Online (Sandbox Code Playgroud)

课程来源ComposerClassMaphttps ://github.com/facade/ignition/blob/master/src/Support/ComposerClassMap.php


小智 0

最简单的方法应该是使用您自己的自动加载器__autoload函数,并在其中保存加载的类名称。这适合你吗?

否则我认为你将不得不处理一些反射方法。

  • 我认为你必须知道类名才能使用“__autoload”。例如:`$a = new MyClass1()`。我只知道名称空间,不知道类名。 (2认同)