PHP: exceções vs erros?

116

Talvez eu esteja perdendo isso em algum lugar do manual do PHP, mas qual é exatamente a diferença entre um erro e uma exceção? A única diferença que posso ver é que erros e exceções são tratados de forma diferente. Mas o que causa uma exceção e o que causa um erro?

Jason Baker
fonte

Respostas:

87

Exceções são lançadas - elas devem ser detectadas. Os erros geralmente são irrecuperáveis. Digamos, por exemplo - você tem um bloco de código que irá inserir uma linha em um banco de dados. É possível que esta chamada falhe (ID duplicado) - você vai querer ter um "Erro" que neste caso é uma "Exceção". Ao inserir essas linhas, você pode fazer algo assim

try {
  $row->insert();
  $inserted = true;
} catch (Exception $e) {
  echo "There was an error inserting the row - ".$e->getMessage();
  $inserted = false;
}

echo "Some more stuff";

A execução do programa continuará - porque você 'detectou' a exceção. Uma exceção será tratada como um erro, a menos que seja detectada. Isso permitirá que você continue a execução do programa depois que ele falhar.

gnarf
fonte
29
Errors are generally unrecoverable<- na verdade, isso não é verdade. E_ERRORe E_PARSEsão os dois erros irrecuperáveis ​​mais comuns (há alguns outros), mas a grande maioria dos erros que você verá no dev são recuperáveis ​​( E_NOTICE, E_WARNINGet al). Infelizmente, o tratamento de erros do PHP é uma bagunça completa - todos os tipos de coisas acionam erros desnecessariamente (a grande maioria das funções do sistema de arquivos, por exemplo). Em geral, as exceções são "o caminho OOP", mas infelizmente algumas das APIs OOP nativas do PHP usam erros em vez de exceções :-(
DaveRandom
1
@DaveRandom E_NOTICE, E_WARNING não são "erros" por definição, são? Sempre pensei que eram 'mensagens' que o PHP exibe para notificar o programador de que algo pode estar errado com o código que ele escreveu.
slhsen de
2
@slhsen o problema é realmente uma terminologia de baixa qualidade, todas as formas dessas mensagens passam pelo "sistema de tratamento de erros" no PHP, semanticamente todos esses eventos são "erros", embora semanticamente aviso / aviso definitivamente não seja o mesmo que um " erro "nesse contexto. Felizmente, o próximo PHP7 pelo menos pavimentou o caminho para resolver essa bagunça por meio de transformar a maioria dessas coisas em exceções capturáveis ​​(por meio de uma nova Throwableinterface), dando uma maneira muito mais expressiva e absoluta de distinguir e manusear adequadamente ambos problemas e mensagens consultivas
DaveRandom
"A execução do programa continuará" mudou, suponho? Visto que o PHP diz "Quando uma exceção é lançada, o código após a instrução não será executado" ( php.net/manual/en/language.exceptions.php )
Robert Sinclair
1
Acho que o que o OP significava era mais sobre a diferença entre os descendentes de ErrorVS e os descendentes de Exception.
XedinUnknown,
55

Eu geralmente uso set_error_handleruma função que pega o erro e lança uma exceção para que aconteça o que acontecer, eu só terei exceções para lidar. Não há mais @file_get_contentsapenas uma tentativa / captura simples e agradável.

Em situações de depuração, também tenho um manipulador de exceções que gera uma página como o asp.net. Estou postando isso na estrada, mas se solicitado, postarei a fonte de exemplo mais tarde.

editar:

Além, conforme prometido, recortei e colei parte do meu código para fazer uma amostra. Salvei o arquivo abaixo em minha estação de trabalho, você NÃO MAIS pode ver os resultados aqui (porque o link está quebrado).

<?php

define( 'DEBUG', true );

class ErrorOrWarningException extends Exception
{
    protected $_Context = null;
    public function getContext()
    {
        return $this->_Context;
    }
    public function setContext( $value )
    {
        $this->_Context = $value;
    }

    public function __construct( $code, $message, $file, $line, $context )
    {
        parent::__construct( $message, $code );

        $this->file = $file;
        $this->line = $line;
        $this->setContext( $context );
    }
}

/**
 * Inspire to write perfect code. everything is an exception, even minor warnings.
 **/
function error_to_exception( $code, $message, $file, $line, $context )
{
    throw new ErrorOrWarningException( $code, $message, $file, $line, $context );
}
set_error_handler( 'error_to_exception' );

function global_exception_handler( $ex )
{
    ob_start();
    dump_exception( $ex );
    $dump = ob_get_clean();
    // send email of dump to administrator?...

    // if we are in debug mode we are allowed to dump exceptions to the browser.
    if ( defined( 'DEBUG' ) && DEBUG == true )
    {
        echo $dump;
    }
    else // if we are in production we give our visitor a nice message without all the details.
    {
        echo file_get_contents( 'static/errors/fatalexception.html' );
    }
    exit;
}

function dump_exception( Exception $ex )
{
    $file = $ex->getFile();
    $line = $ex->getLine();

    if ( file_exists( $file ) )
    {
        $lines = file( $file );
    }

?><html>
    <head>
        <title><?= $ex->getMessage(); ?></title>
        <style type="text/css">
            body {
                width : 800px;
                margin : auto;
            }

            ul.code {
                border : inset 1px;
            }
            ul.code li {
                white-space: pre ;
                list-style-type : none;
                font-family : monospace;
            }
            ul.code li.line {
                color : red;
            }

            table.trace {
                width : 100%;
                border-collapse : collapse;
                border : solid 1px black;
            }
            table.thead tr {
                background : rgb(240,240,240);
            }
            table.trace tr.odd {
                background : white;
            }
            table.trace tr.even {
                background : rgb(250,250,250);
            }
            table.trace td {
                padding : 2px 4px 2px 4px;
            }
        </style>
    </head>
    <body>
        <h1>Uncaught <?= get_class( $ex ); ?></h1>
        <h2><?= $ex->getMessage(); ?></h2>
        <p>
            An uncaught <?= get_class( $ex ); ?> was thrown on line <?= $line; ?> of file <?= basename( $file ); ?> that prevented further execution of this request.
        </p>
        <h2>Where it happened:</h2>
        <? if ( isset($lines) ) : ?>
        <code><?= $file; ?></code>
        <ul class="code">
            <? for( $i = $line - 3; $i < $line + 3; $i ++ ) : ?>
                <? if ( $i > 0 && $i < count( $lines ) ) : ?>
                    <? if ( $i == $line-1 ) : ?>
                        <li class="line"><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? else : ?>
                        <li><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? endif; ?>
                <? endif; ?>
            <? endfor; ?>
        </ul>
        <? endif; ?>

        <? if ( is_array( $ex->getTrace() ) ) : ?>
        <h2>Stack trace:</h2>
            <table class="trace">
                <thead>
                    <tr>
                        <td>File</td>
                        <td>Line</td>
                        <td>Class</td>
                        <td>Function</td>
                        <td>Arguments</td>
                    </tr>
                </thead>
                <tbody>
                <? foreach ( $ex->getTrace() as $i => $trace ) : ?>
                    <tr class="<?= $i % 2 == 0 ? 'even' : 'odd'; ?>">
                        <td><?= isset($trace[ 'file' ]) ? basename($trace[ 'file' ]) : ''; ?></td>
                        <td><?= isset($trace[ 'line' ]) ? $trace[ 'line' ] : ''; ?></td>
                        <td><?= isset($trace[ 'class' ]) ? $trace[ 'class' ] : ''; ?></td>
                        <td><?= isset($trace[ 'function' ]) ? $trace[ 'function' ] : ''; ?></td>
                        <td>
                            <? if( isset($trace[ 'args' ]) ) : ?>
                                <? foreach ( $trace[ 'args' ] as $i => $arg ) : ?>
                                    <span title="<?= var_export( $arg, true ); ?>"><?= gettype( $arg ); ?></span>
                                    <?= $i < count( $trace['args'] ) -1 ? ',' : ''; ?> 
                                <? endforeach; ?>
                            <? else : ?>
                            NULL
                            <? endif; ?>
                        </td>
                    </tr>
                <? endforeach;?>
                </tbody>
            </table>
        <? else : ?>
            <pre><?= $ex->getTraceAsString(); ?></pre>
        <? endif; ?>
    </body>
</html><? // back in php
}
set_exception_handler( 'global_exception_handler' );

class X
{
    function __construct()
    {
        trigger_error( 'Whoops!', E_USER_NOTICE );      
    }
}

$x = new X();

throw new Exception( 'Execution will never get here' );

?>
Kris
fonte
Isso seria útil. Qualquer coisa para amenizar os tempos que fui feito para lidar com PHP ajudará. :-)
Jason Baker
Belo código, obrigado. Eu não entendo de onde vem a classe X, porém, e qual é o seu propósito?
Alec
tudo abaixo de "set_exception_handler ('global_exception_handler');" é apenas uma demonstração, você não precisa disso, é apenas para mostrar o que aconteceria em uma situação de erro normalmente sem exceção.
Kris
O PHP padrão define ErrorException especificamente para ser lançado de um manipulador de erros geral. Você me permitiria editar e atualizar sua postagem?
Tiberiu-Ionuț Stan
@ Tiberiu-IonuțStan: claro, mas o exemplo de trabalho ficará fora de sincronia. Além disso, hoje em dia eu provavelmente apontar as pessoas para github.com/theredhead/red.web/blob/master/src/lib/bootstrap.php de private-void.com vez.
Kris
21

