Je veux obtenir toutes les classes dans un espace de noms. J'ai quelque chose comme ça:
#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.
}
}
J'ai essayé get_declared_classes()
dans getAllClasses()
mais MyClass1
et MyClass2
n'étaient pas dans la liste.
Comment pourrais-je faire ça?
L'approche générique consisterait à obtenir tous les noms de classe pleinement qualifiés (classe avec un espace de noms complet) dans votre projet, puis à les filtrer en fonction de l'espace de noms souhaité.
PHP offre quelques fonctions natives pour obtenir ces classes (get_declared_classes, etc.), mais ils ne pourront pas trouver de classes qui n’ont pas été chargées (include/require). Par conséquent, cela ne fonctionnera pas comme prévu avec des autoloaders (comme Composer pour exemple) . Il s’agit d’un problème majeur, car l’utilisation des autochargeurs est très courante.
Donc, votre dernier recours est de trouver tous les fichiers PHP par vous-même et de les analyser pour extraire leur espace de noms et leur classe:
$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;
}
}
}
Si vous suivez les normes PSR 0 ou PSR 4 (votre arborescence de répertoires reflète votre espace de noms), vous n'avez rien à filtrer: indiquez simplement le chemin qui correspond à l'espace de noms souhaité.
Si vous n'êtes pas amateur de copier/coller les extraits de code ci-dessus, vous pouvez simplement installer cette bibliothèque: https://github.com/gnugat/nomo-spaco . Si vous utilisez PHP> = 5.5, vous pouvez également utiliser la bibliothèque suivante: https://github.com/hanneskod/classtools .
Update: Depuis que cette réponse est devenue un peu populaire, j'ai créé un paquetage pour simplifier les choses. Il contient essentiellement ce que j'ai décrit ici, sans qu'il soit nécessaire d'ajouter la classe vous-même ou de configurer le $appRoot
manuellement. Il pourrait éventuellement prendre en charge plus que le PSR-4.
Ce paquet peut être trouvé ici: haydenpierce/class-Finder .
$ composer require haydenpierce/class-Finder
Voir plus d'informations dans le fichier README.
Je n’étais satisfait d’aucune des solutions proposées ici et j’ai donc construit ma classe pour gérer cela. Cette solution nécessite que vous soyez:
En un mot, cette classe tente de déterminer l’emplacement réel des classes sur votre système de fichiers en fonction des espaces de noms que vous avez définis dans composer.json
. Par exemple, les classes définies dans l'espace de noms Backup\Test
se trouvent dans /home/hpierce/BackupApplicationRoot/src/Test
. Ceci est fiable car la correspondance d'une structure de répertoire avec un espace de noms est requis par PSR-4 :
Noms d'espaces de noms contigus après le "préfixe d'espace de noms" correspondent à un sous-répertoire dans un "répertoire de base", dans lequel le Les séparateurs d'espaces de noms représentent des séparateurs de répertoires. Le sous-répertoire name DOIT correspondre à la casse des noms de sous-espaces de noms.
Vous devrez peut-être ajuster appRoot
pour qu'il pointe vers le répertoire qui contient 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;
}
}
Je pense que beaucoup de gens pourraient avoir un problème comme celui-ci, je me suis donc fié aux réponses de @hpierce et @ loïc-faugeron pour résoudre ce problème.
Avec la classe décrite ci-dessous, vous pouvez avoir toutes les classes d'un espace de noms ou elles respectent un certain terme.
<?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;
});
}
}
Dans ce cas, vous devez utiliser Composer pour effectuer le chargement automatique des classes. En utilisant le ClassMap disponible sur celui-ci, la solution est simplifiée.
Une classe peut être localisée dans le système de fichiers par son nom et son espace de noms, comme le fait l'autoloader. Dans le cas normal, l'espace de noms devrait indiquer le chemin relatif aux fichiers de classe. Les chemins d'inclusion sont les points de départ des chemins relatifs. La fonction get_include_path()
renvoie une liste de chemins d’inclusion dans une chaîne. Chaque chemin d’inclusion peut être testé, s’il existe un chemin relatif qui correspond à l’espace de nom. Si le chemin correspondant est trouvé, vous connaîtrez l'emplacement des fichiers de classe.
Dès que l'emplacement des fichiers de classe est connu, les classes peuvent être extraites des noms de fichiers, car le nom d'un fichier de classe doit comprendre le nom de la classe suivi de .php
.
Voici un exemple de code permettant d'obtenir tous les noms de classe de l'espace de nom foo\bar
sous forme de tableau de chaînes:
$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);
Je vais donner un exemple qui est en fait utilisé dans notre application Laravel 5 mais peut être utilisé presque partout. L'exemple retourne les noms de classe avec l'espace de noms qui peuvent être facilement retirés, si non requis.
$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];
}
Les gens de Laravel peuvent utiliser app_path() . '/{{2}}/*.php'
dans glob ().
class_parents
, spl_classes()
et class_uses
peuvent être utilisés pour récupérer tous les noms de classe
pour symfony, vous pouvez utiliser le composant Finder:
Le moyen le plus simple devrait être d’utiliser votre propre fonction __autoload
d’autoloader et d’enregistrer les noms des classes chargées. Cela vous convient-il?
Sinon, je pense que vous devrez faire face à certaines méthodes de réflexion.
Vous pouvez utiliser get_declared_classes
mais avec un peu de travail supplémentaire.
$needleNamespace = 'MyNamespace';
$classes = get_declared_classes();
$neededClasses = array_filter($classes, function($i) use ($needleNamespace) {
return strpos($i, $needleNamespace) === 0;
});
Donc, vous obtenez d’abord toutes les classes déclarées, puis cochez laquelle des deux commence par votre espace de noms.
Remarque : vous obtiendrez un tableau où les touches ne commencent pas par 0. Pour y parvenir, vous pouvez essayer: array_values($neededClasses);
.