87

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?

yivi
  • 42,438
  • 18
  • 116
  • 138
Pedram Behroozi
  • 2,447
  • 2
  • 27
  • 48

15 Answers15

54

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:

  • Using Composer
  • Using 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\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;
    }
}
Fabien Sa
  • 9,135
  • 4
  • 37
  • 44
HPierce
  • 7,249
  • 7
  • 33
  • 49
  • 2
    @PaulBasenko, Could you open an issue? I'd rather you report issues on Gitlab instead of here. – HPierce Aug 18 '18 at 12:03
  • Oh, so do you still support this package? – Paul Basenko Aug 18 '18 at 12:20
  • I created it like two weeks ago (see edit history on the answer) - as far as I know, you're the first actual user of it. I am still planning on adding more features to it, including fixing the documented limitation: "ClassFinder cannot find 3rd party classes in the vendor directory". I suspect you're running into that, but I'd like to see an error message and steps to reproduce it to be certain. [Gitlab](https://gitlab.com/hpierce1102/ClassFinder) would be a better place for that. – HPierce Aug 18 '18 at 14:47
  • 1
    Ok, thanks for reply. I didn't see that the package is freshly created, sorry))) If I'll reproduce the error in the future, I'll create an issue at the Gitlab package workflow. – Paul Basenko Aug 18 '18 at 15:26
  • While I can't speak to @PaulBasenko's issue, I can say that this was exactly what I was looking for and worked like a charm. Thank you. – Erutan409 Oct 19 '18 at 20:09
  • @Erutan409, Glad to hear it :-). I was able to reproduce Paul's issue and resolved it fairly quickly when it was reported. – HPierce Oct 20 '18 at 00:17
  • 2
    It may be a universal approach but it is very slow. I tried to use your [package](https://gitlab.com/hpierce1102/ClassFinder) in ZF3 with a big number of dependencies. The result - productivity suffers. – Serhii Popov Jun 12 '19 at 08:54
  • @SerhiiPopov That's accurate. I major part of the library is scanning the filesystem to find files that are named using PSR 4 standards. I'm not aware of another possible approach considering those classes aren't loaded initially. I _do_ think there an opportunity to cache the results of the file system scanning - but that's only going to speed up subsequent invokations of the library. If you have other ideas, please do open an issue on Gitlab - I'd love to hear them. Gitlab is a more appropriate place to discuss these types of changes to the library :) – HPierce Jun 12 '19 at 11:35
  • Thanks for your package! – Sauleil Aug 08 '19 at 18:46
  • Your package is great, however, it will "execute" each class, thus classes that cannot be executed (for instance classes that can only be executed in CLI mode) will cause the script to abort. – dearsina May 18 '20 at 20:09
  • 1
    @dearsina, I'm not sure I fully understand what you mean by "executing" a class. I'm 90% sure the library does call `class_exists()` before returning them - I think that will cause an error if it tries to call that on a class that references non existent classes (for example, if you have classes that aren't deployed in production). That may or may not be what you're talking about. I'm definitely open to improvements if you have any suggestions, but I'd prefer for that discussion to occur on Gitlab rather than comments here :) – HPierce May 18 '20 at 23:29
  • haydenpierce/class-finder is really slow :( - my whole application was now averaging 15 seconds response time. – Anos K. Mhazo Mar 16 '21 at 16:38
  • @AnosK.Mhazo - Yeppppp. It scans the file system to find classes based on the various autoloading standards. Some of the scanning might not be needed each time. If it scanned less files, it could potentially speed things up. Pull requests are welcome! – HPierce Mar 17 '21 at 17:08
47

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 .

Loïc Faugeron
  • 657
  • 6
  • 16
  • 1
    +1 for https://github.com/hanneskod/classtools - It's not immediately obvious how to use it to solve the problem in this exact question but with a little experimentation it does the job. It's quite a bit quicker than other packages/techniques listed here (probably because I can target a specific directory) but it's still relatively slow. I use it at build-time/deployment, so it's less of an issue. I would not run it in the browser request lifecycle. – Joel Mellon Oct 26 '21 at 22:06
  • What is the while loop for just after the namespace is captured? I've been testing and I could only see the need for the line `$namespace .= $tokens[$index++][1];` so was hoping someone could enlighten me. – Professor of programming Dec 02 '21 at 09:52
  • @Professorofprogramming good question. I can't remember the reasoning behind it, nor can find one as of now. I'll remove it – Loïc Faugeron Mar 24 '22 at 08:51
9

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.

Julio Marchi
  • 730
  • 6
  • 15
  • 7
    As this is based on `get_declared_class()` it will only find classes that are already loaded. So IMO this solution is incomplete as it misses the main point of the question. – Michael Härtl Sep 08 '21 at 11:23
  • 1
    My apologies but I am not sure about what you mean... Please, enlight me: what is the main point of the question? In regards to your comment, of course `get_declared_class()` will only list classes that have been loaded. It applies to any inbuilt PHP function such as `class_exists()`, `get_declared_interfaces()`, and `get_defined_functions()` (just to name a few). If a file is not loaded, nothing from it yet exists. But, any junior programmer understands such obviousness and only invokes a function that accounts for "specifics" when they know the "specifics" they are accounting for are loaded. – Julio Marchi Sep 09 '21 at 00:46
  • 1
    Exactly, but the main point is: How to get **all** classes in a namespace even if they are **not** loaded. He already tried `get_declared_classes()` and it misses 2 unloaded classes. So that's not enough. And that's why the other answers are more complex as they include the part where they scan the directories to load all relevant class files. – Michael Härtl Sep 09 '21 at 05:29
  • What you say is **NOT** in the question. Nowhere Pedram Behroozi asked for "_classes even if they are **not** loaded_". You are adding it yourself. If you have such a question, please ask, but don't contest answers just because you imagine something that doesn't actually exist in the question. The bottom line is: if you want something to exist in PHP, it must to either be _required_, _included_, or _PSR-loaded_. Just because there are `.php` files in the folders does not mean PHP will use them. If you want to find classes in not loaded files, you will need to parse and interpret them manually. – Julio Marchi Sep 10 '21 at 12:26
  • 1
    Please read again this sentence in the question: *I tried get_declared_classes() inside getAllClasses() but MyClass1 and MyClass2 were not in the list.". So the questioner wants `MyClass1` and `MyClass2` even if they are not loaded. He already tried `get_declared_classes()` and it does not what he wants. So this answer adds nothing which is not already in the question. – Michael Härtl Sep 10 '21 at 15:43
  • Sorry bro, it is *your* interpretation and I believe it is far off from being right. Bottom line is: If the files are not loaded, nothing on it will be accounted for. Classes, functions, variables, etc... Simple as that. Or, if he tries to account for the classes *before* loading the files, it will also be the case the classes won't be listed because they simply do not exist at that point in time. However, why don't _you_ bring an answer to the question that does the job _you_ are proposing. I promise I will applaud it and upvote the answer if you do it right. Otherwise, you are simply wrong. – Julio Marchi Sep 11 '21 at 17:59
  • 1
    @JulioMarchi If OP's classes weren't in the list returned by `get_declared_classes()` then by definition, the classes were not declared. Therefore, it doesn't answer the question. It may very well be @MichaelHärtl 's interpretation but then I'd have to say his interpreter is logically superior. – Joel Mellon Jan 05 '22 at 22:38
  • @JulioMarchi 99.999 chance is that you will never have custom classes loaded because pretty much everyone uses composer's autoloader (or similar), which is sensible enough not to load unrequested classes. I believe what MichaelHärtl meant (politely) that your answer - with the tone of "being better because everyone else is overengineering it" - is actually almost useless for this specific scenario. – Christian Jan 11 '23 at 21:49
6

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

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

thpl
  • 5,810
  • 3
  • 29
  • 43
  • 12
    Actually auto-loading will break your solution as `get_declared_classes()` will only contain already auto-loaded classes. That would require MyClass1 and MyClass2 to be used (say 'auto-loaded') before you can find them. – Hirnhamster Aug 26 '14 at 15:08
5

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.

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.

4

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.

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
)
Cylosh
  • 69
  • 3
  • 1
    I tried `haydenpierce/class-finder` and it worked great but it was super slow. This was approach was fast. – albertski Oct 24 '19 at 21:47
4

Note: This solution seems to work with Laravel directly. For outside Laravel, you might need to copy and modify the ComposerClassMap class from the given source. I didn't try.

If you are already using Composer for PSR-4 compliant autoloading, you can use this method to get all autoloaded classes and filter them (That's the example from my module system, directly copied and pasted from there):

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

Source of the ComposerClassMap class: https://github.com/facade/ignition/blob/master/src/Support/ComposerClassMap.php

Taha Paksu
  • 15,371
  • 2
  • 44
  • 78
  • This answer looks great, but I can't find either ComposerClassMap or Str classes. – Daniel P Jan 14 '20 at 13:50
  • `Str` class belongs to Laravel, that's just there for giving an example. And you can find the source of ComposerClassMap here: https://github.com/facade/ignition/blob/master/src/Support/ComposerClassMap.php – Taha Paksu Jan 14 '20 at 13:54
4

Using finder

composer require symfony/finder

usage

public function getAllNameSpaces($path)
{
    $filenames = $this->getFilenames($path);
    $namespaces = [];
    foreach ($filenames as $filename) {
        $namespaces[] = $this->getFullNamespace($filename) . '\\' . $this->getClassName($filename);
    }
    return $namespaces;
}

private function getClassName($filename)
{
    $directoriesAndFilename = explode('/', $filename);
    $filename = array_pop($directoriesAndFilename);
    $nameAndExtension = explode('.', $filename);
    $className = array_shift($nameAndExtension);
    return $className;
}

private function getFullNamespace($filename)
{
    $lines = file($filename);
    $array = preg_grep('/^namespace /', $lines);
    $namespaceLine = array_shift($array);
    $match = [];
    preg_match('/^namespace (.*);$/', $namespaceLine, $match);
    $fullNamespace = array_pop($match);

    return $fullNamespace;
}

private function getFilenames($path)
{
    $finderFiles = Finder::create()->files()->in($path)->name('*.php');
    $filenames = [];
    foreach ($finderFiles as $finderFile) {
        $filenames[] = $finderFile->getRealpath();
    }
    return $filenames;
}
Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
3

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.

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

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().

Umair Ahmed
  • 2,420
  • 1
  • 21
  • 40
2

Locate Classes

A class can be found 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 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.

Sample Code

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

$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);
Henrik
  • 2,771
  • 1
  • 23
  • 33
1

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

Racil Hilan
  • 24,690
  • 13
  • 50
  • 55
Harsh Chunara
  • 585
  • 3
  • 10
1

You can use get_declared_classes but with a little additional work.

$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);.

