Reflexão

Introdução

PHP 5 vem com uma API completa de reflexão que acrescenta a habilidade de fazer engenharia-reversa em classes, interfaces, funções e métodos assim como extensões. Além disso, a API de reflexão também oferece maneiras de recuperar comentários de documentação para funções, classes e métodos.

A API de reflexão é uma extensão orientada a objetos ao Engine Zend, consistindo das seguintes classes:

<?php
class Reflection { }
interface Reflector { }
class
ReflectionException extends Exception { }
class
ReflectionFunction implements Reflector { }
class
ReflectionParameter implements Reflector { }
class
ReflectionMethod extends ReflectionFunction { }
class
ReflectionClass implements Reflector { }
class
ReflectionObject extends ReflectionClass { }
class
ReflectionProperty implements Reflector { }
class
ReflectionExtension implements Reflector { }
?>

Nota: Para detalhes sobre essas classes, procure nos próximos capítulos.

Se fossemos executar o código no exemplo abaixo:

Exemplo 19-31. Uso básico da API de reflexão

<?php
Reflection
::export(new ReflectionClass('Exception'));
?>

O exemplo acima irá imprimir:

Class [ <internal> class Exception ] {

  - Constants [0] {
  }

  - Static properties [0] {
  }

  - Static methods [0] {
  }

  - Properties [6] {
    Property [ <default> protected $message ]
    Property [ <default> private $string ]
    Property [ <default> protected $code ]
    Property [ <default> protected $file ]
    Property [ <default> protected $line ]
    Property [ <default> private $trace ]
  }

  - Methods [9] {
    Method [ <internal> final private method __clone ] {
    }

    Method [ <internal> <ctor> public method __construct ] {

      - Parameters [2] {
        Parameter #0 [ <required> $message ]
        Parameter #1 [ <required> $code ]
      }
    }

    Method [ <internal> final public method getMessage ] {
    }

    Method [ <internal> final public method getCode ] {
    }

    Method [ <internal> final public method getFile ] {
    }

    Method [ <internal> final public method getLine ] {
    }

    Method [ <internal> final public method getTrace ] {
    }

    Method [ <internal> final public method getTraceAsString ] {
    }

    Method [ <internal> public method __toString ] {
    }
  }
}

ReflectionFunction

A classe ReflectionFunction permite a engenharia-reversa de funções.

<?php
class ReflectionFunction implements Reflector
{
    
final private __clone()
    
public object __construct(string name)
    
public string __toString()
    
public static string export()
    
public string getName()
    
public bool isInternal()
    
public bool isUserDefined()
    
public string getFileName()
    
public int getStartLine()
    
public int getEndLine()
    
public string getDocComment()
    
public array getStaticVariables()
    
public mixed invoke(mixed* args)
    
public mixed invokeArgs(array args)
    
public bool returnsReference()
    
public ReflectionParameter[] getParameters()
    
public int getNumberOfParameters()
    
public int getNumberOfRequiredParameters()
}
?>

Nota: invokeArgs() foi acrescentado no PHP 5.1.0.

Para introspectar uma função, você primeiro terá que criar uma instância da classe ReflectionFunction. Você pode, então, chamar qualquer um dos métodos acima nessa instância.

Exemplo 19-32. Usando a classe ReflectionFunction

<?php
/**
* Um contador simples
*
* @return    int
*/
function counter()
{
    static
$c = 0;
    return
$c++;
}

// Cria uma instância da classe ReflectionFunction
$func = new ReflectionFunction('counter');

// Imprime informações básicas
printf(
    
"===> The %s function '%s'\n".
    
"     declared in %s\n".
    
"     lines %d to %d\n",
    
$func->isInternal() ? 'internal' : 'user-defined',
    
$func->getName(),
    
$func->getFileName(),
    
$func->getStartLine(),
    
$func->getEndline()
);

// Imprime comentários de documentação
printf("---> Documentation:\n %s\n", var_export($func->getDocComment(), 1));

// Imprime variáveis estáticas se existirem
if ($statics = $func->getStaticVariables())
{
    
printf("---> Static variables: %s\n", var_export($statics, 1));
}

// Invoca a função
printf("---> Invokation results in: ");
var_dump($func->invoke());


// Você pode preferir usar o método export()
echo "\nReflectionFunction::export() results:\n";
echo
ReflectionFunction::export('counter');
?>

Nota: O método invoke() aceita um número variável de argumentos que serão passados para a função assim como em call_user_func().

ReflectionParameter

A classe ReflectionParameter recupera informação sobre os parâmetros de uma função ou de um método.

