Laravel’s Custom Error Handler

Laravel’s error handling is one of its most noticeable developer facing features. Like many modern PHP frameworks, Laravel ships with its error reporting set to E_ALL, and strives for Notice free PHP code. Additionally, Laravel includes the Whoops framework, which creates readable stack traces that pinpoint the exact line a PHP error came from.

Where Laravel really distinguishes itself is the steps it takes, on a framework level, to make sure its error handling accounts for all PHP errors. PHP has uncaught exceptions, fatal errors, caught exceptions, and non-fatal errors — making sure you catch every possible “bad” code situation is harder than it sounds, and it’s a credit to the Laravel core team that they’ve managed to thread this needle.

This article with explore Laravel’s systems level error handling code, as well as the myriad ways PHP makes robust error reporting difficult to accomplish.

Bootstrap

Laravel, like most modern PHP frameworks, is built around the idea of a single global application object. Laravel redirects all requests to a central index.php file. If you omit the comments, this file is pretty sparse

#File: index.php
require __DIR__.'/../bootstrap/autoload.php';
$app = require_once __DIR__.'/../bootstrap/start.php';
$app->run();

There’s a line to pull it Laravel’s custom autoloading rules, a line to pull in an application bootstrap start.php file, and a call to the application’s run method. The bootstrap start file is where the Laravel core instantiates the application file, and then returns the application object

#File: bootstrap/start.php
$app = new Illuminate\Foundation\Application;
//...
return $app;    

If you’re used to a more class based PHP framework, you may be surprised to learn you can return a value from a PHP include or require statement.

It’s a good idea to become familiar with everything going on in this file, but the line we’re interested in today is here

#File: bootstrap/start.php
$framework = $app['path.base'].
                 '/vendor/laravel/framework/src';

require $framework.'/Illuminate/Foundation/start.php';

Here Laravel pulls in another file named start.php. Why the second bootstrap? You’ll notice this second file exists in the composer vendor hierarchy — that is, it’s a file distributed with the core packages that make up the Laravel framework. The bootstrap/start.php file, on the other hand, lives under the root project folder. Once Laravel creates its initial application, this first bootstrap file is the responsibility of the application owner/developer. That is, once created, composer or Laravel will never update it again.

Having the first start.php perform a require on the second start.php ensures the application developer can take advantage of future improvements to the framework without needing to merge a file that’s part of their application.

Like the first start.php, there’s a lot of important things going on in this second Illuminate foundation start.php file. The lines we’re interested in are these two.

#File: vendor/laravel/framework/src/Illuminate/Foundation/start.php
$app->startExceptionHandling();

if ($env != 'testing') ini_set('display_errors', 'Off');

The startExceptionHandling method on the application object is where Laravel setups up its custom error handlers. You’ll notice it’s using the global $app object — this is the same object setup in the previous include file. Remember — unless a PHP includes/require file starts with a namespace declaration, it exists in its parent’s scope, and in this case that’s PHP’s global scope.

Before we dive into the startExceptionHandling method, make note of the second line.

#File: vendor/laravel/framework/src/Illuminate/Foundation/start.php
if ($env != 'testing') ini_set('display_errors', 'Off');

This is where Laravel turns off displaying errors during a test run. It’s also an example of the Laravel core developers showing a strong opinion, and using the more-concise-but-prone-to-errors single line bracket-less conditional statement.

A less opinionated developer might write the above like like this

if ($env != 'testing') 
{
    ini_set('display_errors', 'Off');
}

We point it out here mainly so you’ll be aware it’s a common site in the Laravel code base, so adjust your code smell sensors accordingly.

The Laravel Application Container

If we take a look at the application object’s startExceptionHandling method,

#File: vendor/laravel/framework/src/Illuminate/Foundation/Application.php
public function startExceptionHandling()
{
    $this['exception']->register($this->environment());

    $this['exception']->setDebug($this['config']['app.debug']);
}

we see the actual logic is handled by the object in the exception prop— except, wait? How is $this['exception'] even working? Shouldn’t that throw a Cannot use object as array error? The reason this works in Laravel’s Application object extends a base Laravel container class, and the container class implements the PHP ArrayAccess interface.

#File: vendor/laravel/framework/src/Illuminate/Container/Container.php
//...
class Container implements ArrayAccess {
}

If you’re not familiar with it, implementing the ArrayAccess interface allows you to programtically control what happens when you use array syntax ($foo[2]) with a PHP object.

Covering this in full is beyond the scope of this article, but when you see $this['someprop'] in the application object, Laravel’s accessing a service object. Don’t worry if you don’t understand Laravel’s services — that’s a topic for another day. For the purposes of this article, all you need to know is $this['exception'] contains an object who’s class is Illuminate\Exception\Handler, and that’s where we’ll find the PHP code used to setup Laravel’s error handling.

Configuring PHP’s Error Handling

Let’s take the two lines of PHP above

