Category Archives: Zend Framework 2

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.

Introducing AkrabatSession

One of the requirements for a new app that I’m writing is that it has a specific session name. In Zend Framework 2, this is done by creating a SessionManager with the correct configuration and then setting the default manager on the Session Container:

use ZendSessionConfigSessionConfig;
use ZendSessionSessionManager;
use ZendSessionContainer;

$sessionConfig = new SessionConfig();
$sessionConfig->setOptions(array('name'=>'MY_SESSION_NAME');
$sessionManager = new SessionManager($config);
Container::setDefaultManager($sessionManager);

Obviously, I need to be able to configure the name (and potentially other session configuration options) from my config/autoload/global.php file and this is a generically useful requirement, so I created the AkrabatSession module.

This is a really simple module that simply allows you to configure the SessionManager with minimal effort:

  1. Install AkrabatSession.
  2. Enable it as the first module in application.config.php
  3. Add the following to your configuration array in global.php:
        'session' => array(
            'name' => 'MY_SESSION_NAME_HERE',
        ),

Further details are in the README file and of course, it’s available on Packagist.

ZendServiceManager configuration keys

ZendServiceManager is usually configured in two places: an array in a config file or a method within your Module class. In either case, you provide a nested array of configuration information.

For example, in a config file:

return array(
    'service_manager' => array(
        'invokables' => array(
            'session' => 'ZendSessionStorageSessionStorage',
        ),
        'factories' => array(
            'db' => 'ZendDbAdapterAdapterServiceFactory',
        ),
    )
);

Within the service_manager array, there are a set of nested arrays which are generally used to configure how you want a given class to be instantiated. the names of these sub-arrays are hardcoded, so you just need to learn their names and the difference between them:

invokables A string which is the name of a class to be instantiated. The ServiceManager will instantiate the class for you when needed. For example:

'invokables' => array(
    'zfcuser_user' => 'UserServiceUser'
),
services An instance of a class. This is used to register already instantiated objects with the ServiceManager. For example:

'services' => array(
    'rob' => $rob,  // $rob is already instantiated 
),
factories A callback that will return an instantiated class. This is for cases where you need to configure the instance of the object. For example:

'factories' => array(
    'MyModuleMapperComment' =>  function($sm) {
        $mapper = new MyModuleMapperComment();
        $db = $sm->get('ZendDbAdapterAdapter');
        $mapper->setDbAdapter($db);
        return $mapper;
    },
),
aliases Another name for a class. Generally, you see this used within a module so that the module uses it’s own alias name and then the user of the module can configure exactly which class that alias name is to be.
For example:

'aliases' => array(
    'mymodule_zend_db_adapter' => 'ZendDbAdapterAdapter',
),
initializers A callback that is executed every time the ServiceManager creates a new instance of a class. These are usually used to inject an object into the new class instance if that class implements a particular interface.
For example:

'initializers' => array(
    function ($instance, $sm) {
        if ($instance instanceof AuthorizeAwareInterface) {
            $instance->setAuthorizeService($sm->get('auth_service'));
        }
    }
)

In this case, the initialiser checks if $instance implements AuthorizeAwareInterface and if it injects the Authorize service into the instance ready for use. Another really common use-case is injecting a database adapter and Zend Framework supplies ZendDbAdapterAdapterAwareInterface for this case.

There is also the abstract_factories key, but this is rarely used in most apps.

abstract_factories A factory instance that can create multiple services based on the name supplied to the factory. This is used to enable ServiceManager to fallback to another Service Locator system if it can cannot locate the required class from within its own configuration. As an example, you could write an abstract factory that proxies to Symfony’s DependencyInjection component. Items within this sub-key can be either a classname string or an instance of the factory itself For example:

array('abstract_factories' => array( 
    new DiStrictAbstractServiceFactory(),
);

All abstract factories must implement ZendServiceManagerAbstractFactoryInterface.

Controllers, View helpers & Controller plugins

Note that the MVC system instantiates controllers, view helpers, form elements, input filters, controller plugins and others using specialised versions of ServiceManager. This means that the same keys that you use for service manager configuration are used for setting up view helpers and controller plugins, etc. – you just use a different top level configuration key and method name the in Module class:

Manager Key name in configuration array Method name in Module.php
ServiceManager service_manager getServiceConfig()
ViewHelperManager view_helpers getViewHelperConfig()
ControllerPluginManager controller_plugins getControllerPluginConfig()
ControllerManager controllers getControllerConfig()
ValidatorManager validators getValidatorConfig()
FilterManager filters getFilterConfig()
FormElementManager form_elements getFormElementConfig()
RoutePluginManager route_manager getRouteConfig()
SerializerAdapterManager serializers getSerializerConfig()
HydratorManager hydrators getHydratorConfig()
InputFilterManager input_filters getInputFilterConfig()

This is reuse of knowledge at its best!

Sending an HTML with text alternative email with ZendMail

Sending a multi-part email with ZendMail is easy enough, but if you want to send an HTML email with a text alternative, you need to remember to set the content-type in the headers to multipart/alternative. As this is the second time I had to work this out, I’m noting it here for the next time I forget!

use ZendMail;
use ZendMimeMessage as MimeMessage;
use ZendMimePart as MimePart;


function sendMail($htmlBody, $textBody, $subject, $from, $to)
{
    $htmlPart = new MimePart($htmlBody);
    $htmlPart->type = "text/html";

    $textPart = new MimePart($textBody);
    $textPart->type = "text/plain";

    $body = new MimeMessage();
    $body->setParts(array($textPart, $htmlPart));

    $message = new MailMessage();
    $message->setFrom($from);
    $message->addTo($to);
    $message->setSubject($subject);

    $message->setEncoding("UTF-8");
    $message->setBody($body);
    $message->getHeaders()->get('content-type')->setType('multipart/alternative');

    $transport = new MailTransportSendmail();
    $transport->send($message);
}

Module specific layouts in ZF2

If you need different layout scripts to be rendered for different modules in Zend Framework 2, then Evan Coury has made this extremely easy. His new module EdpModuleLayouts is just the ticket!

Once installed, you simply have to add a new array to a config file in the config/autoload folder with the following in it:

array(
    'module_layouts' => array(
        'Application' => 'layout/application',
        'ZfcUser' => 'layout/user',
    ),
);

i.e. you provide a list of the module name against the layout script to use.

What could be easier?

Setting up required fields that can be empty with ZendInputFilter

When you create an input filter entry that has the required element set to true, then by default, the field is set up so that it must have a value in it. If you want the field to be required, but also an empty value is okay, then add the allow_empty element to your definition:

    $inputFilter->add(array(
        'name'        => 'notes',
        'required'    => true,
        'allow_empty' => true,
    ));

In this definition, we have a ‘notes’ element that must exist, but can be an empty string.

That’s all there is to it.

Updated ZF2 tutorial for Beta 5

Zend Framework 2, Beta 5 has been released!

This is an important release as we think we’re at the point where the API has stabilised and expect only small BC breaks between Beta5 and the stable release. We also have two new components:

  • ZendI18n for localisation and translation
  • ZendEscaper for context-specific escaping that targets HTML, HTML attributes, URLs, CSS, and JavaScript.

Lots of other changes happened too, so I recommend reading the announcement for all the details.

Note that ZendI18n requires the ext/intl extension. If you are using the stock PHP 5.3 OS X Lion, then I recommend following Bertrand Mansion’s article PHP with Intl and Gettext on OSX Lion, though note that you need the PHP 5.3.10 source code nowadays.

Obviously, I have also updated my Zend Framework 2 tutorial so that it works with Beta 5. The most obvious difference I noticed was that the bootstrapping is nice as is displaying a form in the view.