A resposta merece falar sobre o elefante na sala

Erros é a maneira antiga de lidar com uma condição de erro em tempo de execução. Normalmente, o código faria uma chamada para algo como set_error_handlerantes de executar algum código. Seguindo a tradição de interrupções em linguagem assembly. Aqui está como ficaria algum código BASIC.

on error :divide_error

print 1/0
print "this won't print"

:divide_error

if errcode = X
   print "divide by zero error"

Era difícil ter certeza de que set_error_handlerseria chamado com o valor certo. E, pior ainda, uma chamada poderia ser feita para um procedimento separado que mudaria o manipulador de erros. Além disso, muitas vezes as chamadas eram intercaladas com set_error_handlerchamadas e manipuladores. Era fácil para o código sair rapidamente do controle. O tratamento de exceções veio em nosso auxílio formalizando a sintaxe e a semântica do que um bom código realmente estava fazendo.

try {
   print 1/0;
   print "this won't print";
} catch (DivideByZeroException $e) {
   print "divide by zero error";
}

Nenhuma função separada ou risco de chamar o manipulador de erros errado. O código agora tem a garantia de estar no mesmo lugar. Além disso, obtemos mensagens de erro melhores.

O PHP costumava ter apenas tratamento de erros, quando muitas outras linguagens já haviam evoluído para o modelo de tratamento de exceções preferível. Eventualmente, os criadores do PHP implementaram o tratamento de exceções. Mas, provavelmente para suportar código antigo, eles mantiveram o tratamento de erros e forneceram uma maneira de fazer o tratamento de erros parecer tratamento de exceção. Exceto que, não há garantia de que algum código não possa redefinir o manipulador de erros, que era exatamente o que o tratamento de exceções deveria fornecer.