#File: vendor/laravel/framework/src/Illuminate/Foundation/Application.php
$this['exception']->register($this->environment());
$this['exception']->setDebug($this['config']['app.debug']);

and consider the second line first. The setDebug method

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
public function setDebug($debug)
{
    $this->debug = $debug;
}

is just a simple setter method that allows the exception service to know the value of the app.debug configuration field. If you’ve ever wondered why changing this value at runtime doesn’t seem to affect the error display, this is why. Once Laravel sets the debug property, it never references the configuration for this value again.

Next up is the method we’re really interested in: register

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php

public function register($environment)
{
    $this->registerErrorHandler();

    $this->registerExceptionHandler();

    if ($environment != 'testing') $this->registerShutdownHandler();
}

Here we see Laravel’s calling three different methods — registerErrorHandler, registerExceptionHandler, and registerShutdownHandler. For folks new to the platform, PHP has both an error system and an exception system. Exceptions were introduced to the language in PHP 5, but the error system was left in place for backwards compatibility reasons.

If we take a look at the definitions for each of these methods.

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
protected function registerErrorHandler()
{
    set_error_handler(array($this, 'handleError'));
}

protected function registerExceptionHandler()
{
    set_exception_handler(array($this, 'handleUncaughtException'));
}

protected function registerShutdownHandler()
{
    register_shutdown_function(array($this, 'handleShutdown'));
}

We can see they’re using the native PHP functions to register callback methods. A PHP callback is a pseudo type that PHP knows how to invoke as a method or function. A callback can be a string (invoked as a function), a first-class-function/closure (invoked as itself), or, as above, an array where the first item is an object, and the second item is an object method.

If you didn’t follow all that, what it means is when we say

set_error_handler(array($this, 'handleError'));

We’re telling PHP to call $this->handleError() as the custom error handler. Similarly, when we say

set_exception_handler(array($this, 'handleUncaughtException'));

We’re telling PHP to call $this->handleUncaughtException() as the custom exception handler. The same is true for $this->handleShutdown and the register shutdown function. Don’t worry if you’re not familiar with the shutdown function — we’ll be talking about it more below.

Tracing an Error

So that’s the exception and error handler set, but what actually happens when your Laravel application issues an error or doesn’t catch an exception?

First, let’s take a look at the error handler

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
public function handleError($level, $message, $file = '', $line = 0, $context = array())
{
    if (error_reporting() & $level)
    {
        throw new ErrorException($message, 0, $level, $file, $line);
    }
}

Here we see that Laravel turns all errors into a thrown ErrorException exception. Actually, that’s a Illuminate\Exception\ErrorException object (since we’re in Illuminate/Exception/Handler.php, and the namespace is Illuminate\Exception). This means almost every error and warning in PHP will also be routed to the Exception handler (unless, of course, application code does a try/catch for their PHP errors).

We say “almost every” because, as you can see above, the exception throwing code is wrapped in a conditional. Look carefully at that conditional

error_reporting() & $level

