Rector é uma ferramenta reconstrutora para PHP. Ele pega o código-fonte e as regras de transformação como entradas e modifica o código de acordo com as regras como saída.

Mesmo que não pensemos nisso, costumamos usar ferramentas para transformar o código PHP. Por exemplo, PHP CodeSniffer pode validar se o código obedece aos padrões PSR e, quando não , executando o comando phpcbf pode corrigi-lo automaticamente. Ou PHP-Scoper definirá o escopo das dependências no projeto para evitar possíveis conflitos.

O Rector é diferente dessas ferramentas por ser um meta-aplicativo. Não tem um objetivo predeterminado, como fixar estilos ou definir o escopo do projeto. Em vez disso, ele transformará o código seguindo as regras, para quaisquer que sejam as regras fornecidas. Então, Rector pode reproduzir perfeitamente PHP CodeSniffer, PHP-Scoper ou qualquer outra ferramenta de transformação de código.

Neste artigo, compartilharei algumas dicas para criar regras no Rector.

Os pilares do reitor

Rector está sobre os ombros de dois gigantes:

  • PHP Parser : uma biblioteca que analisa o código PHP, permitindo a análise e manipulação de código estático
  • PHPStan : uma ferramenta de análise estática

Graças ao PHP Parser, Rector pode manipular o código usando nós em um AST (abreviação de Abstract Syntax Tree ). E graças ao PHPStan, Rector pode entender o código, portanto, é capaz de mapear, navegar e validar os relacionamentos entre as entidades no código, como obter o ancestral de uma classe ou todas as suas interfaces implementadas.

É uma boa ideia ter um conhecimento básico dessas duas bibliotecas antes de começar com o Rector e continuar aprendendo com sua documentação enquanto trabalhamos com o Rector. Na verdade, quanto mais complexa for a regra do Reitor, mais importante se torna ter um bom domínio dessas duas bibliotecas.

Quais são as regras do reitor?

Uma regra é uma classe PHP herdada de AbstractRector , que executa as transformações nos nós do AST (correspondente ao arquivo PHP analisado).

É composto de três métodos principais, que devemos satisfazer:

  • getRuleDefinition : usado para documentar a regra
  • getNodeTypes : em que tipo de nó a regra será aplicada
  • refactor : lógica para produzir o novo nó AST

Por exemplo, a regra DowngradeNullCoalescingOperatorRector substitui o operador ??= introduzido no PHP 7.4 por seu equivalente no PHP 7.3. Ele tem esta implementação: >

 use PhpParser \ Node;
use PhpParser \ Node \ Expr \ Assign;
use PhpParser \ Node \ Expr \ AssignOp \ Coalesce como AssignCoalesce;
use PhpParser \ Node \ Expr \ BinaryOp \ Coalesce;
use Rector \ Core \ Rector \ AbstractRector;
use Symplify \ RuleDocGenerator \ ValueObject \ CodeSample \ CodeSample;
use Symplify \ RuleDocGenerator \ ValueObject \ RuleDefinition; classe final DowngradeNullCoalescingOperatorRector estende AbstractRector
{ public function getRuleDefinition (): RuleDefinition { return new RuleDefinition ('Remover operador de coalescência nulo ??=', [ new CodeSample ( <<<'CODE_SAMPLE'
$ array=[];
$ array ['user_id'] ??='valor';
CODE_SAMPLE , <<<'CODE_SAMPLE'
$ array=[];
$ array ['user_id']=$ array ['user_id'] ??'valor';
CODE_SAMPLE ), ]); } /** * @return string [] */ public function getNodeTypes (): array { return [AssignCoalesce:: class]; } /** * @param AssignCoalesce $ node */ refator de função pública (Node $ node):? Node { retornar new Assign ($ node-> var, new Coalesce ($ node-> var, $ node-> expr)); }
} 

Vamos ver como funciona.

getRuleDefinition

Devemos fornecer um exemplo do código antes e depois da transformação. O Rector então usa esses dois estados para documentar as mudanças, usando o formato diff, como feito aqui :

 $ array=[];
