phpdocumentor/reflection
Static PHP code reflection library that parses files without executing them. Builds an object graph of your project’s structure, including DocBlocks. Can analyze code from PHP 5.2 up to your installed version; suitable for reflecting whole files or projects.
Installation:
composer require phpdocumentor/reflection:~6.0
Ensure vendor/autoload.php is included in your project.
Basic Reflection:
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\File\LocalFile;
// Initialize factory with defaults (PHP 7 parser preferred)
$factory = ProjectFactory::createInstance();
// Reflect a single file
$project = $factory->create(
'MyProject',
[new LocalFile(__DIR__ . '/path/to/YourClass.php')]
);
// Access the project's root namespace
$rootNamespace = $project->getRootNamespace();
First Use Case: Inspect a class's methods and properties:
$class = $rootNamespace->getClass('YourClass');
foreach ($class->getMethods() as $method) {
echo $method->getName() . "\n";
echo $method->getDocBlock()->getSummary() . "\n";
}
// Reflect an entire directory (e.g., Laravel's `app/` folder)
$files = [];
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__ . '/app')) as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$files[] = new LocalFile($file->getPathname());
}
}
$project = $factory->create('LaravelApp', $files);
Extract types from method argument defaults:
$method = $class->getMethod('example');
$param = $method->getParameters()[0];
$defaultValue = $param->getDefaultValue();
$type = $defaultValue->getType(); // e.g., `String_`, `Int_`, `Array_`
$docBlock = $method->getDocBlock();
$summary = $docBlock->getSummary(); // First line after `@`
$tags = $docBlock->getTags(); // `@param`, `@return`, etc.
$attributes = $class->getAttributes();
foreach ($attributes as $attr) {
echo $attr->getName() . "\n";
foreach ($attr->getArguments() as $arg) {
echo " - " . $arg->getName() . ": " . $arg->getValue() . "\n";
}
}
Service Provider Discovery:
Use reflection to dynamically load providers without config/app.php:
$providers = [];
foreach ($project->getNamespaces() as $namespace) {
if ($namespace->getName() === 'App\\Providers') {
foreach ($namespace->getClasses() as $class) {
if (is_subclass_of($class->getFqsen(), \Illuminate\Support\ServiceProvider::class)) {
$providers[] = $class->getFqsen();
}
}
break;
}
}
Route Model Binding: Reflect route parameters to resolve models:
$controller = $project->getClass('App\\Http\\Controllers\\UserController');
$method = $controller->getMethod('show');
$param = $method->getParameters()[1]; // Assume second param is the model
$modelClass = $param->getType()->getFqsen(); // e.g., `App\Models\User`
Middleware Pipelines: Dynamically build middleware stacks from annotated routes:
$route = $project->getClass('Route'); // Hypothetical route class
$method = $route->getMethod('get');
$attributes = $method->getAttributes('middleware');
$middleware = collect($attributes)->pluck('arguments')->flatten()->toArray();
Cache Projects:
Serialize the Project object to avoid re-parsing:
$cachePath = storage_path('framework/reflection.cache');
if (!file_exists($cachePath)) {
$project = $factory->create('App', $files);
file_put_contents($cachePath, serialize($project));
} else {
$project = unserialize(file_get_contents($cachePath));
}
Lazy Loading: Use iterators to avoid loading entire projects into memory:
foreach ($project->getNamespaces() as $namespace) {
foreach ($namespace->getClasses() as $class) {
// Process class without loading all at once
}
}
Parser Version Mismatch:
composer.json and PHP version align. Use phpDocumentor/reflection:^5.3 for legacy support.Circular References:
^6.4.4+ (fixed in #697).Attribute Limitations:
#[Some\Attribute]) are not resolved; only their definition is parsed.getAttributes() to inspect metadata, then manually resolve types if needed.DocBlock Inconsistencies:
phpstan/extension-installer or phpdocumentor/phpdocumentor.PHP 8+ Features:
^6.0+.getVirtualProperties().Parse Errors:
$factory->getParser()->setAttribute(ParserConstants::ATTR_ERROR_HANDLER, function ($error) {
error_log("Parse Error: {$error->getMessage()} in {$error->getFile()} at line {$error->getLine()}");
});
Type Resolution Issues:
null or true may resolve to Null_ or True_ types (fixed in ^6.4.2).var_dump($defaultValue->getType()->getFqsen()) to inspect resolved types.Memory Usage:
Project::getFiles() to iterate.Custom Factories:
Extend phpDocumentor\Reflection\Php\Factory to handle domain-specific elements:
class CustomFactory extends \phpDocumentor\Reflection\Php\Factory {
protected function createCustomElement(Node $node) {
return new CustomElement($node);
}
}
Reducers: Modify parsed elements post-creation (e.g., for attributes):
$factory->addReducer(new class implements Reducer {
public function reduce(Node $node, Node $parent) {
if ($node instanceof Attribute) {
$node->setProcessed(true); // Custom logic
}
return $node;
}
});
Strategies: Override parsing behavior (e.g., ignore certain files):
$factory->addStrategy(new class implements Strategy {
public function shouldProcess(File $file) {
return !str_contains($file->getPath(), 'vendor');
}
});
Facade Resolution:
Facades (e.g., Route::get()) are not directly reflectable. Instead, reflect the underlying class:
$routeClass = $project->getClass('Illuminate\Routing\Router');
Dynamic Properties:
Laravel uses dynamic properties (e.g., $request->input). These are not statically analyzable but can be inferred via:
$method = $controller->getMethod('store');
$docBlock = $method->getDocBlock();
if ($docBlock->hasTag('property-read')) {
// Infer dynamic property usage
}
Service Container: Resolve bound classes via reflection:
$container = app();
$binding = $project->getClass('App\\Providers\\AppServiceProvider');
$method = $binding->get
How can I help you explore Laravel packages today?