It’d be easy to read that as “If error_reporting() AND $level are true, but that’s not what’s going on. That’s a single & operator — which is one of PHP’s [bitwise operators] (http://php.net/manual/en/language.operators.bitwise.php). If you’re not familiar with bitwise operators and how they relate to PHP’s error handling, I’ve written extensively on this in my Survey of PHP error handling article.

For our purposes, know that if (error_reporting() & $level) can be translated into english as “If the error that just happened is an error PHP would report”. This means Laravel’s error handler will obey your custom error levels (see the shutdown handler section below for a caveat to this). It’s also worth mentioning that back up in the Illuminate Foundation Bootstrap start.php, Laravel sets the error handling to -1

#File: vendor/laravel/framework/src/Illuminate/Foundation/start.php
/*
|--------------------------------------------------------------------------
| Set PHP Error Reporting Options
|--------------------------------------------------------------------------
|
| Here we will set the strictest error reporting options, and also turn
| off PHP's error reporting, since all errors will be handled by the
| framework and we don't want any output leaking back to the user.
|
*/

error_reporting(-1);

An error reporting level of -1 is a special shortcut that means show all PHP errors. It’s functionally equivalent to error_reportin(E_ALL), although the reason -1 works is, again, based on PHP’s bitwise error codes.

So, in short, the Laravel error handler will throw an ErrorException whenever PHP issues a reportable error. This means our next stop is the uncaught exception handler.

Tracing an Exception

If we take a look at the exception handling callback, we see

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
public function handleUncaughtException($exception)
{
    $this->handleException($exception)->send();
}

That line might be more clearly written as

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
$this->handleException($exception)
->send();    

Or even

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
$response = $this->handleException($exception);
$response->send();

That is, the exception handler calls another method (handleException), and then calls send on the object returned by the handleException method. Before we hop over to the handleException method, we’ll let you know this call returns a Laravel response object, and the send method will send output back to the browser/user.

With that in mind, here’s handleException

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
public function handleException($exception)
{
    $response = $this->callCustomHandlers($exception);

    // If one of the custom error handlers returned a response, we will send that
    // response back to the client after preparing it. This allows a specific
    // type of exceptions to handled by a Closure giving great flexibility.
    if ( ! is_null($response))
    {
        return $this->prepareResponse($response);
    }

    // If no response was sent by this custom exception handler, we will call the
    // default exception displayer for the current application context and let
    // it show the exception to the user / developer based on the situation.
    return $this->displayException($exception);
}

The first two lines

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
$response = $this->callCustomHandlers($exception);

if ( ! is_null($response))
{
    return $this->prepareResponse($response);
}

reveal an important feature of the Laravel application object. Laravel’s application object has a pushError method. This method allows you to register callbacks to handle specific Exception types yourself. Give the following code a try in your application

app()->pushError(function($exception, $status_code, $is_this_error_from_the_console){
    var_dump(__FILE__ . '::' . __LINE__);
    //var_dump(func_get_args());        
    return 'I am the exception handler now.';
    //return null;  #return null if you don't want to handle it
});    
throw new Exception('Look at me');

We’re not going to dive into the implementation of this feature, except to say callCustomHandlers is the method that goes through and calls all the custom errors pushed onto the application, and the call to $this->prepareResponse($response) takes the value returned by the custom error handler and turns it into a Laravel response object.

Assuming there’s no custom error handler, the next journey for our exception is into the displayException method.

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
$this->displayException($exception);

This method is responsible for displaying the exception. Well, actually, if we look at its definition

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
protected function displayException($exception)
{
    $displayer = $this->debug ? $this->debugDisplayer : $this->plainDisplayer;

    return $displayer->display($exception);
}

we see its responsible for calling display on the Exception handler’s “displayer” object. The first line

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
$displayer = $this->debug ? $this->debugDisplayer : $this->plainDisplayer;

chooses which displayer object the exception handler should use. If the debug property is set on the exception handler, the exception handler will use the debugDisplayer. If not, it uses the plainDisplayer. Once Laravel chooses the displayer, it calls its display method (passing in the exception).

return $displayer->display($exception);

In english, this means if you’re running Laravel with the debug configuration set to true, you’ll get the super fancy “Whoops” exception handler

but if you’re running with the debug configuration set to false, you’ll get the stock Laravel production debug message.

It’s beyond the scope of this article, but if you’re interested in how Laravel sets up the debug property, as well as the debugDisplayer and plainDisplayer, take a look in the register method of the Exception service provider

vendor/laravel/framework/src/Illuminate/Exception/ExceptionServiceProvider.php

Also beyond the scope of this article is how the Whoops exception handler “does its magic”. If you’re curious in tracing that our yourself, by default the debugDisplayer’s class is Illuminate\Exception\WhoopsDisplayer, defined in the following file

vendor/laravel/framework/src/Illuminate/Exception/WhoopsDisplayer.php

All that said, we will take a quick look at how the production/plain displayer works, since it’s relatively simple

#File: vendor/laravel/framework/src/Illuminate/Exception/PlainDisplayer.php
class PlainDisplayer implements ExceptionDisplayerInterface {
    public function display(Exception $exception)
    {
        $status = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500;

        $headers = $exception instanceof HttpExceptionInterface ? $exception->getHeaders() : array();

        return new Response(file_get_contents(__DIR__.'/resources/plain.html'), $status, $headers);
    }

}

As you can see above, Laravel will load the HTML contents of the following file as a string

./vendor/laravel/framework/src/Illuminate/Exception/resources/plain.html

and use this file’s contents to generate a Laravel response object. It’s important to note that this plain.html file exists as part of a vendor package — which means any change you make to it will be overridden when you update the framework. This means an application developer has no way to customize the display of the PlainDisplayer.

Fortunately, Laravel provides a method (via the App facade) to add a custom error handler.

App::error(function(){
    //if this isn't production, bail
    if(App::environment() !== 'production')
    {
        return;
    }
    return '<p>Any valid Laravel Response here</p>';
});

This is very similar to the pushError method, with one important difference. The pushError method adds an error handler to the end of the error handling queue. The more common error method will add your new error as the first error handler. This gives you the opportunity to preempt any other error handlers registered in your system.

So — that’s the exception handler traced out. Through this process Laravel creates and sends a response object for any uncaught exception. Any error message that would normally display an error is turned into an exception. This handles a proverbial 99% of all error cases in PHP — but there’s one last thing we need to consider — and that’s the shutdown handler.

Tracing the Shutdown Handler

You’ll remember back up in the register method, there was a final line where Laravel setup a shutdown handler.

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php

if ($environment != 'testing') $this->registerShutdownHandler();
//...

protected function registerShutdownHandler()
{
    register_shutdown_function(array($this, 'handleShutdown'));
}

A shutdown callback isn’t specifically for handling errors. Instead, it’s a callback that fires when a PHP page/program has finished its execution. However, if we take a look at handleShutdown, we’ll see Laravel’s using it for error/exception handling

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
public function handleShutdown()
{
    $error = error_get_last();

    // If an error has occurred that has not been displayed, we will create a fatal
    // error exception instance and pass it into the regular exception handling
    // code so it can be displayed back out to the developer for information.
    if ( ! is_null($error))
    {
        extract($error);

        if ( ! $this->isFatal($type)) return;

        $this->handleException(new FatalError($message, $type, 0, $file, $line))->send();
    }
}

The shutdown function uses PHP’s error_get_last function to fetch an array of information about the last error. A null response indicates there hasn’t been an error — if that’s the case handeShutdown finishes without taking additional action. However, if error information is returned, and the error type is one of PHP’s fatal errors

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
protected function isFatal($type)
{
    return in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE));
}

