northrook / php-class-finder
Find fully qualified classes from files
Requires
- php: >=8.4
Requires (Dev)
- northrook/php-cs: @dev
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^11.5
This package is auto-updated.
Last update: 2026-07-23 08:58:08 UTC
README
Discover autoloadable PHP classes from directories.
- Requires PHP 8.4+
- No runtime dependencies
Installation
composer require northrook/php-class-finder
Quick start
Point the finder at your project root, scan one or more directories, then filter by attribute:
use Northrook\ClassFinder;
$finder = new ClassFinder(__DIR__);
$services = $finder
->scanDirectories('src/*')
->anyAttribute(App\Attribute\Service::class);
foreach ($services as $classInfo) {
$instance = $classInfo(); // instantiate via __invoke
}
Paths passed to scanDirectories() are relative to the root directory.
Append * for recursive scanning (src/*); omit it to scan only the immediate directory (src).
How it works
Walks .php files with a lightweight line parser, then keeps only classes the autoloader can resolve.
Results are keyed by the absolute file path.
Does not consult Composer’s PSR-4 map; non-autoloadable matches are dropped.
Intended for cold-path work: DI bootstrap, route discovery, and similar.
What gets included
- Namespaced and global classes, including
final,abstract, andreadonlyclasses - Classes with attributes on preceding lines, including multiline
#[...]
What gets skipped
- Hidden files and directories (names starting with
.) - Non-
.phpfiles - Interfaces, enums, and traits
- Files with no class definition
- A second
classin the same file (only the first is read) - Classes that are not autoloadable
- Code after an early
return,exit, ordie - Namespaces listed in
excludeNamespaces()(defaults tocomposer) - Subdirectories listed in
excludeSubdirectories()(default none)
API
ClassFinder
Configure the root path and exclusions, then scan.
$finder = new ClassFinder('/path/to/project');
$finder->excludeNamespaces('Vendor\\Generated', 'Tests');
$finder->excludeSubdirectories('tests', 'vendor', 'src/Generated');
$scan = $finder->scanDirectories('src/*', 'vendor/*', 'config');
Plain subdirectory names match any directory basename under a recursive scan.
Paths with / are resolved against the project root. Excludes do not block an
explicit scanDirectories() target.
| Member | Description |
|---|---|
$lastScan | Most recent ClassScan, or null |
excludeNamespaces(...) | Replace excluded namespaces; no arguments resets to ['composer'] |
excludeSubdirectories(...) | Replace excluded directories; no arguments resets to [] |
scanDirectories(...) | Scan directories relative to the root; returns ClassScan |
Root path validation happens when scanning, not in the constructor.
ClassScan
Immutable scan result. Iterable and countable; values are ClassInfo instances.
$scan = $finder->scanDirectories('src/*');
$scan->count();
$scan->getArray(); // [filePath => ClassInfo]
$scan->anyAttribute(Route::class, Autowire::class); // classes with at least one match
$scan->withAttributes(Autowire::class, LoggerInterface::class); // classes with every attribute
anyAttribute() and withAttributes() match via IS_INSTANCEOF (attribute
subclasses count as the parent type). anyAttribute() with no arguments returns
every class that has at least one attribute.
ClassInfo
Metadata and reflection helpers for a single class.
use Northrook\ClassFinder\ClassInfo;
$info = ClassInfo::from(SomeService::class);
$info->fqcn; // fully qualified class name
$info->basename; // short name
$info->namespace; // namespace without short name
$info->file; // absolute path to the defining file
$info->reflection; // lazy ReflectionClass
$info->hasAttribute(Service::class); // IS_INSTANCEOF
$info->getAttributes(Service::class); // same matching, returns ReflectionAttribute[]
$info(...$args); // instantiate the class
ClassInfo::basename($class, 'strtolower');
ClassInfo::exists('Some\\Class');
Development
composer test
composer phpstan
License
BSD-3-Clause. See LICENSE.