<?php
class ReflectionParameter implements Reflector
{
    
final private __clone()
    
public object __construct(string name)
    
public string __toString()
    
public static string export()
    
public string getName()
    
public bool isPassedByReference()
    
public ReflectionClass getClass()
    
public bool allowsNull()
    
public bool isOptional()
    
public bool isDefaultValueAvailable()
    
public mixed getDefaultValue()
}
?>

Nota: getDefaultValue(), isDefaultValueAvailable(), isOptional() were added in PHP 5.1.0.

Para introspectar parâmetros, você terá que primeiro criar uma instância das classes ReflectionFunction ou ReflectionMethod e então usar o seu método getParameters() para recuperar um array de parâmetros.

Exemplo 19-33. Usando a classe ReflectionParameter

<?php
function foo($a, $b, $c) { }
function
bar(Exception $a, &$b, $c) { }
function
baz(ReflectionFunction $a, $b = 1, $c = null) { }
function
abc() { }

// Crie uma instância de ReflectionFunction com o
// parâmetro dado da linha de comando.   
$reflect = new ReflectionFunction($argv[1]);

echo
$reflect;

foreach (
$reflect->getParameters() as $i => $param) {
    
printf(
        
"-- Parameter #%d: %s {\n".
        
"   Class: %s\n".
        
"   Allows NULL: %s\n".
        
"   Passed to by reference: %s\n".
        
"   Is optional?: %s\n".
        
"}\n",
        
$i,
        
$param->getName(),
        
var_export($param->getClass(), 1),
        
var_export($param->allowsNull(), 1),
        
var_export($param->isPassedByReference(), 1),
        
$param->isOptional() ? 'yes' : 'no'
    
);
}
?>

ReflectionClass

A classe ReflectionClass permite fazer a engenharia-reversa de uma classe.

<?php
class ReflectionClass implements Reflector
{
    
final private __clone()
    
public object __construct(string name)
    
public string __toString()
    
public static string export()
    
public string getName()
    
public bool isInternal()
    
public bool isUserDefined()
    
public bool isInstantiable()
    
public bool hasMethod(string name)
    
public string getFileName()
    
public int getStartLine()
    
public int getEndLine()
    
public string getDocComment()
    
public ReflectionMethod getConstructor()
    
public ReflectionMethod getMethod(string name)
    
public ReflectionMethod[] getMethods()
    
public ReflectionProperty getProperty(string name)
    
public ReflectionProperty[] getProperties()
    
public array getConstants()
    
public mixed getConstant(string name)
    
public ReflectionClass[] getInterfaces()
    
public bool isInterface()
    
public bool isAbstract()
    
public bool isFinal()
    
public int getModifiers()
    
public bool isInstance(stdclass object)
    
public stdclass newInstance(mixed* args)
    
public ReflectionClass getParentClass()
    
public bool isSubclassOf(ReflectionClass class)
    
public array getStaticProperties()
    
public array getDefaultProperties()
    
public bool isIterateable()
    
public bool implementsInterface(string name)
    
public ReflectionExtension getExtension()
    
public string getExtensionName()
}
?>

Nota: hasMethod() was added in PHP 5.1.0.

To introspect a class, you will first have to create an instance of the ReflectionClass class. You can then call any of the above methods on this instance.

Exemplo 19-34. Using the ReflectionClass class

<?php
interface Serializable
{
    
// ...
}

class
Object
{
    
// ...
}

/**
* Uma classe contadora
*/
class Counter extends Object implements Serializable
{
    const
START = 0;
    
private static $c = Counter::START;

    
/**
     * Contador de invocação
     *
     * @access  public
     * @return  int
     */
    
public function count() {
        return
self::$c++;
    }
}

// Crie uma instância da classe ReflectionClass
$class = new ReflectionClass('Counter');

// Imprime informação básica
printf(
    
"===> The %s%s%s %s '%s' [extends %s]\n" .
    
"     declared in %s\n" .
    
"     lines %d to %d\n" .
    
"     having the modifiers %d [%s]\n",
        
$class->isInternal() ? 'internal' : 'user-defined',
        
$class->isAbstract() ? ' abstract' : '',
        
$class->isFinal() ? ' final' : '',
        
$class->isInterface() ? 'interface' : 'class',
        
$class->getName(),
        
var_export($class->getParentClass(), 1),
        
$class->getFileName(),
        
$class->getStartLine(),
        
$class->getEndline(),
        
$class->getModifiers(),
        
implode(' ', Reflection::getModifierNames($class->getModifiers()))
);

// Imprime comentários de documentação
printf("---> Documentation:\n %s\n", var_export($class->getDocComment(), 1));

