Magento CLI 2.4.0 - ErrorHandler está transformando erros de PHP em exceções no modo de produção

Sep 01 2020

Prefácio: Eu prefiro desenvolver no modo de desenvolvedor, fazer com que o Magento detecte todos os E_NOTICE|E_WARNING|etc.erros do PHP , lance uma exceção sobre eles e limpe esses problemas antes de enviar meu código. O problema, entretanto, reside em uma extensão de terceiros e pretendo relatar o problema a eles.

Problema:

Como o título afirma, Magento ainda está convertendo erros de nível de PHP em exceções Magento\Framework\App\ErrorHandler::handlerdurante o modo de produção. Como parte dos meus esforços de desenvolvimento atuais, estou tendo que contar com uma terceira extensão partido, e eles têm um bug lógica que está a causar o seguinte E_NOTICEerro nível: Notice: Trying to access array offset on value of type null in /path/to/project/vendor/magento/module-catalog-import-export/Model/Import/Product.php on line 1281.

Qualquer pessoa familiarizada com esta área específica do código pode se sentir compelido a declarar que o bug precisa ser corrigido ou então a importação de produtos não funcionará devido à falta de dados nas linhas type_ide _attribute_set. Saiba que este não é o caso, pois mais adiante, na extensão de terceiros, o módulo de terceiros configurará adequadamente essas chaves importantes antes da importação real para o banco de dados.

De volta ao meu problema: devido a este problema na extensão de terceiros, não consigo testar a execução de importações localmente até a conclusão enquanto estou no modo de desenvolvedor, porque (como esperado) o Magento converterá este erro de nível E_NOTICE do PHP em uma exceção. Fazendo com que a importação falhe. Portanto, localmente, decidi mudar o Magento para o modo de produção para evitar que esses erros de PHP sejam convertidos em exceções. Infelizmente, isso não parece ter resolvido o problema e ErrorHandlercontinua a lançar exceções para esses problemas enquanto o Magento está no modo de produção.

Eu decidi dar uma olhada no código do Magento, e eu mesmo não consigo determinar como (modo de produção) o Magento deve evitar lançar exceções para erros de PHP em primeiro lugar. Percorrendo o código, posso ver que:

  1. bin/magento será o ponto de entrada da execução, pois estou executando a importação da CLI
  2. bin/magento irá realizar uma inclusão em app/bootstrap.php
  3. bootstrap.php imediatamente define o nível de relatório de erros do PHP para E_ALL
  4. bin/magento prossegue para definir o manipulador de erros do PHP para \Magento\Framework\App\ErrorHandler
  5. bin/magento cria um aplicativo de console Symfony e executa.

Como app/bootstrap.phpdefine o nível de relatório de erro para E_ALL, independentemente do nível de relatório atual, só posso supor que em algum lugar ao longo da pilha de execução o Magento teria que consultar o modo de aplicativo atual. Se estiver executando como "produção", execute algo error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING ...)semelhante a de modo a evitar que o modo de produção gere exceções para esses erros. No entanto, em nenhum lugar consegui encontrar tal lógica. Portanto, não consigo entender como o modo de produção normalmente evita que os ErrorHandlererros do PHP em exceções sejam convertidos em primeiro lugar.

Se eu modificar diretamente ErrorHandler.php, logo antes da condicional que retorna falso, adicionando o seguinte:

echo "errNo: $errorNo\nerror_reporting: ". error_reporting() . "\nbitwise result: " . ($errorNo & error_reporting()) . "\nE_ALL: " . E_ALL . "\n";

Recebo a seguinte saída no console:

errNo: 8
error_reporting: 32767
bitwise result: 8
E_ALL: 32767

Portanto, o return false;nunca é atendido e o código prossegue para converter o E_NOTICEerro de nível em uma exceção. Se estiver diretamente antes desta linha: $errorNo = $errorNo & error_reporting();adiciono o seguinte: error_reporting(E_ALL & ~E_NOTICE);o manipulador de erros retornará falso conforme o esperado. Obviamente, esta não é uma solução, mas aquela a que agora recorri temporariamente para continuar a progredir na minha tarefa atual.

