Category Archives: Zend Framework 2

Returning a ZF2 HydratingResultSet when starting with raw SQL

If you're using Zend Framework 2's Zend\Db and want to write raw SQL that returns a HydratingResultSet, then you can do this:

use ZendDbAdapterAdapterAwareInterface;
use ZendDbAdapterAdapter;
use ZendStdlibHydratorArraySerializable;
use ZendDbResultSetHydratingResultSet;
use ZendDbAdapterDriverResultInterface;

use MyEntityMyEntity;  // some sort of entity object

class MyMapper implements AdapterAwareInterface
{
    protected $dbAdapter;

    public function fetchRowsWithValue($fieldValue)
    {
        $sql = "SELECT * FROM my_table_name
                WHERE my_field_name = ?
                ";

        $params = array(
            $fieldValue,
        );

        return $this->fetch($sql, $params);
    }

    protected function fetch($sql, $params = null)
    {
        $dbAdapter = $this->getDbAdapter();
        $statement = $dbAdapter->createStatement($sql);
        $statement->prepare();

        $result = $statement->execute($params);

        if ($result instanceof ResultInterface) {
            $hydrator     = new ArraySerializable();
            $rowPrototype = new MyEntity();
            $resultset    = new HydratingResultSet($hydrator, $rowPrototype);
            $resultset->initialize($result);

            return $resultset;
        }

        return $result;
    }

    // Don't forget to implement getDbAdapter() and setDbAdapter()!

}

When you iterate over the HydratingResultSet, you get a populated instance of MyEntity. The $hydrator instance is the object that knows how to populate your entity object. In my case, I've used an ArraySerializable hydrator, but there's other options available, or you can write your own.

As an aside, if you don't want hydrated entities, then the $result that's returned from the statement's execute() call is an instance of ZendDbAdapterDriverResultInterface which means that you can iterate over it and get an array for each row in the result set.

Displaying the generated SQL from a ZendDbSql object

If you use ZendDbSql to generate your SQL, then it's useful to find out what the generated SQL looks like.

Consider code like this:

public function fetchAllWithTitleSince($title, $since)
{
    $sql = new Sql($this->dbAdapter);

    $select = $sql->select();
    $select->from($this->tableName);
    $select->columns(array('id', 'title', 'url', 'date_updated'));
    $select->where->like('title', "%$title%");
    $select->where->greaterThanOrEqualTo('date_created', date('Y-m-d', strtotime($since)));

    $statement = $this->dbAdapter->createStatement();
    $select->prepareStatement($this->dbAdapter, $statement);
    return $statement->execute();
}

To find out what the generated SQL will look like, you can use the $select's getSqlString() method:

$select->getSqlString();

For me, this results in lots of warnings along the lines of:

Lots of notices

This is less than helpful, so to avoid the warnings, you need to supply the correct platform information to the method:

    $select->getSqlString($this->dbAdapter->getPlatform());

So we now get:

Expected SQL string

This is much better!

IN and Zend\Db\Sql's where()

This is a short note to myself. Zend\Db\Sql objects allow you to do this:

$id = 2;
$select->where(array('id' => $id));

which generates the (My)SQL:

WHERE `id` = '2'

If you want the SQL generated to use the IN operator, then just pass in an array:

$idList = array(1, 3, 4);
$select->where(array('id' => $idList));

which generates:

WHERE `id` IN ('1', '3', '4')

This obviously also works for ZendDbSql's update() as well as select().

Caching your ZF2 merged configuration

Zend Framework 2's ModuleManager has the ability to cache the merged configuration information for your application. This is very useful as it allows you to separate out your configuration within the config/autoload directory into logical files without worrying about the performance implications of lots of files.

Enabling caching is simply a case of setting these configuration keys in config/application.config.php within the module_listener_options section:

	'module_listener_options' => array(
        'config_cache_enabled'     => true,
        'module_map_cache_enabled' => true,
        'cache_dir'                => 'data/cache/',
		// other keys go here (e.g. module_paths & config_glob_paths)
	),

This then creates the cache files data/cache/module-classmap-cache.php and data/cache/module-config-cache.php and you're done. If you need to regenerate the files, simply delete then.

During development this can be a pain to remember!

We can solve this by only caching when in production. The easiest way to do this is by setting an environment variable in your virtual host. For Apache, use SetEnv; you're on your own for any other web server.

The way I do this is to modify config/application.config.php like this:

