Ultimamente, tenho escrito muito sobre a transpilação de código PHP ( aqui , aqui e aqui ), descrevendo como podemos usar o código PHP mais recente para desenvolvimento mas liberar nosso pacote/plugin/aplicativo para uma versão legada, convertendo nosso código de qualquer coisa entre PHP 8.0 e 7.1.

Eu mesmo transpilei meu plug-in WordPress do PHP 8.0 para 7.1 . Estou muito satisfeito com os resultados, já que minha base de código melhorou sua qualidade: agora posso usar propriedades digitadas e tipos de união, algo que de outra forma não poderia pagar por um plug-in WordPress público.

No entanto, ainda não estou 100% satisfeito com isso. Ao resolver o desafio original (ser capaz de usar PHP 8.0 ao codificar para WordPress), transpilar o código criou alguns novos problemas ao longo do caminho.

Problemas com código de transpilação

Ao codificar meu plug-in no PHP 8.0 e lançá-lo no PHP 7.1, passei a ter os seguintes três problemas:

1. As extensões precisam codificar as assinaturas de método com PHP 7.1, mesmo que exijam PHP 8.0

Meu plug-in, um servidor GraphQL para WordPress, permite que os desenvolvedores estendam o esquema GraphQL com seus próprios tipos, criando um objeto que implementa TypeResolverInterface . Entre outras, esta interface tem a função getID , com esta assinatura :

 interface TypeResolverInterface
{ função pública getID (objeto $ resultItem): string | int;
}

Como podemos ver, esta função usa tipos de união do PHP 8.0 para especificar o tipo de retorno e o tipo de parâmetro objeto do PHP 7.2.

Quando transpilado para PHP 7.1, esta assinatura de método é rebaixada para este código :

 interface TypeResolverInterface
{ /** * @param $ resultItem object * @return string | int */ função pública getID ($ resultItem);
}

Esta assinatura de método é aquela lançada no plugin.

Então, o que acontece quando os desenvolvedores desejam criar uma extensão para meu plug-in e implantá-la em um aplicativo que roda em PHP 8.0? Bem, eles ainda precisam usar o código PHP 7.1 para a assinatura do método, ou seja, removendo o tipo de parâmetro objeto e o tipo de retorno string | int ; caso contrário, o PHP gerará um erro.

Felizmente, essa situação se limita a assinaturas de método. Por exemplo, as extensões ainda podem usar tipos de união para declarar as propriedades em suas classes:

 classe IcecreamTypeResolver implementa IcecreamTypeResolverInterface
{ //O código PHP 8.0 aqui é permitido string privada | int $ id='vanilla'; /** * Código PHP 7.1 na assinatura do método... * * @param $ resultItem object * @return string | int */ função pública getID ($ resultItem) { return $ this-> id; }
}

Mesmo assim, ainda é irritante ter que usar o código PHP 7.1 quando nosso aplicativo requer PHP 8.0. Como um provedor de plug-in, forçar meus usuários a essa situação é um pouco triste.

(Para ser claro, não estou criando a situação; o mesmo acontece ao substituir assinaturas de método para qualquer plugin WordPress que suporte PHP 7.1. Mas parece diferente neste caso apenas porque estou começando com PHP 8.0 com o objetivo de fornecer uma alternativa melhor para meus usuários.)

2. A documentação deve ser fornecida usando PHP 7.1

Como o plug-in foi lançado no PHP 7.1, a documentação sobre como estendê-lo também deve usar o PHP 7.1 para as assinaturas do método, embora o código-fonte original esteja no PHP 8.0.

Além disso, a documentação não pode apontar para o repositório com o código-fonte no PHP 8.0 ou correríamos o risco de os visitantes copiarem/colar um trecho de código que produzirá erros de PHP.

Finalmente, nós, desenvolvedores, normalmente temos orgulho de usar a versão mais recente do PHP. Mas a documentação do plugin não pode refletir isso, pois ainda é baseado no PHP 7.1.

Poderíamos contornar esses problemas explicando o processo de transpilação aos nossos visitantes, encorajando-os a codificar suas extensões com PHP 8.0 e depois transpilar para PHP 7.1. Mas fazer isso aumentará a complexidade cognitiva, reduzindo as chances de eles conseguirem usar nosso software.

3. As informações de depuração usam o código transpilado, não o código-fonte

Digamos que o plug-in lança uma exceção, imprimindo essas informações em algum arquivo debug.log , e usamos o rastreamento de pilha para localizar o problema no código-fonte.

