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 MyClass1
and MyClass2
were not in the list.
How could I do that?
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.
So your last resort is to find all PHP files by yourself and parse them to extract their namespace and class:
$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;
}
}
}
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.
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 .
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 $appRoot
manually. It may eventually support more than just PSR-4.
That package can be found here: haydenpierce/class-finder.
$ composer require haydenpierce/class-finder
See more info in the README file.
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:
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\Test
are found in /home/hpierce/BackupApplicationRoot/src/Test
. This can be trusted because mapping a directory structure to namespace is required by 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 appRoot
to point to the directory that contains 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));
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;
}
}
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 not adding 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 foreach
block.
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.
I hope it helps pave the way for you to achieve what you are looking for.
Note: PHP 5, 7, AND 8 friendly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With