Resposta final

Os erros que foram codificados antes da implementação do tratamento de exceções provavelmente ainda são erros. Novos erros são exceções prováveis. Mas não há design ou lógica para os quais haja erros e quais sejam exceções. É baseado apenas no que estava disponível no momento em que foi codificado e na preferência do programador que o codificou.

Arturo Hernandez
fonte
3
Esta é a verdadeira razão pela qual exceções e erros coexistem. Se projetado do zero, o php deve incluir apenas um ou outro.
Tomas Zubiri
1
É a melhor resposta na minha opinião, pois é a mais detalhada e explicativa.
Robert Kusznier
8

Uma coisa a adicionar aqui é sobre como lidar com exceções e erros. Para o propósito do desenvolvedor de aplicativos, os erros e as exceções são "coisas ruins" que você deseja registrar para aprender sobre os problemas que seu aplicativo tem - para que seus clientes tenham uma experiência melhor no longo prazo.

Portanto, faz sentido escrever um manipulador de erros que faça a mesma coisa que você faz para as exceções.

Alex Weinstein
fonte
Obrigado por fornecer o link!
Mike Moore
@Alex Weinstein: o link está quebrado
Marco Demaio
7

Conforme declarado em outras respostas, definir o manipulador de erros para lançador de exceções é a melhor maneira de lidar com erros em PHP. Eu uso uma configuração um pouco mais simples:

set_error_handler(function ($errno, $errstr, $errfile, $errline ) {
        if (error_reporting()) {
                throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
        }
});

Por favor, observe o error_reporting()cheque para manter@ operador trabalhando. Além disso, não há necessidade de definir exceção personalizada, o PHP tem uma classe legal para isso.

O grande benefício de lançar exceções é que a exceção tem rastreamento de pilha associado a ela, portanto, é fácil encontrar onde está o problema.

Josef Kufner
fonte
5

Re: "mas qual é exatamente a diferença entre um erro e uma exceção?"

Existem muitas respostas boas sobre as diferenças aqui. Vou apenas acrescentar algo que ainda não foi falado - desempenho. Especificamente, isso é para a diferença entre lançar / manipular exceções e manipular um código de retorno (sucesso ou algum erro). Normalmente, em php, isso significa retornar falseou null, mas podem ser mais detalhados, como no upload de arquivos: http://php.net/manual/en/features.file-upload.errors.php Você pode até retornar um objeto Exception !

