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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22761554/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 16:14:24  来源:igfitidea点击:

PHP - get all class names inside a particular namespace

phpnamespaces

提问by Pedram Behroozi

I want to get all classes inside a namespace. I have something like this:

我想将所有类都放在一个命名空间中。我有这样的事情:

#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.
    }
}

I tried get_declared_classes()inside getAllClasses()but MyClass1and MyClass2were not in the list.

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

How could I do that?

我怎么能那样做?

回答by Lo?c Faugeron

The generic approach would be to get all fully qualified classnames (class with full namespace) in your project, and then filter by the wanted namespace.

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

PHP offers some native functions to get those classes (get_declared_classes, etc), but they won't be able to find classes that have not been loaded (include / require), therefore it won't work as expected with autoloaders (like Composer for example). This is a major issue as the usage of autoloaders is very common.

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

So your last resort is to find all PHP files by yourself and parse them to extract their namespace and class:

所以你最后的手段是自己找到所有的 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;
        }
    }
}

If you follow PSR 0 or PSR 4 standards (your directory tree reflects your namespace), you don't have to filter anything: just give the path that corresponds to the namespace you want.

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

If you're not a fan of copying/pasting the above code snippets, you can simply install this library: https://github.com/gnugat/nomo-spaco. If you use PHP >= 5.5, you can also use the following library: https://github.com/hanneskod/classtools.

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

回答by HPierce

Update: Since this answer became somewhat popular, I've created a packagist package to simplify things. It contains basically what I've described here, without the need to add the class yourself or configure the $appRootmanually. It may eventually support more than just PSR-4.

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

That package can be found here: haydenpierce/class-finder.

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

$ composer require haydenpierce/class-finder

See more info in the README file.

在 README 文件中查看更多信息。



I wasn't happy with any of the solutions here so I ended up building my class to handle this. This solution requires that you are:

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

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


In a nutshell, this class attempts to figure out where the classes actually live on your filesystem based on the namespaces you've defined in composer.json. For instance, classes defined in the namespace Backup\Testare found in /home/hpierce/BackupApplicationRoot/src/Test. This can be trusted because mapping a directory structure to namespace is required by PSR-4:

简而言之,这个类试图根据您在composer.json. 例如,在命名空间Backup\Test中定义的类可以在/home/hpierce/BackupApplicationRoot/src/Test. 这是可以信任的,因为PSR-4 需要将目录结构映射到命名空间:

The contiguous sub-namespace names after the "namespace prefix" correspond to a subdirectory within a "base directory", in which the namespace separators represent directory separators. The subdirectory name MUST match the case of the sub-namespace names.

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

You may need to adjust appRootto point to the directory that contains composer.json.

您可能需要调整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;
    }
}

回答by thpl

Pretty interesting that there does not seem to be any reflection method that does that for you. However I came up with a little class that is capable of reading namespace information.

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

In order to do so, you have to traverse trough all defined classes. Then we get the namespace of that class and store it into an array along with the classname itself.

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

<?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'));

The output will be:

输出将是:

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

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

Of course everything besides the NameSpaceFinder class itself if assembled quick and dirty. So feel free to clean up the includemess by using autoloading.

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

回答by Umair Ahmed

I am going to give an example which is actually being used in our Laravel 5 app but can be used almost everywhere. The example returns class names with the namespace which can be easily taken out, if not required.

我将举一个例子,它实际上在我们的 Laravel 5 应用程序中使用,但几乎可以在任何地方使用。该示例返回带有命名空间的类名,如果不需要,可以轻松取出。

Legend

传奇

  • {{1}} - Path to remove from current file's path to get to app folder
  • {{2}} - The folder path from app folder where the target classes exist
  • {{3}} - Namespace path
  • {{1}} - 从当前文件的路径中删除以到达应用程序文件夹的路径
  • {{2}} - 目标类所在的 app 文件夹中的文件夹路径
  • {{3}} - 命名空间路径

Code

代码