Bem, a linha onde o erro ocorre, mostrada no rastreamento da pilha, apontará para o código transpilado, e o número da linha provavelmente será diferente no código-fonte. Portanto, há um pouco de trabalho adicional a fazer para converter de volta do código transpilado para o original.

Primeira solução proposta: Produzindo duas versões do plugin

A solução mais simples a considerar é gerar não uma, mas duas versões:

  • Um com o código PHP 7.1 transpilado
  • Um com o código PHP 8.0 original

Isso é fácil de implementar, pois a nova versão com PHP 8.0 conterá simplesmente o código-fonte original, sem nenhuma modificação.

Tendo o segundo plug-in usando o código PHP 8.0, qualquer desenvolvedor executando um site no PHP 8.0 pode usar este plug-in.

Problemas com a produção de duas versões do plugin

Essa abordagem tem vários problemas que, acredito, a tornam impraticável.

WordPress aceita apenas uma versão por plugin

Para um plug-in WordPress como o meu, não podemos enviar as duas versões para o diretório WordPress.org. Portanto, teríamos que escolher entre eles, o que significa que acabaremos tendo o plugin “oficial” usando PHP 7.1 e o “não oficial” usando PHP 8.0.

Isso complica as coisas significativamente porque, embora o plug-in oficial possa ser carregado (e baixado) dos Plug-ins , o não oficial não pode-a menos que seja publicado como um plugin diferente, o que seria uma péssima ideia. Como resultado, ele teria que ser baixado de seu site ou repositório.

Além disso, é recomendado que o plugin oficial seja baixado apenas de wordpress.org/plugins para não mexer com o diretrizes :

Uma versão estável de um plug-in deve estar disponível em sua página de diretório de plug-ins do WordPress.

A única versão do plug-in distribuída pelo WordPress.org é a do diretório. Embora as pessoas possam desenvolver seu código em outro lugar, os usuários farão o download do diretório, não do ambiente de desenvolvimento.

Distribuir código por meio de métodos alternativos, sem manter o código hospedado aqui atualizado, pode resultar na remoção de um plug-in.

Isso significaria efetivamente que nossos usuários precisarão estar cientes de que existem duas versões diferentes do plugin-uma oficial e outra não oficial-e que elas estão disponíveis em dois lugares diferentes.

Essa situação pode se tornar confusa para usuários desavisados ​​e isso é algo que prefiro evitar.

Não resolve o problema da documentação

Como a documentação deve levar em conta o plugin oficial, que conterá o código PHP 7.1, então emitir “2. A documentação deve ser fornecida usando PHP 7.1 ”ainda vai acontecer.

Nada impede que o plugin seja instalado duas vezes

A transpilação do plugin deve ser feita durante nosso processo de integração contínua. Como meu código está hospedado no GitHub, o plug-in é gerado por meio de Ações do GitHub sempre que marcar o código e é carregado como um recurso de lançamento .

Não pode haver dois recursos de lançamento com o mesmo nome. Atualmente, o nome do plugin é graphql-api.zip . Se eu também gerasse e carregasse o plugin com o código PHP 8.0, teria que chamá-lo de graphql-api-php80.zip .

Isso pode levar a um problema potencial: qualquer pessoa pode baixar e instalar as duas versões do plug-in no WordPress e, como elas têm nomes diferentes, o WordPress instalará efetivamente as duas, lado a lado, nas pastas graphql-api e graphql-api-php80 .

Se isso acontecesse, acredito que a instalação do segundo plugin iria falhar, pois ter as mesmas assinaturas de método em diferentes versões do PHP deveria produzir um erro de PHP, fazendo o WordPress interromper a instalação. Mas, mesmo assim, eu não gostaria de arriscar.

Segunda solução proposta: Incluindo código PHP 7.1 e 8.0 no mesmo plugin

Uma vez que a solução simples acima não é imaculada, é hora de iterar.

Em vez de lançar o plugin usando apenas o código PHP 7.1 transpilado, inclua também o código fonte PHP 8.0 e decida sobre o tempo de execução, com base no ambiente, se usará o código correspondente a uma versão do PHP ou a outra. p>

Vamos ver como isso funcionaria. Meu plugin atualmente envia código PHP em duas pastas, src e vendor , ambas transpiladas para PHP 7.1. Com a nova abordagem, ele incluiria quatro pastas:

  • src-php71 : código transpilado para PHP 7.1
  • vendor-php71 : código transpilado para PHP 7.1
  • src : código original em PHP 8.0
  • vendor : código original em PHP 8.0

As pastas devem ser chamadas de src e vendor em vez de src-php80 e vendor-php80 para que se tivermos uma referência codificada para algum arquivo em qualquer um desses caminhos, ele ainda funcionará sem qualquer modificação.

