Reflection no PHP
Reflection(ou reflexão) é uma técnica onde se pode manipular o próprio programa como sendo seus dados, permitindo assim modificar sua estrutura. Ela permite realizar a engenharia reversa de um classe em tempo de execução, recuperando nomes de métodos para verificar sua existência, descobrir seus parâmetros e tipos, mudar visibilidade de propriedades, invocar métodos dinamicamente entre outras funcionalidades. Ela é muito utilizada em frameworks, por exemplo de testes onde declaramos vários métodos com o nome iniciando com a palavra ‘test‘ e eles são invocados automaticamente.
Para começar precisamos criar uma objeto da ReflectionClass
a partir da classe que queremos manipular. Para nosso exemplo vamos utilizar uma classe simples de exemplo: Cachorro
.
class Cachorro{
private string $nome;
private int $idade;
public function __construct(string $nome, int $idade)
{
$this->nome = $nome;
$this->idade = $idade;
}
public function getNome(): string
{
return $this->nome;
}
public function setNome(string $nome): void
{
$this->nome = $nome;
}
public function latir(): void
{
echo 'au au au!';
}
}
Agora criamos a classe RefletionClass
a partir dela.
include './Cachorro.php';
$refClass = new ReflectionClass( Cachorro::class );
//$refClass = new ReflectionClass( 'Cachorro' );
//$cachorro = new Cachorro('Fifi',3);
//$refClass = new ReflectionClass( $cachorro );
echo $refClass->getName() . PHP_EOL;
Como podemos ver podemos instanciar a ReflectionClass
passando para seu construtor tanto o nome da classe em string(usando a palavra chave class) ou passando um objeto já instanciado.
A partir do objeto $refClass
podemos pegar as informações da classe, no caso utilizamos o método getName
para pegar o nome dela.
Manipulando Propriedades
Para manipular as propriedades precisamos primeiro recuperá-las. Podemos pegar todas utilizando o método getProperties
que retorna um array de objetos ReflectionProperty
$attrs = $refClass->getProperties();
foreach($attrs as $attr){
echo $attr->getName() . PHP_EOL;
}
Também podemos pegar uma propriedade individual pelo seu nome, através do método getProperty
que retorna um objeto ReflectionProperty
.
if($refClass->hasProperty('nome')){
$attr = $reflectionClass->getProperty('nome');
echo $attr->getName() . PHP_EOL;
}
Se tentarmos pegar uma propriedade que não existe um erro será gerado, então é interessante verificar se a classe realmente possui a propriedade que estamos tentando acessar. Isto pode ser feito utilizando o método hasProperty
passando o nome da propriedade.
Com um objeto ReflectionProperty
podemos acessar os dados da propriedade como nome(getName
), tipo(getType
) e seu valor(getValue
), lembrando que para utilizar o método getValue
é necessário passar um objeto instanciado para ele obter o valor do atributo.
$cachorro = new Cachorro('Fifi',3);
$attr = $refClass->getProperty('nome');
echo $attr->getName() . PHP_EOL;
echo $attr->getType() . PHP_EOL;
echo $attr->getValue($cachorro) . PHP_EOL;
Se você executar o código acima receberá um erro na linha 7, isso se deve a que a propriedade nome é privada e não pode ser acessada , podemos contornar isso já que estamos utilizando Reflection, podemos deixar o atributo visível chamando o método setAccessible
, mas antes disso podemos verificar a visibilidade dele através do método isPrivate
, que, como o próprio nome sugere, retorna true se o método é privado ou false se não é. Também temos os métodos isProtected
e isPublic
para verificar as outras visibilidades.
if($attr->isPrivate()){
$attr->setAccessible(true);
}
echo $attr->getValue($cachorro) . PHP_EOL;
Para modificar o valor de uma propriedade utilizamos o método setValue
passando o objeto que queremos modificar e o novo valor da propriedade
$cachorro = new Cachorro('Fifi',3);
$attr = $refClass->getProperty('nome');
if($attr->isPrivate()){
$attr->setAccessible(true);
}
$attr->setValue($cachorro, 'Sãosão');
echo $cachorro->getNome();
Manipulando Métodos
De forma semelhante podemos recuperar todos os métodos de uma classe utilizando o método getMethods
que retorna uma array de ReflectionMethod
.
$methods = $refClass->getMethods();
foreach($methods as $method){
echo $method->getName(). PHP_EOL;
}
Também podemos pegar um método pelo seu nome através de getMethod
, passando o nome do método, mas antes é interessante verificar se o método existe na classe, isto pode ser feito utilizando hasMethod
.
if( $refClass->hasMethod('setNome')){
$method = $refClass->getMethod('setNome');
echo $method->getName(). PHP_EOL;
}
Com um objeto ReflectionMethod
podemos manipular as informações do método, como pegar seu nome com getName
, ou pegar seu tipo de retorno com getReturnType
, ou verificar sua visibilidade através dos métodos isPrivate
, isProtected
ou isPublic
. Também podemos verificar se o método é o construtor da classe, o que pode ser interessante quando estamos percorrendo todos os métodos através do getMethods()
.
echo $method->getReturnType(). PHP_EOL;
echo ($method->isPrivate() ? 'Privado' : 'Não Privado') . PHP_EOL;
echo ($method->isProtected() ? 'Protegido' : 'Não Protegido') . PHP_EOL;
echo ($method->isPublic() ? 'Publico' : 'Não Publico') . PHP_EOL;
echo ($method->isConstructor() ? 'É o construtor' : 'Não é o construtor') . PHP_EOL;
Podemos pegar os parâmetros de um método através do getParameters
, que retorna um array de objetos ReflectionParameter
que fornece informações sobre o parâmetro como nome getName
, tipo getType
, se o parâmetro tem algum valor padrão com isDefaultValueAvailable
ou o próprio valor padrão com getDefaultValue
.
$params = $method->getParameters();
foreach($params as $param){
echo "Nome: {$param->getName()}" . PHP_EOL;
echo "Tipo: {$param->getType()}" . PHP_EOL;
if($param->isDefaultValueAvailable()){
echo "Valor padrão: {$param->getDefaultValue()}". PHP_EOL;
}
echo '------------' . PHP_EOL;
}
Invocando um método via Reflection
Podemos invocar um método através de um objeto ReflectionMethod
, para isso chamamos invoke
passando o objeto instanciado do qual queremos executar o método.
$cachorro = new Cachorro('Fifi',3);
if( $refClass->hasMethod('latir')){
$method = $refClass->getMethod('latir');
$method->invoke($cachorro);
}
// irá chamar latir() exibindo 'au au au!'
Caso o método possua parâmetros podemos passar eles após o objeto, como no método setNome
que aceita uma string de nome.
$cachorro = new Cachorro('Fifi',3);
if( $refClass->hasMethod('setNome')){
$method = $refClass->getMethod('setNome');
$method->invoke($cachorro, 'Sãosão');
}
echo $cachorro->getNome();
Também podemos chamar um método passando seus parâmetros como um array através do método invokeArgs
onde o objeto é passado como primeiro parâmetro e o segundo o array com os parâmetros.
$cachorro = new Cachorro('Fifi',3);
if( $refClass->hasMethod('setNome')){
$method = $refClass->getMethod('setNome');
$method->invokeArgs($cachorro, ['Sãosão']);
}
echo $cachorro->getNome();
Instanciando um objeto através de ReflectionClass
Podemos criar um objeto a partir de um objeto ReflectionClass
, para isso chamamos o método newInstance
passando os parâmetros do construtor para o método.
$cachorro = $refClass->newInstance('fifi', 3);
echo $cachorro->getNome() . PHP_EOL;
$cachorro->latir();
Também podemos passar os argumentos como um array de forma semelhante feita com um método.
$cachorro = $refClass->newInstanceArgs([ 'fifi' , 3 ]);
echo $cachorro->getNome() . PHP_EOL;
echo $cachorro->latir();
Também é possível construir um objeto ignorando seu construtor, fazemos isso através do método newInstanceWithoutConstructor
. Mas devemos ter cuidado com as inicializações, por exemplo, se alguma propriedade estava sendo inicializada no construtor podemos ter erros de propriedade não inicializada ao utilizar algum método que a use. Por exemplo:
$cachorro = $refClass->newInstanceWithoutConstructor();
echo $cachorro->getNome() . PHP_EOL;
echo $cachorro->latir();
Bom era isso o uso básico de Reflection, o uso mais comum é em frameworks e ferramentas, mas é muito interessante entendermos pelo menos um pouco de como isso funciona. Mais métodos podem ser encontrados na documentação do PHP.
T++