-$ array ['user_id'] ??='valor'; 
 + $ array ['user_id']=$ array ['user_id'] ??'valor'; 

getNodeTypes

Nesta função, indicamos em qual nó da AST a transformação será aplicada. Esses nós vêm diretamente do PHP Parser .

No exemplo acima, a regra é aplicada apenas em nós do tipo Coalesce (apelidado de AssignCoalesce ), que é o nó que representa ??=.

Alguns exemplos de outros nós são:

  • FuncCall : sempre que chamar uma função, como var_dump("hello")
  • MethodCall : sempre que chamar um método de uma classe, como $foo->bar()
  • Atribuir : ao atribuir um valor via =
  • Equal , NotEqual , Identical e NotIdentical : sempre que usar o operador binário ==, !=, === ou !==, respectivamente

refactor

Esta função realiza a transformação, se necessário. Possui tipo de retorno ? Node , o que significa:

  • Retorne um novo nó, que substituirá o nó antigo; ou
  • Retorne null , para significar nenhuma mudança

Observe que retornar null significa “não modifique o nó”; não significa “remover o nó”.

A regra acima visa substituir $ foo ??=$ bar por seu equivalente $ foo=$ foo ?? $ bar . A função refactor então retorna este novo nó:

 return new Assign ( $ node-> var, novo Coalesce ( $ node-> var, $ node-> expr )
); 

O novo nó é do tipo Atribuir , que é o = em $ foo=$ foo ?? $ bar . Este tipo requer dois elementos:

  • A variável $ foo , que é recuperada do nó original, como $node->var
  • A expressão $ foo ?? $ bar

Para criar a expressão, aninhamos um novo nó nela, do tipo [Coalesce] ( https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php ) , que é o ?? em $ foo ?? $ bar . O operador de coalescência requer dois elementos:

  • A expressão à esquerda $ foo , que é recuperada do nó original como $node->var
  • A expressão à direita $ bar , que é recuperada do nó original como $node->expr

Este exemplo mostra o conceito básico do que envolve a criação de uma regra:

  • Descubra qual novo nó satisfaz o código-alvo
  • Identifique os dados de que necessita
  • Dados de porta (variáveis, expressões) do nó antigo para o novo nó

Reutilizando código de regras existentes

No momento em que este artigo foi escrito, o repo Rector fornece quase 700 regras , envolvendo transformações de muitos tipos. Essas regras existentes são uma fonte maravilhosa para nos ajudar a implementar nossas próprias regras personalizadas.

Portanto, este é o meu conselho: sempre que precisar criar uma regra personalizada, verifique primeiro se uma lógica semelhante já foi codificada em alguma das regras existentes. Provavelmente, haverá.

Por exemplo, implementei a regra -strip_tags ($ string, ['a','p']); + strip_tags ($ string,'<'. implode ('> <', ['a','p']).'>');

Agora, podemos não saber o tipo do parâmetro durante a análise estática. Por exemplo, esta função retorna uma string ou uma matriz:

 function getStringOrArray () { if (rand (0, 1)) { return ['a','p']; } return'ap';
}

Então, nosso código precisa verificar o tipo do parâmetro durante o tempo de execução:

 -strip_tags ($ string, getStringOrArray ()); 
 + strip_tags ($ string, is_array (getStringOrArray ())? ('<'. implode ('> <', getStringOrArray ()).'>'): getStringOrArray ()); 

Mas agora temos um problema: a função getStringOrArray () é executada duas vezes, o que pode ser caro ou, pior ainda, pode produzir efeitos colaterais indesejados (por exemplo, se aumentar um contador global , ele fará isso duas vezes).

Portanto, a solução é atribuir o valor de getStringOrArray () a uma variável primeiro:

 -strip_tags ($ string, getStringOrArray ()); 
 + $ var=getStringOrArray ();
+ strip_tags ($ string, is_array ($ var)? ('<'. implode ('> <', $ var).'>'): $ var); 

Mas então, não posso escolher aleatoriamente o nome da variável como $ var (ou qualquer outra coisa), pois ela pode já existir e eu estaria substituindo seu valor:

 $ var="blá, blá, blá";