O carregamento da pasta vendor ou vendor-php71 seria feito assim:

 if (PHP_VERSION_ID <80000) { require_once __DIR__.'/vendor-php71/autoload.php';
} senão { require_once __DIR__.'/vendor/autoload.php';
}

O carregamento da pasta src ou src-php71 é feito por meio do arquivo autoload_psr4.php correspondente. O do PHP 8.0 permanece o mesmo:

  array ($ baseDir.'/Src'),
);

Mas aquele transpilado para PHP 7.1, em vendor-php71/composer/autoload_psr4.php , deve alterar o caminho para src-php71 :

 array de retorno ( 'GraphQLAPI \\ GraphQLAPI \\'=> array ($ baseDir.'/Src-php71'),
);

É basicamente isso. Agora, o plug-in pode enviar seu código em 2 versões diferentes do PHP, e os servidores que executam o PHP 8.0 podem usar o código do PHP 8.0.

Vamos ver como essa abordagem resolve os três problemas.

1. As extensões podem usar assinaturas de método do PHP 7.1

Agora, o plugin ainda suporta PHP 7.1, mas, além disso, suporta o uso de código PHP 8.0 nativo ao executar PHP 8.0 no servidor web. Como tal, ambas as versões do PHP são cidadãs de primeira classe.

Dessa forma, o servidor da web executando o PHP 8.0 carregará as assinaturas do método da versão correspondente do PHP 8.0:

 interface TypeResolverInterface
{ função pública getID (objeto $ resultItem): string | int;
}

Os desenvolvedores que estendem o esquema GraphQL para seus próprios sites podem codificar suas extensões usando a assinatura do método PHP 8.0.

2. A documentação pode ser fornecida usando PHP 8.0

Como o PHP 8.0 se tornou um cidadão de primeira classe, a documentação demonstrará o código usando o PHP 8.0.

A cópia/colagem do código-fonte na documentação também pode ser feita a partir do repositório original. Para demonstrar a versão do PHP 7.1, podemos simplesmente adicionar um link para o trecho de código correspondente no repositório transpilado.

3. As informações de depuração usam o código original, sempre que possível

Se o servidor da web executa PHP 8.0, o rastreamento de pilha na depuração imprimirá corretamente o número da linha do código-fonte original.

Se não estiver executando o PHP 8.0, o problema ainda vai acontecer, mas pelo menos nós melhoramos isso.

Por que apenas duas versões do PHP? Agora é possível segmentar toda a gama.

Se estiver implementando esta solução, atualizar o plug-in de usar PHP 8.0 e 7.1 apenas para usar todas as versões de PHP intermediárias é muito fácil.

Por que faríamos isso? Para melhorar o item de solução “1. As extensões podem usar assinaturas de método do PHP 7.1"visto acima, mas permitindo que os desenvolvedores usem qualquer versão do PHP que já estejam usando para suas extensões.

Por exemplo, se estiver executando o PHP 7.3, a assinatura do método para getID apresentada anteriormente não pode usar tipos de união, mas pode usar o tipo de parâmetro objeto . Portanto, a extensão pode usar este código:

 interface TypeResolverInterface
{ /** * @return string | int */ função pública getID (objeto $ resultItem);
}

Implementar esta atualização significa armazenar todos os estágios intermediários de downgrade dentro da versão, como este:

  • src-php71 : código transpilado para PHP 7.1
  • vendor-php71 : código transpilado para PHP 7.1
  • src-php72 : código transpilado para PHP 7.2
  • vendor-php72 : código transpilado para PHP 7.2
  • src-php73 : código transpilado para PHP 7.3
  • vendor-php73 : código transpilado para PHP 7.3
  • src-php74 : código transpilado para PHP 7.4
  • vendor-php74 : código transpilado para PHP 7.4
  • src : código original em PHP 8.0
  • vendor : código original em PHP 8.0

E então, o carregamento de uma ou outra versão é feito assim:

 if (PHP_VERSION_ID <72000) { require_once __DIR__.'/vendor-php71/autoload.php';
} elseif (PHP_VERSION_ID <73000) { require_once __DIR__.'/vendor-php72/autoload.php';
} elseif (PHP_VERSION_ID <74000) { require_once __DIR__.'/vendor-php73/autoload.php';
} elseif (PHP_VERSION_ID <80000) { require_once __DIR__.'/vendor-php74/autoload.php';
} senão { require_once __DIR__.'/vendor/autoload.php';
}

Problemas com a inclusão de código PHP 7.1 e 8.0 no mesmo plug-in

O problema mais evidente com essa abordagem é que estaremos duplicando o tamanho do arquivo do plug-in.