then Laravel will instantiate a FatalError object, and hand this object off the the handleException method, same as an uncaught exception from the exception handler. (The variables in the function above are defined by the call to extract — required reading if you’re not familiar with extract).

So — why is this here? Let’s go back and consider the error handler

#File: login/vendor/laravel/framework/src/Illuminate/Exception/Handler.php
public function handleError($level, $message, $file = '', $line = 0, $context = array())
{
    if (error_reporting() & $level)
    {
        throw new ErrorException($message, 0, $level, $file, $line);
    }
}

You’ll recall this method throws an exception for any error that PHP would normally display — but what happens if your program produces a fatal error, but your error reporting is set to not report an error on that level? In this scenario the handleError method wouldn’t throw an exception — and PHP would do no error reporting. Since the error is fatal this halts PHP’s page rendering with no notice. Some PHP developers refer to this as the “white screen of death”. That’s far from ideal, so Laravel has the shutdown function there to catch any fatal errors.

Put another way, Laravel attempts to obey the system’s error_reporting level unless it’s a fatal error, in which case Laravel attempts to handle the error. I don’t have enough history with the platform to know why this behavior is implemented in a shutdown function instead of in the error handler itself, but my guess would be the shutdown function came as an enhancement/bug-fix later in the framework’s life.

The shutdown function also serves another purpose — there’s a few not-well-documented situations where PHP will raise an error, but not call any custom error handling functions. Some of these are impossible to report on due to the nature of the error (PHP itself seg-faults), but if, for any reason, Laravel’s error and exception handlers fail to catch an error, this shutdown handler is there to grab it.

One negative consequence of this behavior is it’s impossible to turn off Laravel exception handling in the framework without a wholesale redefinition of the application object. That’s because once a shutdown handler is registered, the PHP API provides no way to cancel or change it.

Wrap Up

As you can see, thanks to years of legacy behavior, doing something seemingly simple like controlling how PHP handles all its errors can be a herculean task. While Laravel’s approach isn’t perfect, it is some of the best I’ve seen from a framework, and their use and promotion of whoopos is a clear win for developers. If you’re thinking of creating a new PHP framework, you could do a lot worse than following the lead set by the Laravel core team.

Originally published September 1, 2014

Magento’s Mini Error Framework

Like this article? Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

If you’re a long time reader, you’re probably aware of my article on Magento’s many 404 pages. It’s been one of the more useful articles I’ve written here — I own the Google results for the phrase “Which 404 page” in Magento land.

One thing I didn’t cover was how Magento displays the store exception 404 page. That’s because the how is an article series unto itself, and I’m starting that series today. Magento’s store exception 404 page, as well as its 503 page and exception/error reporting page are all controlled by a mini framework that lives in the errors/ folder.

Over the next few weeks we’ll explore this mini framework — both from a “how can I customize these misunderstood Magento error pages” point of view, as well as a framework design point of view. Today we’re going to walk through the code path of Magento handling a 404 store exception.

Magento Application Main Exception Block

The best place to start is here

#File: app/Mage.php
public static function run($code = '', $type = 'store', $options = array())
{
    try {
        Varien_Profiler::start('mage');
        self::setRoot();
        if (isset($options['edition'])) {
            self::$_currentEdition = $options['edition'];
        }
        self::$_app    = new Mage_Core_Model_App();
        if (isset($options['request'])) {
            self::$_app->setRequest($options['request']);
        }
        if (isset($options['response'])) {
            self::$_app->setResponse($options['response']);
        }
        self::$_events = new Varien_Event_Collection();
        self::_setIsInstalled($options);
        self::_setConfigModel($options);
        // throw new Mage_Core_Model_Store_Exception('wtf?'); 
        // throw new Exception("Wtf");
        self::$_app->run(array(
            'scope_code' => $code,
            'scope_type' => $type,
            'options'    => $options,
        ));

        Varien_Profiler::stop('mage');
    } catch (Mage_Core_Model_Session_Exception $e) {
        header('Location: ' . self::getBaseUrl());
        die();
    } catch (Mage_Core_Model_Store_Exception $e) {
        require_once(self::getBaseDir() . DS . 'errors' . DS . '404.php');
        die();
    } catch (Exception $e) {
        if (self::isInstalled() || self::$_isDownloader) {
            self::printException($e);
            exit();
        }
        try {
            self::dispatchEvent('mage_run_exception', array('exception' => $e));
            if (!headers_sent() && self::isInstalled()) {
                header('Location:' . self::getUrl('install'));
            } else {
                self::printException($e);
            }
        } catch (Exception $ne) {
            self::printException($ne, $e->getMessage());
        }
    }
}