$classPaths = glob(str_replace('{{1}}', '',__DIR__) .'{{2}}/*.php');
$classes = array();
$namespace = '{{3}}';
foreach ($classPaths as $classPath) {
    $segments = explode('/', $classPath);
    $segments = explode('\', $segments[count($segments) - 1]);
    $classes[] = $namespace . $segments[count($segments) - 1];
}

Laravel people can use app_path() . '/{{2}}/*.php'in glob().

Laravel 人们可以app_path() . '/{{2}}/*.php'在 glob() 中使用。

回答by Erick Major Dos Santos

I think a lot of people might have a problem like this, so I relied on the answers from @hpierce and @lo?c-faugeron to solve this problem.

我想很多人可能会有这样的问题,所以我依靠@hpierce 和@lo?c-faugeron 的答案来解决这个问题。

With the class described below, you can have all classes within a namespace or they respect a certain term.

使用下面描述的类,您可以在一个命名空间内拥有所有类,或者它们尊重某个术语。

<?php

namespace Backup\Util;

final class ClassFinder
{
    private static $composer = null;
    private static $classes  = [];

    public function __construct()
    {
        self::$composer = null;
        self::$classes  = [];

        self::$composer = require APP_PATH . '/vendor/autoload.php';

        if (false === empty(self::$composer)) {
            self::$classes  = array_keys(self::$composer->getClassMap());
        }
    }

    public function getClasses()
    {
        $allClasses = [];

        if (false === empty(self::$classes)) {
            foreach (self::$classes as $class) {
                $allClasses[] = '\' . $class;
            }
        }

        return $allClasses;
    }

    public function getClassesByNamespace($namespace)
    {
        if (0 !== strpos($namespace, '\')) {
            $namespace = '\' . $namespace;
        }

        $termUpper = strtoupper($namespace);
        return array_filter($this->getClasses(), function($class) use ($termUpper) {
            $className = strtoupper($class);
            if (
                0 === strpos($className, $termUpper) and
                false === strpos($className, strtoupper('Abstract')) and
                false === strpos($className, strtoupper('Interface'))
            ){
                return $class;
            }
            return false;
        });
    }

    public function getClassesWithTerm($term)
    {
        $termUpper = strtoupper($term);
        return array_filter($this->getClasses(), function($class) use ($termUpper) {
            $className = strtoupper($class);
            if (
                false !== strpos($className, $termUpper) and
                false === strpos($className, strtoupper('Abstract')) and
                false === strpos($className, strtoupper('Interface'))
            ){
                return $class;
            }
            return false;
        });
    }
}

In this case, you must use Composer to perform class autoloading. Using the ClassMap available on it, the solution is simplified.

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

回答by Cylosh

After trying the composer solutions above, was not satisfied with the time it took to obtain the recursive classes inside a namespace, up to 3 seconds but on some machines it took 6-7 seconds which was unacceptable. Below class renders the classes in ~0.05 in a normal 3-4 levels depth directory structure.

在尝试了上面的 composer 解决方案后,对在命名空间内获取递归类所花费的时间不满意,最多 3 秒,但在某些机器上需要 6-7 秒,这是不可接受的。下面的类以正常的 3-4 级深度目录结构呈现 ~0.05 中的类。

namespace Helpers;

use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

class ClassHelper
{
    public static function findRecursive(string $namespace): array
    {
        $namespacePath = self::translateNamespacePath($namespace);

        if ($namespacePath === '') {
            return [];
        }

        return self::searchClasses($namespace, $namespacePath);
    }

    protected static function translateNamespacePath(string $namespace): string
    {
        $rootPath = __DIR__ . DIRECTORY_SEPARATOR;

        $nsParts = explode('\', $namespace);
        array_shift($nsParts);

        if (empty($nsParts)) {
            return '';
        }

        return realpath($rootPath. implode(DIRECTORY_SEPARATOR, $nsParts)) ?: '';
    }

    private static function searchClasses(string $namespace, string $namespacePath): array
    {
        $classes = [];

        /**
         * @var \RecursiveDirectoryIterator $iterator
         * @var \SplFileInfo $item
         */
        foreach ($iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($namespacePath, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::SELF_FIRST
        ) as $item) {
            if ($item->isDir()) {
                $nextPath = $iterator->current()->getPathname();
                $nextNamespace = $namespace . '\' . $item->getFilename();
                $classes = array_merge($classes, self::searchClasses($nextNamespace, $nextPath));
                continue;
            }
            if ($item->isFile() && $item->getExtension() === 'php') {
                $class = $namespace . '\' . $item->getBasename('.php');
                if (!class_exists($class)) {
                    continue;
                }
                $classes[] = $class;
            }
        }

        return $classes;
    }
}

Usage:

用法:

    $classes = ClassHelper::findRecursive(__NAMESPACE__);
    print_r($classes);

Result:

结果:

Array
(
    [0] => Helpers\Dir\Getters\Bar
    [1] => Helpers\Dir\Getters\Foo\Bar
    [2] => Helpers\DirSame\Getters\Foo\Cru
    [3] => Helpers\DirSame\Modifiers\Foo\Biz
    [4] => Helpers\DirSame\Modifiers\Too\Taz
    [5] => Helpers\DirOther\Modifiers\Boo
)

回答by Henrik

Locate Classes

定位类

A class can be localized in the file system by its name and its namespace, like the autoloader does. In the normal case the namespace should tell the relative path to the class files. The include paths are the starting points of the relative paths. The function get_include_path()returns a list of include paths in one string. Each include path can be tested, whether there exists a relative path which matches the namespace. If the matching path is found, you will know the location of the class files.

一个类可以通过其名称和命名空间在文件系统中本地化,就像自动加载器一样。在正常情况下,命名空间应该告诉类文件的相对路径。包含路径是相对路径的起点。该函数get_include_path()返回一个字符串中包含路径的列表。可以测试每个包含路径,是否存在与命名空间匹配的相对路径。如果找到匹配的路径,您将知道类文件的位置。

Get Class Names

获取类名

As soon as the location of the class files is known, the classes can be extracted from the file names, because the name of a class file should consist of the class name followed by .php.

一旦知道类文件的位置,就可以从文件名中提取类,因为类文件的名称应该由类名后跟.php.

Sample Code

示例代码

Here is a sample code to get all class names of the namespace foo\baras a string array:

这是一个示例代码,用于将命名空间的所有类名foo\bar作为字符串数组获取:

$namespace = 'foo\bar';

// Relative namespace path
$namespaceRelativePath = str_replace('\', DIRECTORY_SEPARATOR, $namespace);

// Include paths
$includePathStr = get_include_path();
$includePathArr = explode(PATH_SEPARATOR, $includePathStr);

// Iterate include paths
$classArr = array();
foreach ($includePathArr as $includePath) {
    $path = $includePath . DIRECTORY_SEPARATOR . $namespaceRelativePath;
    if (is_dir($path)) { // Does path exist?
        $dir = dir($path); // Dir handle     
        while (false !== ($item = $dir->read())) {  // Read next item in dir
            $matches = array();
            if (preg_match('/^(?<class>[^.].+)\.php$/', $item, $matches)) {
                $classArr[] = $matches['class'];
            }
        }
        $dir->close();
    }
}

// Debug output
var_dump($includePathArr);
var_dump($classArr);

回答by FreeLightman

You can use get_declared_classesbut with a little additional work.

您可以使用,get_declared_classes但需要做一些额外的工作。

$needleNamespace = 'MyNamespace';
$classes = get_declared_classes();
$neededClasses = array_filter($classes, function($i) use ($needleNamespace) {
    return strpos($i, $needleNamespace) === 0;
});

So first you get all declared classes and then check which of them starts with your namespace.

所以首先你得到所有声明的类,然后检查它们中哪些以你的命名空间开头。

Note: you will get array where keys do not start with 0. To achive this, you can try: array_values($neededClasses);.

注意:您将获得数组,其中键不与0开始为了实现这一目标,你可以试试:array_values($neededClasses);

回答by Harsh Chunara

class_parents, spl_classes()and class_usescan be used to retrieve all the class names

class_parentsspl_classes()并且class_uses可以用来检索所有的类名

回答by Julio Marchi

Quite a few interesting answers above, some actually peculiarly complex for the proposed task.

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

To add a different flavor to the possibilities, here a quick and easy non-optimized function to do what you ask using the most basic techniques and common statements I could think of:

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

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;
}

Use simply as:

简单地使用:

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

var_dump($MyClasses);

I've written this function to assume you are notadding the last"trailing slash" (\) on the namespace, so you won't have to double it to escape it. ;)

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

Please notice this function is only an example and has many flaws. Based on the example above, if you use 'namespace\sub' and 'namespace\sub\deep' exists, the function will return all classes found in both namespaces (behaving as if it was recursive). However, it would be simple to adjust and expand this function for much more than that, mostly requiring a couple of tweaks in the foreachblock.

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

It may not be the pinnacle of the code-art-nouveau, but at least it does what was proposed and should be simple enough to be self-explanatory.

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

I hope it helps pave the way for you to achieve what you are looking for.

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

Note: PHP 5 and 7 friendly.

注意:PHP 5 和 7 友好。