rikudou/reflection-file
A PHP library that adds file-level reflection, making it easy to inspect classes, interfaces, traits, functions, and constants declared in a source file. Useful for tooling, static analysis, and package discovery without manually requiring files.
rikudou/reflection-file is a micro-package that lets you inspect a PHP file and pull out class-level metadata (namespace, class name, use statements) without autoloading or require-ing the file. That is the whole point — it gives you ReflectionClass-style information about classes that don't exist in the runtime yet, or that you don't want to load just to discover.
v1.0.0 has landed, and it shipped with empty release notes. Treat that as the first task: the package was dormant for years at 0.x, so a 1.0.0 bump is simultaneously good news (semver contract going forward, likely a PHP-version refresh) and a risk (the 0.x → 1.0 jump is exactly where breaking changes hide). Before you write any call sites, spend five minutes on the tag diff on GitHub — src/ is a handful of classes, so the whole delta is readable in one sitting. Pay attention to changed method names, new return types, and any new constructor arguments.
Install, then verify:
composer require rikudou/reflection-file
php -r 'require "vendor/autoload.php"; var_dump((new Rikudou\ReflectionFile\ReflectionFile(__FILE__))->getClassName());'
That one-liner catches the two failure modes that matter: a signature change you didn't notice, and a PHP-version incompatibility. Then read vendor/rikudou/reflection-file/src/ end to end — you're not integrating a framework, you're calling one object.
A minimal first use case in a Laravel app — discovering every class in a directory without loading them:
use Rikudou\ReflectionFile\ReflectionFile;
use Symfony\Component\Finder\Finder;
$classes = [];
foreach (Finder::create()->files()->in(app_path('Services'))->name('*.php') as $file) {
$reflection = new ReflectionFile($file->getRealPath());
if ($reflection->getClassName() !== null) {
$classes[] = $reflection->getNamespaceName() . '\\' . $reflection->getClassName();
}
}
This is the kind of job Laravel's own Illuminate\Support\ClassLoader-adjacent helpers, Symfony's ClassMapGenerator, and spatie/laravel-package-tools typically handle — so evaluate whether you actually need this package versus rolling a 10-line token_get_all helper.
Auto-discovery in a service provider. The natural Laravel fit is dynamic binding / registration inside register(), where you want to scan a directory of implementations and bind them by convention:
public function register(): void
{
foreach (Finder::create()->files()->in(app_path('Contracts/Implementations')) as $file) {
$ref = new ReflectionFile($file->getPathname());
$fqcn = $ref->getNamespaceName() . '\\' . $ref->getClassName();
if (is_subclass_of($fqcn, MyContract::class)) {
$this->app->bind(MyContract::class, $fqcn);
}
}
}
Building a class map for a package. When you ship a package with a src/ tree and want a manifest of FQCNs for config merging, command registration, or a generated providers.php, iterate once at build time and cache the result. Combine with php artisan config:cache-style caching so you never call this in a hot request path.
Filtering by use statements before loading. Because you can read imports without autoloading, you can skip files that clearly can't be relevant (e.g. only load classes whose use list references a specific attribute or interface):
$ref = new ReflectionFile($path);
if (! in_array(SomeAttribute::class, $ref->getUseStatements(), true)) {
continue;
}
This is useful for attribute-based discovery on large codebases where autoloading every class to run ReflectionClass::getAttributes() is too expensive. Confirm getUseStatements() survived v1.0.0 unchanged — it's the most likely candidate for a rename or a return-shape change in a major bump.
Companion to a custom console command. A php artisan app:discover command that walks app/, extracts FQCNs, and writes a cache file is the cleanest encapsulation — keep the reflection work in the command, not in a provider that runs on every request.
composer.json for the new php constraint, and run the smoke test above. Also check whether the license changed — a major release is the usual moment for that to happen.composer.json pins ^0.x (likely, given the package's age), composer require won't jump you to 1.0.0 — you'll stay on the old tag and get none of the fixes. Check with composer show rikudou/reflection-file and bump the constraint deliberately, in a PR, so the diff is reviewable.^1.0, not *. A package that wakes up after six years of dormancy often gets a 1.0.1/1.0.2 flurry in the following weeks. A caret constraint gets you the bugfixes without letting a surprise 2.0 in.getUseStatements() returns the literal use targets — it won't expand use Foo as Bar; into Bar → Foo\Bar for you. Do that mapping yourself if you need FQCN resolution.getClassName() will be null for a file containing only functions, interfaces, or traits (depending on what the implementation parses). Always null-check before concatenating the FQCN, or you'll get Namespace\ strings that blow up is_subclass_of().if (! class_exists(...)) { class Foo {} } — tokenizer-based parsers see the class block regardless of runtime condition. Don't trust the output as a guarantee the class is loadable.Cache::rememberForever, or written to bootstrap/cache/) and invalidate on deploy.dd(token_get_all(file_get_contents($path))) against the same file — you're almost always looking at a parsing edge case (heredoc containing class, PHP 8 attributes above the class, enums, readonly classes) rather than a bug in your call site. If v1.0.0 was a PHP 8 modernization pass, these edge cases are precisely what to re-test.ClassDiscoverer service and swap the underlying implementation later — which is exactly why you should keep it behind an interface so you can drop it for symfony/finder + token_get_all without touching callers. That advice is more relevant now, not less: a major version bump is the cheapest possible moment to decide you'd rather not depend on someone else's parser.How can I help you explore Laravel packages today?