Tratamento de erros no PHP ao usar o MVC

12

Eu tenho usado o Codeigniter muito recentemente, mas uma coisa que me dá nos nervos é lidar com erros e exibi-los ao usuário. Eu nunca fui bom em lidar com erros sem ficar confuso. Minha principal preocupação é ao retornar erros ao usuário.

É uma boa prática usar exceções e lançar / capturar exceções em vez de retornar 0 ou 1 de funções e usar if / else para manipular os erros. Dessa forma, é mais fácil informar o usuário sobre o problema.

Eu tendem a me afastar de exceções. Meu tutor de Java na universidade há alguns anos me disse que "as exceções não devem ser usadas no código de produção, é mais para depuração". Tenho a sensação de que ele estava mentindo.

Mas, por exemplo, eu tenho um código que adiciona um usuário a um banco de dados. Durante o processo, mais de uma coisa pode dar errado, como um problema no banco de dados, uma entrada duplicada, um problema no servidor etc. Quando um problema ocorre durante o registro, o usuário precisa conhecê-lo.

Qual é a melhor maneira de lidar com erros em PHP, tendo em mente que estou usando uma estrutura MVC.

James Jeffery
fonte

Respostas:

14

É uma boa prática usar exceções e lançar / capturar exceções em vez de retornar 0 ou 1 de funções e usar if / else para manipular os erros. Dessa forma, é mais fácil informar o usuário sobre o problema.

Não não não!

Não misture exceções e erros. Exceções são, bem, excepcionais. Erros não são. Quando você solicita que um usuário insira uma quantidade de produto e o usuário digita "olá", é um erro. Não é uma exceção: não há nada de excepcional em ver uma entrada inválida do usuário. Por que você não pode usar exceções em casos não excepcionais, como na validação de entrada? Outras pessoas já explicaram e mostraram uma alternativa válida para validação de entrada.

Isso também significa que o usuário não se preocupa com suas exceções , e mostrar as exceções é hostil e perigoso . Por exemplo, uma exceção durante a execução de uma consulta SQL frequentemente revela a própria consulta. Tem certeza de que deseja correr o risco de mostrar essa mensagem a todos?

mais de uma coisa pode dar errado, como um problema no banco de dados, uma entrada duplicada, um problema no servidor etc. Quando um problema ocorre durante o registro, o usuário precisa conhecê-lo.

Errado. Como usuário, não preciso conhecer os problemas de seu banco de dados, entradas duplicadas etc. Não me importo com seus problemas. O que eu faço precisa saber é que eu entrei um nome de usuário que já existe. Como já foi dito, uma entrada errada minha deve desencadear um erro, não uma exceção.

Como gerar esses erros? Depende do contexto. Para um nome de usuário já usado, eu gostaria de ver uma pequena bandeira vermelha aparecendo perto do nome de usuário, antes mesmo de enviar o formulário, dizendo que o nome de usuário já está sendo usado. Sem JavaScript, o mesmo sinalizador deve aparecer após o envio.

Um exemplo de erro ativado por AJAX

Para outros erros, você mostraria uma página inteira com um erro ou escolheria outra maneira de informar ao usuário que algo deu errado (por exemplo, uma mensagem que será exibida e desaparecerá na parte superior da página). A questão está relacionada mais à experiência do usuário do que à programação.

Do ponto de vista dos programadores, dependendo do tipo de erro, você o propagará de maneiras diferentes. Por exemplo, no caso de um nome de usuário já utilizado, uma solicitação AJAX http://example.com/?ajax=1&user-exists=Johnretornará um objeto JSON indicando:

  • Que o usuário já existe,
  • A mensagem de erro para mostrar ao usuário.

O segundo ponto é importante: você deseja garantir que a mesma mensagem apareça ao enviar o formulário com o JavaScript desativado e digitar um nome de usuário duplicado com o JavaScript ativado. Você não deseja duplicar o texto da mensagem de erro no código-fonte do servidor e em JavaScript!

