Generating ZF Autoloader Classmaps with Phing

One of the things I’ve quickly discovered when working on Shoppimon is that we need a build process for our PHP frontend app. While the PHP files themselves do not require any traditional “build” step such as processing or compilation, there are a lot of other tasks that need to happen when taking a version from the development environment to staging and to production: among other things, our build process picks up and packages only the files needed by the app (leaving out things like documentation, unit tests and local configuration overrides), minifies and pre-compresses CSS and JavaScript files,  and performs other helpful optimizations on the app, making it ready for production.

Since Shoppimon is based on Zend Framework 2.0, it also heavily relies on the ZF2.0 autoloader stack. Class autoloading is convenient, and was shown to greatly improve performance over using require_once calls. However, different autoloading strategies have pros and cons: while PSR-0 based autoloading (the so called Standard Autoloader from ZF1 days) works automatically and doesn’t require updating any mapping code for each new class added or renamed, it has a significant performance impact compared to classmap based autoloading.

Fortunately, using ZF2′s autoloader stack and Phing, we can enjoy both worlds: while in development, standard PSR-0 autoloading is used and the developer can work smoothly without worrying about updating class maps. As we push code towards production, our build system takes care of updating class map files, ensuring super-fast autoloading in production using the ClassMapAutoloader. How is this done? Read on to learn.

Before auto-generating class map files, you need to make sure your code supports both using a class map based autoloader, and falling back to the standard autoloader. This is how it is done in the main Shoppimon bootstrap file:

// Set up the autoloader
require_once 'Zend/Loader/AutoloaderFactory.php';
Zend\Loader\AutoloaderFactory::factory(array(
  'Zend\Loader\ClassMapAutoloader' => array(
    include 'library/Shoplift/autoload_classmap.php'
  ),
  'Zend\Loader\StandardAutoloader' => array(
    'namespaces' => array(
      'Shoplift' => realpath('library/Shoplift')
    )
  ),
));

Note that ‘Shoplift’ is the namespace for our in-house library used in addition to Zend Framework 2.0. We have similar code in the Module.php file of each one of our modules:

public function getAutoloaderConfig()
{
  return array(
    'Zend\Loader\ClassmapAutoloader' => array(
      __DIR__ . '/autoload_classmap.php'
    ),
    'Zend\Loader\StandardAutoloader' => array(
      'namespaces' => array(
        __NAMESPACE__ => __DIR__ . '/src'
      )
    ),
  );
}

Both code segments reference a class map file named autoload_classmap.php – these exists for our common library and for each one of our modules – and this is what they look like:

<?php

/**
 * Autoloader classmap
 *
 * Keep the classmap empty - it is auto-generated at build time
 */

return array();

Yes – an empty classmap array is returned. This is how this file is committed to SCM, and we even add it to our .gitignore file to ensure it is not mistakenly overwritten with actual data. Returning an empty class map ensures that the first ClassMapAutoloader always fails, and that the StandardAutoloader is always used. That is, unless a classmap file is properly generated at build time.

We’ve decided to use Phing for building our PHP apps – it wasn’t a hard decision, and was based on the fact that Phing is well known and widely used, it is very similar in concepts and syntax to Ant which we were already using for some of our non-PHP stuff (yes, we have some Java and Python as well, not everything is PHP you know…) and, unlike Ant, we can easily extend it based on our needs using PHP – which is exactly what we’ve done in order to ensure class maps are populated at build time.

Here is a segment from our Phing build.xml file responsible for generating class maps:

<target name="generate-classmap" depends="copyfiles">
  <taskdef classname="phing.tasks.ZfClassmapTask" name="classmap" />
  <classmap outputFile="${builddir}/data/library/Shoplift/autoload_classmap.php">
    <dirset dir="${builddir}/data/library">
      <include name="Shoplift" />
    </dirset>
  </classmap>
  <classmap outputFile="${builddir}/data/module/Frontend/autoload_classmap.php">
    <dirset dir="${builddir}/data/module" includes="Frontend" />
  </classmap>
  <classmap outputFile="${builddir}/data/module/InternalApi/autoload_classmap.php">
    <dirset dir="${builddir}/data/module" includes="InternalApi" />
  </classmap>
</target>

The “generate-classmap” build target takes a set of files (previously copied to a working directory in the “copyfiles” target), scans them and generates several classmap files – one for the library/Shoplift directory, and one for each one of our modules – “Frontend” and “InternalApi”. This is done after defining a new task type: the “classmap” task, defined in the phing/tasks/ZfClassmapTask.php file, which looks like this (this is where the magic is):

We place this file in the ‘phing/tasks’ directory under our source tree, but it can actually be placed anywhere in the include path (see comment inside the file) as long as the classname=”…” attribute of the <taskdef> element is adjusted accordingly.

This is a Phing task class, and was built by adapting the classmap_generator.php tool included in ZF2 to Phing. It scans the directories mentioned in the <dirset> elements, finds PHP classes, and adds them to the generated class map file. This is done automatically at build time, takes a couple of seconds, and allows us to enjoy uninterrupted development as well as good production performance.

BTW as you can see the Phing class was posted as a Gist in Github – so feel free to fork it, fix it and improve it – and please share the results.

2 thoughts on “Generating ZF Autoloader Classmaps with Phing

  1. Pingback: Zend Framework in Action » Shahar Evron: Generating ZF Autoloader Classmaps with Phing

  2. Mind me for not reading the entire article (ZF is not my expertise :-) – I just wanted to note that Phing has an added value when working in the company (vs. working alone or from home, where this advantage is weaker): it provides lots of nice comic relief based upon the pronunciation of Phing as “thing”.

    Used wisely, its a good tool to show your wittiness and enjoy others doing the same.

    I strongly recommend on using Phing. From the described aspect – you’re doing the right thing!