Autoloading classes in an any PHP project with Symfony2 ClassLoader component

Warning: This blog post was written a long time ago and might be no longer relevant.

Symfony ClassLoader component is a PSR-0 standard compliant PHP class autoloader. It's not only able to load namespaced code but also supports old-school PEAR standards (also used by Zend Framework). It's a perfect class loading tool for most of PHP projects.

Note: Code used in this post is available on github: https://github.com/jakzal/SymfonyComponentsExamples

Installation

You can either install it from the Symfony PEAR channel or grab it directly from github. For the purpose of this article we'll clone the sources to the vendor/ directory of the project.

Note: ClassLoader component uses Symfony\Component\ClassLoader namespace. Therefore we'll put it into Symfony/Component/ClassLoader subdirectory of vendor (see PSR-0 standard).

git clone https://github.com/symfony/ClassLoader.git vendor/Symfony/Component/ClassLoader

Basic Usage

Let's say we have two Acme libraries.

First one is located in the src/Acme/Tools. HelloWorld class uses Acme\Tools _namespace and is declared in the _src/Acme/Tools/HelloWorld.php file:

<?php
// src/Acme/Tools/HelloWorld.php

namespace Acme\Tools;

class HelloWorld
{
    public function __construct()
    {
        echo __METHOD__."\n";
    }
}

Second library is stored in the src/Legacy/Acme/Tools. It follows old but well known PEAR naming standards. Legacy_Acme_Tools_HelloWorld class is defined in the src/Legacy/Acme/Tools/HelloWorld.php file:

<?php
// src/Legacy/Acme/Tools/HelloWorld.php

class Legacy_Acme_Tools_HelloWorld
{
    public function __construct()
    {
        echo __METHOD__."\n";
    }
}

To make that our classes are automatically loaded we have to register Acme namespace and _Legacy__ prefix:

<?php
// classloader.php

require_once __DIR__.'/vendor/Symfony/Component/ClassLoader/UniversalClassLoader.php';
$loader = new Symfony\Component\ClassLoader\UniversalClassLoader();
$loader->registerNamespaces(array('Acme' => __DIR__ . '/src'));
$loader->registerPrefixes(array('Legacy_' => __DIR__ . '/src'));
$loader->register();

$helloWorld = new Acme\Tools\HelloWorld();
$legacyHelloWorld = new Legacy_Acme_Tools_HelloWorld();

Of course classes are only loaded when needed. Requiring UniversalClassLoader.php file should be the only require statement used in our code. Other classes should be loaded by the class loader.

Note: There's also a way to define paths with registerNamespaceFallbacks() and registerPrefixFallbacks(). Class loader will use them with namespaces or prefixes which weren't listed explicitly.

Increasing performance

Number of class files in a real-world project is rather big. Class loader might have some impact on performance as it checks for file existence before requiring it. To avoid disk operations we can cache results in APC with ApcUniversalClassLoader:

<?php
// classloadercached.php

require_once __DIR__.'/vendor/Symfony/Component/ClassLoader/UniversalClassLoader.php';
require_once __DIR__.'/vendor/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php';

$loader = new Symfony\Component\ClassLoader\ApcUniversalClassLoader('ClassLoader');
$loader->registerNamespaces(array('Acme' => __DIR__ . '/src'));
$loader->registerPrefixes(array('Legacy_' => __DIR__ . '/src'));
$loader->register();

$helloWorld = new Acme\Tools\HelloWorld();
$legacyHelloWorld = new Legacy_Acme_Tools_HelloWorld();

Note: Examples are run in a command line. Therefore there's no performance gain from using APC. In fact it can hurt performance as cache is initialized every time our script is run in cli. This is a limitation of APC.