Lidando com dicas do tipo castrado no Magento

15

Apenas imaginando se alguém tem estratégias melhores do que eu pensava para a verificação de tipo coexistir com o manipulador de erros personalizado do Magento. Especificamente, estou me perguntando sobre um "Erros fatais capturáveis" lançados no caso de uma incompatibilidade de parâmetro com sugestão de tipo. Aqui está um exemplo da Mageclasse:

/**
 * Write exception to log
 *
 * @param Exception $e
 */
public static function logException(Exception $e)
{
    if (!self::getConfig()) {
        return;
    }
    $file = self::getStoreConfig('dev/log/exception_file');
    self::log("\n" . $e->__toString(), Zend_Log::ERR, $file);
}

Por causa do manipulador de erros, qualquer coisa pode ser passada para o método, incluindo um Zend_Date(que funcionará bem, mas parece super confuso no seu log de exceção) ou umMage_Core_Model_App , que na verdade terá um erro fatal.

É possível reimplementar a verificação de tipo na parte superior de um método: $e instanceof Exception reimplementar a mas essas táticas anulam o objetivo de uma dica de tipo.

Alguma sugestão de dicas ?

mpw
fonte

Respostas:

5

Boa pergunta +1

Fiz algumas pesquisas e testes após um bom ponto na direção após minha discussão com @mpw na minha primeira resposta. Eu parcialmente entendi errado da primeira vez.

Adicionará algum código para esclarecer, para que outras pessoas entendam melhor o problema.

Uma nota antes de decolar

Eu nunca tive esses problemas até isso aparecer. Desenvolvendo no Magento com o modo de desenvolvedor ativado, eu nem penso um segundo sobre isso. Então, toda vez que peido , ele aparecerá e será corrigido de acordo.

O problema com uma amostra explicativa

Sua declaração de erros fatais será registrada (se ativada) e o código continuará normalmente, porque nenhum erro é gerado mageCoreErrorHandlerou o programa seráexit .

Primeiro manipulador de erros do Magento para erros inatacáveis app/code/core/Mage/Core/functions.php

/**
 * Custom error handler
 *
 * @param integer $errno
 * @param string $errstr
 * @param string $errfile
 * @param integer $errline
 */
function mageCoreErrorHandler($errno, $errstr, $errfile, $errline){
    /**
     * Some internal logic here for building the error message
     */

    $errorMessage .= ": {$errstr}  in {$errfile} on line {$errline}";
    if (Mage::getIsDeveloperMode()) {
        throw new Exception($errorMessage);
    } else {
        Mage::log($errorMessage, Zend_Log::ERR);
    }
}

Como você pode ver, no modo de desenvolvedor, ele diz algo útil, gera um erro. Quando desligado, ele registrará (se ativado) e continuará.

A prova

Minhas testfile.php

require 'app/Mage.php';
Mage::app('admin')->setUseSessionInUrl(false);

// Test function which expect Customer_Model_Customer
function test(Customer_Model_Customer $customer)
{
    var_dump('Do not show me because ' . get_class($customer) . ' is not a customer.');
}

// Enabled developer mode
Mage::setIsDeveloperMode(true);

// Put a var in here
$noGood = Mage::app();

// Make some context
var_dump('hello');
try {
    // Call test function with a not accepted var
    test($noGood);

    // Tell if we get here
    var_dump('And we are here!');

} catch (Exception $e) {
    var_dump('You should die, because I am doing something which I should not do');
}

O resultado

Modo de desenvolvedor ativado. Resultado correto

string(5) "hello"
string(66) "You should die, because I am doing something which I should not do"

Modo do desenvolvedor desativado, resultado incorreto

string(5) "hello"
string(61) "Do not show me because Mage_Core_Model_App is not a customer."
string(16) "And we are here!"

Portanto, ele eventualmente pulará o erro e continuará na próxima linha de código. Talvez com resultados ainda mais estranhos. (como @mpw aponta)

Conclusão

Ele poderia acontecer que alguém está se desenvolvendo de uma forma que os erros vão despercebidas e irá , eventualmente, dar resultados inesperados.