This is the main Magento run method. This is the method that Magento’s main index.php calls to start Magento’s page/URL processing. There’s a lot of important-but-extraneous-to-us code in there, so let’s consider this simplified version

#File: app/Mage.php
public static function run($code = '', $type = 'store', $options = array())
{
    try {
        //...
        self::$_app    = new Mage_Core_Model_App();
        //...
        self::$_app->run(array(
            'scope_code' => $code,
            'scope_type' => $type,
            'options'    => $options,
        ));
    } catch (Mage_Core_Model_Session_Exception $e) {
        //...
        die();
    } catch (Mage_Core_Model_Store_Exception $e) {
        //...
        die();
    } catch (Exception $e) {
        //...
        die();
    }
}

The Mage::run method is, at its heart, a simple try/catch block. Magento instantiates a Mage_Core_Model_App object, and then calls its run method. If no exceptions occur during processing, everything continues as normal. However, if an exception occurs, three different error/catch branches are considered. One catches a Mage_Core_Model_Session_Exception exception, another looks for a Mage_Core_Model_Store_Exception exception, and the final one looks for a plain old PHP Exception exception (which will also include any exception type not caught above, including the Mage_Core_Exception exception). Magento handles each exception type differently.

The exception type we’ll look at today is the Mage_Core_Model_Store_Exception exception.

Store Exception

All Magento requests have a global “store” object. This object is responsible for keeping track of the currently set store id. Anytime the core code needs to know the store id (to lookup a store specific configuration, price, behavior, etc.) it uses the Magento core/store singleton (a Mage_Core_Model_Store object)

If Magento can’t instantiate this store object, or find a store_id/code/scope-type for the store object, this means there’s something seriously wrong. Most of the application functionality will break under these condition. The Mage_Core_Model_Store_Exception exists to signal that this is the case, and that Magento should shut down the request.

If we take a look at the store exception catch block, we see the following