use ZendStdlibArrayUtils;

$config = array(
	// all standard application configuration ...
);


$localAppConfigFilename = 'config/application.config.' . getenv('APPLICATION_ENV') . '.php';
if (is_readable($localAppConfigFilename)) {
    $config = ArrayUtils::merge($config, require($localAppConfigFilename));
}

return $config;

I then create a separate configuration file for each environment. For production, I turn on caching:

config/application.config.production.php:

return array(
    'module_listener_options' => array(
        'config_cache_enabled' => true,
        'module_map_cache_enabled' => true,
    ),
);

and for my local development, I add some development modules:

config/application.config.development.php:

return array(
    'modules' => array(
        'BjyProfiler',
        'ZendDeveloperTools',
    ),
);

This system allows me to have a faster production site and specific modules loaded when developing that make life easier.

Injecting configuration into a ZF2 controller

One thing you may find yourself needing to do is access configuration information in a controller or service class.

The easiest way to do this is to use the ServiceManger's initialiser feature. This allows you to write one piece of injection code that can be applied to multiple objects. It's easier to show this in action!

Let's assume that we have this configuration file:

config/autoload/global.php:

return array(
    'application' => array(
        'setting_1' => 234,
    )
);

That is, we have a key called 'application' that contains application-specific configuration that we would like to access in our controllers (or service classes).

Firstly we define a interface, ConfigAwareInterface:

module/Application/src/Application/ConfigAwareInterface.php:

namespace Application;

interface ConfigAwareInterface
{
    public function setConfig($config);
}

We can now add this to a controller:

module/Application/src/Application/Controller/IndexController.php:

namespace ApplicationController;

use ZendMvcControllerAbstractActionController;
use ZendViewModelViewModel;
use ApplicationConfigAwareInterface;

class IndexController extends AbstractActionController
    implements ConfigAwareInterface
{
    protected $config;

    public function setConfig($config)
    {
        $this->config = $config;
    }

    // action methods, etc.
}

In the controller, we add a use statement, implement our interface and the required setConfig() method.

Finally, we add an initializer to the Module class:

module/Application/Module.php:

class Module
{
    // Other methods, such as OnBoostrap(), getAutoloaderConfig(), etc.

    public function getControllerConfig()
    {
        return array(
             'initializers' => array(
                function ($instance, $sm) {
                    if ($instance instanceof ConfigAwareInterface) {
                        $locator = $sm->getServiceLocator();
                        $config  = $locator->get('Config');
                        $instance->setConfig($config['application']);
                    }
                }
            )
        );
    }
}

(We also have a use ApplicationConfigAwareInterface; statement at the top!)

As getControllerConfig() is used by a specific ServiceManager only for controllers, we need to retrieve the main ServiceManager using getServiceLocator() in order to collect the merged configuration. As we only want the settings from within the 'application' key, we only pass that into the controller's setConfig() method.

The configuration settings are now available to us in any controller that implements ConfigAwareInterface.

We can also do this for service classes – we simply add another initalizer to the Module:

module/Application/Module.php:

    public function getServiceConfig()
    {
        return array(
             'initializers' => array(
                function ($instance, $sm) {
                    if ($instance instanceof ConfigAwareInterface) {
                        $config  = $sm->get('Config');
                        $instance->setConfig($config['application']);
                    }
                }
            )
        );
    }

It also follows that you can use initializers for any type of generic injection, such as mappers, db adapters, loggers, etc. Simply create an interface and write an initalizer.

Simple logging of ZF2 exceptions

I recently had a problem with a ZF2 based website where users were reporting seeing the error page displayed, but I couldn't reproduce in testing. To find this problem I decided to log every exception to a file so I could then go back and work out what was happening. In a standard ZF2 application, the easiest way to do this is to add a listener to the 'dispatch.error' event and log using ZendLog.

To do this, I started with the Application's Module class and added an event listener:

    public function onBootstrap($e)
    {
        $eventManager = $e->getApplication()->getEventManager();
        $eventManager->attach('dispatch.error', function($event){
            $exception = $event->getResult()->exception;
            if ($exception) {
                $sm = $event->getApplication()->getServiceManager();
                $service = $sm->get('ApplicationServiceErrorHandling');
                $service->logException($exception);
            }
        });
    }