Eu fiz algumas execuções de desempenho em diferentes linguagens / sistemas. De modo geral, o tratamento de exceções é cerca de 10.000 vezes mais lento do que a verificação de um código de retorno de erro.

Então, se absolutamente, positivamente, precisar terminar a execução antes mesmo de começar - bem, você está sem sorte porque a viagem no tempo não existe. Sem viagem no tempo, os códigos de retorno são a opção mais rápida disponível.

Editar:

PHP é altamente otimizado para tratamento de exceções. Testes do mundo real mostram que lançar uma exceção é apenas 2 a 10 vezes mais lento do que retornar um valor.

Evan
fonte
3
Claro, mas a quantidade de ciclos perdidos para lançar Exceptions é mais do que compensada pelos poderes descritivos extras que você obtém com Exceptions. Você pode lançar tipos específicos de exceções, até mesmo adicionar dados à exceção para conter os códigos de erro. Eu duvido seriamente da sua reivindicação de 10.000 * também. Mesmo se você estiver certo sobre a diferença de tempo, o tempo gasto fazendo return & if vs. new Execption, throw, catch em qualquer cenário do mundo real é provavelmente tão minúsculo em comparação com o código executado que esta é definitivamente uma otimização prematura. Jogue exceções, eles são mais agradáveis ​​de lidar em 90% do tempo.
gnarf
1
1. 10.000x é preciso - com algumas variações baseadas nas opções de linguagem e compilador 2. Você não precisa retornar nulo / falso. Você pode retornar um número - até MAX_ULONG códigos de retorno ali mesmo. Você pode, alternativamente, retornar uma string com falha e apenas verificar se há uma string de sucesso ou int ou null. 3. Em cenários do mundo real, cada ciclo de clock conta. O Facebook tem 552 milhões de usuários ativos diariamente. Supondo que as exceções sejam apenas 2x e que a verificação de usuário / passe leva 0,001s, o que significa uma economia de 153 horas de processamento todos os dias. A 10.000 vezes, ele economiza 175 anos. Apenas para verificar as tentativas de login - todos os dias.
evan
@evan: Para sua informação, aqui eles testaram o código com exceções e não parece ser mais lento: stackoverflow.com/a/445094/260080
Marco Demaio
@MarcoDemaio Essa questão cobre apenas o bloco try / catch sem lançar uma exceção. Um teste melhor seria retornar um valor em noexcept () e lançar uma exceção em except (). Além disso, deve surgir por meio de várias funções. stackoverflow.com/a/104375/505172 afirma que a diferença no PHP é na verdade 54x. Eu executei meu próprio teste em tempo real e parece estar 2-10x mais lento. Isso é muito melhor do que o esperado.
evan
@evan: Eu não ficaria preocupado então, eu uso exceções apenas para rastrear erros inesperados / irrecuperáveis, então mesmo que fosse 100 vezes mais lento, eu não me importaria. Minhas preocupações eram em tornar o código mais lento simplesmente adicionando blocos try / catch.
Marco Demaio
4

Acho que a resposta que você está procurando é essa;

Erros são coisas padrão com as quais você está acostumado, como ecoar uma variável $ que não existe.
As exceções são apenas do PHP 5 em diante e ocorrem ao lidar com objetos.

Para simplificar:

As exceções são os erros que você obtém ao lidar com objetos. A instrução try / catch permite que você faça algo sobre eles, porém, e é usada de forma muito semelhante à instrução if / else. Tente fazer isso, se o problema não importa, faça isso.

Se você não "capturar" uma exceção, ela se tornará um erro padrão.

Os erros são os erros fundamentais do php que geralmente interrompem o seu script.

Try / catch é freqüentemente usado para estabelecer conexões de banco de dados como PDO, o que é bom se você quiser redirecionar o script ou fazer outra coisa se a conexão não funcionar. Mas se você deseja apenas exibir a mensagem de erro e interromper o script, então não precisa disso, a exceção não capturada se transforma em um erro fatal. Ou você também pode usar uma configuração de tratamento de erros em todo o site.

espero que ajude

Lan
fonte
3
As exceções também podem ser usadas com código procedural em PHP.
Tiberiu-Ionuț Stan
2

No PHP 7.1 e posterior, um bloco catch pode especificar várias exceções usando o caractere pipe (|). Isso é útil quando diferentes exceções de diferentes hierarquias de classes são tratadas da mesma forma.

try {
  // do something
} catch (Error | Exception $e) {
  echo $e->getMessage();
}
Jehong Ahn
fonte
1

As exceções são lançadas intencionalmente pelo código usando um lançamento, erros ... nem tanto.

