Setting up autoloading in Zend Framework can be a nuisance sometimes because a class is either found or not found (with few debugging opportunities) and in between various options have to be set, configurations included, paths set and class names registered with underscores or uppercased or both. Let’s review the specifics for Zend Framework 2 (ZF2).

The Zend library provides the StandardAutoloader class. To see how it works first create a vendor/Company directory. Then create a file named “Test.php” in this directory with content:

class Test {
public function sayHi(){
        return 'hi';
    }
}

The objective is to able to access the class Test from the index.php in the public directory of the project.

// make sure Zend can be found
set_include_path(__DIR__ . '/../vendor/zendframework/zendframework/library');
require_once 'Zend/Loader/StandardAutoloader.php';
// create an instance of the standard autoloader
$loader = new Zend\Loader\StandardAutoloader();
// Register a vendor prefix:
$loader->registerPrefix('Company', __DIR__ . '/../vendor/Company');
// include the correct file through the loader
$loader->autoload('Company_Test');

// class is found
$companyTest = new Test;

echo $companyTest->sayHi();

An instance of the StandardAutoloader is created, a vendor prefix is registered with the directory prefix and the correct file can be loaded.

In the next iteration the loader is registered with the native PHP spl_autoload_register() method through the register() method. For this to work the class name has to change to Company_Test.

set_include_path(__DIR__ . '/../vendor/zendframework/zendframework/library');
require_once 'Zend/Loader/StandardAutoloader.php';
$loader = new Zend\Loader\StandardAutoloader();
$loader->registerPrefix('Company', __DIR__ . '/../vendor/Company');

//Register with spl_autoload:
$loader->register();

// class is found
$companyTest = new Company_Test;
echo $companyTest->sayHi()

With this method the class Company_Sub_Test in subdirectory Sub is also found.

It is also possible to set the loader as a fallback autoloader with setFallbackAutoloader(). This option invokes the native method stream_resolve_include_path() which resolves a class name against the include path. In this example no prefix is registered at all but instead the vendor directory is added to the include_path.

set_include_path(__DIR__ . '/../vendor/zendframework/zendframework/library:' .
    __DIR__ . '/../vendor');
require_once 'Zend/Loader/StandardAutoloader.php';
$loader = new Zend\Loader\StandardAutoloader();
$loader->setFallbackAutoloader(true);
$loader->register();

// class is found
$companyTest = new Company_Test;

echo $companyTest->sayHi();

The result is the same but this setting is not recommended because expensive.

Namespaces

Files can also be found via autoloading a namespace. The loader is the same, but instead of registering a prefix (as with vendors) now a namespace is registered together with a directory prefix. The class Test now looks like this:

namespace Company;

class Test {
public function sayHi(){
        return 'hi';
    }
}

And autoloading takes place like:

use Company\Test;
use Zend\Loader\StandardAutoloader;

set_include_path(__DIR__ . '/../vendor/zendframework/zendframework/library');
require_once 'Zend/Loader/StandardAutoloader.php';
$loader = new StandardAutoloader();
$loader->registerNamespace('Company', __DIR__ . '/../vendor/Company');

//Register with spl_autoload:
$loader->register();
$companyTest = new Test;
echo $companyTest->sayHi();

The class Company\Test is found by first extracting the namespace “Company” from the class name. The directory prefix is now __DIR__ . ‘/../vendor/Company’. An attempt is now made to include a file called __DIR__ . ‘/../vendor/Company/Test.php. In order to find the class Company\Sub\Test the directory prefix is the same but the remainder of the class name (Sub/Test) is now broken up by the namespace separator (a \ backslash) and reconstituted as Sub/Test with the directory separator (a / forward slash). The file __DIR__ . ‘/../vendor/Company/Sub/Test.php is then included. Note how the use of namespaces greatly reduces the length of a class name. In Zend framework I a typical class name would be Zend_Db_Table_Abstract. The StandardAutoloader class is expensive because of all the class name string handling it has to perform. This is where class maps come in.

Classmaps

An alternative to autoloading are classmaps. Next to index.php in the public directory create a file called autoload_classmap.php with this content:

return array(
'Company_Test' => __DIR__ . '/../vendor/Company/Test.php'
);

This map is then registered with the ClassMapAutoloader:

set_include_path(__DIR__ . '/../vendor/zendframework/zendframework/library');
require_once 'Zend/Loader/ClassMapAutoloader.php';
$loader = new Zend\Loader\ClassMapAutoloader();

// Register the class map:
$loader->registerAutoloadMap('autoload_classmap.php');

// Register with spl_autoload:
$loader->register();

$companyTest = new Company_Test;
echo $companyTest->sayHi();

Just as the StandardAutoloader above this loader implements SplAutoloader with a mandatory autoload() method. All the autoload class does here is return the array value based on the class name as key.

Creating a class map manually can be time consuming but fortunately Zend comes with a classmap_generator.php executable file located in the bin directory. On the command line move to the public folder and enter:

cd /path/to/bin; 
php classmap_generator.php -w --library ../vendor/Company --output autoload_classmap.php

This action will recreate the autoload_classmap.php containing all classes found in the library.

Module autoloading

For ZF2 to be able to work with modules, the module namespace needs to be registered. In each module, Module.php contains a getAutoloaderConfig method which is called by the AutoloaderListener class which in turn calls the StandardAutoloader().

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


In this way any module class can be found but this method is again expensive because it involves the StandardAutoloader class. The alternative is to use the ClassMapAutoloader. Two steps are required. Firstly, inform Zend that an autoload class map is in place:

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

Secondly, create this autoload_classmap.php file in the root of the Module folder by the classmap generator:

 php ../../path/to/zend/bin/classmap_generator.php
Creating class file map for library in '/path/to/project/module/Application'...
Wrote classmap file to '/path/to/project/module/Application/autoload_classmap.php'

autoload_classmap.php will now look like this:

return array(
/* other entries */
  'Application\Controller\Plugin\Pdf'  => __DIR__ . '/src/Application/Application\Controller\Plugin\Pdf.php',
);

From now on the Zend autoloader will first check with the ClassMapAutoloader and then as a last resort fall back to the StandardAutoloader.

Composer

Composer is also very good at creating class maps. Composer can already import your vendor libraries for you and each composer.json file it encounters contains class map information. Composer can make sure all vendors combine this information in a single class map. For example, the well-known ZF module zfc-user has this section:

"autoload": {
        "psr-0": {
            "ZfcUser": "src/"
        },
        "classmap": [
            "./Module.php"
        ]
    }

It informs the composer class map generator to register "ZfcUser" as a namespace together with its directory prefix in the file autoload_namespaces.php in de vendor/composer directory:

return array(
/* other entries */
    'ZfcUser' => array($vendorDir . '/zf-commons/zfc-user/src')
}

The file autoload_classmap.php in the same directory then contains this line:

return array(
/* other entries */
     'ZfcUser\\Module' => $vendorDir . '/zf-commons/zfc-user/Module.php',
}

How to inform ZF2 what class map to use? The framework's index.php contains the line

require 'init_autoloader.php'; 

For setting op the autoloader.

The init loader then includes de vendor autoloader

if (file_exists('vendor/autoload.php')) {
    $loader = include 'vendor/autoload.php';
}

which returns the fantastically named ComposerAutoloaderInit7001620945dde57e32ff4c045254c51b::getLoader() method which is fed into the spl_autoload_register() method. Because the class name contains a unique hash it will not collide with other methods fed into the spl autoloader.

Advertisements