개발-PHP

[함수] set_error_handler 함수 한계 극복하기

WEBKIKIS 2016. 3. 30. 21:03
반응형

[함수] set_error_handler 함수 한계 극복하기


PHPSchool을 안지는 오래되었지만 글은 정말 오랜만에 적는 것 같네요.
최근에는 php로 프로젝트를 진행하고 있습니다. 예전에 했던 php와 많이 달라졌네요. ㅎㅎ
많은 분들이 알고 계신 것이겠지만 혹여나 도움되길 바라며.... ^^

블로그에 올린 글이라 반말입니다. 양해주시길... ^^

set_error_handler()나 set_exception_handler()로 php상에서 발생하는 에러나 예외를 모두 원하는 방식으로 처리하려고 했다. 가령, 중간에 에러를 출력하지 않고 DB나 Text로 저장하던가 특정 에러는 e-mail로 보내줘야 한다던가 하는 일들을 위해서이다. 또는 앱과 같은 외부플랫폼과 통신하기 위해 특정 통신 프로토콜(json이나 xml등)을 사용하게 되는데 예외 및 에러가 발생할때 이 규격에 맞지 않게 외부에 그냥 텍스트를 출력해버리면 곤란하기 때문이다. 그래서 이 함수를 사용한 코드의 인터페이스는 아래처럼 만들수 있게 된다.

=============================================
class Core {
  static public function run() {
      header('Content-Type:application/json;charset=utf-8');
      error_reporting( E_ALL | E_STRICT );
      ini_set( 'display_errors', 0 ); //에러를 출력하지 않는다.
      set_exception_handler( array( __CLASS__, '__exception_handler' ) );
      set_error_handler( array( __CLASS__, '__error_handler' ) );
      .....
  }
  static public function __exception_handler( $e ) { .... }
  static public function __error_handler( $no, $str, $file, $line  ) { .... }
}
Core::run(); //구동시작
=============================================

내 코드는 json을 반환하고 싶은 것이고 모든 에러를 리포팅하되 출력하지는 못하도록 했다. 원래 목적대로 모든 에러나 예외를 global 수준에서 처리하도록 하기 위해서이다.

하지만 set_error_handler()는 한가지 예외가 있었다. php 레퍼런스에 보면 다음문구가 있다.

=============================================
The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.
=============================================

즉, set_error_handler() 함수는 "E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING" 에러는 처리 못함을 알려주고 있다.  실제 코딩상에 발생되는 오류나 문제를 set_error_handler()로 커버할 수 없다는 뜻이다. 맞는 말이긴 하지만 나의 목표에는 부족함이 있다.
검색해보니 이런 형태로 커버하는 것도 보았다.

=============================================
$url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
ini_set('error_prepend_string','<head><META http-equiv=" refresh"="" content="0;URL=/error.php?url='.$url.'&msg=');
ini_set('error_append_string','">');
=============================================

물론 일부 해결방법이긴 하지만 그저 텍스트를 출력하는 템플릿만 바꿨을 뿐이다. 뭔가 오류발생시 따로 가공해야한다면 부족함은 여전하다.

조금더 검색해보니 register_shutdown_function() 함수와 error_get_last() 함수를 사용하는 방법이 있다.

아래코드는 아래 링크에 답글에 있는 일부이다.
http://stackoverflow.com/questions/2331582/catch-php-fatal-error

=============================================
define('E_FATAL', E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR |
        E_COMPILE_ERROR | E_RECOVERABLE_ERROR);
define('ENV', 'dev');

//Custom error handling vars
define('DISPLAY_ERRORS', TRUE);
define('ERROR_REPORTING', E_ALL | E_STRICT);
define('LOG_ERRORS', TRUE);

register_shutdown_function('shut');
set_error_handler('handler');

//Function to catch no user error handler function errors...
function shut(){

    $error = error_get_last();

    if($error && ($error['type'] & E_FATAL)){
        handler($error['type'], $error['message'], $error['file'], $error['line']);
    }

}

