roave/better-reflection
Better Reflection is an enhanced PHP reflection API for static analysis. Reflect on classes without loading them, from PHP code strings, and on closures; extract method/function AST and type declarations. Feature-rich but slower than native reflection (not for runtime).
Installation:
composer require roave/better-reflection
Add to composer.json under require-dev if only needed for static analysis.
First Use Case: Reflect a class without autoloading it (e.g., for unloaded classes or strings):
use Roave\BetterReflection\BetterReflection;
$reflector = (new BetterReflection())->reflector();
$classInfo = $reflector->reflectClass('App\\Models\\User'); // Works even if class isn't loaded
Key Entry Points:
BetterReflection::reflector(): Core reflector instance.reflectClass(), reflectMethod(), reflectFunction(): Reflection methods.reflectFile(): Reflect an entire file (e.g., for AST extraction).Reflection API.Extract return/parameter types from methods (including PHP 7+ declarations):
$method = $reflector->reflectMethod('App\\Services\\UserService::find');
$returnType = $method->getReturnType(); // Returns `Roave\BetterReflection\Reflection\Type\StaticType`
$parameterTypes = $method->getParameters(); // Array of `ReflectionParameter` objects
Analyze method bodies without executing code:
$method = $reflector->reflectMethod('App\\Jobs\\ProcessOrder::handle');
$ast = $method->getDocComment(); // PHPDoc blocks
$bodyAst = $method->getBodyNodes(); // Abstract Syntax Tree nodes
Inspect anonymous functions:
$closure = fn($x) => $x * 2;
$reflected = $reflector->reflectClosure($closure);
$parameters = $reflected->getParameters(); // [ReflectionParameter]
Register BetterReflection as a singleton in AppServiceProvider:
public function register()
{
$this->app->singleton(BetterReflection::class, fn() => new BetterReflection());
}
Useful for:
HasFactory).handle() method).Example: Validate a macroable class:
$class = $reflector->reflectClass('App\\Models\\User');
$macroMethods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($macroMethods as $method) {
if ($method->getName() === 'macro') {
// Validate macro signature...
}
}
Build CLI tools for code analysis:
use Roave\BetterReflection\BetterReflection;
class AnalyzeCommand extends Command
{
protected $signature = 'code:analyze {class}';
protected $description = 'Analyze a class for compliance';
public function handle(BetterReflection $reflection)
{
$class = $reflection->reflector()->reflectClass($this->argument('class'));
$this->info($class->getDocComment());
}
}
BetterReflection::reflector() once and reuse the instance.Reflection for performance-critical paths.Generate test doubles or validate method signatures:
public function testUserServiceFindMethod()
{
$method = (app(BetterReflection::class)->reflector())
->reflectMethod('App\\Services\\UserService::find');
$this->assertEquals('App\\Models\\User', $method->getReturnType()->getClassName());
}
Use with tools like Infection to ensure reflection logic handles edge cases (e.g., unloaded classes, traits).
Runtime Performance:
Reflection. Avoid using it for runtime introspection (e.g., in loops or hot paths).Unloaded Classes:
$reflector->reflectClassFromName('App\\Models\\User'); // Fails if not autoloaded
$reflector->reflectFile(__DIR__.'/../app/Models/User.php'); // Works
PHP 8+ Features:
IS_PROTECTED_SET_COMPATIBILITY).AST Limitations:
getBodyNodes() for simple cases; fall back to native reflection for critical paths.Closure Reflection:
reflectClosure(). Lambdas in strings/files require reflectFile() + manual parsing.Verify Class Loading:
Use BetterReflection::reflectFile() to debug unloaded classes:
try {
$class = $reflector->reflectClass('NonExistentClass');
} catch (\Roave\BetterReflection\Exception\IdentifierNotFoundException $e) {
$this->error('Class not found. Check file paths or autoloader.');
}
Inspect AST Nodes: Dump AST for complex methods:
$nodes = $method->getBodyNodes();
$this->dump($nodes); // Use a debugger or `var_dump()`
Compatibility Mode: Enable strict compatibility checks:
$reflector = (new BetterReflection())->setCompatibilityMode(true);
Custom Reflections:
Extend Roave\BetterReflection\Reflection\ReflectionClass to add domain-specific logic:
class CustomReflectionClass extends \Roave\BetterReflection\Reflection\ReflectionClass
{
public function hasCustomMethod()
{
return $this->hasMethod('customMethod');
}
}
Plugin System:
Use BetterReflection::withExtensions() to add custom providers:
$betterReflection = BetterReflection::create()
->withExtensions([new CustomExtension()]);
File Source Integration: Override file sources for custom paths (e.g., vendor classes):
$betterReflection = BetterReflection::createFromIde(
BetterReflection::createFromIde()->getSourceLocator()
);
IDE Support:
BetterReflection::createFromIde() for PHPStorm/PSR-4 compatibility:
$reflector = BetterReflection::createFromIde()->reflector();
Memory Limits:
setCache() to persist reflections:
$betterReflection = BetterReflection::create()
->setCache(new \Roave\BetterReflection\SourceLocator\Type\SetCache());
PHP 8.2+ Deprecations:
composer.json targets PHP 8.3+:
"require": {
"php": "^8.3"
}
$class = $reflector->reflectClass('App\\Models\\Order');
$properties = $class->getProperties();
// Generate a DataTransferObject class dynamically...
How can I help you explore Laravel packages today?