northrook/php-class-finder

Find fully qualified classes from files

Maintainers

Package info

codeberg.org/northrook/php-class-finder/

Issues

pkg:composer/northrook/php-class-finder

Transparency log

Statistics

Installs: 20

Dependents: 2

Suggesters: 0

dev-main 2026-07-23 09:42 UTC

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, and readonly classes
  • Classes with attributes on preceding lines, including multiline #[...]

What gets skipped

  • Hidden files and directories (names starting with .)
  • Non-.php files
  • Interfaces, enums, and traits
  • Files with no class definition
  • A second class in the same file (only the first is read)
  • Classes that are not autoloadable
  • Code after an early return, exit, or die
  • Namespaces listed in excludeNamespaces() (defaults to composer)
  • 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.

MemberDescription
$lastScanMost 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.