API de Reflexão do PHP


O PHP implementa uma API de reflexão que permite com que façamos “engenharia reversa” para extrair e até mesmo alterar características internas de classes, interfaces, métodos e até mesmo de extensões, tudo isso em tempo de execução.

Os casos de uso para reflexão não costumam ser tão comuns no dia a dia de desenvolvimento, mas podemos destacar alguns:

  • Core de frameworks (para alguns comportamentos especiais como, por exemplo, no container de injeção de dependência para saber se determinada classe pode ser instanciada ou não);
  • Para testes (frameworks de teste usam principalmente para mocks);
  • Extrair informações de classes e gerar documentação;
  • Criar hydrators que extraiam/alimentem objetos com dados de forma dinâmica;
  • Extrair comentários de classes e criar meta-dados estendendo o comportamento delas (anotações);

Tudo sobre a API de reflexão você pode consultar direto na documentação oficial do PHP. Nesse artigo vou passar os aspectos que considero principais.

Desenvolvedor PHP Júnior
Formação: Desenvolvedor PHP Júnior
Nesta formação você aprenderá todos os fundamentos necessário para iniciar do modo correto com a linguagem PHP, uma das mais utilizadas no mercado. Além dos conceitos de base, você também conhecerá as características e a sintaxe da linguagem de forma prática.
CONHEÇA A FORMAÇÃO

Um exemplo elementar pra dar a ideia do que é possível extrair:

<?php
/** @psalm-immutable */final class Email
{
public string $email;
public function __construct(string $email)
{
$this->email = $email;
}
}
$reflectionClass = new ReflectionClass('Email');
echo $reflectionClass->getName() . PHP_EOL;
echo ($reflectionClass->isFinal() ? 'Final' : 'Not final') . PHP_EOL;
echo $reflectionClass->getDocComment() . PHP_EOL;
echo $reflectionClass->getConstructor()->getNumberOfParameters() . PHP_EOL;
echo PHP_EOL . 'Property:' . PHP_EOL . PHP_EOL;
/** @var ReflectionProperty $property */$property = $reflectionClass->getProperties()[0];
echo $property->getName() . PHP_EOL;
echo ($property->isPrivate() ? 'public' : 'not public') . PHP_EOL;
echo $property->getType() . PHP_EOL;

O resultado da execução desse exemplo:

Email
Final
/** @psalm-immutable */1
Property:
email
not public
string

A ReflectionClass é responsável por extrair os dados de uma classe. Extraímos o nome da classe, se ela é final, o comentário associado a ela e o número de argumentos do seu construtor. O método getConstructor()retorna uma instância de ReflectionMethod. Depois, usando getProperties(), obtemos um array com os atributos da classe, mas na forma de instâncias de ReflectionProperty, que provê outros métodos de acesso.

Se quisermos dar uma espécie de var_dump()em uma classe:

<?php
/** @psalm-immutable */final class Email
{
public string $email;
public function __construct(string $email)
{
$this->email = $email;
}
}
Reflection::export(new ReflectionClass('Email'));

O resultado será:

/** @psalm-immutable */Class [ <user> final class Email ] {
@@ /home/kennedy/Documents/www/php-reflection/index.php 4-12
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [1] {
Property [ <default> public $email ]
}
- Methods [1] {
Method [ <user, ctor> public method __construct ] {
@@ /home/kennedy/Documents/www/php-reflection/index.php 8 - 11
- Parameters [1] {
Parameter #0 [ <required> string $email ]
}
}
}
}

Podemos extrair diretamente as informações de um método sem precisar usar a ReflectionClass:

<?php
final class Node
{
private Node $next;
private function next(): Node
{
return $this->next;
}
}
$reflectionMethod = new ReflectionMethod('Node', 'next');
echo $reflectionMethod->getName() . PHP_EOL; // next
echo ($reflectionMethod->isPrivate() ? 'private' : 'not private') . PHP_EOL; // private
echo $reflectionMethod->getReturnType() . PHP_EOL; // Node

Da mesma forma que ReflectionClass, também temos a ReflectionFunction:

<?php
function foo(int $bar) : int {
return $bar + 1;
}
$reflectionFunction = new ReflectionFunction('foo');
echo $reflectionFunction->getName() . PHP_EOL; // foo
echo $reflectionFunction->getReturnType() . PHP_EOL; // int
$parameter = $reflectionFunction->getParameters()[0];
echo $parameter->getName() . PHP_EOL; // bar
echo $parameter->getType() . PHP_EOL; // int
var_dump($reflectionFunction->getClosure()); // class Closure#2 (1) {

Outra classe muito importante do “combo” é a ReflectionObject, que trabalha diretamente em uma instância de objeto:

<?php
final class Node
{
private int $value;
private ?Node $next;
public function __construct(int $value, ?Node $next = null)
{
$this->value = $value;
$this->next = $next;
}
private function next(): Node
{
return $this->next;
}
public function value(): int
{
return $this->value;
}
}
$node = new Node(1, new Node(2));
$reflectionObject = new ReflectionObject($node);
echo $reflectionObject->getName() . PHP_EOL; // Node
echo $reflectionObject->getConstructor()->getNumberOfParameters() . PHP_EOL; // 2
echo $reflectionObject->getMethod('next')->getReturnType() . PHP_EOL; // Node
echo $reflectionObject->getMethod('value')->getReturnType() . PHP_EOL; // int