É claro que quando se desenvolve de maneira profissional. Erros vai ser notado e atenção é pago. A maneira de evitar isso no Magento é sempre ativar o modo de desenvolvedor em um ambiente de desenvolvedor / teste.

IMHO nunca deve chegar a esse ponto de discussão, onde verificar uma variável pela segunda vez (pelo menos é assim que eu descreveria) é o caminho a percorrer. O código deve ser testado antes da liberação nos ambientes de produção. Ele deve não ser necessário.

Segundas intenções

Talvez o Magento deva parar após um erro fatal. Ou gere um relatório e mostre-o ao visitante. Dessa forma, as próximas linhas de código nunca serão executadas e as coisas serão notadas.

Jeroen
fonte
> De grosseiro quando se desenvolve de maneira profissional. Erros serão notados e atenção será prestada. A maneira de evitar isso no Magento é sempre ativar o modo de desenvolvedor em um ambiente de desenvolvedor / teste. ¶ Eu concordo com isso. Meu objetivo é fazer com que o Magento respeite as regras de idioma no modo de produção. Parece que talvez exija um módulo personalizado. Obrigado pela sua compreensão!
MPW
Talvez o Magento deva lançar uma exceção nos dois casos. Será apresentada ao usuário uma página de log de erros do Magento e em var / exception terá um arquivo de log correspondente, o mesmo que as exceções regulares. O grande problema aqui é que o código não será executado sem aviso prévio. Você pode copiar o arquivo de funções para o app / code / local e sempre lançar uma exceção
Jeroen
11
Decidi marcar isso como a resposta. Embora eu ainda ache que abafar erros como esse é perigoso, parece improvável que haja uma maneira de garantir que o Magento respeite as dicas de tipo sem abrir outros problemas. O lembrete para manter dev modo ligado é uma boa para futuros leitores, e isso é o takeaway mais importante
MPW
2

Boa pergunta. Eu acho que esse é um problema geral E_RECOVERABLE_ERRORno PHP.

O que você tem na sua pergunta é o manipulador de exceções, não o manipulador de erros. O manipulador de erros está causando o problema real que você discute aqui com erros fatais capturáveis ( E_RECOVERABLE_ERROR) .

PHP 7 e HHVM já resolveram isso.

É pior com o Magento porque o manipulador de erros não lida com isso desde a classe de erro do PHP 5.2.

Um tipo mais útil de tratamento de erros seria lidar com essa classe de erro e transformá-los em ErrorException s. Exemplo (não por mim, daqui ):

set_error_handler(function($errno, $errstr, $errfile, $errline) {
    if ($errno === E_RECOVERABLE_ERROR) {
        throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
    }
    return false;
});

Portanto, à luz do Magento, o manipulador de erros padrão é a função global mageCoreErrorHandlerno app/code/core/Mage/Core/functions.php. Ele é registrado via Mage::app()pelo init()método Mage_Core_Model_App ( app/code/core/Mage/Core/Model/App.php) (via _initEnvironment()método protegido ).

Um observador nocontroller_front_init_before qual registra seu próprio manipulador de erros PHP no topo deve ser suficiente (os manipuladores de erros no PHP são empilháveis):

$previous = set_error_handler(function($errno, $errstr, $errfile, $errline) use (&$previous) {
    if ($errno === E_RECOVERABLE_ERROR) {
        throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
    }
    if ($previous) {
        return call_user_func($previous, $errno, $errstr, $errfile, $errline);
    }
    return false;
});

erros fatais capturáveis são transformados em exceções e você pode lidar com eles em seu próprio código de extensão ou eles são detectados e serão vistos no log de exceções (em vez de fazer com que sua loja execute gaga com tipos errados, como o comportamento atual, programas mortos não minta ). No PHP 7, a exceção a procurar não é ErrorException , mas TypeException (que é uma BaseException ) para os erros fatais agora capturáveis .

Todos os outros erros são passados ​​para o manipulador de erros do Magento.

