roave/better-reflection
Enhanced PHP reflection for static analysis: reflect classes without loading them, from PHP code strings or closures, extract AST from functions/methods, and read type declarations and docblocks. Feature-rich but slower than native reflection.
composer.json:
composer require roave/better-reflection
use Roave\BetterReflection\BetterReflection;
$reflector = (new BetterReflection())->reflector();
$class = $reflector->reflectClass(\App\Models\User::class);
docs/usage.md for basic patterns.docs/features.md for supported capabilities (e.g., AST extraction, closure reflection).docs/compatibility.md to avoid unsupported methods (e.g., newInstance).Pattern: Use BetterReflection for pre-runtime analysis (e.g., validation, code generation).
// Validate method signatures dynamically
$method = $reflector->reflectMethod(\App\Services\Payment::class, 'process');
$params = $method->getParameters();
foreach ($params as $param) {
if (!$param->hasType() && $param->isOptional()) {
throw new \RuntimeException("Missing type hint for optional param: {$param->getName()}");
}
}
Pattern: Extract Abstract Syntax Trees (AST) for custom logic (e.g., linting, refactoring).
$class = $reflector->reflectClass(\App\Jobs\QueueJob::class);
$ast = $class->getMethod('handle')->getDocComment(); // Or use `getMethodBodyNodes()` for AST
Pattern: Inspect closures (e.g., for middleware or event listeners).
$closure = fn($x) => $x * 2;
$reflection = (new BetterReflection())->reflector()->reflectClosure($closure);
$params = $reflection->getParameters(); // Analyze closure signature
Pattern: Find declarations at specific file lines (e.g., for IDE plugins or error pinpointing).
$finder = (new BetterReflection())->findReflectionsOnLine();
$reflection = $finder(app_path('Http/Controllers/UserController.php'), 42);
if ($reflection instanceof \Roave\BetterReflection\Reflection\ReflectionMethod) {
// Handle method reflection
}
Pattern: Override autoloading for non-standard paths (e.g., vendor-agnostic reflection).
use Roave\BetterReflection\SourceLocator\SingleFileSourceLocator;
$locator = new SingleFileSourceLocator('/custom/path/ToClass.php');
$reflector = new \Roave\BetterReflection\Reflector\ClassReflector($locator);
$class = $reflector->reflectClass('ToClass');
Pattern: Enforce type contracts (e.g., for DTOs or API responses).
$method = $reflector->reflectMethod(\App\Actions\CreateUser::class, 'execute');
$returnType = $method->getReturnType();
if ($returnType && $returnType->getName() !== \App\DTO\User::class) {
throw new \InvalidArgumentException("Method must return User DTO");
}
BetterReflection for runtime operations (e.g., in loops or hot paths). Cache reflections:
static $reflections = [];
if (!isset($reflections[$className])) {
$reflections[$className] = $reflector->reflectClass($className);
}
docs/compatibility.md before using methods like newInstance() or getClosureThis().$this binding). Use reflectClosure() only for static analysis.ComposerSourceLocator by default).SingleFileSourceLocator for custom paths).Reflector\ReflectorInterface for domain-specific logic (e.g., database-backed reflection).$locator = new \Roave\BetterReflection\SourceLocator\CompositeSourceLocator([
new ComposerSourceLocator(),
new SingleFileSourceLocator('/custom/path'),
]);
BetterReflection\Reflection\ReflectionMethod to add custom node visitors for AST manipulation.BetterReflection::create() with a SourceLocator to control loading order.SourceLocator filters.ReflectionClass::createFromName(): Simpler than BetterReflection for one-off cases:
$class = \Roave\BetterReflection\Reflection\ReflectionClass::createFromName(\App\Model::class);
phpstan/phpdoc-parser: Use BetterReflection for type hints and phpdoc-parser for docblock analysis.// In PHPUnit tests
$this->assertTrue($method->hasReturnType());
$this->assertEquals(\stdClass::class, $method->getReturnType()->getName());
How can I help you explore Laravel packages today?