Handling exceptions in a Front Controller plugin

20th December 2010

If you have a Zend Framework Front Controller plugin which throws an exception, then the action is still executed and then the error action is then called, so that the displayed output shows two actions rendered, with two layouts also rendered. This is almost certainly not what you want or what you expected.

This is how to handle errors in a Front Controller plugin:

  1. Prefer preDispatch() over dispatchLoopStartup() as it is called from within the dispatch loop
  2. Catch the exception and the modify the request so that the error controller's error action is dispatched.
  3. Create an error handler object so that the error action works as expected.

This is the code:

<?php

class Application_Plugin_Foo extends Zend_Controller_Plugin_Abstract
{
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    { 
        try {

            // do something that throws an exception

        } catch (Exception $e) {
            // Repoint the request to the default error handler
            $request->setModuleName('default');
            $request->setControllerName('error');
            $request->setActionName('error');

            // Set up the error handler
            $error = new Zend_Controller_Plugin_ErrorHandler();
            $error->type Zend_Controller_Plugin_ErrorHandler::EXCEPTION_OTHER;
            $error->request = clone($request);
            $error->exception $e;
            $request->setParam('error_handler'$error);
        }
    }

}

That's it.

View helpers in modules

13th December 2010

I came across a situation last week where I needed to access a view helper that was in the default module's views/helpers directory when I was in another module. This came about because my layout.phtml uses a view helper that is in application/views/helpers.

By default, it doesn't work and you get an error along the lines of:

Plugin by name 'LoggedInAs' was not found in the registry; used paths:
Plan_View_Helper_: /www/funkymongoose/habuplan/application/modules/plan/views/helpers/
Zend_View_Helper_: Zend/View/Helper/

The fix is simple enough. All you need to do is add this line to your application.ini file:

resources.view.helperPath.Zend_View_Helper = APPLICATION_PATH "/views/helpers"

This will then ensure that the view helpers in your application/views/helpers directory are always available.

This also works for accessing the view helpers in any other module too where the syntax looks like:

resources.view.helperPath.{Module}_View_Helper =
    APPLICATION_PATH "/modules/{module}/views/helpers"

Finally, don't forget that the order is important if you have view helpers of the same name in different modules. Personally, I would try to avoid that if you possibly can :)

PHP 5.3 is quicker than PHP 5.2

6th December 2010

I know that everyone already knows this, but I happened to find out for myself recently!

I was looking at the way view helpers work in ZF2 and thought it would be more convenient if we altered the syntax a little. A side-effect of the change was that we'd have to use call_user_func_array, which is perceived as slow. I thought I'd whip up a simple test to find out how much slower it would be over a direct method call.

That is, how much slower is this code:

$a = new A();
call_user_func_array(array($a'b'), array(1));

than this code:

$a = new A();
$a->b(1);

Bear in mind that I don't do formal benchmarks and have no clue on methodology. Paul Jones is your man for proper testing of performance.

With that caveat out of the way this is the code I wrote to test:

benchmark.php

<?php

class A
{
    function b($a)
    {
        return;
    }
}

define ('ITERATIONS'10000000);
$start microtime(true);
$a = new A();
for ($i 0$i ITERATIONS; ++$i) {
   $a->b(1);
}
$stop microtime(true);
echo  'Direct method call: ' . ($stop $start) . ' seconds' PHP_EOL$start microtime(true);
for ($i 0$i ITERATIONS; ++$i) {
   call_user_func_array(array($a'b'), array(1));
}
$stop microtime(true);
echo  'call_user_func_array call: ' . ($stop $start) . ' seconds' PHP_EOL

This was the simplest possible scenario I could imagine and so would show call_user_func_array in the best possible light.

I ran the test on my laptop (using the command line) and got these results:


Direct method call21.155211925507 seconds
call_user_func_array call72.147792100906 seconds

i.e. call_user_func_array is around 3.5 times slower over 10 million iterations.

Nobody uses their laptop to serve a website though! Being the curious sort, I thought I'd test the difference of this script between PHP 5.2 and PHP 5.3 in a completely unscientific way as I'm lazy! I happen to have a server with both PHP 5.2 and PHP 5.3. on it, so I used that:

PHP 5.2:


Direct method call8.3424661159515 seconds
call_user_func_array call26.904649972916 seconds

PHP 5.3:


Direct method call3.35181903839 seconds
call_user_func_array call11.5989868641 seconds

As you can see, my server is much faster than my laptop :) Also, the relative difference is the same. i.e. PHP 5.3's call_user_func_array is as slow as it is in PHP 5.2.

I think I'll move everything to PHP 5.3 for the free performance gain! Of course, The question remains as to whether we should avoid call_user_func_array or not.

I would however recommend testing your own apps and see if it makes any difference and please don't take this post as anything other than something interesting I found out!

Local config files and Zend_Application

29th November 2010

A friend of mine recently had a requirement where she wanted to have two config files loaded into Zend_Application, so that the specific settings for the server were not stored in the version control system.

Hence she has two config files: application.ini and local.ini where local.ini is different on each server.

Update:
As takeshin points out with ZF 1.10 or later, an easier solution to this is to pass both ini files into Zend_Application:

$application = new Zend_Application(
    APPLICATION_ENV,
    array(
        'config' => array(
            APPLICATION_PATH '/configs/application.ini',
            APPLICATION_PATH '/configs/local.ini'
        )
    )
);

Original content:

The obvious way to approach this problem is to load the two files within index.php, merge them and then pass the merged config file to Zend_Application.

The code to do this looks like this:

require_once 'Zend/Application.php';
require_once 'Zend/Config/Ini.php';

$config = new Zend_Config_Ini(APPLICATION_PATH '/configs/application.ini'APPLICATION_ENV,
            array('allowModifications'=>true));
$localConfig = new Zend_Config_Ini(APPLICATION_PATH '/configs/local.ini'APPLICATION_ENV);
$config->merge($localConfig);
$config->setReadOnly();

// Create application, bootstrap, and run
$application = new Zend_Application(
    APPLICATION_ENV,
    $config
);
$application->bootstrap()
            ->run();

I've included the require_once 'Zend/Application.php'; call for context.

As you can see, we create two Zend_Config objects, one for application.ini and one for local.ini and we load up the correct APPLICATION_ENV section in both cases. This means that local.ini must have the same set of "master" environment sections that application.ini has.

For application.ini, we set the option "allowModifications' so that we can then use the merge() method to override the $config with the new data within $localConfig.

Having merged the local config into the main one, we can then setReadOnly() to ensure that the config isn't changed anymore.

Finally, when we instantiate Zend_Application, we pass in our $config object rather than the name of the config file and then Zend_Application does the right thing.

Some notes on SQL Server blobs with sqlsrv

22nd November 2010

I recently updated my use of SQL Server with Zend_Db_Adapter_Sqlsrv to use UTF-8 throughout. This turned out to be easy enough:

  • Use ntext, nvarchar types in the database
  • add:
    resources.db.params.driver_options.CharacterSet "UTF-8"
    

    to your application.ini

I subsequently noticed a problem with storing binary data to a varbinary(max) field. The error was:

An error occurred translating string for input param 2 to UCS-2: No mapping for the Unicode character exists in the target multi-byte code page.

urgh!

The code looked something like this:

$data['filename'] = 'test.gif';
$data["file_contents"] = $binaryData;
$db->insert($data);

Fortunately, my friend Elizabeth Smith, pointed me in the right direction by suggesting I find out about bindings. So I did some research and it turns out that I just need to use an array for the parameter that I pass into the update() or insert() method of Zend_Db_Adapter_Sqlsrv.

It turns out that all you need to do is change the 'file_contents' element of the array to an array that also specifies the data type. The code I ended up with now looks something like this:

$data['filename'] = 'test.gif';
$data["file_contents"] = $binaryData;
if ($adapter == 'Zend_Db_Adapter_Sqlsrv') {
    $data["file_contents"] = array($binaryDataSQLSRV_PARAM_IN, 
        SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), SQLSRV_SQLTYPE_VARBINARY('max'));
}
$db->insert($data);

And all is fine.

The list of constants for the Sql Server Driver for PHP is helpfully available on MSDN and the documentation for sqlsrv_query() is worth reading too!