FreeLightman
  • 2,224
  • 2
  • 27
  • 42
  • Nice and short and gives you ultimate flexibility. However be aware that one should include \\ after the namespace to exclude any namespace that contains the namespace of the needle. Like in the namespace `App`, the class `AppendIterator` would be included aswell. – Ogier Schelvis Jul 03 '19 at 09:19
0

The easiest way should be to use your own autoloader __autoload function and inside of it save the loaded classes names. Does that suits You ?

Otherwise I think You will have to deal with some reflection methods.

Uriziel
  • 177
  • 1
  • 10
  • 2
    I think you have to know the class names in order to use `__autoload`. example: `$a = new MyClass1()`. I only know the namespace, not the class names. – Pedram Behroozi Mar 31 '14 at 13:05
0

I just did something similar, this is relatively simple but can be built off of.

  public function find(array $excludes, ?string $needle = null)
  {
    $path = "../".__DIR__;
    $files = scandir($path);
    $c = count($files);
    $models = [];
    for($i=0; $i<$c; $i++) {
      if ($files[$i] == "." || $files[$i] == ".." || in_array($dir[$i], $excludes)) {
        continue;
      }
      $model = str_replace(".php","",$dir[$i]);
      if (ucfirst($string) == $model) {
        return $model;
      }
      $models[] = $model;
    }
    return $models;
  }
matt
  • 62
  • 7
-1

for symfony you can use the Finder Component:

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

$result1 = $finder->in(__DIR__)->files()->contains('namespace foo;');
$result2 = $finder->in(__DIR__)->files()->contains('namespace bar;');
Sebastian Viereck
  • 5,455
  • 53
  • 53