Class Loading
Class loading is an essential part of any PHP application that makes heavy use of classes and interfaces. Unfortunately, a lot of people and projects spend a lot of time and effort on custom and specialized class loading strategies. It can quickly become a pain to understand what is going on when using multiple libraries and/or frameworks, each with its own way to do class loading. Class loading should be simple and it is an ideal candidate for convention over configuration.
Overview
The Doctrine Common ClassLoader implements a simple and efficient approach to class loading that is easy to understand and use. The implementation is based on the widely used and accepted convention of mapping namespace and class names to a directory structure. This approach is used for example by Symfony2, the Zend Framework and of course, Doctrine.
For example, the following class:
resides in the following directory structure:
src/
/MyProject
/Shipping
ShippingStrategy.php
Note that the name of "src" or the structure above or beside this directory is completely arbitrary. "src" could be named "classes" or "lib" or whatever. The only convention to adhere to is to map namespaces to directories and classes to files named after the class name.
Usage
To use a Doctrine Common ClassLoader, you first need to load the
class file containing the ClassLoader. This is the only class file
that actually needs to be loaded explicitly via require
. All
other classes will be loaded on demand by the configured class
loaders.
A ClassLoader
takes two constructor parameters, both optional.
In the normal case both arguments are supplied. The first argument
specifies the namespace prefix this class loader should be
responsible for and the second parameter is the path to the root
directory where the classes can be found according to the
convention mentioned previously.
The class loader in the example above would thus be responsible for all classes under the 'MyProject' namespace and it would look for the class files starting at the directory '/path/to/src'.
Also note that the prefix supplied in the first argument need not be a root namespace but can be an arbitrarily nested namespace as well. This allows you to even have the sources of subnamespaces split across different directories. For example, all projects under the Doctrine umbrella reside in the Doctrine namespace, yet the sources for each project usually do not reside under a common root directory. The following is an example of configuring three class loaders, one for each used Doctrine project:
1 <?php
use Doctrine\Common\ClassLoader;
require '/path/to/Doctrine/Common/ClassLoader.php';
$commonLoader = new ClassLoader('Doctrine\Common', '/path/to/common/lib');
$dbalLoader = new ClassLoader('Doctrine\DBAL', '/path/to/dbal/lib');
$ormLoader = new ClassLoader('Doctrine\ORM', '/path/to/orm/lib');
$commonLoader->register();
$dbalLoader->register();
$ormLoader->register();
2
3
4
5
6
7
8
9
Do not be afraid of using multiple class loaders. Due to the
efficient class loading design you will not incur much overhead
from using many class loaders. Take a look at the implementation of
ClassLoader#loadClass
to see how simple and efficient the class
loading is. The iteration over the installed class loaders happens
in C (with the exception of using ClassLoader::classExists
).
A ClassLoader can be used in the following other variations, however, these are rarely used/needed:
- If only the second argument is not supplied, the class loader will be responsible for the namespace prefix given in the first argument and it will rely on the PHP include_path.
- If only the first argument is not supplied, the class loader will be responsible for all classes and it will try to look up all classes starting at the directory given as the second argument.
- If both arguments are not supplied, the class loader will be responsible for all classes and it will rely on the PHP include_path.
File Extension
By default, a ClassLoader uses the .php
file extension for all
class files. You can change this behavior, for example to use a
ClassLoader to load classes from a library that uses the
".class.php" convention (but it must nevertheless adhere to the
directory structure convention!):
Namespace Separator
By default, a ClassLoader uses the \
namespace separator. You
can change this behavior, for example to use a ClassLoader to load
legacy Zend Framework classes that still use the underscore "_"
separator:
Failing Silently and class_exists
A lot of class/autoloaders these days try to fail silently when a
class file is not found. For the most part this is necessary in
order to support using class_exists('ClassName', true)
which is
supposed to return a boolean value but triggers autoloading. This
is a bad thing as it basically forces class loaders to fail
silently, which in turn requires costly file_exists or fopen calls
for each class being loaded, even though in at least 99% of the
cases this is not necessary (compare the number of
class_exists(..., true) invocations to the total number of classes
being loaded in a request).
The Doctrine Common ClassLoader does not fail silently, by design.
It therefore does not need any costly checks for file existence. A
ClassLoader is always responsible for all classes with a certain
namespace prefix and if a class is requested to be loaded and can
not be found this is considered to be a fatal error. This also
means that using class_exists(..., true) to check for class
existence when using a Doctrine Common ClassLoader is not possible
but this is not a bad thing. What class_exists(..., true) actually
means is two things: 1) Check whether the class is already
defined/exists (i.e. class_exists(..., false)) and if not 2) check
whether a class file can be loaded for that class. In the Doctrine
Common ClassLoader the two responsibilities of loading a class and
checking for its existence are separated, which can be observed by
the existence of the two methods loadClass
and
canLoadClass
. Thereby loadClass
does not invoke
canLoadClass
internally, by design. However, you are free to
use it yourself to check whether a class can be loaded and the
following code snippet is thus equivalent to class_exists(...,
true):
The only problem with this is that it is inconvenient as you need
to have a reference to the class loaders around (and there are
often multiple class loaders in use). Therefore, a simpler
alternative exists for the cases in which you really want to ask
all installed class loaders whether they can load the class:
ClassLoader::classExists($className)
:
This static method can basically be used as a drop-in replacement
for class_exists(..., true). It iterates over all installed class
loaders and asks each of them via canLoadClass
, returning early
(with TRUE) as soon as one class loader returns TRUE from
canLoadClass
. If this sounds like it can potentially be rather
costly then because that is true but it is exactly the same thing
that class_exists(..., true) does under the hood, it triggers a
complete interaction of all class/auto loaders. Checking for class
existence via invoking autoloading was never a cheap thing to do
but now it is more obvious and more importantly, this check is no
longer interleaved with regular class loading, which avoids having
to check each and every class for existence prior to loading it.
The vast majority of classes to be loaded are not optional and a
failure to load such a class is, and should be, a fatal error. The
ClassLoader design reflects this.
If you have code that requires the usage of class_exists(..., true) or ClassLoader::classExists during normal runtime of the application (i.e. on each request) try to refactor your design to avoid it.
Summary
No matter which class loader you prefer to use (Doctrine classes do not care about how they are loaded), we kindly encourage you to adhere to the simple convention of mapping namespaces and class names to a directory structure.
Class loading should be simple, automated and uniform. Time is better invested in actual application development than in designing special directory structures, autoloaders and clever caching strategies for class loading.