Table Of Contents
- Introduction
- Understanding PHP Reflection Fundamentals
- Advanced Class Inspection Techniques
- Dynamic Property and Method Access
- Code Generation and Dynamic Class Creation
- Advanced Reflection Patterns
- Performance Considerations and Optimization
- Frequently Asked Questions (FAQ)
- Conclusion
Introduction
PHP's Reflection API is one of the language's most powerful yet underutilized features, enabling developers to inspect and manipulate code at runtime. Whether you're building frameworks, creating automated testing tools, or developing dynamic applications that adapt based on their own structure, the Reflection API provides the foundation for sophisticated metaprogramming techniques.
This comprehensive guide will take you from basic reflection concepts to advanced dynamic code generation patterns, showing you how to leverage PHP's introspection capabilities to build more flexible, maintainable, and intelligent applications.
Understanding PHP Reflection Fundamentals
What is Reflection?
Reflection in PHP allows programs to examine their own structure and behavior at runtime. It provides a way to inspect classes, methods, properties, functions, and even extensions without executing them. Think of it as a mirror that lets your code look at itself and understand its own composition.
Core Reflection Classes
PHP's Reflection API consists of several core classes:
<?php
// Core reflection classes
$reflectionClasses = [
'ReflectionClass', // Inspect classes
'ReflectionMethod', // Inspect methods
'ReflectionProperty', // Inspect properties
'ReflectionFunction', // Inspect functions
'ReflectionParameter', // Inspect parameters
'ReflectionExtension', // Inspect extensions
'ReflectionObject', // Inspect objects
'ReflectionType', // Inspect type hints
'ReflectionAttribute' // Inspect attributes (PHP 8+)
];
foreach ($reflectionClasses as $class) {
if (class_exists($class)) {
echo "✓ $class available\n";
}
}
?>
Basic Reflection Operations
<?php
class ExampleClass {
private string $privateProperty = 'secret';
protected int $protectedProperty = 42;
public array $publicProperty = ['a', 'b', 'c'];
public function __construct(string $name = 'default') {
$this->name = $name;
}
public function publicMethod(string $param): string {
return "Hello, $param!";
}
private function privateMethod(): void {
echo "This is private\n";
}
protected static function staticMethod(): string {
return "Static method called";
}
}
// Basic class reflection
$reflection = new ReflectionClass(ExampleClass::class);
echo "Class name: " . $reflection->getName() . "\n";
echo "Short name: " . $reflection->getShortName() . "\n";
echo "Namespace: " . $reflection->getNamespaceName() . "\n";
echo "Is instantiable: " . ($reflection->isInstantiable() ? 'Yes' : 'No') . "\n";
echo "Is abstract: " . ($reflection->isAbstract() ? 'Yes' : 'No') . "\n";
echo "Is final: " . ($reflection->isFinal() ? 'Yes' : 'No') . "\n";
?>
Advanced Class Inspection Techniques
Complete Class Analysis Tool
<?php
class ClassAnalyzer {
private ReflectionClass $reflection;
private array $analysis = [];
public function __construct(string $className) {
if (!class_exists($className)) {
throw new InvalidArgumentException("Class $className does not exist");
}
$this->reflection = new ReflectionClass($className);
$this->performAnalysis();
}
private function performAnalysis(): void {
$this->analysis = [
'basic_info' => $this->getBasicInfo(),
'inheritance' => $this->getInheritanceInfo(),
'properties' => $this->getPropertiesInfo(),
'methods' => $this->getMethodsInfo(),
'constants' => $this->getConstantsInfo(),
'traits' => $this->getTraitsInfo(),
'interfaces' => $this->getInterfacesInfo(),
'attributes' => $this->getAttributesInfo(),
'metrics' => $this->calculateMetrics()
];
}
private function getBasicInfo(): array {
return [
'name' => $this->reflection->getName(),
'short_name' => $this->reflection->getShortName(),
'namespace' => $this->reflection->getNamespaceName(),
'filename' => $this->reflection->getFileName(),
'start_line' => $this->reflection->getStartLine(),
'end_line' => $this->reflection->getEndLine(),
'doc_comment' => $this->reflection->getDocComment(),
'modifiers' => [
'abstract' => $this->reflection->isAbstract(),
'final' => $this->reflection->isFinal(),
'instantiable' => $this->reflection->isInstantiable(),
'cloneable' => $this->reflection->isCloneable(),
'iterable' => $this->reflection->isIterable()
]
];
}
private function getInheritanceInfo(): array {
$info = [
'parent_class' => null,
'parent_classes' => [],
'implements' => [],
'uses_traits' => []
];
// Parent class
$parent = $this->reflection->getParentClass();
if ($parent) {
$info['parent_class'] = $parent->getName();
// Get all parent classes
while ($parent) {
$info['parent_classes'][] = $parent->getName();
$parent = $parent->getParentClass();
}
}
// Interfaces
foreach ($this->reflection->getInterfaces() as $interface) {
$info['implements'][] = $interface->getName();
}
// Traits
foreach ($this->reflection->getTraits() as $trait) {
$info['uses_traits'][] = $trait->getName();
}
return $info;
}
private function getPropertiesInfo(): array {
$properties = [];
foreach ($this->reflection->getProperties() as $property) {
$propertyInfo = [
'name' => $property->getName(),
'visibility' => $this->getVisibility($property),
'static' => $property->isStatic(),
'default_value' => null,
'type' => null,
'doc_comment' => $property->getDocComment(),
'attributes' => $this->getPropertyAttributes($property)
];
// Get type information
if ($property->hasType()) {
$type = $property->getType();
$propertyInfo['type'] = $this->getTypeInfo($type);
}
// Get default value
if ($property->hasDefaultValue()) {
$propertyInfo['default_value'] = $property->getDefaultValue();
}
$properties[] = $propertyInfo;
}
return $properties;
}
private function getMethodsInfo(): array {
$methods = [];
foreach ($this->reflection->getMethods() as $method) {
$methodInfo = [
'name' => $method->getName(),
'visibility' => $this->getVisibility($method),
'static' => $method->isStatic(),
'abstract' => $method->isAbstract(),
'final' => $method->isFinal(),
'constructor' => $method->isConstructor(),
'destructor' => $method->isDestructor(),
'parameters' => $this->getParametersInfo($method),
'return_type' => null,
'doc_comment' => $method->getDocComment(),
'attributes' => $this->getMethodAttributes($method),
'declaring_class' => $method->getDeclaringClass()->getName()
];
// Return type
if ($method->hasReturnType()) {
$returnType = $method->getReturnType();
$methodInfo['return_type'] = $this->getTypeInfo($returnType);
}
$methods[] = $methodInfo;
}
return $methods;
}
private function getParametersInfo(ReflectionMethod $method): array {
$parameters = [];
foreach ($method->getParameters() as $parameter) {
$paramInfo = [
'name' => $parameter->getName(),
'position' => $parameter->getPosition(),
'optional' => $parameter->isOptional(),
'default_value' => null,
'type' => null,
'variadic' => $parameter->isVariadic(),
'passed_by_reference' => $parameter->isPassedByReference(),
'attributes' => $this->getParameterAttributes($parameter)
];
// Type information
if ($parameter->hasType()) {
$type = $parameter->getType();
$paramInfo['type'] = $this->getTypeInfo($type);
}
// Default value
if ($parameter->isDefaultValueAvailable()) {
$paramInfo['default_value'] = $parameter->getDefaultValue();
}
$parameters[] = $paramInfo;
}
return $parameters;
}
private function getConstantsInfo(): array {
$constants = [];
foreach ($this->reflection->getConstants() as $name => $value) {
$constants[] = [
'name' => $name,
'value' => $value,
'visibility' => 'public' // Default for class constants
];
}
return $constants;
}
private function getTraitsInfo(): array {
$traits = [];
foreach ($this->reflection->getTraits() as $trait) {
$traits[] = [
'name' => $trait->getName(),
'filename' => $trait->getFileName(),
'methods' => array_map(fn($m) => $m->getName(), $trait->getMethods())
];
}
return $traits;
}
private function getInterfacesInfo(): array {
$interfaces = [];
foreach ($this->reflection->getInterfaces() as $interface) {
$interfaces[] = [
'name' => $interface->getName(),
'methods' => array_map(fn($m) => $m->getName(), $interface->getMethods())
];
}
return $interfaces;
}
private function getAttributesInfo(): array {
if (PHP_VERSION_ID < 80000) {
return [];
}
$attributes = [];
foreach ($this->reflection->getAttributes() as $attribute) {
$attributes[] = [
'name' => $attribute->getName(),
'arguments' => $attribute->getArguments(),
'target' => 'class'
];
}
return $attributes;
}
private function calculateMetrics(): array {
$methods = $this->reflection->getMethods();
$properties = $this->reflection->getProperties();
return [
'total_methods' => count($methods),
'public_methods' => count(array_filter($methods, fn($m) => $m->isPublic())),
'private_methods' => count(array_filter($methods, fn($m) => $m->isPrivate())),
'protected_methods' => count(array_filter($methods, fn($m) => $m->isProtected())),
'static_methods' => count(array_filter($methods, fn($m) => $m->isStatic())),
'total_properties' => count($properties),
'public_properties' => count(array_filter($properties, fn($p) => $p->isPublic())),
'private_properties' => count(array_filter($properties, fn($p) => $p->isPrivate())),
'protected_properties' => count(array_filter($properties, fn($p) => $p->isProtected())),
'static_properties' => count(array_filter($properties, fn($p) => $p->isStatic())),
'lines_of_code' => $this->reflection->getEndLine() - $this->reflection->getStartLine() + 1
];
}
private function getVisibility($reflector): string {
if ($reflector->isPublic()) return 'public';
if ($reflector->isProtected()) return 'protected';
if ($reflector->isPrivate()) return 'private';
return 'unknown';
}
private function getTypeInfo($type): array {
$info = [
'name' => (string)$type,
'builtin' => $type->isBuiltin(),
'nullable' => false
];
if (method_exists($type, 'allowsNull')) {
$info['nullable'] = $type->allowsNull();
}
return $info;
}
private function getPropertyAttributes(ReflectionProperty $property): array {
if (PHP_VERSION_ID < 80000) {
return [];
}
$attributes = [];
foreach ($property->getAttributes() as $attribute) {
$attributes[] = [
'name' => $attribute->getName(),
'arguments' => $attribute->getArguments()
];
}
return $attributes;
}
private function getMethodAttributes(ReflectionMethod $method): array {
if (PHP_VERSION_ID < 80000) {
return [];
}
$attributes = [];
foreach ($method->getAttributes() as $attribute) {
$attributes[] = [
'name' => $attribute->getName(),
'arguments' => $attribute->getArguments()
];
}
return $attributes;
}
private function getParameterAttributes(ReflectionParameter $parameter): array {
if (PHP_VERSION_ID < 80000) {
return [];
}
$attributes = [];
foreach ($parameter->getAttributes() as $attribute) {
$attributes[] = [
'name' => $attribute->getName(),
'arguments' => $attribute->getArguments()
];
}
return $attributes;
}
public function getAnalysis(): array {
return $this->analysis;
}
public function exportToJson(): string {
return json_encode($this->analysis, JSON_PRETTY_PRINT);
}
public function generateReport(): string {
$report = "=== CLASS ANALYSIS REPORT ===\n\n";
$basic = $this->analysis['basic_info'];
$report .= "Class: {$basic['name']}\n";
$report .= "File: {$basic['filename']}\n";
$report .= "Lines: {$basic['start_line']}-{$basic['end_line']}\n\n";
$metrics = $this->analysis['metrics'];
$report .= "METRICS:\n";
$report .= " Methods: {$metrics['total_methods']} (Public: {$metrics['public_methods']}, Private: {$metrics['private_methods']}, Protected: {$metrics['protected_methods']})\n";
$report .= " Properties: {$metrics['total_properties']} (Public: {$metrics['public_properties']}, Private: {$metrics['private_properties']}, Protected: {$metrics['protected_properties']})\n";
$report .= " Lines of Code: {$metrics['lines_of_code']}\n\n";
if (!empty($this->analysis['inheritance']['parent_class'])) {
$report .= "INHERITANCE:\n";
$report .= " Parent: {$this->analysis['inheritance']['parent_class']}\n";
}
if (!empty($this->analysis['inheritance']['implements'])) {
$report .= " Implements: " . implode(', ', $this->analysis['inheritance']['implements']) . "\n";
}
if (!empty($this->analysis['inheritance']['uses_traits'])) {
$report .= " Uses Traits: " . implode(', ', $this->analysis['inheritance']['uses_traits']) . "\n";
}
return $report;
}
}
// Usage example
$analyzer = new ClassAnalyzer(DateTime::class);
echo $analyzer->generateReport();
?>
Dynamic Property and Method Access
Runtime Property Manipulation
<?php
class DynamicPropertyManager {
public static function getPropertyValue(object $object, string $propertyName): mixed {
$reflection = new ReflectionClass($object);
if (!$reflection->hasProperty($propertyName)) {
throw new InvalidArgumentException("Property $propertyName does not exist");
}
$property = $reflection->getProperty($propertyName);
// Make property accessible if it's private or protected
if (!$property->isPublic()) {
$property->setAccessible(true);
}
return $property->getValue($object);
}
public static function setPropertyValue(object $object, string $propertyName, mixed $value): void {
$reflection = new ReflectionClass($object);
if (!$reflection->hasProperty($propertyName)) {
throw new InvalidArgumentException("Property $propertyName does not exist");
}
$property = $reflection->getProperty($propertyName);
// Make property accessible if it's private or protected
if (!$property->isPublic()) {
$property->setAccessible(true);
}
$property->setValue($object, $value);
}
public static function hasProperty(object $object, string $propertyName): bool {
$reflection = new ReflectionClass($object);
return $reflection->hasProperty($propertyName);
}
public static function getPropertyMetadata(object $object, string $propertyName): array {
$reflection = new ReflectionClass($object);
if (!$reflection->hasProperty($propertyName)) {
throw new InvalidArgumentException("Property $propertyName does not exist");
}
$property = $reflection->getProperty($propertyName);
return [
'name' => $property->getName(),
'public' => $property->isPublic(),
'private' => $property->isPrivate(),
'protected' => $property->isProtected(),
'static' => $property->isStatic(),
'has_type' => $property->hasType(),
'type' => $property->hasType() ? (string)$property->getType() : null,
'has_default_value' => $property->hasDefaultValue(),
'default_value' => $property->hasDefaultValue() ? $property->getDefaultValue() : null,
'doc_comment' => $property->getDocComment(),
'declaring_class' => $property->getDeclaringClass()->getName()
];
}
public static function copyProperties(object $source, object $target, array $propertyNames = []): void {
$sourceReflection = new ReflectionClass($source);
$targetReflection = new ReflectionClass($target);
$properties = empty($propertyNames)
? $sourceReflection->getProperties()
: array_filter(
$sourceReflection->getProperties(),
fn($p) => in_array($p->getName(), $propertyNames)
);
foreach ($properties as $sourceProperty) {
$propertyName = $sourceProperty->getName();
if (!$targetReflection->hasProperty($propertyName)) {
continue;
}
$targetProperty = $targetReflection->getProperty($propertyName);
// Make properties accessible
if (!$sourceProperty->isPublic()) {
$sourceProperty->setAccessible(true);
}
if (!$targetProperty->isPublic()) {
$targetProperty->setAccessible(true);
}
$value = $sourceProperty->getValue($source);
$targetProperty->setValue($target, $value);
}
}
}
// Example usage
class TestClass {
private string $privateProperty = 'secret';
protected int $protectedProperty = 42;
public array $publicProperty = ['a', 'b', 'c'];
}
$object = new TestClass();
// Get private property value
$secretValue = DynamicPropertyManager::getPropertyValue($object, 'privateProperty');
echo "Private property value: $secretValue\n";
// Set private property value
DynamicPropertyManager::setPropertyValue($object, 'privateProperty', 'new secret');
$newValue = DynamicPropertyManager::getPropertyValue($object, 'privateProperty');
echo "Updated private property: $newValue\n";
// Get property metadata
$metadata = DynamicPropertyManager::getPropertyMetadata($object, 'privateProperty');
print_r($metadata);
?>
Dynamic Method Invocation
<?php
class DynamicMethodInvoker {
public static function invokeMethod(object $object, string $methodName, array $arguments = []): mixed {
$reflection = new ReflectionClass($object);
if (!$reflection->hasMethod($methodName)) {
throw new InvalidArgumentException("Method $methodName does not exist");
}
$method = $reflection->getMethod($methodName);
// Make method accessible if it's private or protected
if (!$method->isPublic()) {
$method->setAccessible(true);
}
return $method->invokeArgs($object, $arguments);
}
public static function invokeStaticMethod(string $className, string $methodName, array $arguments = []): mixed {
$reflection = new ReflectionClass($className);
if (!$reflection->hasMethod($methodName)) {
throw new InvalidArgumentException("Method $methodName does not exist");
}
$method = $reflection->getMethod($methodName);
if (!$method->isStatic()) {
throw new InvalidArgumentException("Method $methodName is not static");
}
// Make method accessible if it's private or protected
if (!$method->isPublic()) {
$method->setAccessible(true);
}
return $method->invokeArgs(null, $arguments);
}
public static function getMethodSignature(object $object, string $methodName): array {
$reflection = new ReflectionClass($object);
if (!$reflection->hasMethod($methodName)) {
throw new InvalidArgumentException("Method $methodName does not exist");
}
$method = $reflection->getMethod($methodName);
$parameters = [];
foreach ($method->getParameters() as $param) {
$paramInfo = [
'name' => $param->getName(),
'type' => $param->hasType() ? (string)$param->getType() : 'mixed',
'optional' => $param->isOptional(),
'default' => $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null,
'variadic' => $param->isVariadic(),
'by_reference' => $param->isPassedByReference()
];
$parameters[] = $paramInfo;
}
return [
'name' => $method->getName(),
'return_type' => $method->hasReturnType() ? (string)$method->getReturnType() : 'mixed',
'parameters' => $parameters,
'visibility' => $method->isPublic() ? 'public' : ($method->isProtected() ? 'protected' : 'private'),
'static' => $method->isStatic(),
'abstract' => $method->isAbstract(),
'final' => $method->isFinal()
];
}
public static function createMethodProxy(object $object, string $methodName): Closure {
$reflection = new ReflectionClass($object);
$method = $reflection->getMethod($methodName);
if (!$method->isPublic()) {
$method->setAccessible(true);
}
return function(...$args) use ($object, $method) {
return $method->invokeArgs($object, $args);
};
}
}
// Example usage
class Calculator {
private function add(int $a, int $b): int {
return $a + $b;
}
protected function multiply(int $a, int $b): int {
return $a * $b;
}
public static function divide(float $a, float $b): float {
if ($b === 0.0) {
throw new DivisionByZeroError('Division by zero');
}
return $a / $b;
}
}
$calculator = new Calculator();
// Invoke private method
$result = DynamicMethodInvoker::invokeMethod($calculator, 'add', [5, 3]);
echo "5 + 3 = $result\n";
// Invoke static method
$result = DynamicMethodInvoker::invokeStaticMethod(Calculator::class, 'divide', [10, 2]);
echo "10 / 2 = $result\n";
// Get method signature
$signature = DynamicMethodInvoker::getMethodSignature($calculator, 'multiply');
print_r($signature);
// Create method proxy
$addProxy = DynamicMethodInvoker::createMethodProxy($calculator, 'add');
echo "Proxy result: " . $addProxy(10, 20) . "\n";
?>
Code Generation and Dynamic Class Creation
Dynamic Class Builder
<?php
class DynamicClassBuilder {
private string $className;
private string $namespace = '';
private string $extends = '';
private array $implements = [];
private array $traits = [];
private array $properties = [];
private array $methods = [];
private array $constants = [];
private string $docComment = '';
public function __construct(string $className) {
$this->className = $className;
}
public function setNamespace(string $namespace): self {
$this->namespace = $namespace;
return $this;
}
public function extends(string $parentClass): self {
$this->extends = $parentClass;
return $this;
}
public function implements(string ...$interfaces): self {
$this->implements = array_merge($this->implements, $interfaces);
return $this;
}
public function useTrait(string ...$traits): self {
$this->traits = array_merge($this->traits, $traits);
return $this;
}
public function addProperty(string $name, string $visibility = 'private', ?string $type = null, mixed $defaultValue = null): self {
$this->properties[$name] = [
'visibility' => $visibility,
'type' => $type,
'default' => $defaultValue,
'static' => false
];
return $this;
}
public function addStaticProperty(string $name, string $visibility = 'private', ?string $type = null, mixed $defaultValue = null): self {
$this->properties[$name] = [
'visibility' => $visibility,
'type' => $type,
'default' => $defaultValue,
'static' => true
];
return $this;
}
public function addMethod(string $name, string $body, string $visibility = 'public', ?string $returnType = null, array $parameters = []): self {
$this->methods[$name] = [
'visibility' => $visibility,
'return_type' => $returnType,
'parameters' => $parameters,
'body' => $body,
'static' => false,
'abstract' => false,
'final' => false
];
return $this;
}
public function addStaticMethod(string $name, string $body, string $visibility = 'public', ?string $returnType = null, array $parameters = []): self {
$this->methods[$name] = [
'visibility' => $visibility,
'return_type' => $returnType,
'parameters' => $parameters,
'body' => $body,
'static' => true,
'abstract' => false,
'final' => false
];
return $this;
}
public function addConstant(string $name, mixed $value): self {
$this->constants[$name] = $value;
return $this;
}
public function setDocComment(string $docComment): self {
$this->docComment = $docComment;
return $this;
}
public function generateCode(): string {
$code = "<?php\n\n";
// Namespace
if (!empty($this->namespace)) {
$code .= "namespace {$this->namespace};\n\n";
}
// Class declaration
if (!empty($this->docComment)) {
$code .= $this->docComment . "\n";
}
$code .= "class {$this->className}";
if (!empty($this->extends)) {
$code .= " extends {$this->extends}";
}
if (!empty($this->implements)) {
$code .= " implements " . implode(', ', $this->implements);
}
$code .= "\n{\n";
// Traits
foreach ($this->traits as $trait) {
$code .= " use {$trait};\n";
}
if (!empty($this->traits)) {
$code .= "\n";
}
// Constants
foreach ($this->constants as $name => $value) {
$valueStr = is_string($value) ? "'{$value}'" : var_export($value, true);
$code .= " public const {$name} = {$valueStr};\n";
}
if (!empty($this->constants)) {
$code .= "\n";
}
// Properties
foreach ($this->properties as $name => $config) {
$code .= " {$config['visibility']}";
if ($config['static']) {
$code .= " static";
}
if ($config['type']) {
$code .= " {$config['type']}";
}
$code .= " \${$name}";
if ($config['default'] !== null) {
$defaultStr = is_string($config['default']) ? "'{$config['default']}'" : var_export($config['default'], true);
$code .= " = {$defaultStr}";
}
$code .= ";\n";
}
if (!empty($this->properties)) {
$code .= "\n";
}
// Methods
foreach ($this->methods as $name => $config) {
$code .= " {$config['visibility']}";
if ($config['static']) {
$code .= " static";
}
$code .= " function {$name}(";
// Parameters
$paramStrings = [];
foreach ($config['parameters'] as $param) {
$paramStr = '';
if (isset($param['type'])) {
$paramStr .= $param['type'] . ' ';
}
$paramStr .= '$' . $param['name'];
if (isset($param['default'])) {
$defaultStr = is_string($param['default']) ? "'{$param['default']}'" : var_export($param['default'], true);
$paramStr .= " = {$defaultStr}";
}
$paramStrings[] = $paramStr;
}
$code .= implode(', ', $paramStrings) . ')';
if ($config['return_type']) {
$code .= ": {$config['return_type']}";
}
$code .= "\n {\n";
$code .= " " . str_replace("\n", "\n ", trim($config['body'])) . "\n";
$code .= " }\n\n";
}
$code .= "}\n";
return $code;
}
public function createClass(): string {
$code = $this->generateCode();
// Evaluate the code to create the class
eval(str_replace('<?php', '', $code));
$fullClassName = empty($this->namespace) ? $this->className : $this->namespace . '\\' . $this->className;
return $fullClassName;
}
public function saveToFile(string $filename): void {
$code = $this->generateCode();
file_put_contents($filename, $code);
}
}
// Example: Create a dynamic model class
$builder = new DynamicClassBuilder('User')
->setNamespace('App\\Models')
->setDocComment('/**
* Dynamically generated User model
* @author Dynamic Class Builder
*/')
->addProperty('id', 'private', 'int')
->addProperty('name', 'private', 'string')
->addProperty('email', 'private', 'string')
->addProperty('createdAt', 'private', 'DateTime')
->addConstant('TABLE_NAME', 'users')
->addMethod('__construct',
'id = $id;
$this->name = $name;
$this->email = $email;
$this->createdAt = new DateTime();',
'public',
null,
[
['name' => 'id', 'type' => 'int'],
['name' => 'name', 'type' => 'string'],
['name' => 'email', 'type' => 'string']
]
)
->addMethod('getId', 'return $this->id;', 'public', 'int')
->addMethod('getName', 'return $this->name;', 'public', 'string')
->addMethod('getEmail', 'return $this->email;', 'public', 'string')
->addMethod('setName', '$this->name = $name;', 'public', 'void', [['name' => 'name', 'type' => 'string']])
->addMethod('setEmail', '$this->email = $email;', 'public', 'void', [['name' => 'email', 'type' => 'string']])
->addMethod('toArray',
'return [
"id" => $this->id,
"name" => $this->name,
"email" => $this->email,
"created_at" => $this->createdAt->format("Y-m-d H:i:s")
];',
'public',
'array'
);
// Generate and save the class
echo $builder->generateCode();
?>
Dynamic Interface and Trait Generation
<?php
class InterfaceGenerator {
private string $interfaceName;
private string $namespace = '';
private array $extends = [];
private array $methods = [];
private string $docComment = '';
public function __construct(string $interfaceName) {
$this->interfaceName = $interfaceName;
}
public function setNamespace(string $namespace): self {
$this->namespace = $namespace;
return $this;
}
public function extends(string ...$interfaces): self {
$this->extends = array_merge($this->extends, $interfaces);
return $this;
}
public function addMethod(string $name, ?string $returnType = null, array $parameters = []): self {
$this->methods[$name] = [
'return_type' => $returnType,
'parameters' => $parameters
];
return $this;
}
public function setDocComment(string $docComment): self {
$this->docComment = $docComment;
return $this;
}
public function generateCode(): string {
$code = "<?php\n\n";
// Namespace
if (!empty($this->namespace)) {
$code .= "namespace {$this->namespace};\n\n";
}
// Interface declaration
if (!empty($this->docComment)) {
$code .= $this->docComment . "\n";
}
$code .= "interface {$this->interfaceName}";
if (!empty($this->extends)) {
$code .= " extends " . implode(', ', $this->extends);
}
$code .= "\n{\n";
// Methods
foreach ($this->methods as $name => $config) {
$code .= " public function {$name}(";
// Parameters
$paramStrings = [];
foreach ($config['parameters'] as $param) {
$paramStr = '';
if (isset($param['type'])) {
$paramStr .= $param['type'] . ' ';
}
$paramStr .= '$' . $param['name'];
$paramStrings[] = $paramStr;
}
$code .= implode(', ', $paramStrings) . ')';
if ($config['return_type']) {
$code .= ": {$config['return_type']}";
}
$code .= ";\n";
}
$code .= "}\n";
return $code;
}
}
class TraitGenerator {
private string $traitName;
private string $namespace = '';
private array $properties = [];
private array $methods = [];
private string $docComment = '';
public function __construct(string $traitName) {
$this->traitName = $traitName;
}
public function setNamespace(string $namespace): self {
$this->namespace = $namespace;
return $this;
}
public function addProperty(string $name, string $visibility = 'private', ?string $type = null, mixed $defaultValue = null): self {
$this->properties[$name] = [
'visibility' => $visibility,
'type' => $type,
'default' => $defaultValue
];
return $this;
}
public function addMethod(string $name, string $body, string $visibility = 'public', ?string $returnType = null, array $parameters = []): self {
$this->methods[$name] = [
'visibility' => $visibility,
'return_type' => $returnType,
'parameters' => $parameters,
'body' => $body
];
return $this;
}
public function generateCode(): string {
$code = "<?php\n\n";
// Namespace
if (!empty($this->namespace)) {
$code .= "namespace {$this->namespace};\n\n";
}
// Trait declaration
if (!empty($this->docComment)) {
$code .= $this->docComment . "\n";
}
$code .= "trait {$this->traitName}\n{\n";
// Properties
foreach ($this->properties as $name => $config) {
$code .= " {$config['visibility']}";
if ($config['type']) {
$code .= " {$config['type']}";
}
$code .= " \${$name}";
if ($config['default'] !== null) {
$defaultStr = is_string($config['default']) ? "'{$config['default']}'" : var_export($config['default'], true);
$code .= " = {$defaultStr}";
}
$code .= ";\n";
}
if (!empty($this->properties)) {
$code .= "\n";
}
// Methods
foreach ($this->methods as $name => $config) {
$code .= " {$config['visibility']} function {$name}(";
// Parameters
$paramStrings = [];
foreach ($config['parameters'] as $param) {
$paramStr = '';
if (isset($param['type'])) {
$paramStr .= $param['type'] . ' ';
}
$paramStr .= '$' . $param['name'];
if (isset($param['default'])) {
$defaultStr = is_string($param['default']) ? "'{$param['default']}'" : var_export($param['default'], true);
$paramStr .= " = {$defaultStr}";
}
$paramStrings[] = $paramStr;
}
$code .= implode(', ', $paramStrings) . ')';
if ($config['return_type']) {
$code .= ": {$config['return_type']}";
}
$code .= "\n {\n";
$code .= " " . str_replace("\n", "\n ", trim($config['body'])) . "\n";
$code .= " }\n\n";
}
$code .= "}\n";
return $code;
}
}
?>
Advanced Reflection Patterns
Annotation/Attribute System
<?php
// PHP 8+ Attribute-based system
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)]
class Route {
public function __construct(
public string $path,
public array $methods = ['GET'],
public string $name = '',
public array $middleware = []
) {}
}
#[Attribute(Attribute::TARGET_PROPERTY)]
class Validate {
public function __construct(
public array $rules = [],
public ?string $message = null
) {}
}
#[Attribute(Attribute::TARGET_METHOD)]
class Cache {
public function __construct(
public int $ttl = 3600,
public ?string $key = null
) {}
}
class AttributeProcessor {
public static function processRoutes(object $controller): array {
$reflection = new ReflectionClass($controller);
$routes = [];
// Process class-level routes
$classAttributes = $reflection->getAttributes(Route::class);
$classRoute = null;
if (!empty($classAttributes)) {
$classRoute = $classAttributes[0]->newInstance();
}
// Process method-level routes
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
$methodAttributes = $method->getAttributes(Route::class);
if (!empty($methodAttributes)) {
$routeAttribute = $methodAttributes[0]->newInstance();
// Combine class and method paths
$fullPath = ($classRoute ? rtrim($classRoute->path, '/') : '') . '/' . ltrim($routeAttribute->path, '/');
$routes[] = [
'path' => $fullPath,
'methods' => $routeAttribute->methods,
'handler' => [$controller, $method->getName()],
'name' => $routeAttribute->name ?: $method->getName(),
'middleware' => array_merge(
$classRoute ? $classRoute->middleware : [],
$routeAttribute->middleware
)
];
}
}
return $routes;
}
public static function processValidation(object $object): array {
$reflection = new ReflectionClass($object);
$validationRules = [];
foreach ($reflection->getProperties() as $property) {
$attributes = $property->getAttributes(Validate::class);
if (!empty($attributes)) {
$validateAttribute = $attributes[0]->newInstance();
$validationRules[$property->getName()] = [
'rules' => $validateAttribute->rules,
'message' => $validateAttribute->message
];
}
}
return $validationRules;
}
public static function processCaching(object $object, string $methodName): ?array {
$reflection = new ReflectionClass($object);
if (!$reflection->hasMethod($methodName)) {
return null;
}
$method = $reflection->getMethod($methodName);
$attributes = $method->getAttributes(Cache::class);
if (empty($attributes)) {
return null;
}
$cacheAttribute = $attributes[0]->newInstance();
return [
'ttl' => $cacheAttribute->ttl,
'key' => $cacheAttribute->key ?: $reflection->getName() . '::' . $methodName
];
}
}
// Example usage
#[Route('/api/users', middleware: ['auth'])]
class UserController {
#[Route('/profile', ['GET'], 'user.profile')]
#[Cache(ttl: 1800)]
public function getProfile(int $userId): array {
return ['id' => $userId, 'name' => 'John Doe'];
}
#[Route('/{id}', ['GET'], 'user.show')]
public function show(int $id): array {
return ['id' => $id];
}
#[Route('', ['POST'], 'user.create')]
public function create(array $data): array {
return ['created' => true];
}
}
class User {
#[Validate(rules: ['required', 'string', 'max:255'])]
public string $name;
#[Validate(rules: ['required', 'email', 'unique:users'])]
public string $email;
#[Validate(rules: ['required', 'min:8'])]
public string $password;
}
$controller = new UserController();
$routes = AttributeProcessor::processRoutes($controller);
print_r($routes);
$user = new User();
$validationRules = AttributeProcessor::processValidation($user);
print_r($validationRules);
$cacheInfo = AttributeProcessor::processCaching($controller, 'getProfile');
print_r($cacheInfo);
?>
Dependency Injection Container
<?php
class DependencyContainer {
private array $bindings = [];
private array $instances = [];
private array $singletons = [];
public function bind(string $abstract, $concrete = null, bool $singleton = false): void {
if ($concrete === null) {
$concrete = $abstract;
}
$this->bindings[$abstract] = [
'concrete' => $concrete,
'singleton' => $singleton
];
}
public function singleton(string $abstract, $concrete = null): void {
$this->bind($abstract, $concrete, true);
}
public function instance(string $abstract, object $instance): void {
$this->instances[$abstract] = $instance;
}
public function resolve(string $abstract): mixed {
// Check if we have a cached instance
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
// Check if we have a binding
if (!isset($this->bindings[$abstract])) {
// Try to auto-resolve if it's a class
if (class_exists($abstract)) {
return $this->build($abstract);
}
throw new InvalidArgumentException("No binding found for {$abstract}");
}
$binding = $this->bindings[$abstract];
$concrete = $binding['concrete'];
// Resolve the concrete
if ($concrete instanceof Closure) {
$object = $concrete($this);
} elseif (is_string($concrete)) {
$object = $this->build($concrete);
} else {
$object = $concrete;
}
// Cache singleton instances
if ($binding['singleton']) {
$this->instances[$abstract] = $object;
}
return $object;
}
private function build(string $concrete): object {
$reflection = new ReflectionClass($concrete);
if (!$reflection->isInstantiable()) {
throw new InvalidArgumentException("Class {$concrete} is not instantiable");
}
$constructor = $reflection->getConstructor();
if ($constructor === null) {
return new $concrete();
}
$parameters = $constructor->getParameters();
$dependencies = $this->resolveDependencies($parameters);
return $reflection->newInstanceArgs($dependencies);
}
private function resolveDependencies(array $parameters): array {
$dependencies = [];
foreach ($parameters as $parameter) {
$type = $parameter->getType();
if ($type === null) {
// No type hint, check for default value
if ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
} else {
throw new InvalidArgumentException(
"Cannot resolve parameter {$parameter->getName()} without type hint"
);
}
} elseif ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
// Class dependency
$dependencies[] = $this->resolve($type->getName());
} else {
// Built-in type, check for default value
if ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
} else {
throw new InvalidArgumentException(
"Cannot resolve built-in type {$type->getName()} for parameter {$parameter->getName()}"
);
}
}
}
return $dependencies;
}
public function call(callable $callback, array $parameters = []): mixed {
if (is_array($callback)) {
[$object, $method] = $callback;
$reflection = new ReflectionMethod($object, $method);
} elseif ($callback instanceof Closure) {
$reflection = new ReflectionFunction($callback);
} else {
throw new InvalidArgumentException('Invalid callback provided');
}
$methodParameters = $reflection->getParameters();
$dependencies = $this->resolveMethodDependencies($methodParameters, $parameters);
return $reflection->invokeArgs(is_array($callback) ? $object : null, $dependencies);
}
private function resolveMethodDependencies(array $parameters, array $provided = []): array {
$dependencies = [];
foreach ($parameters as $parameter) {
$name = $parameter->getName();
// Check if parameter was provided
if (array_key_exists($name, $provided)) {
$dependencies[] = $provided[$name];
continue;
}
$type = $parameter->getType();
if ($type === null) {
if ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
} else {
throw new InvalidArgumentException(
"Cannot resolve parameter {$name} without type hint"
);
}
} elseif ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
$dependencies[] = $this->resolve($type->getName());
} else {
if ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
} else {
throw new InvalidArgumentException(
"Cannot resolve built-in type {$type->getName()} for parameter {$name}"
);
}
}
}
return $dependencies;
}
public function has(string $abstract): bool {
return isset($this->bindings[$abstract]) || isset($this->instances[$abstract]);
}
public function forget(string $abstract): void {
unset($this->bindings[$abstract], $this->instances[$abstract]);
}
public function getBindings(): array {
return $this->bindings;
}
}
// Example usage
interface LoggerInterface {
public function log(string $message): void;
}
class FileLogger implements LoggerInterface {
public function __construct(private string $logFile = 'app.log') {}
public function log(string $message): void {
file_put_contents($this->logFile, date('Y-m-d H:i:s') . " - {$message}\n", FILE_APPEND);
}
}
class DatabaseLogger implements LoggerInterface {
public function log(string $message): void {
// Log to database
echo "Logging to database: {$message}\n";
}
}
class UserService {
public function __construct(
private LoggerInterface $logger,
private string $apiKey = 'default-key'
) {}
public function createUser(string $name, string $email): array {
$this->logger->log("Creating user: {$name} ({$email})");
return [
'id' => rand(1, 1000),
'name' => $name,
'email' => $email,
'created_at' => date('Y-m-d H:i:s')
];
}
}
class UserController {
public function __construct(private UserService $userService) {}
public function store(string $name, string $email): array {
return $this->userService->createUser($name, $email);
}
}
// Set up container
$container = new DependencyContainer();
// Bind interfaces to implementations
$container->bind(LoggerInterface::class, FileLogger::class);
// Bind with custom parameters
$container->bind(UserService::class, function($container) {
return new UserService(
$container->resolve(LoggerInterface::class),
'custom-api-key'
);
});
// Resolve and use
$controller = $container->resolve(UserController::class);
$result = $container->call([$controller, 'store'], [
'name' => 'John Doe',
'email' => 'john@example.com'
]);
print_r($result);
?>
Performance Considerations and Optimization
Reflection Performance Analysis
<?php
class ReflectionPerformanceAnalyzer {
private array $timings = [];
private array $memoryUsage = [];
public function benchmarkReflectionOperations(string $className, int $iterations = 1000): array {
$results = [];
// Test class instantiation
$results['class_instantiation'] = $this->benchmarkOperation(
'Class Instantiation',
function() use ($className) {
new ReflectionClass($className);
},
$iterations
);
// Test method reflection
$reflection = new ReflectionClass($className);
$results['method_reflection'] = $this->benchmarkOperation(
'Method Reflection',
function() use ($reflection) {
$reflection->getMethods();
},
$iterations
);
// Test property reflection
$results['property_reflection'] = $this->benchmarkOperation(
'Property Reflection',
function() use ($reflection) {
$reflection->getProperties();
},
$iterations
);
// Test method invocation
if ($reflection->hasMethod('__construct')) {
$object = $reflection->newInstance();
$method = $reflection->getMethod('__construct');
$results['method_invocation'] = $this->benchmarkOperation(
'Method Invocation',
function() use ($method, $object) {
$method->invoke($object);
},
$iterations
);
}
return $results;
}
private function benchmarkOperation(string $name, callable $operation, int $iterations): array {
$startTime = microtime(true);
$startMemory = memory_get_usage(true);
for ($i = 0; $i < $iterations; $i++) {
$operation();
}
$endTime = microtime(true);
$endMemory = memory_get_usage(true);
$totalTime = $endTime - $startTime;
$memoryUsed = $endMemory - $startMemory;
return [
'name' => $name,
'iterations' => $iterations,
'total_time' => $totalTime,
'avg_time_per_operation' => $totalTime / $iterations,
'operations_per_second' => $iterations / $totalTime,
'memory_used' => $memoryUsed,
'memory_per_operation' => $memoryUsed / $iterations
];
}
public function compareReflectionVsDirectAccess(object $object, string $methodName, int $iterations = 10000): array {
$reflection = new ReflectionClass($object);
$method = $reflection->getMethod($methodName);
// Direct method call
$directResult = $this->benchmarkOperation(
'Direct Method Call',
function() use ($object, $methodName) {
$object->$methodName();
},
$iterations
);
// Reflection method call
$reflectionResult = $this->benchmarkOperation(
'Reflection Method Call',
function() use ($method, $object) {
$method->invoke($object);
},
$iterations
);
return [
'direct' => $directResult,
'reflection' => $reflectionResult,
'overhead_factor' => $reflectionResult['avg_time_per_operation'] / $directResult['avg_time_per_operation']
];
}
public function analyzeReflectionCaching(string $className, int $iterations = 1000): array {
// Without caching
$withoutCacheResult = $this->benchmarkOperation(
'Without Caching',
function() use ($className) {
$reflection = new ReflectionClass($className);
$reflection->getMethods();
$reflection->getProperties();
},
$iterations
);
// With caching
$cachedReflection = new ReflectionClass($className);
$withCacheResult = $this->benchmarkOperation(
'With Caching',
function() use ($cachedReflection) {
$cachedReflection->getMethods();
$cachedReflection->getProperties();
},
$iterations
);
return [
'without_cache' => $withoutCacheResult,
'with_cache' => $withCacheResult,
'improvement_factor' => $withoutCacheResult['avg_time_per_operation'] / $withCacheResult['avg_time_per_operation']
];
}
public function generatePerformanceReport(array $results): string {
$report = "=== REFLECTION PERFORMANCE ANALYSIS ===\n\n";
foreach ($results as $testName => $result) {
if (isset($result['name'])) {
// Single operation result
$report .= "Operation: {$result['name']}\n";
$report .= " Iterations: {$result['iterations']}\n";
$report .= " Total Time: " . number_format($result['total_time'], 6) . "s\n";
$report .= " Avg Time: " . number_format($result['avg_time_per_operation'] * 1000000, 2) . "μs\n";
$report .= " Ops/sec: " . number_format($result['operations_per_second'], 0) . "\n";
$report .= " Memory: " . $this->formatBytes($result['memory_used']) . "\n\n";
} else {
// Comparison result
$report .= "Test: $testName\n";
foreach ($result as $key => $data) {
if (is_array($data) && isset($data['name'])) {
$report .= " {$data['name']}: " . number_format($data['avg_time_per_operation'] * 1000000, 2) . "μs\n";
} elseif ($key === 'overhead_factor') {
$report .= " Overhead Factor: " . number_format($data, 2) . "x\n";
} elseif ($key === 'improvement_factor') {
$report .= " Improvement Factor: " . number_format($data, 2) . "x\n";
}
}
$report .= "\n";
}
}
return $report;
}
private function formatBytes(int $bytes): string {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = abs($bytes);
for ($i = 0; $bytes >= 1024 && $i < count($units) - 1; $i++) {
$bytes /= 1024;
}
return round($bytes, 2) . ' ' . $units[$i];
}
}
// Example usage
class TestClass {
private string $property = 'test';
public function testMethod(): string {
return 'test result';
}
public function complexMethod(string $param1, int $param2 = 42): array {
return ['param1' => $param1, 'param2' => $param2];
}
}
$analyzer = new ReflectionPerformanceAnalyzer();
// Benchmark basic reflection operations
$basicResults = $analyzer->benchmarkReflectionOperations(TestClass::class, 5000);
// Compare direct vs reflection method calls
$object = new TestClass();
$comparisonResults = $analyzer->compareReflectionVsDirectAccess($object, 'testMethod', 10000);
// Analyze caching benefits
$cachingResults = $analyzer->analyzeReflectionCaching(TestClass::class, 1000);
// Generate report
$allResults = [
'basic_operations' => $basicResults,
'direct_vs_reflection' => $comparisonResults,
'caching_analysis' => $cachingResults
];
echo $analyzer->generatePerformanceReport($allResults);
?>
Frequently Asked Questions (FAQ)
When should I use Reflection instead of direct method calls?
Use Reflection when you need dynamic behavior that can't be determined at compile time, such as:
- Building frameworks or libraries that work with unknown classes
- Creating dependency injection containers
- Implementing serialization/deserialization logic
- Building ORM systems that map database rows to objects
- Creating debugging and profiling tools
Avoid Reflection for regular application logic where the class structure is known at development time, as it's significantly slower than direct calls.
How can I improve Reflection performance in production applications?
Several strategies can improve Reflection performance:
- Cache ReflectionClass instances: Create them once and reuse
- Use opcache: PHP's opcache can help with Reflection operations
- Lazy loading: Only perform reflection when absolutely necessary
- Compile-time generation: Generate code at build time instead of using runtime reflection
- Minimize reflection depth: Avoid deep introspection when possible
Is it safe to use Reflection to access private properties and methods?
While technically possible, accessing private members breaks encapsulation and can lead to maintenance issues. Use this capability sparingly and only for:
- Testing frameworks that need to verify internal state
- Debugging and development tools
- Framework code that requires deep object inspection
- Migration scripts that need to access legacy code
Always document when and why you're breaking encapsulation.
Can Reflection work with PHP 8 attributes effectively?
Yes, PHP 8's Reflection API has excellent support for attributes. You can:
- Use
getAttributes()
to retrieve attribute instances - Filter attributes by type with
getAttributes(AttributeClass::class)
- Access attribute arguments and instantiate attribute objects
- Build powerful metadata-driven systems similar to Java annotations or C# attributes
The attribute system is much more powerful and type-safe compared to parsing docblock comments.
Conclusion
PHP's Reflection API is a powerful tool that enables sophisticated metaprogramming patterns, from simple class inspection to complex framework development. While it comes with performance overhead, the flexibility it provides makes it indispensable for building dynamic, adaptable applications.
Key takeaways for effective Reflection usage:
- Use judiciously: Reserve Reflection for scenarios requiring true dynamic behavior
- Cache when possible: Reuse ReflectionClass instances to minimize overhead
- Combine with modern features: Leverage PHP 8 attributes for type-safe metadata
- Consider alternatives: Sometimes design patterns can achieve similar results with better performance
The examples and patterns shown in this guide provide a solid foundation for incorporating Reflection into your PHP projects. Start with simple introspection tasks and gradually build more complex dynamic systems as your understanding grows.
Ready to add dynamic capabilities to your PHP applications? Begin experimenting with the ClassAnalyzer and work your way up to building your own dependency injection container. Share your Reflection-powered creations and use cases in the comments below!
Add Comment
No comments yet. Be the first to comment!