← Voltar ao blog

Magento 2 no PHP 8.3 e 8.4: corrigindo problemas de compatibilidade

Rodrigo Biazzi

O Magento 2.4.8 roda no PHP 8.3 e 8.4, e as versões mais antigas do PHP estão chegando ao fim da vida. Para a maioria das lojas, o core não é o problema. A Adobe testa o core nas versões suportadas do PHP. Os problemas vêm dos módulos customizados em app/code e das extensões de terceiros que foram escritas para o PHP 7.4 e nunca mais tocadas.

Este artigo cobre as mudanças de linguagem que de fato quebram código Magento, como achá-las sem ler todos os arquivos e como corrigir mantendo o código legível.

O que mudou entre o PHP 7.4 e o 8.4

Se uma loja pula do PHP 7.4 ou 8.1 direto para o 8.3 ou 8.4, ela junta vários anos de mudanças de uma vez. Estas são as que mais vejo em código Magento.

Passar null para funções internas (8.1)

Desde o PHP 8.1, passar null para um parâmetro não nullable de uma função nativa gera uma deprecação. Código como strlen($value), trim($value) ou explode(',', $value), em que $value vem de getData(), está em toda extensão Magento. O getData() devolve null para chaves ausentes, então a deprecação dispara com frequência.

// Before
$sku = trim($product->getData('vendor_sku'));

// After
$sku = trim((string) $product->getData('vendor_sku'));

Um cast resolve quando uma string vazia é um fallback válido. Quando null significa algo diferente de vazio, trate explicitamente em vez de esconder.

Propriedades dinâmicas (8.2)

Criar uma propriedade que não foi declarada na classe está deprecado desde o 8.2. Código Magento antigo faz isso com frequência em blocks e helpers, por exemplo atribuindo $this->cache dentro de um método sem declarar. Classes que estendem Magento\Framework\DataObject não são afetadas no acesso estilo setData(), porque ele passa por métodos mágicos e um array interno. Classes simples são afetadas.

class PriceHelper
{
    private array $cache = [];

    public function get(int $productId): ?float
    {
        return $this->cache[$productId] ?? null;
    }
}

A correção é declarar a propriedade, com tipo sempre que der. Não adicione o atributo #[\AllowDynamicProperties] como correção geral. Ele esconde o problema no seu próprio código, onde declarar a propriedade é uma mudança de uma linha.

Parâmetros implicitamente nullable (8.4)

Esta é a mudança que gera mais warnings no PHP 8.4. Um parâmetro com tipo e default null costumava ser nullable automaticamente. Essa forma agora está deprecada.

// Deprecated in PHP 8.4
public function execute(OrderInterface $order = null)

// Correct
public function execute(?OrderInterface $order = null)

Os constructors do Magento estão cheios desse padrão, porque dependências opcionais eram comumente declaradas como Json $serializer = null para manter a compatibilidade retroativa. A correção é mecânica e segura, o que a torna um bom trabalho para o Rector.

Outras mudanças que vale conferir

  • A interpolação de string ${var} está deprecada desde o 8.2. Use {$var}.
  • utf8_encode() e utf8_decode() estão deprecadas desde o 8.2. Use mb_convert_encoding().
  • O PHP 8.0 mudou a comparação de string com número. 0 == 'abc' agora é false. Código que dependia de comparações soltas com valores de atributos pode mudar de comportamento sem nenhum warning.
  • O PHP 8.0 fez muitas funções internas lançarem TypeError ou ValueError em vez de devolver false com um warning. Código que checava false depois de passar um tipo errado agora recebe uma exceção.
  • Declarações de tipo de retorno em interfaces internas. Classes que implementam ArrayAccess, Iterator, Countable ou JsonSerializable sem os tipos de retorno correspondentes disparam deprecações desde o 8.1.

Achando os problemas

Rodar a loja e esperar os erros só encontra os caminhos de código que você por acaso percorre. A análise estática encontra o resto. Eu uso três ferramentas juntas.

PHPCompatibility

O PHPCompatibility é um standard do PHP_CodeSniffer que verifica o código contra uma versão alvo do PHP. O coding standard do Magento já o puxa, então se você tem o magento/magento-coding-standard instalado provavelmente tem esse também. Confira qual versão você tem, porque releases antigas não conhecem as versões mais novas do PHP.

vendor/bin/phpcs -i
vendor/bin/phpcs --standard=PHPCompatibility \
    --runtime-set testVersion 8.3- \
    --extensions=php,phtml \
    app/code