This code attaches an anonymous function to the 'dispatch.error' event which retrieves the exception from the event's result and passes it to the logException() method in an ErrorHandling class. We retrieve ErrorHandling from the service manager which allows us to inject an instance of ZendLog into it:

    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'ApplicationServiceErrorHandling' =>  function($sm) {
                    $logger = $sm->get('ZendLog');
                    $service = new ErrorHandlingService($logger);
                    return $service;
                },
                'ZendLog' => function ($sm) {
                    $filename = 'log_' . date('F') . '.txt';
                    $log = new Logger();
                    $writer = new LogWriterStream('./data/logs/' . $filename);
                    $log->addWriter($writer);

                    return $log;
                },
            ),
        );
    }

There's obviously a few use statements at the top of the file for this to work:

use ApplicationServiceErrorHandling as ErrorHandlingService;
use ZendLogLogger;
use ZendLogWriterStream as LogWriterStream;

The logging itself is done within the ErrorHandling class:

namespace ApplicationService;

class ErrorHandling
{
    protected $logger;

    function __construct($logger)
    {
        $this->logger = $logger;
    }

    function logException(Exception $e)
    {
        $trace = $e->getTraceAsString();
        $i = 1;
        do {
            $messages[] = $i++ . ": " . $e->getMessage();
        } while ($e = $e->getPrevious());

        $log = "Exception:n" . implode("n", $messages);
        $log .= "nTrace:n" . $trace;

        $this->logger->err($log);
    }
}

The logException method simply creates a string containing the exception's message along with any previous exception messages and the trace. We then call the Log's err method to store the log and can peruse at our leisure.

Update: I have updated the logException method to use a do..while() loop as it's neater and doesn't cause a an out-of-memory error that the previous code did. That is, it's a good idea to reuse the same variable when calling getPrevious()!

Changing the format of a ZendForm DateTime element

If you want to change the format of the value of a DateTime element, the easiest way to do this in your Form class is to do this:

        $this->add(array(
            'name' => 'next_appointment',
            'type' => 'ZendFormElementDateTime',
            'options' => array(
                'label' => 'Next callback time',
            ),
            'attributes' => array(
                'min' => '1 Jan 2013, 00:00',
            ),
        ));
        $this->get('next_appointment')->setFormat('j M Y, H:i');

The two things to note:

  1. You can't set the format within the array – it has to be via a setFormat() call.
  2. If you change the format, you must set the min attribute in the same format, as otherwise it will try to set it with the hardcoded string of '1970-01-01T00:00Z' which will not work with your specified format.

Thoughts on module directory structure

I've been working on a Zend Framework 2 module within a larger project that doesn't have that many PHP class files. Specifically, it has a controller, a mapper, an entity, a service and a form.

As a result, the traditional Zend Framework 2 directory structure for the Account module looks like this (with class names in brackets):

module/
    Account/
        config/
        src/
            Account/
                Controller/
                    CaseController.php (Account\Controller\CaseController)
                Entity/
                    CaseEntity.php     (Account\Entity\CaseEntity)
                Form/
                    CaseForm.php       (Account\Form\CaseForm)
                Mapper/
                    CaseMapper.php     (Account\Mapper\CaseMapper)
                Service/
                    CaseService.php    (Account\Service\CaseService)
        view/
        Module.php

That's a lot of directories for not many files!

As a result, I decided to flatten it to this:

module/
    Account/
        config/
        src/
            Account/
                CaseController.php (Account\CaseController)
                CaseEntity.php     (Account\CaseEntity)
                CaseForm.php       (Account\CaseForm)
                CaseMapper.php     (Account\CaseMapper)
                CaseService.php    (Account\CaseService)
        view/
        Module.php

This is much more sane for a module with so few classes.

Minimising even more

Interestingly, while ZendLoaderStandardAutoloader is PSR-0 compliant, it also allows for a different top-level directory name for the classes within a single namespace. This would allow for the removal of the Account folder within src too, i.e a structure like this:

module/
    Account/
        config/
        src/
            CaseController.php (Account\CaseController)
            CaseEntity.php     (Account\CaseEntity)
            CaseForm.php       (Account\CaseForm)
            CaseMapper.php     (Account\CaseMapper)
            CaseService.php    (Account\CaseService)
        view/
        Module.php

This has no extraneous directories at all, but obviously you can only have one namespace within src.

To do this, you simply modify getAutoloaderConfig() within Module.php, so that it looks like this:

    public function getAutoloaderConfig()
    {
        return array(
            'ZendLoaderStandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src',
                ),
            ),
        );
    }