Na maioria das situações, porém, isso não será uma preocupação crítica porque esses plug-ins são executados no lado do servidor, sem nenhum efeito no desempenho do aplicativo (como duplicar o tamanho de um arquivo JS ou CSS faria). No máximo, vai demorar um pouco mais para baixar o arquivo e um pouco mais para instalá-lo no WordPress.

Além disso, apenas o código PHP será necessariamente duplicado, mas os ativos (como arquivos CSS/JS ou imagens) podem ser mantidos apenas sob vendor e src e removidos sob vendor-php71 e src-php71 , então o tamanho do arquivo do plugin pode ser menor que o dobro do tamanho.

Então, não é grande coisa.

O segundo problema é mais sério: as extensões públicas também precisariam ser codificadas com as duas versões do PHP. Dependendo da natureza do pacote/plug-in/aplicativo, esse problema pode ser um obstáculo.

Infelizmente, esse é o caso do meu plug-in, conforme explico abaixo.

Extensões públicas também precisam incluir código PHP 8.0 e 7.1

O que acontece com as extensões que estão publicamente disponíveis para todos? Qual versão do PHP eles devem usar?

Por exemplo, o plugin GraphQL API permite que os usuários tenham o esquema GraphQL estendido para buscar dados de qualquer outro plugin WordPress. Conseqüentemente, plug-ins de terceiros são capazes de fornecer suas próprias extensões (pense em “WooCommerce para GraphQL API” ou “Yoast para GraphQL API”). Essas extensões também podem ser carregadas no repositório de plug-ins do WordPress.org para que qualquer pessoa faça o download e instale em seus sites.

Agora, essas extensões não saberão com antecedência qual versão do PHP será usada pelo usuário. E eles não podem ter o código usando apenas uma versão (PHP 7.1 ou 8.0) porque isso certamente produzirá erros de PHP quando a outra versão do PHP estiver sendo usada. Como consequência, essas extensões também precisariam incluir seu código no PHP 7.1 e 8.0.

Isso é certamente factível de um ponto de vista técnico. Mas, por outro lado, é uma ideia terrível. Por mais que eu ame transpilar meu código, não posso forçar os outros a fazerem o mesmo. Como posso esperar que um ecossistema floresça em torno do meu plug-in ao impor requisitos tão altos?

Portanto, decidi que, para a API GraphQL, seguir essa abordagem não vale a pena.

Qual é a solução, então?

Vamos revisar o status até agora:

Transpilar o código do PHP 8.0 para 7.1 tem alguns problemas:

  1. As extensões precisam codificar as assinaturas de método com PHP 7.1, mesmo que exijam PHP 8.0
  2. A documentação deve ser fornecida usando PHP 7.1
  3. As informações de depuração usam o código transpilado, não o código-fonte

A primeira solução proposta, produzindo duas versões do plugin, não funciona bem porque:

  1. WordPress aceita apenas versão por plug-in
  2. Não resolve o problema da documentação
  3. Nada impede que o plug-in seja instalado duas vezes

A segunda solução proposta, incluindo código PHP 7.1 e 8.0 no mesmo plugin, pode ou não funcionar:

  • Se o plugin pode ser estendido por terceiros, essas extensões também precisarão ser transpiladas. Isso provavelmente aumentará a barreira de entrada, fazendo com que não valha a pena
  • Caso contrário, deve funcionar bem

No meu caso, a API GraphQL é afetada pela segunda solução proposta. Então, o círculo se completa e estou de volta ao ponto de partida-sofrendo os três problemas para os quais tentei encontrar uma solução.

Apesar deste contratempo, não mudo minha opinião positiva em relação à transpilação. Na verdade, se eu não estivesse transpilando meu código-fonte, teria que usar PHP 7.1 (ou possivelmente PHP 5.6), então eu não estaria muito melhor. (Apenas o problema sobre as informações de depuração não apontando para o código-fonte seria resolvido.)

Concluindo

Comecei este artigo descrevendo os três problemas que experimentei até agora ao transpilar meu plug-in WordPress do PHP 8.0 para 7.1. Então eu propus duas soluções, a primeira das quais não funcionará bem.

A segunda solução funcionará bem, exceto para pacotes/plug-ins/aplicativos que podem ser estendidos por terceiros. Esse é o caso do meu plugin, então estou de volta onde comecei, sem uma solução para os três problemas.

Portanto, ainda não estou 100 por cento feliz com a transpilação. Apenas 93 por cento.

A postagem Incluindo o código PHP 7.1 e 8.0 no mesmo plugin... ou não? apareceu primeiro no LogRocket Blog .