Existem outras classes e elas possuem outros vários métodos a serem explorados. Mas o que vimos até aqui é o essencial para que avancemos um pouco mais em exemplos palpáveis, do mundo real.

Um caso prático pra uso de reflexão

Criaremos uma classe transporter base. Um transporter é um conceito para transacionar dados (do input do usuário, por exemplo) entre objetos. Se você já ouviu falar de DTO (Data Transfer Objects), é basicamente isso, mas com outro nome, só pra causar confusão mesmo. 😛

Mas deixando a ladainha de lado, show me the code:

<?php
declare(strict_types=1);
abstract class Transporter
{
public function __construct(array $properties = [])
{
$reflection = new ReflectionObject($this);
foreach ($properties as $name => $value) {
$property = $reflection->getProperty($name);
if ($property->isPublic() || !$property->isStatic()) {
$this->$name = $value;
}
}
}
public function toArray(): array
{
$reflection = new ReflectionObject($this);
return array_map(
function (ReflectionProperty $property) {
return [
$property->getName() => $property->getValue($this)
];
},
$reflection->getProperties(ReflectionProperty::IS_PUBLIC)
);
}
}
final class UserTransporter extends Transporter
{
public int $age;
public string $firstName;
public string $lastName;
}
$transporter = new UserTransporter([
'age' => 29,
'firstName' => 'Kennedy',
'lastName' => 'Parreira',
]);
echo $transporter->age . PHP_EOL;
echo $transporter->firstName . PHP_EOL;
echo $transporter->lastName . PHP_EOL;
var_dump($transporter->toArray());

O resultado:

29
Kennedy
Parreira
/home/kennedy/Documents/www/php-reflection/index.php:50:
array(3) {
[0] =>
array(1) {
'age' =>
int(29)
}
[1] =>
array(1) {
'firstName' =>
string(7) "Kennedy"
}
[2] =>
array(1) {
'lastName' =>
string(8) "Parreira"
}
}

Esse exemplo usou basicamente o que já tínhamos visto anteriormente. Ele permite que recebamos os dados via array (no construtor) e então alimenta os atributos do objeto dinamicamente. O método toArray()itera nos atributos públicos e não estáticos da classes e os retorna em um array.

Queue jobs do Laravel

Outro caso de uso interessante de se passar aqui, pra mostrar como reflexão pode ser útil em código de framework, refere-se às Queue Jobs do Laravel:

<?php
namespace AppJobs;
use AppPodcast;
use AppAudioProcessor;
use IlluminateBusQueueable;
use IlluminateQueueSerializesModels;
use IlluminateQueueInteractsWithQueue;
use IlluminateContractsQueueShouldQueue;
use IlluminateFoundationBusDispatchable;
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $podcast;
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
public function handle(AudioProcessor $processor)
{
// Process uploaded podcast...
}
}

Veja que essa classe implementa a interface ShouldQueue. No Dispacher de jobs tem um método que recebe o nome da classe pra decidir se ela deve entrar na fila ou não, e esse método usa reflexão para verificar se a classe implementa a interface ShouldQueue:

protected function handlerShouldBeQueued($class)
{
try {
return (new ReflectionClass($class))->implementsInterface(
ShouldQueue::class
);
} catch (Exception $e) {
return false;
}
}

Nesse contexto específico é melhor usar reflexão que futilmente gerar uma instância da classe para depois verificar se o objeto gerado implementa a requerida interface. Se fôssemos implementar instanciando a classe, ficaria mais ou menos assim:

protected function handlerShouldBeQueued($class)
{
$object = new $class();
return $object instanceof ShouldQueue;
}

(E note que nesse protótipo nem nos preocupamos com as possíveis dependências que o construtor de $classpoderia precisar receber).

Anotações

Você já deve ter visto libraries/componentes que estendem seus comportamentos a partir de anotações. Uma delas é a Annotations, para Laravel, que permite definir comportamentos diretamente por anotações nas classes, por exemplo:


/** * @Middleware("guest", except={"logout"}, prefix="/your/prefix") */class AuthController extends Controller { /** * @Get("logout", as="logout") * @Middleware("auth") */public function logout() { $this->auth->logout(); return redirect(route('login')); } }

E usa-se reflexão para obter essas anotações.

Palavras finais

É bem provável que no dia a dia de desenvolvimento no código de aplicação não usemos reflexão, mas são uma importante ferramenta, principalmente para libraries e frameworks.

Até a próxima!

Desenvolvedor PHP Pleno
Formação: Desenvolvedor PHP Pleno
Nesta formação você aprenderá aspectos mais avançados da linguagem PHP, indo adiante nas características da linguagem, orientação a objetos, práticas de mercado, além da parte de integração com banco de dados. Ao final, estará apto a desenvolver sistemas usando banco de dados relacionais.
CONHEÇA A FORMAÇÃO




Fonte Kennedy Tedesco
Data da Publicação Original: 20 March 2020 | 1:50 pm


You may also like

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *