fsi/reflection
Optimized Reflection for PHP 5.3 that caches ReflectionClass/Method/Property objects via factory() so they’re never created twice. Returned methods/properties are pre-set accessible for private/protected access; some uncached operations throw exceptions.
Installation:
Add the package to your composer.json under require:
"fsi/reflection": "0.9.*"
Run composer update.
Replace Constructors:
Replace all direct instantiations of reflection classes (e.g., new ReflectionClass()) with factory methods:
// Before (throws exception)
$reflection = new ReflectionClass('App\Models\User');
// After (cached)
$reflection = ReflectionClass::factory('App\Models\User');
First Use Case:
Use it in a Laravel service or command to inspect a model or class dynamically. For example, in a UserService:
use FSi\Reflection\ReflectionClass;
public function getPrivateProperty($userId)
{
$reflection = ReflectionClass::factory(User::class);
$property = $reflection->getProperty('privateField');
$property->setAccessible(true); // Automatically handled by the package
return $property->getValue(User::find($userId));
}
Verify Memory Savings:
Use memory_get_usage() before/after reflection operations in a loop to confirm reduced memory churn.
Factory-Based Reflection: Always use factory methods for reflection classes:
// Class reflection
$classReflection = ReflectionClass::factory('App\Models\Post');
// Method reflection
$methodReflection = $classReflection->getMethod('privateMethod');
// Direct factory call
$methodReflection = ReflectionMethod::factory('App\Models\Post', 'privateMethod');
Automatic Accessibility: Private/protected properties/methods are automatically accessible:
$property = ReflectionProperty::factory('App\Models\User', 'hiddenToken');
$value = $property->getValue($user); // No need for setAccessible(true)
Integration with Laravel:
$container->bindIf('app.models.user.reflection', function () {
return ReflectionClass::factory(User::class);
});
retrieved or saved hooks for dynamic property access:
Model::retrieved(function ($model) {
$reflection = ReflectionClass::factory(get_class($model));
// Inspect or modify properties dynamically
});
$method = ReflectionMethod::factory(User::class, 'validatePassword');
$method->setAccessible(true);
$result = $method->invoke($user, $password);
Dynamic Proxies:
Use in AOP or proxy-based systems (e.g., Laravel’s ProxyQueryBuilder):
$proxy = new DynamicProxy(
ReflectionClass::factory('App\Services\PaymentGateway')
);
Code Generation: Reduce overhead in tools that generate classes/methods at runtime (e.g., custom ORM builders):
$classReflection = ReflectionClass::factory('GeneratedClass');
// Analyze or modify generated code
Reflection-Heavy Loops: Cache reflection objects outside loops to avoid repeated instantiation:
$reflections = collect($classes)
->map(fn ($class) => ReflectionClass::factory($class))
->all();
foreach ($reflections as $reflection) {
// Process each reflection (cached)
}
Conditional Reflection: Use factories for conditional reflection (e.g., in plugins or dynamic modules):
if ($featureEnabled) {
$reflection = ReflectionClass::factory('DynamicModule');
// Enable feature-specific logic
}
Dependency Injection: Inject reflection factories into services for lazy loading:
public function __construct(private ReflectionFactory $reflectionFactory) {}
public function analyze()
{
$reflection = $this->reflectionFactory->createClass('App\Service');
// ...
}
Laravel Service Providers:
Register a global reflection helper in AppServiceProvider:
public function boot()
{
if (! function_exists('reflect')) {
function reflect(string $class, string $type = 'class'): mixed {
return match ($type) {
'class' => ReflectionClass::factory($class),
'method' => throw new \InvalidArgumentException('Use getMethod on ReflectionClass'),
default => throw new \InvalidArgumentException("Unsupported reflection type: {$type}"),
};
}
}
}
Testing: Mock reflection factories in unit tests:
$this->app->instance(
ReflectionClass::class,
Mockery::mock(ReflectionClass::class)
);
Performance Profiling: Use Laravel Debugbar or Blackfire to compare memory usage before/after adoption:
// Before
$reflection = new ReflectionClass('App\Models\User'); // High memory churn
// After
$reflection = ReflectionClass::factory('App\Models\User'); // Cached
Partial Adoption: Start with non-critical paths (e.g., CLI commands) before applying to core logic:
// In a command
$reflection = ReflectionClass::factory('App\Commands\GenerateReport');
Constructor Exceptions:
new ReflectionClass()) throws exceptions.ReflectionClass::factory() everywhere. For CI/CD, whitelist paths where direct constructors are unavoidable (e.g., third-party code).// phpstan.neon
rules:
FSi\Reflection\ReflectionClass::class:
no-new: true
Partial Method Coverage:
getMethod() on non-cached classes) may still create objects.// Cache per class
static $reflections = [];
$reflection = $reflections[$class] ??= ReflectionClass::factory($class);
PHP 5.3 Limitations:
ReflectionAttribute) may break modern Laravel code.Thread Safety:
Debugging Complexity:
set_error_handler(function ($errno, $errstr) {
if (str_contains($errstr, 'Reflection')) {
return false; // Let Laravel handle it
}
return false;
});
Third-Party Conflicts:
doctrine/orm or symfony/property-access may use reflection internally and fail with factory exceptions.Enable Reflection Logging: Add a debug wrapper to log factory calls:
ReflectionClass::factory = function ($class) {
\Log::debug("ReflectionClass factory called for: {$class}");
return \FSi\Reflection\ReflectionClass::factory($class);
};
Memory Leak Detection:
Use memory_get_usage() in loops to identify reflection-related memory spikes:
$before = memory_get_usage();
foreach ($classes as $class) {
$reflection = ReflectionClass::factory($class);
}
$after = memory_get_usage();
\Log::info("Memory used: " . ($after - $before));
Cache Validation: Verify cached objects are reused:
$ref1 = ReflectionClass::factory('App\Models\User');
$ref2 = ReflectionClass::factory('App\Models\User');
\Log::info("Same object: " . ($ref1 === $ref2)); // Should be true
Exception Handling: Catch factory exceptions gracefully:
try {
$reflection = ReflectionClass::factory('NonExistentClass');
}
How can I help you explore Laravel packages today?