#File: app/Mage.php
catch (Mage_Core_Model_Store_Exception $e) {
    require_once(self::getBaseDir() . DS . 'errors' . DS . '404.php');
    die();
catch (Exception $e) {

This means Magento will require in the errors/404.php file. This brings us to today’s real topic — Magento’s mini error framework.

Error Processor

If you take a look inside 404.php, you’ll see the following

#File: errors/404.php
require_once 'processor.php';

$processor = new Error_Processor();
$processor->process404();

When considering the case of the Magento store exception above, this looks like a file that requires another file, instantiates an object, and then calls a method. However, consider that this 404.php file is also accessible directly via the browser

http://store.magento.com/errors/404.php

In this context, the Error_Processor object starts to look like a simple controller, and the process404 method starts to look like a corresponding controller action. This idea is strengthened if you look at the 503.php page.

#File: errors/503.php
require_once 'processor.php';

$processor = new Error_Processor();
$processor->process503();

Here we have the same pattern. Magento instantiates the same Error_Processor object (a controller), and calls the process503 method (a controller action).

The rest of this article will walk though the execution of the process404 method, and we’ll start to see other ways these error handling pages/scripts resemble a simple, rudimentary PHP framework.

Processing an Error

If we take a look at the process404 method, we see a pretty simple definition

#File: errors/processor.php    
public function process404()
{
    $this->pageTitle = 'Error 404: Not Found';
    $this->_sendHeaders(404);
    $this->_renderPage('404.phtml');
}

Magento sets a page title property on the object, calls the _sendHeaders method, and then calls the _renderPage method. We’ll return to the pageTitle property and renderPage method later, but first let’s look at the _sendHeaders method.

protected function _sendHeaders($statusCode)
{
    $serverProtocol = !empty($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0';
    switch ($statusCode) {
        case 404:
            $description = 'Not Found';
            break;
        case 503:
            $description = 'Service Unavailable';
            break;
        default:
            $description = '';
            break;
    }

    header(sprintf('%s %s %s', $serverProtocol, $statusCode, $description), true, $statusCode);
    header(sprintf('Status: %s %s', $statusCode, $description), true, $statusCode);
}

Here we see a simple method that takes a numeric $statusCode, translates it into a description, and then issues two HTTP headers. The first header call

header(sprintf('%s %s %s', $serverProtocol, $statusCode, $description), true, $statusCode);

also uses the $serverProtocol variable. Consider the first line of this method

$serverProtocol = !empty($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0';

The above code will populate $serverProtocol with HTTP/1.0 or whatever is set in PHP’s $_SERVER['SERVER_PROTOCOL'] variable (mostly likely HTTP/1.1). If we expand the sprintf call, this would look something like

header('HTTP/1.1 404 Not Found', true, 404);

The second header call

header(sprintf('Status: %s %s', $statusCode, $description), true, $statusCode);

is similar, in that it outputs a status header that looks something like this

header('Status: 404 Not Found');

If we use curl to look at the headers of a 404.php request, we’ll see both these headers

$ curl -I 'http://store.example.com/errors/404.php'
HTTP/1.1 404 Not Found
//...
Status: 404 Not Found
//...    
Content-Type: text/html

There’s a few interesting things here worth talking about. First, the initial header call uses the header function’s seldom seen second and third parameters. If we take a look at the function prototype from the docs

void header ( string $string [, bool $replace = true [, int $http_response_code ]] )

We see the second $replace parameter

indicates whether the header should replace a previous similar header, or add a second header of the same type. By default it will replace, but if you pass in FALSE as the second argument you can force multiple headers of the same type.

and the third $http_response_code

Forces the HTTP response code to the specified value. Note that this parameter only has an effect if the string is not empty.

The use of this syntax is puzzling. The $replace variable defaults to true, and the $http_status_code parameter is meant for use with the Location: header and setting 301/302/etc. status codes. Whether these explicit parameters point to the original core team’s inexperience with PHP/HTTP, or their encounters with strange PHP systems in the wild that behaved erratically (or both!) is hard to say.

The second interesting thing is the protocol header being explicitly set to match PHP’s. This seems like a needless bit of extra programming busy work since both 404 and 503 are HTTP 1.0 status codes, and near all web browsers understand them. However, if you scroll down to the comments on the PHP docs, you’ll see this

I had big troubles with an Apache/2.0.59 (Unix) answering in HTTP/1.0 while I (accidentally) added a “HTTP/1.1 200 Ok” - Header. Most of the pages were displayed correct, but on some of them apache added weird content to it: …

Here we have a case where, to an outsider who works on a single projects, Magento’s code looks extra explicit, busy and verbose, but to the Magento core team it looks like code that will help their users deal with an obscure bit of weird apache behavior.

Finally, I don’t know why Magento adds a second, explicit Status header, but given the range of systems Magento needed to work with, I wouldn’t be surprised if it’s another obscure “good enough” edge case bug in some web server or browser.

All these problems are a nice illustration of why, as an application developer, you want your framework of choice handling these sorts of low level details so that all you need to say is “Send a not found header”, and be back to concentrating on your application instead of 15 years of HTTP specifications and edge-casey browser/server behavior.

Rendering the Page

If we jump back to our main “action”

#File: errors/processor.php    
public function process404()
{
    $this->pageTitle = 'Error 404: Not Found';
    $this->_sendHeaders(404);
    $this->_renderPage('404.phtml');
}

After our _sendHeaders call, we have a _renderPage method call. If we’re thinking about this in a framework context, this is our request to render a view layer. If we pop down to the renderPage method

#File: errors/processor.php
protected function _renderPage($template)
{
    $baseTemplate = $this->_getTemplatePath('page.phtml');
    $contentTemplate = $this->_getTemplatePath($template);

    if ($baseTemplate && $contentTemplate) {
        require_once $baseTemplate;
    }
}

we can see rendering a page means

  1. Fetching the path to a base template

  2. Fetching the path to a content template

  3. If we can fetch both paths, then require in the base template, effectively rendering the page.

We’re going to save the actual template path fetching for another time, and tell you the paths fetched are

$baseTemplate    = '/path/to/magento/errors/default/page.phtml
$contentTemplate = '/path/to/magento/errors/default/404.phtml';

If you look at page.phtml (the $baseTemplate), you’ll see a mostly static HTML file. There’s a few lines of PHP that are worth calling out

#File: errors/default/page.phtml
//...
1. <title><?php echo $this->pageTitle?></title>
//...
2. <base href="<?php echo $this->getSkinUrl()?>" />
//...
3. <a href="<?php echo $this->getBaseUrl()?>" 
//
4. <?php require_once $contentTemplate; ?>

The first line prints our page title. You’ll remember back up in process404 we set this page title

#File: errors/processor.php    
public function process404()
{
    $this->pageTitle = 'Error 404: Not Found';
    $this->_sendHeaders(404);
    $this->_renderPage('404.phtml');
}

The second line outputs a URL for the <base/> tag. This lets the browser know where it can find image and css files for the 404 page. The third line outputs a base URL inside of a link. This links end-user-customers back to the main Magento site from the 404 page. Although getSkinUrl and getBaseUrl share names with methods in the main Magento framework, they’re different methods, defined on the Error_Processor class in errors/processor.php. We’ll return to these methods later.

Finally, we reach the most important line

#File: errors/default/page.phtml
<?php require_once $contentTemplate; ?>

The page.phtml file is a base template for the HTML page. The $contentTemplate path is the actual page we’re rendering. You’ll recall we set $contentTemplate up in the _renderPage method. When you include/require a file in PHP inside a method/function, the included file has the same variable context as the method. Also, this is what allows our template to reference the special PHP variable $this, which will point back to the Error_Processor object.

As we mentioned earlier, the $contentTemplate variable points to

$contentTemplate = '/path/to/magento/errors/default/404.phtml';

If we pop this file open, we’ll see a simple HTML fragment that contains our 404 text

<!-- File: errors/default/page.phtml -->
<div id="main" class="col-main">
<!-- [start] content -->
    <div class="page-title">
        <h1>404 error: Page not found.</h1>
    </div>
<!-- [end] content -->
</div>

With this final phtml file included, that completes our page rendering. When invoked via a URL

http://store.magento.com/errors/404.php

the require is the last bit of PHP code invoked. When invoked via the store exception catch block, Magento adds an explicit die to make sure the code exits.

catch (Mage_Core_Model_Store_Exception $e) {
    require_once(self::getBaseDir() . DS . 'errors' . DS . '404.php');
    die();
}

With that, our page rendering is complete.

Wrap Up

Although I’ve described this as a framework, I’m sure some of your are skeptical. Compared to robust systems like Symfony, Laravel, Zend, and Magento proper itself, the Error_Processing class hardly seems like a framework.

With the information presented so far, that’s a fair assessment. However, in our next few articles we’ll discuss some of the framework like features of this class. How it handles different actions, how it has its own global configuration parsing similar to the main Magento framework, and how it’s used for a variety of different error cases.

Beyond that though, it’s sometimes useful to think of all programming projects as having a framework. Even when developers are banging out code in an unfamiliar system under an unrealistic deadline, unwritten rules develop about what code goes where, and how to handle certain common tasks. A framework always develops out of projects longer than a weekend hackathon. Being able to put yourself in the mind of the programmers who worked on a particular sub-system is the first step to working effectively with that sub-system.

Originally published July 24, 2014

Validating a Magento Connect Extension

Like this article? Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

If you’ve followed my work here, on Stack Overflow, or on Twitter, you know I have a perpetual bee in my bonnet over Magento Connect’s file permissions issues. There’s a few common cases where Magento Connect will tell you an extension is installed, but in actuality Connect couldn’t install the extension because of insufficient file permissions. Permissions are always a thorn in a developer’s side — but the Connect application’s willingness to lie to its users about an extension being installed crosses some invisible line of unacceptable behavior.

The Magento 2 team at eBay is aware of the problem, and working on it, but that leaves working Magento developers with the problem of never knowing what’s actually installed when clients or customers use Magento Connect. That’s why I’ve created a new command for n98-magerun called extension:validate. Today we’ll cover how to use the command, as well as discuss some implementation details so you understand what the command is doing.

Using the Command

The extension:validate command requires no arguments. If you run it you’ll see something like this

$ n98-magerun extension:validate

Customchecoutstep
--------------------------------------------------


Pulsestorm_Commercebug
--------------------------------------------------


Auctionmaid_Matrxrate
--------------------------------------------------

Problem: /path/to/magento/app/code/community/Webshopapps/Matrixrate/etc/config.xml
    Hash: MISMATCH
Problem: /path/to/magento/app/etc/modules/Webshopapps_Matrixrate.xml
    Path: FILE NOT FOUND

When invoked with no options, extension:validate will look at all the third party Magento Connect extensions a user has installed on their system, and validate their contents against the package.xml manifest.

In the above example, our system has the Magento Connect extensions Customchecoutstep, Pulsestorm_Commercebug, and Auctionmaid_Matrxrate installed. Customchecoutstep and Pulsestorm_Commercebug were fine, but the Auctionmaid_Matrxrate was missing a file (Webshopapps_Matrixrate.xml), and one of the files didn’t match the stored hash (config.xml). Not matching the stored hash means the file’s been modified/edited.

Troubleshooting Connect Details

While this tool is useful, it’s not a cure all. Once you’ve detected problems with an extension you’ll need to figure out why they happened. In other words, are they real problems or intended changes from more reckless developers. There’s a good chance the original package for an extension will be in the

downloader/.cache/community/

folder. This where Magento Connect downloads the packages prior to installing them. Copying a package from here and extracting the files will let you replace missing files or run a diff to see what’s changed.

The extension:validate command reads the package information from the

downloader/cache.cfg

file. Despite being named cache.cfg, this really isn’t a cache file — it’s the file Magento Connect reads from when it lists out the installed packages on the system, and effectively acts as the source of truth for what is and isn’t installed.

If I speculate, the reason this file is named cache is it’s a cached list of what’s installed on the system. In other words the entire Magento system itself is the actual thing, and this file is just a cache. However, this falls apart when you consider there’s no way to query the Magento system itself for a list of installed packages, and the Magento Connect downloader always reads from this file. Another example of “The best laid plans …” problem.

Command Options

The extension:validate command will also let you check a single extension. Just pass in the extension’s name as the first argument

$ n98-magerun extension:validate Auctionmaid_Matrxrate

Auctionmaid_Matrxrate
--------------------------------------------------

Problem: /path/to/magento/app/code/community/Webshopapps/Matrixrate/etc/config.xml
    Hash: MISMATCH
Problem: /path/to/magento/app/etc/modules/Webshopapps_Matrixrate.xml
    Path: FILE NOT FOUND

This is useful to run right after you’ve installed an extension to make sure it’s installed correctly. Keep in mind you need to use the Magento Connect Name for an extension, and not the Namespace_Modulename module name. (Auctionmaid_Matrxrate vs Webshopapps_Matrixrate above)

If you only want to do a specific type of check — that is only check if a file is missing, or if the hash matches — there’s options to skip each test type.

$ n98-magerun help extension:validate
...
 --skip-file           If set, command will skip reporting the existence of package files
 --skip-hash           If set, command will skip validating the package file hashes

You use them like this.

n98-magerun extension:validate --skip-file 
n98-magerun extension:validate --skip-hash

n98-magerun extension:validate Auctionmaid_Matrxrate --skip-file 
n98-magerun extension:validate Auctionmaid_Matrxrate --skip-hash

There’s also the --full-report command which will list all the files for an extension — not just the problematic ones.

$ n98-magerun extension:validate Auctionmaid_Matrxrate --full-report

Auctionmaid_Matrxrate
--------------------------------------------------

Checking: /path/to/magento/app/code/community/Webshopapps/Matrixrate/Block/Adminhtml/Shipping/Carrier/Matrixrate/Grid.php
    Path: OK
    Hash: OK
Checking: /path/to/magento/app/code/community/Webshopapps/Matrixrate/Block/Adminhtml/System/Config/Form/Field/Exportmatrix.php
    Path: OK
    Hash: OK
//...
Checking: /path/to/magento/app/etc/modules/Webshopapps_Matrixrate.xml
Problem: /path/to/magento/app/etc/modules/Webshopapps_Matrixrate.xml
    Path: FILE NOT FOUND

Useful if you’re curious what, exactly, a Magento Connect package has dropped on your server.

Magento Connect for Upgrades

In addition to installing third party extensions, Magento Connect’s other big feature is enabling package based updates of the Magento system itself. It does this by having the following 36 packages installed by default when you initialize a Magento Connect system.

'Cm_RedisSession','Interface_Adminhtml_Default','
Interface_Frontend_Base_Default','Interface_Frontend_Default','
Interface_Frontend_Rwd_Default','Interface_Install_Default','Lib_Cm','
Lib_Credis','Lib_Google_Checkout','Lib_Js_Calendar','Lib_Js_Ext','
Lib_Js_Mage','Lib_Js_Prototype','Lib_Js_TinyMCE','Lib_LinLibertineFont',
'Lib_Mage','Lib_Magento','Lib_Phpseclib','Lib_Varien','Lib_ZF','
Lib_ZF_Locale','Mage_All_Latest','Mage_Centinel','Mage_Compiler','
Mage_Core_Adminhtml','Mage_Core_Modules','Mage_Downloader','
Mage_Locale_de_DE','Mage_Locale_en_US','Mage_Locale_es_ES','
Mage_Locale_fr_FR','Mage_Locale_nl_NL','Mage_Locale_pt_BR','
Mage_Locale_zh_CN','Magento_Mobile','Phoenix_Moneybookers'

This has always struck me as a little bit crazy. Because Magento Connect installations aren’t transactional, this creates a situation where an install that fails due to network connectivity issues or un-handled corner cases can leave a Magento installation in a “half-upgraded” state. If you’ve spent any time on the Magento forums or the various Stack Exchange sites, you know this is more than a theoretically concern.

Regardless, if you or your client are managing upgrades via Magento Connect, you may include these packages with the --include-default flag.

$ n98-magerun extension:validate --include-default

Be warned though, since it’s effectively running through every Connect managed file in your Magento application it’ll take a bit longer to finish. This is also a potentially useful way to keep your application “core hack” free. Finally, these extension names are hard-coded into the extension:validate command. If Magento adds any packages in the future, extension:validate will see them as 3rd party extensions until the n98-magerun command is updated with a new extension list. If anyone know a way to query something for a list of official packages, pull requests are welcome.

Wrap Up

The extension:validate command is available in the develop branch of the n98-magerun source. It looks like the application is getting monthly-ish releases, so this should be available in the official release soon. If you run into problems or have feature suggestions, please let me know.

Originally published June 9, 2014