O guia definitivo para transpilar código PHP
Publicados: 2021-09-22Em circunstâncias ideais, devemos usar o PHP 8.0 (a versão mais recente no momento em que escrevemos isso) para todos os nossos sites e atualizá-lo assim que uma nova versão for lançada. No entanto, os desenvolvedores geralmente precisam trabalhar com versões anteriores do PHP, como ao criar um plug-in público para WordPress ou trabalhar com código legado que impede a atualização do ambiente do servidor web.
Nessas situações, poderíamos perder a esperança de usar o código PHP mais recente. Mas existe uma alternativa melhor: ainda podemos escrever nosso código-fonte com PHP 8.0 e transpilá-lo para uma versão anterior do PHP — até mesmo para o PHP 7.1.
Neste guia, ensinaremos tudo o que você precisa saber sobre transpilar código PHP.
O que é transpilar?
A transpilação converte o código-fonte de uma linguagem de programação em um código-fonte equivalente da mesma ou de uma linguagem de programação diferente.
Transpilar não é um conceito novo no desenvolvimento web: os desenvolvedores do lado do cliente provavelmente estarão familiarizados com o Babel, um transpilador para código JavaScript.
O Babel converte o código JavaScript da versão ECMAScript 2015+ moderna em uma versão legada compatível com navegadores mais antigos. Por exemplo, dada uma função de seta ES2015:
[2, 4, 6].map((n) => n * 2);
…Babel irá convertê-lo em sua versão ES5:
[2, 4, 6].map(function(n) { return n * 2; });
O que é transpilar PHP?
O que é potencialmente novo no desenvolvimento web é a possibilidade de transpilar o código do lado do servidor, em particular o PHP.
A transpilação do PHP funciona da mesma forma que a transpilação do JavaScript: o código-fonte de uma versão moderna do PHP é convertido em um código equivalente para uma versão mais antiga do PHP.
Seguindo o mesmo exemplo anterior, uma função de seta do PHP 7.4:
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);
…pode ser transpilado em sua versão equivalente do PHP 7.3:
$nums = array_map( function ($n) { return $n * 2; }, [2, 4, 6] );
As funções de seta podem ser transpiladas porque são açúcar sintático, ou seja, uma nova sintaxe para produzir um comportamento existente. Esta é a fruta mais baixa.
No entanto, também existem novos recursos que criam um novo comportamento e, como tal, não haverá código equivalente para versões anteriores do PHP. Esse é o caso dos tipos de união, introduzidos no PHP 8.0:
function someFunction(float|int $param): string|float|int|null { // ... }
Nessas situações, a transpilação ainda pode ser feita desde que o novo recurso seja necessário para o desenvolvimento, mas não para a produção. Então, podemos simplesmente remover o recurso completamente do código transpilado sem consequências sérias.
Um exemplo são os tipos de união. Esse recurso é usado para verificar se não há incompatibilidade entre o tipo de entrada e seu valor fornecido, o que ajuda a evitar erros. Se houver um conflito com os tipos, haverá um erro já em desenvolvimento, e devemos pegá-lo e corrigi-lo antes que o código chegue à produção.
Portanto, podemos remover o recurso do código para produção:
function someFunction($param) { // ... }
Se o erro ainda ocorrer em produção, a mensagem de erro lançada será menos precisa do que se tivéssemos tipos de união. No entanto, essa desvantagem potencial é superada pela capacidade de usar tipos de união em primeiro lugar.
Vantagens de transpilar código PHP
A transpilação permite codificar um aplicativo usando a versão mais recente do PHP e produzir uma versão que também funciona em ambientes que executam versões mais antigas do PHP.
Isso pode ser particularmente útil para desenvolvedores que criam produtos para sistemas de gerenciamento de conteúdo (CMS) legados. O WordPress, por exemplo, ainda suporta oficialmente o PHP 5.6 (mesmo que recomende o PHP 7.4+). A porcentagem de sites WordPress que executam as versões 5.6 a 7.2 do PHP - que são todos End-of-Life (EOL), o que significa que não estão mais recebendo atualizações de segurança - chega a consideráveis 34,8%, e aqueles executados em qualquer versão do PHP que não seja 8.0 está em incríveis 99,5%:

Consequentemente, os temas e plugins do WordPress direcionados a um público global provavelmente serão codificados com uma versão antiga do PHP para aumentar seu alcance possível. Graças à transpilação, eles podem ser codificados usando PHP 8.0 e ainda serem liberados para uma versão mais antiga do PHP, visando o maior número possível de usuários.
De fato, qualquer aplicativo que precise suportar qualquer versão do PHP diferente da mais recente (mesmo dentro do intervalo das versões PHP atualmente suportadas) pode se beneficiar.
Este é o caso do Drupal, que requer PHP 7.3. Graças à transpilação, os desenvolvedores podem criar módulos Drupal disponíveis publicamente usando PHP 8.0 e liberá-los com PHP 7.3.
Outro exemplo é ao criar código personalizado para clientes que não podem executar o PHP 8.0 em seus ambientes por um motivo ou outro. No entanto, graças à transpilação, os desenvolvedores ainda podem codificar suas entregas usando PHP 8.0 e executá-las nesses ambientes legados.
Quando transpilar PHP
O código PHP sempre pode ser transpilado, a menos que contenha algum recurso do PHP que não tenha equivalente na versão anterior do PHP.
Esse é possivelmente o caso dos atributos, introduzidos no PHP 8.0:
#[SomeAttr] function someFunc() {} #[AnotherAttr] class SomeClass {}
No exemplo anterior usando funções de seta, o código pode ser transpilado porque as funções de seta são açúcar sintático. Os atributos, ao contrário, criam um comportamento completamente novo. Esse comportamento também pode ser reproduzido com o PHP 7.4 e inferior, mas apenas codificando-o manualmente, ou seja, não automaticamente com base em uma ferramenta ou processo (a IA pode fornecer uma solução, mas ainda não chegamos lá).
Atributos destinados ao uso de desenvolvimento, como #[Deprecated]
, podem ser removidos da mesma forma que os tipos de união são removidos. Mas os atributos que modificam o comportamento do aplicativo em produção não podem ser removidos e também não podem ser transpilados diretamente.
A partir de hoje, nenhum transpilador pode pegar código com atributos PHP 8.0 e produzir automaticamente seu código PHP 7.4 equivalente. Conseqüentemente, se seu código PHP precisar usar atributos, então transpilá-lo será difícil ou inviável.
Recursos PHP que podem ser transpilados
Esses são os recursos do PHP 7.1 e superiores que podem ser transpilados atualmente. Se seu código usa apenas esses recursos, você pode ter a certeza de que seu aplicativo transpilado funcionará. Caso contrário, você precisará avaliar se o código transpilado produzirá falhas.
Versão do PHP | Recursos |
---|---|
7.1 | Tudo |
7.2 | - tipo de object – alargamento do tipo de parâmetro – PREG_UNMATCHED_AS_NULL em preg_match |
7.3 | – Atribuições de referência em list() / desestruturação de array ( exceto dentro de foreach — #4376)– Sintaxe flexível de Heredoc e Nowdoc – Vírgulas à direita em chamadas de funções – set(raw)cookie aceita o argumento $option |
7.4 | – Propriedades digitadas – Funções de seta – Operador de atribuição de coalescência nulo – Desempacotando dentro de arrays – Separador literal numérico – strip_tags() com array de nomes de tags– tipos de retorno covariantes e tipos de parâmetros contravariantes |
8,0 | – Tipos de união – pseudo tipo mixed – tipo de retorno static – ::class magic constante em objetos– match expressões– catch exceções apenas por tipo– Operador de segurança nula – Promoção de propriedade de construtor de classe – Vírgulas à direita em listas de parâmetros e listas de use de fechamento |
Transpiladores PHP
Atualmente, existe uma ferramenta para transpilar código PHP: Rector.
Rector é uma ferramenta de reconstrução PHP, que converte código PHP com base em regras programáveis. Nós inserimos o código-fonte e o conjunto de regras a serem executados, e o Rector transformará o código.
O Rector é operado via linha de comando, instalado no projeto via Composer. Quando executado, o Rector produzirá um “diff” (adições em verde, remoções em vermelho) do código antes e depois da conversão:

Qual versão do PHP para transpilar
Para transpilar código entre versões do PHP, as regras correspondentes devem ser criadas.
Hoje, a biblioteca Rector inclui a maioria das regras para transpilar código dentro do intervalo do PHP 8.0 a 7.1. Portanto, podemos transpilar com segurança nosso código PHP até a versão 7.1.
Existem também regras para transpilar do PHP 7.1 para o 7.0 e do 7.0 para o 5.6, mas não são exaustivas. O trabalho está em andamento para completá-los, então podemos eventualmente transpilar o código PHP para a versão 5.6.
Transpiling vs Backporting
Backporting é semelhante ao transpiling, mas mais simples. O código de backport não depende necessariamente de novos recursos de uma linguagem. Em vez disso, a mesma funcionalidade pode ser fornecida a uma versão mais antiga da linguagem simplesmente copiando/colando/adaptando o código correspondente da nova versão da linguagem.
Por exemplo, a função str_contains
foi introduzida no PHP 8.0. A mesma função para PHP 7.4 e abaixo pode ser facilmente implementada assim:
if (!defined('PHP_VERSION_ID') || (defined('PHP_VERSION_ID') && PHP_VERSION_ID < 80000)) { if (!function_exists('str_contains')) { /** * Checks if a string contains another * * @param string $haystack The string to search in * @param string $needle The string to search * @return boolean Returns TRUE if the needle was found in haystack, FALSE otherwise. */ function str_contains(string $haystack, string $needle): bool { return strpos($haystack, $needle) !== false; } } }
Porque o backporting é mais simples do que o transpiling, devemos optar por esta solução sempre que o backporting fizer o trabalho.
Com relação ao intervalo entre PHP 8.0 a 7.1, podemos usar as bibliotecas polyfill do Symfony:
- Polyfill PHP 7.1
- Polyfill PHP 7.2
- Polyfill PHP 7.3
- Polyfill PHP 7.4
- Polyfill PHP 8.0
Essas bibliotecas suportam as seguintes funções, classes, constantes e interfaces:
Versão do PHP | Recursos |
---|---|
7.2 | Funções:
Constantes:
|
7.3 | Funções:
Exceções:
|
7.4 | Funções:
|
8,0 | Interfaces:
Aulas:
Constantes:
Funções:
|
Exemplos de PHP Transpilado
Vamos inspecionar alguns exemplos de código PHP transpilado e alguns pacotes que estão sendo totalmente transpilados.
Código PHP
A expressão match
foi introduzida no PHP 8.0. Este código fonte:
function getFieldValue(string $fieldName): ?string { return match($fieldName) { 'foo' => 'foofoo', 'bar' => 'barbar', 'baz' => 'bazbaz', default => null, }; }
…será transpilado para sua versão equivalente do PHP 7.4, usando o operador switch
:
function getFieldValue(string $fieldName): ?string { switch ($fieldName) { case 'foo': return 'foofoo'; case 'bar': return 'barbar'; case 'baz': return 'bazbaz'; default: return null; } }
O operador nullsafe também foi introduzido no PHP 8.0:
public function getValue(TypeResolverInterface $typeResolver): ?string { return $this->getResolver($typeResolver)?->getValue(); }
O código transpilado precisa atribuir primeiro o valor da operação a uma nova variável, para evitar executar a operação duas vezes:
public function getValue(TypeResolverInterface $typeResolver): ?string { return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null; }
O recurso de promoção de propriedade do construtor, também introduzido no PHP 8.0, permite que os desenvolvedores escrevam menos código:
class QueryResolver { function __construct(protected QueryFormatter $queryFormatter) { } }
Ao transpilar para PHP 7.4, o código completo é produzido:
class QueryResolver { protected QueryFormatter $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
O código transpilado acima contém propriedades tipadas, que foram introduzidas no PHP 7.4. Transpilar esse código para PHP 7.3 os substitui por docblocks:
class QueryResolver { /** * @var QueryFormatter */ protected $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
Pacotes PHP
As seguintes bibliotecas estão sendo transpiladas para produção:
Biblioteca/descrição | Código/notas |
---|---|
Reitor Ferramenta de reconstrução PHP que torna possível a transpilação | - Código fonte – Código transpilado - Notas |
Padrões de codificação fácil Ferramenta para que o código PHP siga um conjunto de regras | - Código fonte – Código transpilado - Notas |
API GraphQL para WordPress Plugin que fornece um servidor GraphQL para WordPress | - Código fonte – Código transpilado - Notas |
Prós e contras de transpilar PHP
O benefício de transpilar PHP já foi descrito: ele permite que o código fonte use PHP 8.0 (ou seja, a versão mais recente do PHP), que será transformada em uma versão inferior para PHP para produção rodar em um aplicativo ou ambiente legado.
Isso efetivamente nos permite nos tornarmos melhores desenvolvedores, produzindo código com maior qualidade. Isso porque nosso código-fonte pode usar os tipos de união do PHP 8.0, as propriedades tipadas do PHP 7.4 e os diferentes tipos e pseudotipos adicionados a cada nova versão do PHP ( mixed
do PHP 8.0, object
do PHP 7.2), entre outros recursos modernos do PHP.
Usando esses recursos, podemos detectar melhor os bugs durante o desenvolvimento e escrever um código mais fácil de ler.
Agora, vamos dar uma olhada nas desvantagens.
Deve ser codificado e mantido
O Rector pode transpilar o código automaticamente, mas o processo provavelmente exigirá alguma entrada manual para fazê-lo funcionar com nossa configuração específica.
Bibliotecas de terceiros também devem ser transpiladas
Isso se torna um problema sempre que transpilá-los produz erros, pois devemos então mergulhar em seu código-fonte para descobrir o possível motivo. Se o problema puder ser corrigido e o projeto for de código aberto, precisaremos enviar uma solicitação pull. Se a biblioteca não for de código aberto, podemos encontrar um obstáculo.
Reitor não nos informa quando o código não pode ser transpilado
Se o código fonte contiver atributos PHP 8.0 ou qualquer outro recurso que não possa ser transpilado, não podemos prosseguir. No entanto, o Rector não verificará essa condição, portanto, precisamos fazê-lo manualmente. Isso pode não ser um grande problema em relação ao nosso próprio código-fonte, pois já estamos familiarizados com ele, mas pode se tornar um obstáculo em relação às dependências de terceiros.
As informações de depuração usam o código transpilado, não o código-fonte
Quando o aplicativo produz uma mensagem de erro com um rastreamento de pilha em produção, o número da linha apontará para o código transpilado. Precisamos converter de volta do código transpilado para o código original para encontrar o número da linha correspondente no código-fonte.
O Código Transpilado Também Deve Ser Prefixado
Nosso projeto transpilado e alguma outra biblioteca também instalada no ambiente de produção pode usar a mesma dependência de terceiros. Essa dependência de terceiros será transpilada para nosso projeto e manterá seu código-fonte original para a outra biblioteca. Portanto, a versão transpilada deve ser prefixada via PHP-Scoper, Strauss ou alguma outra ferramenta para evitar possíveis conflitos.
A transpilação deve ocorrer durante a integração contínua (CI)
Como o código transpilado substituirá naturalmente o código-fonte, não devemos executar o processo de transpilação em nossos computadores de desenvolvimento, ou corremos o risco de criar efeitos colaterais. A execução do processo durante uma execução de CI é mais adequada (mais sobre isso abaixo).
Como transpilar PHP
Primeiro, precisamos instalar o Rector em nosso projeto para desenvolvimento:
composer require rector/rector --dev
Em seguida, criamos um arquivo de configuração rector.php
no diretório raiz do projeto contendo os conjuntos de regras necessários. Para fazer o downgrade do código do PHP 8.0 para 7.1, usamos esta configuração:

use Rector\Set\ValueObject\DowngradeSetList; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->import(DowngradeSetList::PHP_80); $containerConfigurator->import(DowngradeSetList::PHP_74); $containerConfigurator->import(DowngradeSetList::PHP_73); $containerConfigurator->import(DowngradeSetList::PHP_72); };
Para garantir que o processo seja executado conforme o esperado, podemos executar o comando process
do Rector em modo seco, passando os locais a serem processados (neste caso, todos os arquivos na pasta src/
):
vendor/bin/rector process src --dry-run
Para realizar a transpilação, executamos o comando process
do Rector, que modificará os arquivos dentro de sua localização existente:
vendor/bin/rector process src
Observe: se executarmos o rector process
em nossos computadores de desenvolvimento, o código-fonte será convertido no local, sob src/
. No entanto, queremos produzir o código convertido em um local diferente para não substituir o código-fonte ao fazer o downgrade do código. Por esse motivo, executar o processo é mais adequado durante a integração contínua.
Otimizando o processo de transpilação
Para gerar uma entrega transpilada para produção, apenas o código para produção deve ser convertido; código necessário apenas para desenvolvimento pode ser ignorado. Isso significa que podemos evitar transpilar todos os testes (para nosso projeto e suas dependências) e todas as dependências para desenvolvimento.
Com relação aos testes, já saberemos onde estão localizados os do nosso projeto — por exemplo, na pasta tests/
. Também devemos descobrir onde estão as dependências — por exemplo, em suas subpastas tests/
, test/
e Test/
(para bibliotecas diferentes). Então, dizemos ao Rector para pular o processamento dessas pastas:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ // Skip tests '*/tests/*', '*/test/*', '*/Test/*', ]); };
Com relação às dependências, o Composer sabe quais são para desenvolvimento (as da entrada require-dev
em composer.json
) e quais são para produção (as da entrada require
).
Para recuperar do Composer os caminhos de todas as dependências para produção, executamos:
composer info --path --no-dev
Este comando produzirá uma lista de dependências com seu nome e caminho, assim:
brain/cortex /Users/leo/GitHub/leoloso/PoP/vendor/brain/cortex composer/installers /Users/leo/GitHub/leoloso/PoP/vendor/composer/installers composer/semver /Users/leo/GitHub/leoloso/PoP/vendor/composer/semver guzzlehttp/guzzle /Users/leo/GitHub/leoloso/PoP/vendor/guzzlehttp/guzzle league/pipeline /Users/leo/GitHub/leoloso/PoP/vendor/league/pipeline
Podemos extrair todos os caminhos e alimentá-los no comando Rector, que processará a pasta src/
do nosso projeto mais as pastas que contêm todas as dependências para produção:
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')" $ vendor/bin/rector process src $paths
Uma melhoria adicional pode impedir que o Rector processe essas dependências que já estejam usando a versão de destino do PHP. Se uma biblioteca foi codificada com PHP 7.1 (ou qualquer versão abaixo), não há necessidade de transpilá-la para PHP 7.1.
Para conseguir isso, podemos obter a lista de bibliotecas que requerem PHP 7.2 e superior e processar apenas aquelas. Obteremos os nomes de todas essas bibliotecas através do comando why-not
do Composer, assim:
composer why-not php "7.1.*" | grep -o "\S*\/\S*"
Como esse comando não funciona com o sinalizador --no-dev
, para incluir apenas dependências para produção, primeiro precisamos remover as dependências para desenvolvimento e gerar novamente o autoloader, executar o comando e adicioná-las novamente:
$ composer install --no-dev $ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*") $ composer install
O comando info --path
do compositor recupera o caminho para um pacote, com este formato:
# Executing this command $ composer info psr/cache --path # Produces this response: psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache
Executamos este comando para todos os itens em nossa lista para obter todos os caminhos para transpilar:
for package in $packages do path=$(composer info $package --path | cut -d' ' -f2-) paths="$paths $path" done
Por fim, fornecemos esta lista ao Rector (mais a pasta src/
do projeto):
Precisa de uma solução de hospedagem que lhe dê uma vantagem competitiva? Kinsta cobre você com velocidade incrível, segurança de última geração e dimensionamento automático. Confira nossos planos
vendor/bin/rector process src $paths
Armadilhas a evitar ao transpilar o código
Transpilar código pode ser considerado uma arte, muitas vezes exigindo ajustes específicos para o projeto. Vamos ver alguns problemas que podemos encontrar.
As regras encadeadas nem sempre são processadas
Uma regra encadeada é quando uma regra precisa converter o código produzido por uma regra anterior.
Por exemplo, a biblioteca symfony/cache
contém este código:
final class CacheItem implements ItemInterface { public function tag($tags): ItemInterface { // ... return $this; } }
Ao transpilar do PHP 7.4 para o 7.3, a tag
de função deve sofrer duas modificações:
- O tipo de retorno
ItemInterface
deve primeiro ser convertido paraself
, devido à regraDowngradeCovariantReturnTypeRector
- O tipo de retorno
self
deve então ser removido, devido à regraDowngradeSelfTypeDeclarationRector
O resultado final deve ser este:
final class CacheItem implements ItemInterface { public function tag($tags) { // ... return $this; } }
No entanto, Rector apenas emite o estágio intermediário:
final class CacheItem implements ItemInterface { public function tag($tags): self { // ... return $this; } }
A questão é que o Reitor nem sempre pode controlar a ordem em que as regras são aplicadas.
A solução é identificar quais regras encadeadas não foram processadas e executar uma nova execução do Rector para aplicá-las.
Para identificar as regras encadeadas, executamos o Rector duas vezes no código-fonte, assim:
$ vendor/bin/rector process src $ vendor/bin/rector process src --dry-run
Na primeira vez, executamos o Rector como esperado, para executar o transpiling. Na segunda vez, usamos o --dry-run
para descobrir se ainda há alterações a serem feitas. Se houver, o comando sairá com um código de erro e a saída “diff” indicará quais regras ainda podem ser aplicadas. Isso significaria que a primeira execução não foi concluída, com alguma regra encadeada não sendo processada.

Uma vez que identificamos a regra (ou regras) encadeada não aplicada, podemos criar outro arquivo de configuração do Rector — por exemplo, rector-chained-rule.php
executará a regra ausente. Em vez de processar um conjunto completo de regras para todos os arquivos em src/
, desta vez, podemos executar a regra ausente específica no arquivo específico em que ela precisa ser aplicada:
// rector-chained-rule.php use Rector\Core\Configuration\Option; use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeSelfTypeDeclarationRector; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $services = $containerConfigurator->services(); $services->set(DowngradeSelfTypeDeclarationRector::class); $parameters = $containerConfigurator->parameters(); $parameters->set(Option::PATHS, [ __DIR__ . '/vendor/symfony/cache/CacheItem.php', ]); };
Por fim, informamos ao Rector em sua segunda passagem para usar o novo arquivo de configuração via input --config
:
# First pass with all modifications $ vendor/bin/rector process src # Second pass to fix a specific problem $ vendor/bin/rector process --config=rector-chained-rule.php
As dependências do compositor podem ser inconsistentes
As bibliotecas podem declarar uma dependência a ser programada para desenvolvimento (ou seja, sob require-dev
em composer.json
), mas ainda assim, referenciar algum código delas para produção (como em alguns arquivos em src/
, não tests/
).
Normalmente, isso não é um problema porque esse código pode não ser carregado na produção, portanto, nunca haverá um erro no aplicativo. No entanto, quando o Rector processa o código-fonte e suas dependências, ele valida que todo o código referenciado pode ser carregado. O Rector lançará um erro se algum arquivo referenciar algum pedaço de código de uma biblioteca não instalada (porque foi declarado necessário apenas para desenvolvimento).
Por exemplo, a classe EarlyExpirationHandler
do componente Cache do Symfony implementa a interface MessageHandlerInterface
do componente Messenger:
class EarlyExpirationHandler implements MessageHandlerInterface { //... }
No entanto, symfony/cache
declara que symfony/messenger
é uma dependência para desenvolvimento. Então, ao executar o Rector em um projeto que depende de symfony/cache
, ele lançará um erro:
[ERROR] Could not process "vendor/symfony/cache/Messenger/EarlyExpirationHandler.php" file, due to: "Analyze error: "Class Symfony\Component\Messenger\Handler\MessageHandlerInterface not found.". Include your files in "$parameters->set(Option::AUTOLOAD_PATHS, [...]);" in "rector.php" config. See https://github.com/rectorphp/rector#configuration".
Existem três soluções para este problema:
- Na configuração do Rector, ignore o processamento do arquivo que faz referência a esse trecho de código:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php', ]); };
- Baixe a biblioteca ausente e adicione seu caminho para ser carregado automaticamente pelo Rector:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::AUTOLOAD_PATHS, [ __DIR__ . '/vendor/symfony/messenger', ]); };
- Faça com que seu projeto dependa da biblioteca ausente para produção:
composer require symfony/messenger
Transpilação e Integração Contínua
Como mencionado anteriormente, em nossos computadores de desenvolvimento, devemos usar o --dry-run
ao executar o Rector, caso contrário, o código-fonte será substituído pelo código transpilado. Por esse motivo, é mais adequado executar o processo de transpilação real durante a integração contínua (CI), onde podemos criar executores temporários para executar o processo.
Um momento ideal para executar o processo de transpilação é ao gerar a liberação para nosso projeto. Por exemplo, o código abaixo é um fluxo de trabalho para GitHub Actions, que cria o lançamento de um plugin WordPress:
name: Generate Installable Plugin and Upload as Release Asset on: release: types: [published] jobs: build: name: Build, Downgrade and Upload Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Downgrade code for production (to PHP 7.1) run: | composer install vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php - name: Build project for production run: | composer install --no-dev --optimize-autoloader mkdir build - name: Create artifact uses: montudor/[email protected] with: args: zip -X -r build/graphql-api.zip . -x *.git* node_modules/\* .* "*/\.*" CODE_OF_CONDUCT.md CONTRIBUTING.md ISSUE_TEMPLATE.md PULL_REQUEST_TEMPLATE.md rector.php *.dist composer.* dev-helpers** build** - name: Upload artifact uses: actions/[email protected] with: name: graphql-api path: build/graphql-api.zip - name: Upload to release uses: JasonEtco/[email protected] with: args: build/graphql-api.zip application/zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Este fluxo de trabalho contém um procedimento padrão para liberar um plug-in do WordPress por meio do GitHub Actions. A nova adição, para transpilar o código do plugin do PHP 7.4 para o 7.1, acontece nesta etapa:
- name: Downgrade code for production (to PHP 7.1) run: | vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php
Juntos, esse fluxo de trabalho agora executa as seguintes etapas:
- Verifica o código-fonte de um plugin do WordPress em seu repositório, escrito com PHP 7.4
- Instala suas dependências do Composer
- Transpila seu código do PHP 7.4 para 7.1
- Modifica a entrada “Requires PHP” no cabeçalho do arquivo principal do plugin de
"7.4"
para"7.1"
- Remove as dependências necessárias para o desenvolvimento
- Cria o arquivo .zip do plug-in, excluindo todos os arquivos desnecessários
- Faz upload do arquivo .zip como um recurso de lançamento (e, além disso, como um artefato para a ação do GitHub)
Testando o código transpilado
Uma vez que o código foi transpilado para o PHP 7.1, como sabemos que ele funciona bem? Ou, em outras palavras, como sabemos que foi completamente convertido, e nenhum resquício de versões superiores do código PHP foi deixado para trás?
Semelhante à transpilação do código, podemos implementar a solução dentro de um processo de CI. A ideia é configurar o ambiente do runner com PHP 7.1 e rodar um linter no código transpilado. Se algum pedaço de código não for compatível com o PHP 7.1 (como uma propriedade tipada do PHP 7.4 que não foi convertida), então o linter lançará um erro.
Um linter para PHP que funciona bem é o PHP Parallel Lint. Podemos instalar esta biblioteca como uma dependência para desenvolvimento em nosso projeto ou fazer com que o processo de CI a instale como um projeto autônomo do Composer:
composer create-project php-parallel-lint/php-parallel-lint
Sempre que o código contém PHP 7.2 e superior, o PHP Parallel Lint lançará um erro como este:
Run php-parallel-lint/parallel-lint layers/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php PHP 7.1.33 | 10 parallel jobs ............................................................ 60/2870 (2 %) ............................................................ 120/2870 (4 %) ... ............................................................ 660/2870 (22 %) .............X.............................................. 720/2870 (25 %) ............................................................ 780/2870 (27 %) ... ............................................................ 2820/2870 (98 %) .................................................. 2870/2870 (100 %) Checked 2870 files in 15.4 seconds Syntax error found in 1 file ------------------------------------------------------------ Parse error: layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php:55 53| '0.8.0', 54| \__('GraphQL API for WordPress', 'graphql-api'), > 55| ))) { 56| $plugin->setup(); 57| } Unexpected ')' in layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php on line 55 Error: Process completed with exit code 1.
Vamos adicionar o linter ao fluxo de trabalho do nosso CI. As etapas a serem executadas para transpilar código do PHP 8.0 para 7.1 e testá-lo são:
- Confira o código fonte
- Faça com que o ambiente execute PHP 8.0, para que o Rector possa interpretar o código-fonte
- Transpile o código para PHP 7.1
- Instale a ferramenta PHP linter
- Mude a versão do PHP do ambiente para 7.1
- Execute o linter no código transpilado
Este fluxo de trabalho do GitHub Action faz o trabalho:
name: Downgrade PHP tests jobs: main: name: Downgrade code to PHP 7.1 via Rector, and execute tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Set-up PHP uses: shivammathur/[email protected] with: php-version: 8.0 coverage: none - name: Local packages - Downgrade PHP code via Rector run: | composer install vendor/bin/rector process # Prepare for testing on PHP 7.1 - name: Install PHP Parallel Lint run: composer create-project php-parallel-lint/php-parallel-lint --ansi - name: Switch to PHP 7.1 uses: shivammathur/[email protected] with: php-version: 7.1 coverage: none # Lint the transpiled code - name: Run PHP Parallel Lint on PHP 7.1 run: php-parallel-lint/parallel-lint src/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php
Observe que vários arquivos bootstrap80.php
das bibliotecas polyfill do Symfony (que não precisam ser transpilados) devem ser excluídos do linter. Esses arquivos contêm PHP 8.0, então o linter geraria erros ao processá-los. No entanto, excluir esses arquivos é seguro, pois eles serão carregados em produção apenas ao executar o PHP 8.0 ou superior:
if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; }
Resumo
Este artigo nos ensinou como transpilar nosso código PHP, permitindo usar o PHP 8.0 no código fonte e criar uma versão que funcione no PHP 7.1. A transpilação é feita via Rector, uma ferramenta de reconstrução do PHP.
Transpilar nosso código nos torna melhores desenvolvedores, pois podemos detectar melhor os bugs no desenvolvimento e produzir código que é naturalmente mais fácil de ler e entender.
A transpilação também nos permite desacoplar nosso código com requisitos específicos de PHP do CMS. Agora podemos fazer isso se desejarmos usar a versão mais recente do PHP para criar um plugin WordPress disponível publicamente ou módulo Drupal sem restringir severamente nossa base de usuários.
Você ainda tem alguma dúvida sobre a transpilação do PHP? Deixe-nos saber na seção de comentários!