// Imprime quais interfaces são implementadas por essa classe
printf("---> Implements:\n %s\n", var_export($class->getInterfaces(), 1));

// Imprime as constantes da classe
printf("---> Constants: %s\n", var_export($class->getConstants(), 1));

// Imprime as propriedades da classe
printf("---> Properties: %s\n", var_export($class->getProperties(), 1));

// Imprime os métodos da classe
printf("---> Methods: %s\n", var_export($class->getMethods(), 1));

// Se essa classe for instanciável, cria uma instância
if ($class->isInstantiable()) {
    
$counter = $class->newInstance();

    echo
'---> $counter is instance? ';
    echo
$class->isInstance($counter) ? 'yes' : 'no';

    echo
"\n---> new Object() is instance? ";
    echo
$class->isInstance(new Object()) ? 'yes' : 'no';
}
?>

Nota: O método newInstance() aceita um número variável de argumentos que são passados para a função assim como em call_user_func().

Nota: $class = new ReflectionClass('Foo'); $class->isInstance($arg) é equivalente a$arg instanceof Foo ou is_a($arg, 'Foo').

ReflectionMethod

A classe ReflectionMethod permite fazer a engenharia-reversa de métodos de classes.

<?php
class ReflectionMethod extends ReflectionFunction
{
    
public __construct(mixed class, string name)
    
public string __toString()
    
public static string export()
    
public mixed invoke(stdclass object, mixed* args)
    
public moxed invokeArgs(stdclass object, array args)
    
public bool isFinal()
    
public bool isAbstract()
    
public bool isPublic()
    
public bool isPrivate()
    
public bool isProtected()
    
public bool isStatic()
    
public bool isConstructor()
    
public bool isDestructor()
    
public int getModifiers()
    
public ReflectionClass getDeclaringClass()

    
// Herdado de ReflectionFunction
    
final private __clone()
    
public string getName()
    
public bool isInternal()
    
public bool isUserDefined()
    
public string getFileName()
    
public int getStartLine()
    
public int getEndLine()
    
public string getDocComment()
    
public array getStaticVariables()
    
public bool returnsReference()
    
public ReflectionParameter[] getParameters()
    
public int getNumberOfParameters()
    
public int getNumberOfRequiredParameters()
}
?>

Para introspectar um método, você tem que primeiro criar uma instância da classe ReflectionMethod. Você pode, então, chamar qualquer um dos métodos acima nessa instância.

Exemplo 19-35. Usando a classe ReflectionMethod

<?php
class Counter
{
    
private static $c = 0;

    
/**
     * Incrementa o contador
     *
     * @final
     * @static
     * @access  public
     * @return  int
     */
    
final public static function increment()
    {
        return ++
self::$c;
    }
}

// Cria uma instância da classe ReflectionMethod
$method = new ReflectionMethod('Counter', 'increment');

// Imprime informação básica
printf(
    
"===> The %s%s%s%s%s%s%s method '%s' (which is %s)\n" .
    
"     declared in %s\n" .
    
"     lines %d to %d\n" .
    
"     having the modifiers %d[%s]\n",
        
$method->isInternal() ? 'internal' : 'user-defined',
        
$method->isAbstract() ? ' abstract' : '',
        
$method->isFinal() ? ' final' : '',
        
$method->isPublic() ? ' public' : '',
        
$method->isPrivate() ? ' private' : '',
        
$method->isProtected() ? ' protected' : '',
        
$method->isStatic() ? ' static' : '',
        
$method->getName(),
        
$method->isConstructor() ? 'the constructor' : 'a regular method',
        
$method->getFileName(),
        
$method->getStartLine(),
        
$method->getEndline(),
        
$method->getModifiers(),
        
implode(' ', Reflection::getModifierNames($method->getModifiers()))
);

// Imprime comentários de documentação
printf("---> Documentation:\n %s\n", var_export($method->getDocComment(), 1));

// Imprime variáveis estáticas se existirem
if ($statics= $method->getStaticVariables()) {
    
printf("---> Static variables: %s\n", var_export($statics, 1));
}

// Invoke the method
printf("---> Invokation results in: ");
var_dump($method->invoke(NULL));
?>

Nota: Tentar invocar métodos privados, protegidos ou abstratos resultarão numa exceção sendo disparada do método invoke()

Nota: Para métodos estáticos como visto acima, você deve passar NULL como o primeiro argumento para invoke(). Para métodos não-estáticos, passe uma instância da classe.

ReflectionProperty

A classe ReflectionProperty permite fazer engenharia-reversa das propriedades da classe.