Essa é realmente a técnica usada pelos sites Stack Exhange. Por exemplo, se eu tentar aprovar minha própria resposta, a resposta AJAX conterá o erro a ser exibido:

{"Success":false,"Warning":false,"NewScore":0,"Message":"You can't vote for your own post.",
"Refresh":false}

Você também pode escolher outra abordagem e predefinir os erros na página HTML antes do preenchimento do formulário. Prós: você não precisa enviar a mensagem de erro na resposta AJAX. Contras: e a acessibilidade? Tente navegar na página sem CSS e você verá todos os erros possíveis.

Arseni Mourzenko
fonte
Agradeço a resposta. É exatamente com isso que estou lutando. Você tem algum recurso para relatar erros, especialmente em termos de experiência do usuário?
James Jeffery
Bem, como eu disse, isso realmente depende do erro, e o relatório de erros para o usuário está fortemente vinculado à interface do usuário. Também destaquei duas maneiras principais de relatar erros: integração estrita (uma bandeira vermelha habilitada para AJAX perto da entrada com um valor errado) e erros de página inteira, muito menos amigáveis, usados ​​para casos mais graves. Isso não responde à sua pergunta?
Arseni Mourzenko
2
+1 por ser mais um problema de experiência do usuário e não técnico
Charles Sprayberry
2
Besteira, MainMa. Apenas besteira. Os códigos de erro são tão 80 e 90. As exceções são uma maneira muito mais limpa de lidar com circunstâncias especiais, como entrada incorreta (ValidationException, por exemplo). Você não é obrigado a exibir todas as exceções ao usuário. Eu já vi melhores respostas suas.
Falcon
2
E caso você não o conheça: Você pode controlar quais exceções deseja apresentar ao usuário e quais não deseja que sejam apresentadas. Portanto, isso não é realmente um argumento.
Falcon
13

É uma boa prática usar exceções e lançar / capturar exceções em vez de retornar 0 ou 1 de funções e usar if / else para manipular os erros. Dessa forma, é mais fácil informar o usuário sobre o problema.

Sim Sim Sim!

Se você deseja ter um código limpo, use quase exclusivamente exceções e não se preocupe em usar códigos de erro. Os códigos de erro não têm sentido. Eles quase sempre estão ligados a alguma constante numérica que não traz muita informação. Isso pode tornar seu código ilegível e dificultar a propagação de dados junto com o erro.

Exceções, no entanto, são classes e podem conter qualquer informação que você desejar. Portanto, o usuário inseriu uma entrada incorreta, como 'abc' para um campo numérico. Com um código de erro, você não seria capaz de propagar essas informações para o manipulador do erro sem muito esforço. Algo que as exceções fornecem gratuitamente. Além disso, as exceções permitem que você tenha valores de retorno significativos em funções e métodos enquanto ainda tem uma maneira de falhar com elegância. Melhor ainda, as exceções são propagadas diretamente para o local em que você deseja lidar com elas! Imagine a quantidade de código espaguete necessária para propagar um código de erro com dados significativos para um manipulador uma ou duas camadas acima.

Além disso, as exceções expressam muito mais semanticamente que os códigos de erro. Os códigos de erro levam ao código de espaguete, onde o tratamento de exceção leva ao código limpo.