function handler( $errno, $errstr, $errfile, $errline ) {

    switch ($errno){

        case E_ERROR: // 1 //
            $typestr = 'E_ERROR'; break;
        case E_WARNING: // 2 //
            $typestr = 'E_WARNING'; break;
        case E_PARSE: // 4 //
            $typestr = 'E_PARSE'; break;
        case E_NOTICE: // 8 //
            $typestr = 'E_NOTICE'; break;
        case E_CORE_ERROR: // 16 //
            $typestr = 'E_CORE_ERROR'; break;
        case E_CORE_WARNING: // 32 //
            $typestr = 'E_CORE_WARNING'; break;
        case E_COMPILE_ERROR: // 64 //
            $typestr = 'E_COMPILE_ERROR'; break;
        case E_CORE_WARNING: // 128 //
            $typestr = 'E_COMPILE_WARNING'; break;
        case E_USER_ERROR: // 256 //
            $typestr = 'E_USER_ERROR'; break;
        case E_USER_WARNING: // 512 //
            $typestr = 'E_USER_WARNING'; break;
        case E_USER_NOTICE: // 1024 //
            $typestr = 'E_USER_NOTICE'; break;
        case E_STRICT: // 2048 //
            $typestr = 'E_STRICT'; break;
        case E_RECOVERABLE_ERROR: // 4096 //
            $typestr = 'E_RECOVERABLE_ERROR'; break;
        case E_DEPRECATED: // 8192 //
            $typestr = 'E_DEPRECATED'; break;
        case E_USER_DEPRECATED: // 16384 //
            $typestr = 'E_USER_DEPRECATED'; break;

    }

    $message = ''.$typestr.': '.$errstr.' in '.$errfile.' on line '.$errline.'
';

    if(($errno & E_FATAL) && ENV === 'production'){

        header('Location: 500.html');
        header('Status: 500 Internal Server Error');

    }

    if(!($errno & ERROR_REPORTING))
        return;

    if(DISPLAY_ERRORS)
        printf('%s', $message);

    //Logging error on php file error log...
    if(LOG_ERRORS)
        error_log(strip_tags($message), 0);

}

ob_start();

@include 'content.php';

ob_end_flush();
=============================================

그렇다. register_shutdown_function() 함수는 뭔가 php가 모든 일을 마치고 종료되었을때나 exit()로 종료되었을때 인자로 등록한 핸들러 함수가 호출되게 한다. 즉 뭔가 마무리 하고 싶을때나 처리하고 싶은 일이 있을때 사용할 수 있다. 나의 경우에는 에러가 발생하는 경우 처리하고 싶은 것이므로 처음 코드를 다음처럼 만들면 된다.

=============================================
class Core {
  static public function run() {
      header('Content-Type:application/json;charset=utf-8');
      error_reporting( E_ALL | E_STRICT );
      ini_set( 'display_errors', 0 ); //에러를 출력하지 않는다.
      set_exception_handler( array( __CLASS__, '__exception_handler' ) );
      set_error_handler( array( __CLASS__, '__error_handler' ) );
      register_shutdown_function( array( __CLASS__, '__shutdown_handler' ) );
    .....
  }
  static public function __exception_handler( $e ) { .... }
  static public function __error_handler( $no, $str, $file, $line  ) { .... }
  static public function __shutdown_handler() {
      $e = error_get_last();
      if( $e ) {
            self::__error_handler( $e['type'], $e['message'], $e['file'], $e['line'] );
      }
  }
}
Core::run(); //구동시작
=============================================

이제 왠만한 에러, 예외는 내가 통제할 수 있게 되었다.

원본 : http://blog.jidolstar.com/849


error_handler 를 등록했으므로 echo 로 뿌리지 않는 한 display_error 정의와 상관 없이 에러가 출력되지 않을겁니다.
.data.json, .data.xml 같은 확장자에 rewrite 를 걸어서 요청에 따른 자동 구별이 되게 하면 편리할 것 같네요.

/query/name.php 인데
/query/name.data.json 로 요청이오면 name.php 를 실행하지만 결과는 json
/query/name.data.xml " " " " 결과는 xml


출처 phpschool

반응형