Os erros ocorrem como resultado de algo que não é tratado normalmente. (Erros de IO, erros de TCP / IP, erros de referência nula)

cgp
fonte
1
Isto não é necessariamente verdade. Em muitos casos, os erros são verificados e os códigos de retorno são enviados de volta intencionalmente, conforme apropriado. Na verdade, esse é o caso de toda linguagem não orientada a objetos. As exceções são apenas exceções à regra. Em ambos os casos, algo dá errado, é notado e deve ser tratado. O upload do arquivo PHP é um exemplo de tratamento de erro intencional por meio de códigos de retorno - php.net/manual/en/features.file-upload.errors.php
evan
1

Pretendo apresentar a você uma discussão muito incomum sobre controle de erros.

Eu criei um gerenciador de erros muito bom em uma linguagem anos atrás e, embora alguns dos nomes tenham mudado, os princípios do processamento de erros são os mesmos hoje. Eu tinha um sistema operacional multitarefa personalizado e precisava ser capaz de me recuperar de erros de dados em todos os níveis, sem vazamentos de memória, crescimento da pilha ou travamentos. Portanto, o que se segue é meu entendimento de como os erros e exceções devem operar e como eles diferem. Direi apenas que não tenho um entendimento de como funciona o funcionamento interno do try catch, então estou supondo até certo ponto.

A primeira coisa que acontece nos bastidores para o processamento de erros é pular de um estado do programa para outro. Como isso é feito? Eu vou chegar lá.

Historicamente, os erros são mais antigos e simples, e as exceções são mais recentes e um pouco mais complexas e capazes. Os erros funcionam bem até que você precise colocá-los em bolha, o que equivale a entregar um problema difícil ao seu supervisor.

Os erros podem ser números, como números de erros e, às vezes, com uma ou mais strings associadas. Por exemplo, se ocorrer um erro de leitura de arquivo, você poderá relatar o que é e possivelmente falhar normalmente. (Hay, é um passo além de apenas bater como nos velhos tempos.)

O que não é freqüentemente dito sobre exceções é que exceções são objetos dispostos em camadas em uma pilha de exceções especial. É como uma pilha de retorno para o fluxo do programa, mas mantém um estado de retorno apenas para erros e capturas. (Eu costumava chamá-los de ePush e ePop, e? Abort era um lançamento condicional que iria ePop e se recuperaria para aquele nível, enquanto Abort era um dado completo ou saída.)

Na parte inferior da pilha estão as informações sobre o chamador inicial, o objeto que sabe sobre o estado quando o try externo foi iniciado, que geralmente é quando seu programa foi iniciado. Além disso, ou a próxima camada na pilha, com up sendo os filhos e down sendo os pais, está o objeto de exceção do próximo bloco try / catch interno.

Se você colocar uma tentativa dentro de uma tentativa, estará empilhando a tentativa interna sobre a tentativa externa. Quando ocorre um erro no try interno e o catch interno não consegue lidar com isso ou o erro é lançado para o try externo, o controle é passado para o bloco catch externo (objeto) para ver se ele pode tratar o erro, ou seja, seu supervisor.

Portanto, o que essa pilha de erros realmente faz é ser capaz de marcar e restaurar o fluxo do programa e o estado do sistema, em outras palavras, permite que um programa não bloqueie a pilha de retorno e bagunce as coisas para os outros (dados) quando as coisas dão errado. Portanto, ele também salva o estado de quaisquer outros recursos, como pools de alocação de memória, e pode limpá-los quando a captura for concluída. Em geral, isso pode ser uma coisa muito complicada e é por isso que o tratamento de exceções costuma ser lento. Em geral, um pouco de estado precisa entrar nesses blocos de exceção.

Assim, um tipo de bloco try / catch define um estado ao qual poderemos retornar se todo o resto for bagunçado. É como um pai. Quando nossas vidas ficam confusas, podemos cair de volta no colo de nossos pais e eles vão consertar tudo de novo.

Espero não ter te desapontado.

Vista elíptica
fonte
1

Você pode adicionar este comentário

function doSomething()
{
   /** @noinspection PhpUnhandledExceptionInspection */
   throw new Exception();
}
Yolo
fonte
0

Uma vez que set_error_handler () é definido, o manipulador de erros é semelhante ao de Exception. Veja o código abaixo:

 <?php
 function handleErrors( $e_code ) {
   echo "error code: " . $e_code . "<br>";
 }

 set_error_handler( "handleErrors" ); 

 trigger_error( "trigger a fatal error", E_USER_ERROR);
 echo "after error."; //it would run if set_error_handler is defined, otherwise, it wouldn't show
?>
N Zhang
fonte