Obviously, this is no longer PSR-0 compliant, but does mean that you don't have to worry about that extra directory in src. Also, obviously, you can only have one namespace within src too.

Of course, you can have subdirectories (sub-namespaces) if you wanted to too. e.g. you could organise the files into something like this:

module/
    Account/
        config/
        src/
            Controller/
                Case.php        (Account\Controller\Case)
            Form/
                Case.php        (Account\Form\Case)
            Model/
                CaseEntity.php  (Account\Model\CaseEntity)
                CaseMapper.php  (Account\Model\CaseMapper)
                CaseService.php (Account\Model\CaseService)
        view/
        Module.php

As this is no-longer PSR-0 compliant, it's arguable that it's not a "best practice", however it is very clear and understandable.

Remove src/

Finally, you could even remove the src folder and put the class files directly in the module's root directory:

module/
    Account/
        config/
        Controller/
            Case.php        (Account\Controller\Case)
        Form/
            Case.php        (Account\Form\Case)
        Model/
            CaseEntity.php  (Account\Model\CaseEntity)
            CaseMapper.php  (Account\Model\CaseMapper)
            CaseService.php (Account\Model\CaseService)
        Module.php
        view/

The autoloader configuration looks like this:

    public function getAutoloaderConfig()
    {
        return array(
            'ZendLoaderStandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__,
                ),
            ),
        );
    }

Ironically, this is PSR-0 compliant, even though it's less clear in this situation!

However, if your module consists solely of PHP classes and maybe a config file, then there's no need for a src directory at all.

Take away

So, in summary, the standard ZF2 module directory structure that you see everywhere is just a recommendation. There's no need to follow it slavishly if your needs are better served with a different structure.

Using ZendSession

This is a quick note on how to use ZendSession.

Although the component name is ZendSession, you actually interact with ZendSessionContainer to store and retrieve session data:

use ZendSessionContainer;

$session = new Container('SomeKeyName');

ZendSessionContainer's constructor takes a string argument which is the name for this container ('SomeKeyName' in this case). It's optional and if you don't set it, then it is set to 'Default'. The name allows you to use the same session keys in different containers.

To set data into the session:

$session->pageNumber = 2;

and to retrieve it again:

$pageNumber = $session->pageNumber;

Behind the scenes, ZendSession has replaced _SESSION with an instance of ZendSessionStorageSessionStorage. Fortunately this object extends ArrayObject, so you can still access $_SESSION as if it was an array. Our particular piece of data is at $_SESSION['SomeKeyName']['pageNumber'] and is set it to 2.

Integrating BjyAuthorize with ZendNavigation

If you are using BjyAuthorize for ACL configuration and want to use ZendNavigation's ZendAcl integration features, then you need to set the Acl and Role information into ZendNavigation.

The easiest way to do this is to add the following to ApplicationModule::onBoostrap():

        $sm = $e->getApplication()->getServiceManager();

        // Add ACL information to the Navigation view helper
        $authorize = $sm->get('BjyAuthorizeServiceAuthorize');
        $acl = $authorize->getAcl();
        $role = $authorize->getIdentity();
        ZendViewHelperNavigation::setDefaultAcl($acl);
        ZendViewHelperNavigation::setDefaultRole($role);

This assumes that you've set up BjyAuthorize with some resources and rules. For example, in my config/autoload/bjyauthorize.global.php, I have a 'bug' resource and have a rule that allows the reporter role access to the list and add privileges:

        'resource_providers' => array(
            'BjyAuthorizeProviderResourceConfig' => array(
                'bug' => array(),
            ),
        ),

        'rule_providers' => array(
            'BjyAuthorizeProviderRuleConfig' => array(
                'allow' => array(
                    array(array('reporter'), 'bug', array('list', 'add')),
                ),
            ),
        ),

My ZendNavigation configuration for the bug menu item is in my Bug module's module.config.php and it looks like:

    'navigation' => array(
        'site' => array(
            'bug' => array(
                'label' => 'Bugs',
                'route' => 'bug',
                'resource' => 'bug',
                'privilege' => 'list',
                'pages' => array(
                    'create' => array(
                        'label' => 'Create new project',
                        'route' => 'bug/create',
                        'resource' => 'bug',
                        'privilege' => 'add',
                    ),                        
                ),
            ),
        ),        
    ),    

That's all there is to it.