Nota: Eu não tentei isso, é uma revisão, mas eu sei o problema que você está perguntando e a análise de tratamento de erros foi feita na 1.5.1.0 e verificada na 1.9.1.0 através da análise de código. O empilhamento do manipulador de erros deve funcionar. Anexo um pequeno código de exemplo estendido que demonstra a maioria das peças funcionando.

Ainda não o empacotei como uma extensão magento, mas deve ser direta com o modman. Vou colocá-lo no github então.

Apêndice: Demonstração do Manipulador de Erros

O seguinte exemplo de código ( demonstração online ) demonstra o empilhamento de manipuladores de erros e a exceção lançando um erro fatal capturável :

<?php
/**
 * error handler demonstration
 *
 * stackable error handle with previous call and catchable error exceptions
 *
 * @author hakre <http://hakre.wordpress.com>
 * @link /magento//a/64972/4115
 */

set_error_handler(function() {
    $args = func_get_args();
    var_dump("me is the previous error handler", $args);
});

$previous = set_error_handler(function($errno, $errstr, $errfile, $errline) use (&$previous) {
    if ($errno === E_RECOVERABLE_ERROR) {
        throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
    }
    if ($previous) {
        return call_user_func($previous, $errno, $errstr, $errfile, $errline);
    }
    return false;
});

$test = function(callable $test) {};

$a = $undefined; // provoke little warning

$test(new stdClass); // provoke catchable fatal error

Saída do Programa

string(32) "me is the previous error handler"
array(4) {
  [0]=>
  int(8)
  [1]=>
  string(29) "Undefined variable: undefined"
  [2]=>
  string(45) "/tmp/execpad-0eca072b619d/source-0eca072b619d"
  [3]=>
  int(28)
}

Fatal error: Uncaught exception 'ErrorException' with message 'Argument 1 passed to {closure}() must be callable, object given, called in /tmp/execpad-0eca072b619d/source-0eca072b619d on line 30 and defined' in /tmp/execpad-0eca072b619d/source-0eca072b619d:26
Stack trace:
#0 /tmp/execpad-0eca072b619d/source-0eca072b619d(26): {closure}(4096, 'Argument 1 pass...', '/tmp/execpad-0e...', 26, Array)
#1 /tmp/execpad-0eca072b619d/source-0eca072b619d(30): {closure}(Object(stdClass))
#2 {main}
  thrown in /tmp/execpad-0eca072b619d/source-0eca072b619d on line 26
hakre
fonte
Excelente redação. Durante o teste, houve uma degradação mensurável do desempenho ao redefinir o manipulador de erros?
MPW
Eu não tenho até agora. Há também uma área relacionada no núcleo em que, no modo de desenvolvimento, todos os avisos / erros são convertidos em Exceção (e não em ErrorExceptuion - nem mesmo registrados). Talvez isso exija um conjunto de patches para corrigir isso de maneira sã. Para o manipulador de erro, não existe um método bom expedição disponível, também aqui eu de alguma forma tendem a central de patch até mesmo para trazer um manipulador de erro padrão fixo no.
hakre
1

Ele já é tratado pelo PHP padrão adicionando (Exception $e)a definição de parâmetro de função.

Você não pode passar mais nada para essa função além de uma exceção ou extensão de exceção.

Jeroen
fonte
Dê uma olhada na mageCoreErrorHandlerfunção. Um erro acionado por parâmetros incorretos será tratado e suprimido no modo não-desenvolvedor e lançará um Exceptionno modo desenvolvedor.
MPW
Algo está seriamente errado quando isso acontece em primeiro lugar. O Magento precisa mageCoreErrorHandlergarantir que os visitantes não recebam um erro. Você pode criar um próprio try{}catch(){}para agarrá-los você mesmo e se não puder transmiti-los.
Jeroen
Considerando que nenhuma exceção é lançada no caso de um erro fatal suprimível suprimido, o que a tentativa / captura me levaria?
MPW
11
Finalmente estou conseguindo, depois de um teste local ... Você está certo, o erro foi suprimido e o código continuará. Atualizarei minha resposta e acrescentarei algumas idéias adicionais.
Jeroen
Vou postar uma nova resposta, caso contrário, nossa conversa não faz sentido.
Jeroen