testVersion 8.3- significa PHP 8.3 e acima. Rode em app/code e nas pastas vendor das extensões que você mantém ou pretende corrigir.

Rector para as correções mecânicas

O Rector reescreve código automaticamente. Em um upgrade eu prefiro regras selecionadas a level sets completos do PHP, porque os sets completos também reescrevem o código para usar sintaxe nova, o que deixa o diff enorme e mais difícil de revisar. Uma config mínima para a correção de nullable do 8.4:

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/app/code'])
    ->withRules([
        ExplicitNullableParamTypeRector::class,
    ]);
vendor/bin/rector process --dry-run
vendor/bin/rector process

Rode sempre o dry run primeiro e leia o diff. O Rector é confiável para essas regras simples, mas você continua responsável pelo que vai para o commit.

PHPStan

O PHPStan pega os erros que não são deprecações: tipos errados passados a métodos, chamadas a métodos que não existem, nulls chegando em código que não sabe lidar com eles. Com a extensão Magento da bitExpert ele entende factories e proxies gerados. Comece em um level baixo e suba com o tempo. Mais sobre o setup no guia do PHPStan.

Vendo deprecações em runtime

As ferramentas estáticas perdem código montado a partir de strings ou carregado por configuração. Para pegá-lo, registre as deprecações em log enquanto testa. Na configuração do PHP local ou de staging:

error_reporting = E_ALL
display_errors = Off
log_errors = On
error_log = /var/log/php/deprecations.log

Depois navegue pelos fluxos principais: categoria, produto, carrinho, checkout, conta do cliente, telas de produto e pedido no admin. Agrupe o log por arquivo e corrija primeiro as mais frequentes. A maior parte do ruído costuma vir de poucos métodos helper chamados em toda página.

Extensões de terceiros

Para extensões instaladas pelo Composer, a correção certa é uma versão nova do fornecedor. Procure em cada fornecedor um release que declare suporte à versão alvo do PHP no composer.json. Se a constraint do pacote exclui a sua versão do PHP, o Composer vai se recusar a instalar, que é o comportamento que você quer.

Quando o fornecedor sumiu ou é lento, você tem duas opções:

  • Aplicar um patch no pacote com um Composer patch enquanto espera um release oficial. Mantenha o patch pequeno e limitado a correções de compatibilidade.
  • Trocar a extensão. Extensões antigas que ninguém mantém são um risco de segurança além de um risco de compatibilidade. Uma auditoria de extensões ajuda a decidir quais manter.

Nunca resolva um conflito do Composer com --ignore-platform-reqs em builds de produção. Ele instala pacotes que os próprios autores disseram não suportar a sua versão do PHP, e a falha aparece depois, em runtime.

Uma ordem de trabalho sensata

  1. Atualize o PHP no ambiente local mantendo a versão atual do Magento, se essa combinação for suportada.
  2. Rode PHPCompatibility e PHPStan em app/code. Aplique o Rector nas correções mecânicas.
  3. Atualize ou corrija as extensões de terceiros.
  4. Teste com o log de deprecações ligado e corrija o que aparecer.
  5. Faça o release do upgrade de PHP em produção sozinho.
  6. Atualize o Magento em um release separado.

Separar o upgrade de PHP do upgrade do Magento é o hábito mais útil aqui. Quando algo quebra, você sabe qual mudança causou.

FAQ

Qual versão do PHP devo usar no Magento 2.4.8?

O 2.4.8 suporta PHP 8.3 e 8.4. Confira a página de requisitos de sistema para o seu patch release exato e confirme que toda extensão que você usa suporta a versão escolhida.

Deprecações do PHP são erros?

Não. Uma deprecação é um aviso de que o código vai quebrar em uma versão futura do PHP. A loja continua funcionando, mas você deve corrigi-las antes que o próximo upgrade as transforme em erros fatais.

Posso usar AllowDynamicProperties para silenciar os warnings do PHP 8.2?

Funciona, mas esconde um problema que normalmente se resolve declarando a propriedade. Use só para código de fornecedor que você ainda não consegue mudar.

O Rector muda o comportamento do meu código?

Regras como a ExplicitNullableParamTypeRector só mudam a sintaxe. Level sets maiores podem mudar mais, então rode um dry run e revise o diff antes de fazer o commit.

Atendo como freelancer em Magento e Adobe Commerce, incluindo upgrades de PHP e correções de extensões. Se precisar de ajuda com isso, fale comigo.

Artigos relacionados