PHP – получить все имена классов внутри определенного пространства имен

Я хочу получить все классы внутри пространства имен. У меня есть что-то вроде этого:

#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() но MyClass1 и MyClass2 не были в списке.

Как я мог это сделать?

Общий подход состоял бы в том, чтобы получить в своем проекте все полностью квалифицированные классы (класс с полным пространством имен), а затем отфильтровать требуемое пространство имен.

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]) { $index += 2; // Skip class keyword and whitespace $fqcns[] = $namespace.'\\'.$tokens[$index][1]; } } } 

Если вы следуете стандартам PSR 0 или PSR 4 (ваше дерево каталогов отражает ваше пространство имен), вам не нужно ничего фильтровать: просто укажите путь, который соответствует требуемому пространству имен.

Если вы не являетесь поклонником копирования / вставки приведенных выше фрагментов кода, вы можете просто установить эту библиотеку: https://github.com/gnugat/nomo-spaco . Если вы используете PHP> = 5.5, вы также можете использовать следующую библиотеку: https://github.com/hanneskod/classtools .

Я не был доволен ни одним из решений здесь, поэтому я закончил создание своего класса, чтобы справиться с этим. Это решение требует, чтобы вы :

  • Использование композитора
  • Использование 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)); } $undefinedNamespaceFragments[] = array_pop($namespaceFragments); } return false; } } 

Довольно интересно, что, похоже, нет никакого метода отражения, который сделает это для вас. Однако я придумал небольшой класс, способный читать информацию о пространстве имен.

Для этого вам нужно пройти через все определенные классы. Затем мы получаем пространство имен этого класса и храним его в массив вместе с самим именем класса.

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

Выход будет:

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

Конечно, все, кроме самого класса NameSpaceFinder, если оно собрано быстро и грязно. Поэтому не стесняйтесь очищать include беспорядок, используя автозагрузку.

Найти классы

Класс может быть локализован в файловой системе по имени и пространству имен, как это делает автозагрузчик. В нормальном случае пространство имен должно указывать относительный путь к файлам классов. Входящие пути являются отправными точками относительных путей. Функция get_include_path() возвращает список get_include_path() путей в одну строку. Каждый включенный путь может быть протестирован, существует ли относительный путь, который соответствует пространству имен. Если найден подходящий путь, вы узнаете расположение файлов классов.

Получить имена классов

Как только будет определено расположение файлов классов, классы могут быть извлечены из имен файлов, поскольку имя файла класса должно состоять из имени класса, за которым следует .php .

Образец кода

Вот пример кода, чтобы получить все имена классов пространства имен 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); 

Я приведу пример, который фактически используется в нашем приложении Laravel 5, но его можно использовать почти везде. В примере возвращаются имена классов с пространством имён, которые можно легко извлечь, если это не требуется.

легенда

  • {{1}} – Путь для удаления из пути текущего файла, чтобы попасть в папку приложения
  • {{2}} – путь к папке из папки приложения, где существуют целевые классы
  • {{3}} – путь пространства имен

Код

 $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 могут использовать app_path() . '/{{2}}/*.php' app_path() . '/{{2}}/*.php' в glob ().

Я думаю, что у многих людей может быть такая проблема, поэтому я решил использовать ответы от @hpierce и @ loïc-faugeron для решения этой проблемы.

С описанным ниже классом вы можете иметь все классы в пространстве имен или соблюдать определенный термин.

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

В этом случае вы должны использовать Composer для выполнения автозагрузки классов. Используя доступную на нем ClassMap, решение упрощается.

Самый простой способ – использовать собственную функцию автозагрузчика __autoload и внутри нее сохранить загруженные имена классов. Это вам подходит?

В противном случае, я думаю, вам придется иметь дело с некоторыми методами отражения.

class_parents , spl_classes() и class_uses могут использоваться для извлечения всех имен классов

Вы можете использовать get_declared_classes но с небольшой дополнительной работой.

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

Поэтому сначала вы получаете все объявленные классы, а затем проверяете, какие из них начинаются с вашего пространства имен.

Примечание : вы получите массив, где ключи не начинаются с 0. Чтобы добиться этого, вы можете попробовать: array_values($neededClasses); ,

для symfony вы можете использовать компонент Finder:

http://symfony.com/doc/current/components/finder.html