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-27. Uso básico da API de reflexão

<?php
  Reflection
::export(new ReflectionClass('Exception'));
?>
Veríamos:
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> method __construct ] {
    }

    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 {
      
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 bool returnsReference()
      
public ReflectionParameter[] getParameters()
  }
?>

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-28. 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 um método.

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

Nota: isOptional() foi adicionado no 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-29. 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(
            
"-- Parâmetro #%d: %s {\n".
            
"   Classe: %s\n".
            
"   Permite NULL: %s\n".
            
"   Passado por referência: %s\n".
            
"   É opcional?: %s\n".
            
"}\n",
            
$i,
            
$param->getName(),
            
var_export($param->getClass(), 1),
            
var_export($param->allowsNull(), 1),
            
var_export($param->isPassedByReference(), 1),
            
$param->isOptional() ? 'sim' : 'não'
        
);
    }
?>

ReflectionClass

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

<?php
  
class ReflectionClass implements Reflector {
      
public __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 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 bool isInstantiable()
      
public bool isInterface()
      
public bool isFinal()
      
public bool isAbstract()
      
public int getModifiers()
      
public bool isInstance(stdclass object)
      
public stdclass newInstance(mixed* args)
      
public ReflectionClass[] getInterfaces()
      
public ReflectionClass getParentClass()
      
public bool isSubclassOf(ReflectionClass class)
  }
?>

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

Exemplo 19-30. Usando a classe ReflectionClass

<?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 static string export()
      
public mixed invoke(stdclass object, mixed* args)
      
public bool isFinal()
      
public bool isAbstract()
      
public bool isPublic()
      
public bool isPrivate()
      
public bool isProtected()
      
public bool isStatic()
      
public bool isConstructor()
      
public int getModifiers()
      
public ReflectionClass getDeclaringClass()

      
/* Herdado de ReflectionFunction */
      
public string __toString()
      
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()
  }
?>

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-31. Usando a classe ReflectionMethod

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

      
/**
       * Incrementa contador
       *
       * @final
       * @static
       * @access  public
       * @return  int
       */
      
final public static function increment()
      {
          
self::$c++;
          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));
  }

  
// Invoca o método
  
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 {
      
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-32. 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 {
      
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()
  }
?>

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-33. Usando a classe ReflectionExtension

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

  
// Imprima informação básica
  
printf(
      
"Name        : %s\n".
      
"Version     : %s\n".
      
"Functions   : [%d] %s\n".
      
"Constants   : [%d] %s\n".
      
"INI entries : [%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)
  );
?>

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-34. 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