Além disso, é fácil esquecer de verificar os códigos de status. Em linguagens como Java, você é forçado a lidar com exceções (algo que, por exemplo, C # erra).

Qual é a melhor maneira de lidar com erros em PHP, tendo em mente que estou usando uma estrutura MVC.

Use exceções e lide com elas em seus controladores.

Falcão
fonte
Eu concordo muito com você !! Altough exceções não são tão forçada n PHP como em outras línguas, é bom saber que muitas pessoas estão incorprating eles ...
David Conde
6
Concordo que, na maioria dos casos, os códigos de erro não têm sentido algum. No entanto, lançar exceções, querendo ou não, é extremamente ruim! As exceções devem ser reservadas apenas para circunstâncias excepcionais. As exceções causam um fluxo imprevisível do programa, podem dificultar o acompanhamento do código (e, portanto, manter), e no PHP elas carregam uma penalidade de desempenho bastante significativa em comparação ao IF / THEN / ELSE. Eu tendem a preferir métodos para retornar verdadeiro no sucesso, falso no fracasso e apenas lançar uma exceção de algo que dá errado.
5602 GordonM
6

Considere esta pequena classe útil:

class FunkyFile {               

    private $path;
    private $contents = null;

    public function __construct($path) { 
        $this->setPath($path); 
    }

    private function setPath($path) {
        if( !is_file($path) || !is_readable($path) ) 
            throw new \InvalidArgumentException("Hm, that's not a valid file!");

        $this->path = realpath($path);
        return $this; 
    }

    public function getContents() {
        if( is_null($this->contents) ) {
            $this->contents = @file_get_contents( $this->path );
            if($this->contents === false) 
                throw new \Exception("Hm, I can't read the file, for some reason!");                                 
        }

        return $this->contents;            
    }

}

Esse é um ótimo uso de exceções. Da FunkyFile'sperspectiva, não há absolutamente nada que possa ser feito para remediar a situação se o caminho for inválido ou file_get_contentsfalhar. Uma situação verdadeiramente excepcional;)

Mas existe algum valor para o usuário saber que você encontrou um caminho de arquivo errado, em algum lugar do seu código? Por exemplo:

class Welcome extends Controller {

    public function index() {

        /**
         * Ah, let's show user this file she asked for
         */                 
        try {
            $file = new File("HelloWorld.txt");
            $contents = $file->getContents();   
            echo $contents;
        } catch(\Exception $e) {
            log($e->getMessage());

            echo "Sorry, I'm having a bad day!"; 
        }                           
    }        
}

Além de dizer às pessoas que você está tendo um dia ruim, suas opções são:

  1. Cair pra trás

    Você tem outra maneira de obter as informações? No meu exemplo simples acima, não parece provável, mas considere um esquema de banco de dados mestre / escravo. O mestre pode ter falhado em responder, mas talvez, apenas talvez, o escravo ainda esteja lá fora (ou vice-versa).

  2. A culpa é do usuário?

    O usuário enviou uma entrada com defeito? Bem, conte a ela sobre isso. Você pode latir uma mensagem de erro ou ser gentil e acompanhar essa mensagem de erro com um formulário para que ela possa digitar o caminho correto.

  3. A culpa é sua?

    E por você, quero dizer qualquer coisa que não seja o usuário, que varia de você digitando um caminho de arquivo errado a algo dando errado no seu servidor. A rigor, é hora de um erro HTTP 503 , pois o serviço não está disponível. O CI tem uma show_404()função, você pode criar facilmente show_503().

Conselho, você deve levar em consideração exceções não autorizadas. CodeIgniter é um pedaço de código confuso e você nunca sabe quando uma exceção será exibida. Da mesma forma, você pode esquecer suas próprias exceções, e a opção mais segura é implementar um manipulador de capturar todas as exceções. No PHP, você pode fazer isso com set_exception_handler :

function FunkyExceptionHandler($exception) {
    if(ENVIRONMENT == "production") {
        log($e->getMessage());
        show_503();
    } else {
        echo "Uncaught exception: " , $exception->getMessage(), "\n";
    }   
}

set_exception_handler("FunkyExceptionHandler");

E você também pode cuidar de erros não autorizados , via set_error_handler . Você pode escrever o mesmo manipulador das exceções ou, alternativamente, converter todos os erros ErrorExceptione permitir que o manipulador de exceções lide com eles:

function FunkyErrorHandler($errno, $errstr, $errfile, $errline) {
    // will be caught by FunkyExceptionHandler if not handled
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}

set_error_handler("FunkyErrorHandler");
yannis
fonte
Isso foi realmente informativo, um brinde!
10133 James