<?php
class ReflectionProperty implements Reflector
{
    
final private __clone()
    
public __construct(mixed class, string name)
    
public string __toString()
    
public static string export()
    
public string getName()
    
public bool isPublic()
    
public bool isPrivate()
    
public bool isProtected()
    
public bool isStatic()
    
public bool isDefault()
    
public int getModifiers()
    
public mixed getValue(stdclass object)
    
public void setValue(stdclass object, mixed value)
    
public ReflectionClass getDeclaringClass()
}
?>

Para introspectar um método, você tem que primeiro criar uma instância da classe ReflectionProperty. Você pode, então, chamar qualquer um dos métodos acima nessa instância.

Exemplo 19-36. Usando a classe ReflectionProperty

<?php
class String
{
    
public $length  = 5;
}

// Cria uma instância da classe ReflectionProperty
$prop = new ReflectionProperty('String', 'length');

// Imprime informação básica
printf(
    
"===> The%s%s%s%s property '%s' (which was %s)\n" .
    
"     having the modifiers %s\n",
        
$prop->isPublic() ? ' public' : '',
        
$prop->isPrivate() ? ' private' : '',
        
$prop->isProtected() ? ' protected' : '',
        
$prop->isStatic() ? ' static' : '',
        
$prop->getName(),
        
$prop->isDefault() ? 'declared at compile-time' : 'created at run-time',
        
var_export(Reflection::getModifierNames($prop->getModifiers()), 1)
);

// Cria uma instância de String
$obj= new String();

// Pega o valor atual
printf("---> Value is: ");
var_dump($prop->getValue($obj));

// Muda o valor
$prop->setValue($obj, 10);
printf("---> Setting value to 10, new value is: ");
var_dump($prop->getValue($obj));

// Destrói o objeto
var_dump($obj);
?>

Nota: Tentar pegar ou editar o valor de propriedades privadas ou protegidaas de uma classe resultará no disparo de uma exceção.

ReflectionExtension

A classe ReflectionExtension permite fazer engenharia-reversa de extensões. Você pode recuperar todas as extensões em tempo de execução usando a função get_loaded_extensions().

<?php
class ReflectionExtension implements Reflector {
    
final private __clone()
    
public __construct(string name)
    
public string __toString()
    
public static string export()
    
public string getName()
    
public string getVersion()
    
public ReflectionFunction[] getFunctions()
    
public array getConstants()
    
public array getINIEntries()
    
public ReflectionClass[] getClasses()
    
public array getClassNames()
}
?>

Para introspectar um método, você tem que primeiro criar uma instância da classe ReflectionProperty. Você pode, então, chamar qualquer um dos métodos acima nessa instância.

Exemplo 19-37. Usando a classe ReflectionExtension

<?php
// Cria uma instância da classe ReflectionProperty
$ext = new ReflectionExtension('standard');

// Imprime informação básica
printf(
    
"Name        : %s\n" .
    
"Version     : %s\n" .
    
"Functions   : [%d] %s\n" .
    
"Constants   : [%d] %s\n" .
    
"INI entries : [%d] %s\n" .
    
"Classes     : [%d] %s\n",
        
$ext->getName(),
        
$ext->getVersion() ? $ext->getVersion() : 'NO_VERSION',
        
sizeof($ext->getFunctions()),
        
var_export($ext->getFunctions(), 1),

        
sizeof($ext->getConstants()),
        
var_export($ext->getConstants(), 1),

        
sizeof($ext->getINIEntries()),
        
var_export($ext->getINIEntries(), 1),

        
sizeof($ext->getClassNames()),
        
var_export($ext->getClassNames(), 1)
);
?>

Herdando as classes de reflexão

Caso você queira criar versões especializdas das classes built-in (digamos, para criar HTML colorido quando sendo exportado, tendo variáveis membros de fácil acesso ao invés de métodos ou tendo métodos utilitários), você pode herdá-las.

Exemplo 19-38. Herdando as classes built-in

<?php
/**
* Minha classe ReflectionMethod
*/
class My_Reflection_Method extends ReflectionMethod
{
    
public $visibility = '';

    
public function __construct($o, $m)
    {
        
parent::__construct($o, $m);
        
$this->visibility= Reflection::getModifierNames($this->getModifiers());
    }
}

/**
* Classe demo #1
*
*/
class T {
    
protected function x() {}
}

/**
* Classe demo #2
*
*/
class U extends T {
    function
x() {}
}

// Imprime informação
var_dump(new My_Reflection_Method('U', 'x'));
?>

Nota: Cuidado: Se você estiver sobrescrevendo um construtor, lembre-se de chamar o construtor do pai _antes_ de qualquer código que você acrescentar. Não fazer isso resultará no seguinte: Fatal error: Internal error: Failed to retrieve the reflection object