-strip_tags ($ string, getStringOrArray ()); 
 + $ var=getStringOrArray ();
+ strip_tags ($ string, is_array ($ var)? ('<'. implode ('> <', $ var).'>'): $ var);  var_dump ($ var);
//Ele espera"blá, blá, blá". Obteve"ap"

Eu não tinha ideia de como lidar com isso. Então, eu naveguei na lista de todas as regras no repo , verificando se alguma delas resolveria esse problema, ou seja, criar uma nova variável com um nome não utilizado.

E eu encontrei. A regra ForRepeatedCountToOwnVariableRector faz esta transformação:

 classe SomeClass { execução de função pública ($ items) {
-para ($ i=5; $ i <=count ($ items); $ i ++) {
 + $ itemsCount=count ($ items);
+ para ($ i=5; $ i <=$ itemsCount; $ i ++) { echo $ items [$ i]; } } }

A variável $ itemsCount está sendo criada do nada. Verificando como isso é feito, descobri o serviço VariableNaming , que pode identificar se a variável $ itemsCount já existe. Em caso afirmativo, ele tenta novamente $ itemsCount2 e assim por diante até encontrar um nome de variável que não foi adicionado.

Depois, copiei/colei a lógica para usar o serviço, daqui :

 $ variableName=$ this-> variableNaming-> resolveFromFuncCallFirstArgumentWithSuffix ( $ node, 'Contar', 'itemsCount', $ forScope
);

Como uma observação geral, acho o código-fonte no repositório Rector bastante elegante. Eu particularmente gosto que ele faz uso extensivo dos componentes do Symfony , incluindo injeção de dependência, comandos CLI e localizador de arquivos e diretórios. E aprendi um pouco sobre as práticas recomendadas de programação durante a navegação, então recomendo que você também faça.

Dicas para teste

Aqui estão algumas dicas úteis para testar regras conectadas ao PHPUnit .

Ao executar phpunit para testar uma regra, se a regra tem muitos testes e apenas um está falhando, podemos executar apenas aquele passando --filter=test # X , onde X é o número do pedido do teste de fixação.

Por exemplo, ao executar o seguinte:

 vendor/bin/phpunit rules/solid/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/FinalizeClassesWithoutChildrenRectorTest.php 

Eu obteria este erro:

 Houve 1 falha: 1) Rector \ DowngradePhp73 \ Tests \ Rector \ List_ \ DowngradeListReferenceAssignmentRector \ DowngradeListReferenceAssignmentRectorTest:: teste com conjunto de dados # 4 (objeto Symplify \ SmartFileSystem \ SmartFileInfo (...))
rules/downgrade-php73/tests/Rector/List_/DowngradeListReferenceAssignmentRector/Fixture/nested_list.php.inc
Falha ao afirmar que a string corresponde à descrição do formato. 

Pelo erro, podemos dizer que o teste nested_list.php.inc é # 4 , então eu pude executar apenas aquele teste assim:

 vendor/bin/phpunit rules/solid/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/FinalizeClassesWithoutChildrenRectorTest.php--filter=test # 4 

Isso é útil para depuração, para executar o método rápido e fácil de despejar a saída na tela de modo a visualizar onde pode estar o problema.

Se precisarmos despejar o conteúdo de um nó, podemos fazer isso dentro da classe de regra, como este:

 dump ($ this-> print ($ node));
morrer;

Devemos usar dump , de componente VarDumper do Symfony , em vez de var_dump porque:

  • Formata a saída para torná-la mais compreensível
  • O nó pode conter referências cíclicas e dump os identifica e interrompe, mas var_dump não, então a saída na tela continuaria indefinidamente

Conclusão

Rector é uma ferramenta maravilhosa para transformar o código PHP. Estou usando para Transpilar meu aplicativo de PHP 7.4 para 7.1 para que eu possa codificá-lo usando recursos PHP modernos, mas implantá-lo no ambiente com suporte de meus clientes.

A postagem Dicas para criar sua primeira regra de reitor para transformar O código PHP apareceu primeiro no LogRocket Blog .

Source link