Posso tentar / receber um aviso?

358

Eu preciso pegar alguns avisos sendo lançados a partir de algumas funções nativas do php e depois lidar com eles.

Especificamente:

array dns_get_record  ( string $hostname  [, int $type= DNS_ANY  [, array &$authns  [, array &$addtl  ]]] )

Emite um aviso quando a consulta DNS falha.

try/ catchnão funciona porque um aviso não é uma exceção.

Agora tenho 2 opções:

  1. set_error_handler parece exagero porque tenho que usá-lo para filtrar todos os avisos da página (isso é verdade?);

  2. Ajuste o relatório / exibição de erros para que esses avisos não sejam exibidos na tela e verifique o valor de retorno; se for false, nenhum registro será encontrado para o nome do host.

Qual é a melhor prática aqui?

user121196
fonte
11
stackoverflow.com/questions/136899/… é uma boa discussão sobre coisas como esta.
Mez
houve uma resposta abaixo que foi excluída? pelo proprietário ou por alguém?
user121196
Veja também: stackoverflow.com/questions/1087365
dreftymac 11/11
@ user121196: Sim. Pelo proprietário.
Lightness Races in Orbit em

Respostas:

373

Definir e restaurar manipulador de erros

Uma possibilidade é definir seu próprio manipulador de erros antes da chamada e restaurar o manipulador de erros anterior posteriormente com restore_error_handler().

set_error_handler(function() { /* ignore errors */ });
dns_get_record();
restore_error_handler();

Você pode desenvolver essa ideia e escrever um manipulador de erros reutilizável que registre os erros para você.

set_error_handler([$logger, 'onSilencedError']);
dns_get_record();
restore_error_handler();

Transformando erros em exceções

Você pode usar set_error_handler()e a ErrorExceptionclasse para transformar todos os erros de php em exceções.

set_error_handler(function($errno, $errstr, $errfile, $errline, $errcontext) {
    // error was suppressed with the @-operator
    if (0 === error_reporting()) {
        return false;
    }

    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});

try {
    dns_get_record();
} catch (ErrorException $e) {
    // ...
}

O importante a ser observado ao usar seu próprio manipulador de erros é que ele ignorará a error_reportingconfiguração e passará todos os erros (avisos, avisos etc.) para o manipulador de erros. Você pode definir um segundo argumento set_error_handler()para definir quais tipos de erro deseja receber ou acessar a configuração atual usando ... = error_reporting()dentro do manipulador de erros.

Suprimindo o aviso

Outra possibilidade é suprimir a chamada com o operador @ e verificar o valor de retorno dns_get_record()posteriormente. Mas eu aconselho isso, pois os erros / avisos são acionados para serem manipulados, não para serem suprimidos.

Philippe Gerber
fonte
3
é aconselhável definir meu próprio manipulador de erros antes da chamada da função e, em seguida, restore_error_handler quando a verificação de erros for concluída?
User121196
2
isso será seguro para threads se houver muitas solicitações simultâneas e cada solicitação for 1.set_error_handler (). 2.doit 3.restore_error_handler?
user121196
4
Obrigado; isso ajuda. (E dizem que PHP não é um desastre.)
Aaron Miller
2
+1 para evitar o uso de @ para suprimir erros. E_WARNING é realmente um erro não fatal. Em geral, você deve sempre tentar manipular os erros adequadamente. Se seu aplicativo exigir o uso de set_error_handler, faça-o. Geralmente, é recomendável registrar erros e desativar a exibição deles em um ambiente de produção. Ao verificar os logs, você pode ver onde fazer alterações em seu ambiente de desenvolvimento. Muitas instâncias em que vi @ fopen / @ unlink e me pergunto por que o desenvolvedor não executou verificações para evitar os erros ou manipular o erro usando set_error_handler.
precisa saber é
5
Uma observação sobre como transformar avisos em exceções: um aviso não abortará seu aplicativo - uma exceção não detectada servirá!
Álvaro González
149

A solução que realmente funciona acabou por definir um manipulador de erros simples com o E_WARNINGparâmetro, da seguinte maneira:

set_error_handler("warning_handler", E_WARNING);
dns_get_record(...)
restore_error_handler();

function warning_handler($errno, $errstr) { 
// do something
}
Robert
fonte
4
Também anônimo callablepode ser usado aqui em vez de string com declaração da função
vp_arth
Obrigado, mas como posso remover o manipulador de erros após o bloco crítico?
Yevgeniy Afanasyev
3
Excelente! Apenas trow new \Exception($errstr, $errno);dentro da warning_handlerfunção. Obrigado.
Vladimir Vukanac 29/07
Esta é a melhor resposta aqui!
lewis4u
28

Tenha cuidado com o @operador - enquanto suprime avisos, também suprime erros fatais. Passei muito tempo depurando um problema em um sistema em que alguém havia escrito @mysql_query( '...' )e o problema era que o suporte ao mysql não era carregado no PHP e gerava um erro fatal silencioso. Será seguro para aquelas coisas que fazem parte do núcleo do PHP, mas por favor, usá-lo com cuidado.

bob@mypc:~$ php -a
Interactive shell

php > echo @something(); // this will just silently die...

Não há mais saída - boa sorte depurando isso!

bob@mypc:~$ php -a
Interactive shell

php > echo something(); // lets try it again but don't suppress the error
PHP Fatal error:  Call to undefined function something() in php shell code on line 1
PHP Stack trace:
PHP   1. {main}() php shell code:0
bob@mypc:~$ 

Desta vez, podemos ver por que ele falhou.

GuruBob
fonte
5

Eu queria tentar / capturar um aviso, mas ao mesmo tempo manter o log de aviso / erro usual (por exemplo, in /var/log/apache2/error.log); para o qual o manipulador deve retornar false. No entanto, como a instrução "throw new ..." basicamente interrompe a execução, é necessário executar o truque "wrap in function", também discutido em:

Existe uma maneira estática de lançar exceção em php

Ou, resumidamente:

  function throwErrorException($errstr = null,$code = null, $errno = null, $errfile = null, $errline = null) {
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
  }
  function warning_handler($errno, $errstr, $errfile, $errline, array $errcontext) {
    return false && throwErrorException($errstr, 0, $errno, $errfile, $errline);
    # error_log("AAA"); # will never run after throw
    /* Do execute PHP internal error handler */
    # return false; # will never run after throw
  }
  ...
  set_error_handler('warning_handler', E_WARNING);
  ...
  try {
    mkdir($path, 0777, true);
  } catch (Exception $e) {
    echo $e->getMessage();
    // ...
  }

EDIT: após uma inspeção mais minuciosa, verifica-se que não funciona: o " return false && throwErrorException ..." basicamente não lançará a exceção e apenas fará o logon no log de erros; remover a false &&parte " ", como em " return throwErrorException ...", fará com que a exceção funcione, mas não fará logon no error_log ... Eu ainda manteria isso informado, pois não vi esse comportamento documentado em outro lugar.

sdaau
fonte
4

Você provavelmente deve tentar se livrar completamente do aviso, mas se isso não for possível, você pode anexar a chamada com @ (por exemplo, @dns_get_record (...)) e, em seguida, usar todas as informações para descobrir se o aviso ocorreu. ou não.

rpjohnst
fonte
4

Normalmente você nunca deve usar @, a menos que seja a única solução. Nesse caso específico, a função dns_check_record deve ser usada primeiro para saber se o registro existe.

florynth
fonte
3

A combinação dessas linhas de código em torno de uma file_get_contents()chamada para um URL externo me ajudou a lidar com avisos como " falha ao abrir o fluxo: a conexão expirou " muito melhor:

set_error_handler(function ($err_severity, $err_msg, $err_file, $err_line, array $err_context)
{
    throw new ErrorException( $err_msg, 0, $err_severity, $err_file, $err_line );
}, E_WARNING);
try {
    $iResult = file_get_contents($sUrl);
} catch (Exception $e) {
    $this->sErrorMsg = $e->getMessage();
}
restore_error_handler();

Essa solução também funciona no contexto do objeto. Você poderia usá-lo em uma função:

public function myContentGetter($sUrl)
{
  ... code above ...
  return $iResult;
}
Bugfighter
fonte
2

Se dns_get_record()falhar, ele deve retornar FALSE, para que você possa suprimir o aviso com @e depois verificar o valor de retorno.

Âmbar
fonte
0

tente verificar se ele retorna algum valor booleano, então você pode simplesmente colocá-lo como uma condição. Encontrei isso com o oci_execute (...), que estava retornando alguma violação com minhas chaves exclusivas.

ex.
oci_parse($res, "[oracle pl/sql]");
if(oci_execute){
...do something
}
gborjal
fonte
0

FolderStructure

index.php //Script File
logs //Folder for log Every warning and Errors
CustomException.php //Custom exception File

CustomException.php

/**
* Custom error handler
*/
function handleError($code, $description, $file = null, $line = null, $context = null) {
    $displayErrors = ini_get("display_errors");;
    $displayErrors = strtolower($displayErrors);
    if (error_reporting() === 0 || $displayErrors === "on") {
        return false;
    }
    list($error, $log) = mapErrorCode($code);
    $data = array(
        'timestamp' => date("Y-m-d H:i:s:u", time()),
        'level' => $log,
        'code' => $code,
        'type' => $error,
        'description' => $description,
        'file' => $file,
        'line' => $line,
        'context' => $context,
        'path' => $file,
        'message' => $error . ' (' . $code . '): ' . $description . ' in [' . $file . ', line ' . $line . ']'
    );
    $data = array_map('htmlentities',$data);
    return fileLog(json_encode($data));
}

/**
* This method is used to write data in file
* @param mixed $logData
* @param string $fileName
* @return boolean
*/
function fileLog($logData, $fileName = ERROR_LOG_FILE) {
    $fh = fopen($fileName, 'a+');
    if (is_array($logData)) {
        $logData = print_r($logData, 1);
    }
    $status = fwrite($fh, $logData . "\n");
    fclose($fh);
//    $file = file_get_contents($filename);
//    $content = '[' . $file .']';
//    file_put_contents($content); 
    return ($status) ? true : false;
}

/**
* Map an error code into an Error word, and log location.
*
* @param int $code Error code to map
* @return array Array of error word, and log location.
*/
function mapErrorCode($code) {
    $error = $log = null;
    switch ($code) {
        case E_PARSE:
        case E_ERROR:
        case E_CORE_ERROR:
        case E_COMPILE_ERROR:
        case E_USER_ERROR:
            $error = 'Fatal Error';
            $log = LOG_ERR;
            break;
        case E_WARNING:
        case E_USER_WARNING:
        case E_COMPILE_WARNING:
        case E_RECOVERABLE_ERROR:
            $error = 'Warning';
            $log = LOG_WARNING;
            break;
        case E_NOTICE:
        case E_USER_NOTICE:
            $error = 'Notice';
            $log = LOG_NOTICE;
            break;
        case E_STRICT:
            $error = 'Strict';
            $log = LOG_NOTICE;
            break;
        case E_DEPRECATED:
        case E_USER_DEPRECATED:
            $error = 'Deprecated';
            $log = LOG_NOTICE;
            break;
        default :
            break;
    }
    return array($error, $log);
}
//calling custom error handler
set_error_handler("handleError");

basta incluir o arquivo acima em seu arquivo de script como este

index.php

error_reporting(E_ALL);
ini_set('display_errors', 'off');
define('ERROR_LOG_FILE', 'logs/app_errors.log');

include_once 'CustomException.php';
echo $a; // here undefined variable warning will be logged into logs/app_errors.log
Juned Ansari
fonte
-2

Eu recomendaria apenas usar @ para suprimir avisos quando for uma operação direta (por exemplo, $ prop = @ ($ high / ($ width - $ depth)); para pular a divisão por zero avisos). No entanto, na maioria dos casos, é melhor lidar com isso.

tanovellino
fonte
2
Esta é uma vez que você definitivamente não quer usar @ - você tem controle sobre a operação e pode verificar se é uma divisão por zero ou não antes de fazê-lo.
Eborbob