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 require roave/better-reflection
use Roave\BetterReflection\BetterReflection;
$reflector = (new BetterReflection())->reflector();
$class = $reflector->reflectClass(\App\Models\User::class);
$methods = $class->getMethods();
$ast = $class->getMethod('someMethod')->getDocComment();
BetterReflection: Main facade for creating reflectors.reflector(): Returns a DefaultReflector (uses Composer autoloader by default).findReflectionsOnLine(): Helper for line-based reflection (e.g., IDE tooling).Use SingleFileSourceLocator for classes not autoloaded by Composer:
use Roave\BetterReflection\SourceLocator\SingleFileSourceLocator;
$locator = new SingleFileSourceLocator('/path/to/UnloadedClass.php');
$reflector = new \Roave\BetterReflection\Reflector\DefaultReflector($locator);
$class = $reflector->reflectClass('UnloadedClass');
Use StringSourceLocator for dynamic code analysis:
use Roave\BetterReflection\SourceLocator\StringSourceLocator;
$locator = new StringSourceLocator('class DynamicClass { public function foo() {} }');
$reflector = new \Roave\BetterReflection\Reflector\DefaultReflector($locator);
$class = $reflector->reflectClass('DynamicClass');
Find reflections on specific lines (e.g., for error reporting):
$finder = (new BetterReflection())->findReflectionsOnLine();
$reflection = $finder(__FILE__, __LINE__ - 1); // Reflect previous line
Get Abstract Syntax Tree for static analysis:
$method = $class->getMethod('someMethod');
$ast = $method->getNode(); // Returns PhpParser\Node\Stmt\ClassMethod
Extract parameter/return types (PHP 7+):
$method = $class->getMethod('typedMethod');
$returnType = $method->getReturnType(); // Returns ReflectionType
$params = $method->getParameters();
foreach ($params as $param) {
$type = $param->getType(); // ReflectionType or null
}
BetterReflection in AppServiceProvider:
public function register()
{
$this->app->singleton(BetterReflection::class, function () {
return new BetterReflection();
});
}
use Roave\BetterReflection\BetterReflection;
class AnalyzeCommand extends Command
{
protected $reflector;
public function __construct()
{
$this->reflector = (new BetterReflection())->reflector();
}
public function handle()
{
$class = $this->reflector->reflectClass(\App\Models\User::class);
// Analyze logic...
}
}
Avoid repeated parsing by caching reflectors:
$cache = new \Roave\BetterReflection\SourceLocator\CachedLocator(
new ComposerSourceLocator(),
new \Symfony\Component\Cache\Adapter\FilesystemAdapter()
);
$reflector = new \Roave\BetterReflection\Reflector\DefaultReflector($cache);
Runtime Performance:
BetterReflection is not optimized for runtime use (e.g., instantiating classes).\ReflectionClass for runtime operations. BetterReflection is for static analysis only.Closure Limitations:
ReflectionMethod::createFromClosure unsupported).StringSourceLocator to parse closure strings or rely on ReflectionFunction.Autoloader Dependencies:
AutoloadSourceLocator fails if Composer autoloader doesn’t resolve classes to files.SingleFileSourceLocator or StringSourceLocator for custom paths.AST vs. Reflection:
getNode() returns a PhpParser\Node (not a Reflection* object).getDocComment(), getReturnType(), etc., for reflection data; use getNode() for AST traversal.Unsupported Methods:
newInstance(), getExtension(), or getClosureThis() throw exceptions.PHP 8 Attributes:
ReflectionAttribute) has limited support (e.g., newInstance() unsupported).getArguments() and getName() for static analysis.Verify Source Locators:
SourceLocator correctly resolves files:
$locator = new ComposerSourceLocator();
$source = $locator->findFileForClass(\App\Models\User::class);
if (!$source) {
throw new \RuntimeException("Class not found in autoloader");
}
Handle Missing Classes Gracefully:
try {
$class = $reflector->reflectClass('NonExistentClass');
} catch (\Roave\BetterReflection\Exception\IdentifierNotFoundException $e) {
// Fallback logic
}
AST Debugging:
$node = $method->getNode();
var_dump($node->getStmts()); // Inspect statements
Line-Based Reflection:
findReflectionsOnLine() carefully—it may return null for non-declaration lines:
$reflection = $finder->find(__FILE__, 42);
if (!$reflection) {
$this->error("No reflection found on line 42");
}
Custom Source Locators:
SourceLocatorInterface for custom paths (e.g., vendor-specific files):
class CustomSourceLocator implements SourceLocatorInterface
{
public function findFileForClass($class): ?string
{
return '/custom/path/' . str_replace('\\', '/', $class) . '.php';
}
}
AST Transformations:
PhpParser to modify AST nodes before reflection:
use PhpParser\ParserFactory;
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$stmts = $parser->parse($sourceCode);
// Transform $stmts...
$reflector->reflectClassFromAst($stmts);
Caching Strategies:
CachedLocator to add custom cache keys or invalidation logic:
$cache = new CachedLocator(
new ComposerSourceLocator(),
new \Symfony\Component\Cache\Adapter\TagAwareAdapter()
);
$cache->invalidateTag('model'); // Invalidate all model-related reflections
Integration with PHPStan/Psalm:
BetterReflection as a backend for static analyzers by implementing their ReflectionProvider interfaces.Composer Autoloader:
autoload and autoload-dev are configured in composer.json for full coverage.PHP Version:
ReflectionType for PHP 7.4+) require PHP 7.4+. Test on target versions.Memory Usage:
CachedLocator to mitigate.Case Sensitivity:
reflectClass() are case-sensitive. Use strtolower() if needed for case-insensitive environments.phpDocumentor: Use getDocComment() toHow can I help you explore Laravel packages today?