Class Autoloading

When our application is small enough, we can put all the source code into a single file. Unfortunately, the code grows fast, and quite soon, this file will become too large to work with effectively.

Including files with the include and require expressions

We can extract some pieces of code into separate files and inject them back in using the include or require expressions. The include expression takes the contents of a file and evaluates it as if the file contents were in the middle of the code. The require expression does the same, except it raises an error instead of a warning if the file is missing or inaccessible.

main.php
b.php
a.php
<?php
// Injecting files a.php and b.php in two different ways
include 'a.php';
require 'b.php';
// Code that used definitions from a.php and b.php
echo $a;
echo $b;

Repeated include issue

We might get errors if we try to declare functions or classes in the included files. Imagine we have four files with a diamond-shape dependency structure.

Diamond-shaped dependency structure
Diamond-shaped dependency structure
main.php
bob.php
alice.php
hello.php
<?php
// Require two files with the same dependencies.
// It will result in an error.
require 'alice.php';
require 'bob.php';

When we run main.php, we expect to see greetings for Alice and Bob. In reality, we’ll see the error “Fatal error: Cannot redeclare hello().” We see it because we included the file hello.php twice and defined the function hello() each time.

To avoid this error, we can use the include_once and require_once expressions. They do the same thing as include and require, but they do nothing if the file was already included. If, in the example above, we replace every require with require_once, it will start working as expected.

main.php
bob.php
alice.php
hello.php
<?php
// Require two files with the same dependencies using require_once.
// It will work.
require_once 'alice.php';
require_once 'bob.php';

Autoloading classes

With require_once, we must explicitly include every dependency file in every file we have. Even worse, whenever we want to use an existing class, we must remember its location and provide a relative path from the current file to the dependency file. That’s a tedious job we’d like to avoid.

Fortunately, PHP has a mechanism of semiautomatic file inclusion called class autoloading. It allows us to register a function that gets a string with a class name and includes a file containing this class. PHP will call this function every time our code tries to use an undeclared class. With an autoloader, PHP loads all the classes we use automatically.

The best practice for storing source code in PHP is to wrap all the logic in classes and store every class in a separate file to make it easy to autoload them. Also, the class files shouldn’t have any code outside the class definition because we can’t predict when PHP will load our file and execute this code.

This is how the previous example would look with class autoloading:

main.php
Bob.php
Alice.php
Hello.php
<?php
// Register autoloading function
spl_autoload_register(function (string $class) {
include $class . '.php';
});
// Use classes defined in other files.
// The files will be autoloaded based on the class name.
Alice::greet();
Bob::greet();

PSR-4: Standard recommendation for autoloading

We might want to make a perfect class autoloader for the project now. We shouldn’t do that. Class autoloading is a common task, so there are existing solutions we can and should use. This way, we can focus on project-specific logic.

In the PHP community, there’s a PHP Framework Interop Group that consists of those who maintain major PHP frameworks, applications, and tools. They create standard recommendations that make their projects work better together. Although sticking to these standards is unnecessary, we would benefit from it.

The most important of their standards is PSR-4. It tells us how to name files that depend on class names to support autoloading.

Specification

  1. Every class, interface, or trait must have a namespace.
  2. There should be a part at the beginning of a namespace called namespace prefix that corresponds to a base directory. 3/ Other namespace parts should correspond to subdirectories inside the base directory.
  3. The file name must be the terminal class name plus the .php extension.

Examples

PSR-4 Examples

Fully Qualified Class Name

Namespace Prefix

Base Directory

Resulting File Path

\Acme\Log\Writer\File_Writer

Acme\Log\Writer

./acme-log-writer/lib/

./acme-log-writer/lib/File_Writer.php

\Aura\Web\Response\Status

Aura\Web

/path/to/aura-web/src/

/path/to/aura-web/src/Response/Status.php

\Symfony\Core\Request

Symfony\Core

./vendor/Symfony/Core/

./vendor/Symfony/Core/Request.php

\Zend\Acl

Zend

/usr/includes/Zend/

/usr/includes/Zend/Acl.php

Why it matters

By sticking to this standard, we can reuse existing class autoloaders so we don’t have to waste time recreating them.

In the next lesson, we’ll learn about the Composer tool and how to use it as a class autoloader.

Note: Before namespaces were common in PHP, there was a PSR-0 standard. This standard is almost the same as PSR-4 but treats underscores in the class name as directory separators. This way, the class PHPUnit_Framework_TestCase is located in the file PHPUnit/Framework/TestCase.php. This standard is deprecated, but the old code can still have such class names.

Conclusion

The include and require expressions are used to load PHP code located in other files. There are also the include_once and require_once expressions that are used to avoid loading a single file twice.

PHP lets us define functions to load class files automatically. We can use existing class autoloaders because of the PSR-4 standard. Itdefines each class in a separate file, which maps paths to the class namespace and name.