Algumas coisas adicionais a serem observadas:

  • Tentei passar para o binário do PHP -d display_errors=0, que não produz nenhum efeito (já que o PHP ainda relatará erros para o error_log)
  • Eu tentei passar para o binário PHP -d log_errors=0, que não produz nenhum efeito
  • Eu tentei passar para o binário PHP -d error_reporting=0, o que obviamente não produz nenhum efeito devido à bootstrap.phpalteração do Magento para E_ALL.
  • Tentei todos os itens acima individualmente, e também imediatamente.
  • Limpei o cache, bem como desativei o cache, e também limpei var/cacheegenerated/code
  • Eu destruí todo o meu aplicativo (fazendo backup app/etc/env.phpprimeiro), procedi para realizar um hard reset do HEAD, execute composer installpara buscar novas dependências, de modo a limpar qualquer coisa que eu possa ter perdido que está em cache, o que não produz efeito (restaurado env.phpe restaurado -compilar modo de produção)
  • O cache está atualmente configurado para o cache do sistema de arquivos pronto para uso no var/cachediretório do Magento

Últimas notas:

  • Para a maior parte, isso ainda é quase uma instalação fora da caixa do Magento 2.4.0. Pouco foi feito em termos de customização, certamente nenhuma revisão do error_reporting()nível por mim mesmo (ou outros desenvolvedores, já que sou o único desenvolvedor que está tocando o código neste momento).
  • A extensão de terceiros é a firebear/importexportversão 3.4.3. A revisão desta extensão também não mostra que ela está mudando o error_reporting()nível.
  • versão php: 7.4.9

TL; DR: Eu não sei por que o modo de produção do Magento continua a permitir o ErrorHandlerlançamento de exceções para pequenos erros de PHP.

EDITAR 2020.10.07

Resposta à resposta do FactoryAidan: (não cobre a pergunta original, mas explica por que o erro de nível E_NOTICE ocorre em primeiro lugar)

É minha opinião pessoal que a ocorrência do erro de nível E_NOTICE é principalmente a culpada devido a um bug lógico no Firebear_ImportExport, ao contrário de um bug lógico no código principal do Magento. Deixe-me também prefixar o seguinte com uma reiteração de que concordo com sua declaração "se o código do núcleo do Magento nullfoi verificado se foi retornado $this->skuProcessor->getNewSku($lastSku), isso não seria um problema em primeiro lugar ao usar a extensão do Firebear.

Agora, quanto ao meu raciocínio por que eu ainda aponto o dedo para o Firebear por isso (também incluirão algumas críticas ao Magento, mas o principal problema aqui é que o Firebear não segue o que o Magento fez).

No Magento 2.4.0, se olharmos as \Magento\CatalogImportExport\Model\Import\Productlinhas 2481-2488, encontraremos o seguinte:

if ($this->isSkuExist($sku) && Import::BEHAVIOR_REPLACE !== $this->getBehavior()) { // can we get all necessary data from existent DB product? check for supported type of existing product if (isset($this->_productTypeModels[$this->getExistingSku($sku)['type_id']])) {
        $this->skuProcessor->addNewSku( $sku,
            $this->prepareNewSkuData($sku)
        );

Verbalizando essa lógica, podemos ver que o código está fazendo o seguinte (omitindo aspectos não importantes):

  • Se o sku já existe
  • Se o sku existente type_idé válido
  • Em seguida, adicione este sku existente ao array de novos skus do SkuProcessor .

-- Espere o que? adicionar o existente ao novo array skus ? porque não é novo, literalmente apenas confirmamos que já existe. (é aqui que reside a minha crítica ao Magento.

Escolhendo os métodos invocados aqui, essencialmente chegamos à conclusão de que: "O Magento garante que, ao atualizar skus existentes, não temos permissão para modificar o type_ide o attribute_set_id". Magento faz isso de uma maneira um tanto estranha, apoiando-se na \Magento\CatalogImportExport\Model\Import\Product::_prepareNewSkuData()construção de um array que tem type_ide attr_set_codevalores do existente, passando esse array para o SkuProcessor como um "novo sku", apesar de ser na verdade dados de um existente.

Então, por meio dessas travessuras, o Magento agora enganou-se para sempre definir especificamente o type_ide attr_set_codedentro do \Magento\CatalogImportExport\Model\Import\Product::_prepareRowForDb()método ao lidar com skus existentes.

Quanto ao urso de fogo? Eles substituíram \ Magento \ CatalogImportExport \ Model \ Import \ Product , and these lines 2481-2488 of the original model don't ever execute. Firebear (intentionally, per their documentation on their website) allows for changing of both type_id andattr_set_code` em produtos existentes. O que, na minha opinião, está bom. No entanto, após um produto ter passado na validação inicial, eles se esquecem de fazer o mesmo "adicionar os dados sku existentes à matriz de novos skus", e é por isso que o erro de nível E_NOTICE agora ocorre.

Talvez eles não tenham feito isso intencionalmente, para que type_ide attr_set_codenão sejam "redefinidos" pelo _prepareRowForDb()método nativo . No entanto, mesmo se esse for o caso, seria muito simples para eles simplesmente chamar o _prepareRowForDbmétodo pai e, em seguida, definir esses dois valores de chave de array com os dados fornecidos pela importação.

No final das Model\Import\Productcontas , minha solução foi retomar a classe mais uma vez, estendendo o Firebear, que estende o Magento. Aqui, eu reviso o nível de relatório de erro antes de chamar parent::_prepareRowForDb()o definir o nível de relatório de erro de volta ao seu valor original posteriormente.

Meu código fonte:

    /***
     * E_NOTICE level error is breaking import on updates of existing products
     * because \Magento\Framework\App\ErrorHandler is catching the error
     * and converting it into an exception.
     *
     * This error is occurring due to a logical bug in firebear/importexport
     *
     * Native magento/module-catalog-import-export will invoke
     * \Magento\CatalogImportExport\Model\Import\Product\SkuProcessor::addNewSku()
     * for rows that have passed validation, when the import behavior update.
     *
     * firebear/importexport does not, and so the SkuProcessor returns
     * null to \Magento\CatalogImportExport\Model\Import\Product::_prepareRowForDb()
     * when updating existing SKUs. Immediately afterwards, this error occurs:
     *
     * Notice: Trying to access array offset on value of type null in
     * vendor/magento/module-catalog-import-export/Model/Import/Product.php on line 1281
     *
     * Combinations of package versions that first discovered to yield this logic error:
     *      Magento Open Source 2.4.0
     *          firebear/importexport: 3.4.3
     *          magento/module-catalog-import-export: 101.1.0
     *
     * {@inheritDoc}
     * @see \Firebear\ImportExport\Model\Import\Product::_prepareRowForDb()
     * phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
     */
    protected function _prepareRowForDb(array $rowData): array { $level = error_reporting();
        if (E_NOTICE & $level === 0) { return parent::_prepareRowForDb($rowData);
        }

        error_reporting($level & ~E_NOTICE); $rowData = parent::_prepareRowForDb($rowData); error_reporting($level);

        return $rowData;
    }

Respostas

2 FactoryAidan Oct 08 2020 at 05:48

🔥 Também estou usando Magento 2.4.0 e Firebear.

⚠️ Este é o resultado de um bug do Magento Core em:

  • vendor/magento/module-catalog-import-export/Model/Import/Product.php::_prepareRowForDb

Onde:

  • Linha 1280: $this->skuProcessor->getNewSku($lastSku)pode ter um tipo de retorno de, array|nullmas nullnão é tratada.

✅ Use o seguinte .patcharquivo em sua instalação existente, que atualizará o módulo do núcleo com bug:

  • Patch M2.4.0 para lidar com ambos os tipos de retorno de array | nulos de getNewSku()

Para instalar o patch composer.json, veja o primeiro comentário no link principal ☝️.

ℹ️ Esta solução passou com sucesso nos testes de ambientes Magento Enterprise Cloud Edition.