Welcome to Abdul Malik Ikhsan's Blog

Zend Framework 2 : Handle and Catch E_* PHP errors

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on January 21, 2014

zf2-zendframework2I made this post because real application is hard ( err…, not as easy as we imagine ) . One error exposed to user can be harmful for our site. As developer, there is a time that something can go wrong even it unit tested ( because we are human!). The ‘dispatch.error’ and ‘render.error’ are errors that handled by Framework to handle “framework specific” error, like  service not found, or view file not found. But what if the error is PHP Error Constant, like you forgot to handle empty array and just :

$array = array();
//many complex things here
//and you echoing..
 echo $array[1];
//that is empty...

You will got error like this : Notice: Undefined offset: 1 in /your/path/to/file.
It’s very dangerous because it exposed to your user. We need to fix it as soon as possible!, so when we need on site access, we need to automate logging ( for example, save error to file and send mail) with the error described and show user that something is go wrong and developer is working to fix it.

Ok, first, prepare the view file that will show user that something is go wrong :

<!-- //module/Application/view/error/e_handler.phtml -->
website is down right now :), we are working on it. please come back again...

Next, we create code to handle the PHP E_* errors :

// ./e_errorhandler.php in root of ZF2 app
//adapt from http://stackoverflow.com/questions/277224/how-do-i-catch-a-php-fatal-error
define('E_FATAL',  E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR |
        E_COMPILE_ERROR | E_RECOVERABLE_ERROR);

define('DISPLAY_ERRORS', TRUE);
define('ERROR_REPORTING', E_ALL | E_STRICT);

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

//catch function
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 = " Error PHP in file : ".$errfile." at line : ".$errline."
    with type error : ".$typestr." : ".$errstr." in ".$_SERVER['REQUEST_URI'];

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

    if (DISPLAY_ERRORS) {
        //logging...
        $logger = new Zend\Log\Logger;
		
        //stream writer			
        $writerStream = new Zend\Log\Writer\Stream(__DIR__.'/data/logs/'.date('Ymd').'-log.txt');
        //mail writer
        $mail = new Zend\Mail\Message();
        $mail->setFrom('system@yoursite.com', 'Sender\'s name');
        $mail->addTo('team@yoursite.com', 'Your Site Team');
        $transport = new Zend\Mail\Transport\Sendmail(); 
        $writerMail = new Zend\Log\Writer\mail($mail, $transport);
        $writerMail->setSubjectPrependText("PHP Error :  $typestr : $errstr ");
        
        $logger->addWriter($writerStream);
        $logger->addWriter($writerMail);
      
        //log it!
        $logger->crit($message);
        
        //show user that's the site is down right now
        include __DIR__.'/module/Application/view/error/e_handler.phtml';
        die;
    }
}

The code line 61-87 is show how to log it and show user custom error page immediately.
Ok, time to include the handler in public/index.php and keep silent ( @ for parse error ) for showing error :

/**
 * This makes our life easier when dealing with paths. Everything is relative
 * to the application root now.
 */
chdir(dirname(__DIR__));

// Decline static file requests back to the PHP built-in webserver
if (php_sapi_name() === 'cli-server' && is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))) {
    return false;
}

// Setup autoloading
require 'init_autoloader.php';
//Setup error handler
require_once 'e_errorhandler.php';

// Run the application!
@Zend\Mvc\Application::init(require 'config/application.config.php')->run();

Ok, done! Hope helpful ;)

References :
1. http://stackoverflow.com/questions/277224/how-do-i-catch-a-php-fatal-error
2. http://zf2.readthedocs.org/en/latest/modules/zend.mail.introduction.html
3. http://zf2.readthedocs.org/en/latest/modules/zend.log.writers.html

About these ads

11 Responses

Subscribe to comments with RSS.

  1. Lucas CORBEAUX (@lucascorbeaux) said, on January 21, 2014 at 7:10 pm

    Hi, and thanks for this article.

    As it may be useful in dev environment, I think it’s not a good idea to handle PHP errors in your code because :

    * Error handling can be a bit slow, if performance matters for your app (and it surely will) you should reconsider it.
    * PHP can do this too without any code if you configure your production environment to not display errors and to log them (display_errors off and error_log /path/to/log.file in your php.ini). Of course you should always at least disable error display in production environment.

    • samsonasik said, on January 21, 2014 at 7:22 pm

      Hi, thanks for the suggestion. The reason for it is instead of logging in file, it can send mail to developer writing a log file using logger writer to enable developer fix it as soon as possible.

      • Lucas CORBEAUX (@lucascorbeaux) said, on January 21, 2014 at 7:53 pm

        Thanks for your reply. I agree that your code snippet is really convenient, but I’m not really comfortable with this solution : I think you’re adding (a bit) complexity to your application only for the team workflow, by the way you’re increasing (a bit) future update costs without adding any value for your users.

        You can achieve something similar by create a log watcher on your server that email your developers (or notify them via HipChat or Jabber… whatever you use) : it’s not really difficult to do, and it let your application’s code focus on what matters : business logic.

      • samsonasik said, on January 21, 2014 at 9:06 pm

        Thanks, my post propose programmatically handle its errors, for example : we want to not always send error to mail, for example : with same ‘type’ of error in same file or only for some errors should be send to mail or with same day, the same error should be send once/twice. btw, your solution is very good, it would be better if you can propose blog post for it, and I will recommend your solution as better solution to handle it.

  2. Max Gulturyan said, on January 21, 2014 at 8:24 pm

    You skip Fatal errors.
    see Debug_ErrorHook (https://github.com/DmitryKoterov/debug_errorhook/tree/master) for catch it.

  3. Mohammad Nomaan Patel said, on January 23, 2014 at 4:54 pm

    Thanks for wonderful post!!!
    How this could be achieved when we use ajax…

    • samsonasik said, on January 25, 2014 at 8:29 am

      you’re welcome. you can actually do same because everything is comes from public/index.php

  4. Pervaiz Iqbal said, on July 4, 2014 at 1:46 pm

    thanks for this tutorial, please do help me i cant find the log file and folder in directory.

  5. Greg Szczotka said, on August 26, 2014 at 4:28 am

    I’m impressed by your blog posts. Very useful. Thanks.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 251 other followers

%d bloggers like this: