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.

8 thoughts on “Thoughts on module directory structure

  1. Can also somebody describe about class name. What better, use Form/CaseForm.php or Form/Case.php ? And why ?

    1. In this particular situation, case is a PHP keyword and so can’t be used a classname. Hence it needs a prefix or postfix. If it was another word, eg. Pack, then a prefix/postfix would be optional and depend on whether you like seeing lots of Pack.php in your editor when multiple files are open!

  2. Nice article Rob, a good demonstration of how zf2 is flexible, and not as rigid as people might think!

    One question – Is it possible to have a view helper for example in the last case (no src directory)? In examples I’ve seen, you might have src/Account/View/Helper/Foo.php… If you remove the src and are on a case-insensitive platform, you cannot have “view” and “View” folders… I guess a solution would be to just create a ViewHelper folder or something? Or change the path the views are found (e.g. to view_scripts). What are your thoughts on that?

  3. For view helpers, there is no automatic mapping from the view helper name you use in the view script and the class in your PHP that implements it.

    Hence, you can simply create the class/file you want, e.g: module/Account/ViewHelper/CaseInformation.php

    Then in your Module.php, you can do something like this:

        public function getViewHelperConfig()
        {
            return array(
                'factories' => array(
                    'caseInformation' => function($sm) {
                        // $sm is the view helper manager, so we need to fetch the
                        // main service manager
                        $locator = $sm->getServiceLocator();
                        return new ViewHelperCaseInformation($locator->get('ModelCaseMapper'));
                    },
                ),
            );
    

    In this case, the CaseInformation view helper requires the mapper, so I’ve used getViewHelperConfig(). The other option is to use module.config.php:

    < ?php
    return array(
        //...
        'view_helpers' => array(
            'invokables' => array(
                'caseInformation' => 'AccountViewHelperCaseInformation',
            ),
        )
    
  4. simple and genius !
    i thing a good directory structure may also take in consideration unit tests. (and i think there is more than one way to do it)

    thanks !!!

  5. I want admin module with sub module like below structure in zend 2

    application/
    -admin-modules/
    --default/
    ---controller/
    ---model/
    ---etc..
    --user/
    ---controllers/
    ---etc..
    -frontend/
    --index/
    ---controller/
    ---model/
    

    Regards

  6. Hi Rob,
    First of all, thanks a ton for your contribution to the Zend Framework community!
    My question was around the directory structure. I am still trying to come to grips with the “recommended” structure. I know that we do not have to follow that but I wanted to understand why that particular structure is recommended.
    In the Module.php file, as I understand, we can have only one namespace declaration. So why not just have all the subfolders under /src/{namespace}/* be directly under the Module folder (as in your third iteration above)?
    Also, why have a config folder with just one file module.config.php? Why not remove that folder too? The file name says it all so why not keep it under the folder with the Module name itself (e.g. in the tutorial example, instead of Module/Album/config/module.config.php, why not Module/Album/module.config.php)?

    Thanks again for your help.
    Regards
    Abhijeet

Comments are closed.