Skip to content

Loader


NOTE

The Phalcon\Autoload\Loader class has been renamed Phalcon\Autoload\Loader. The functionality remains the same.

Overview

Phalcon\Autoload\Loader is an autoloader that implements PSR-4. Just like any autoloader, depending on its setup, it will try to find the files your code is looking for based on file, class, namespace, etc. Since this component is written in C, it offers the lowest overhead when processing its setup, thus offering a performance boost.

This component relies on PHP's autoloading classes capability. If a class defined in the code has not been included yet, a special handler will try to load it. Phalcon\Autoload\Loader serves as the special handler for this operation. By loading classes on a need-to-load basis, the overall performance is increased since the only file reads that occur are for the files needed. This technique is called lazy initialization.

The component offers options for loading files based on their class, file name, directories on your file system as well as file extensions.

Registration

Usually, we would use the spl_autoload_register() to register a custom autoloader for our application. Phalcon\Autoload\Loader hides this complexity. After you define all your namespaces, classes, directories and files you will need to call the register() function, and the autoloader is ready to be used.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setNamespaces(
    [
       'MyApp'        => 'app/library',
       'MyApp\Models' => 'app/models',
    ]
);

$loader->register();

register() uses spl_autoload_register() internally. As a result, it accepts also accepts the boolean prepend parameter. If supplied and is true, the autoloader will be prepended on the autoload queue instead of appended (default behavior).

You can always call the isRegistered() method to check if your autoloader is registered or not.

NOTE

If there is an error in registering the autoloader, the component will throw an exception.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setNamespaces(
    [
       'MyApp'        => 'app/library',
       'MyApp\Models' => 'app/models',
    ]
);

$loader->register();

echo $loader->isRegistered(); // true

Unregistering the autoloader is similarly easy. All you need to do is call unregister().

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setNamespaces(
    [
       'MyApp'        => 'app/library',
       'MyApp\Models' => 'app/models',
    ]
);

$loader->register();

if (true === $loader->isRegistered()) {
    $loader->unregister();
}

Security Layer

Phalcon\Autoload\Loader incorporates a security layer, by sanitizing class names by default i.e. removing invalid characters. As such it makes it more difficult for malicious code to be injected in your application.

Consider the following example:

<?php

spl_autoload_register(
    function (string $className) {
        $filepath = $className . '.php';

        if (file_exists($filepath)) {
            require $filepath;
        }
    }
);

The above autoloader lacks any kind of security. If a part of your code accidentally calls the autoloader with a name that points to a script containing malicious code, then your application will be compromised.

<?php

$className = '../processes/important-process';

if (class_exists($className)) {
    // ...
}

In the above snippet, if ../processes/important-process.php is a valid file, that could have been uploaded by a hacker or from a not-so-careful upload process, then an external user could execute the code without any authorization and subsequently get access to the application if not the server.

To avoid most of these kinds of attacks, Phalcon\Autoload\Loader removes invalid characters from the class name.

Namespaces

A very popular way to organize your application is with directories, each representing a particular namespace. Phalcon\Autoload\Loader can register those namespaces to directory mapping and traverse those directories to search the file that your application requires.

The setNamespaces() method accepts an array, where keys are the namespaces and values are the actual directories in the file system. The namespace separator will be replaced by the directory separator when the loader tries to find the classes.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setNamespaces(
    [
       'MyApp'             => 'app/library',
       'MyApp\Controllers' => 'app/controllers',
       'MyApp\Models'      => 'app/models',
    ]
);

$loader->register();

In the above example, whenever we reference a controller, the loader will search for it in app/controllers and its subdirectories. Similarly, for a model, the search will occur in app/models.

You do not need to register the sub-namespaces, if the actual files are located in subdirectories that map the namespace naming convention.

So for instance the above example defines our MyApp namespace to point to app/library. If we have a file:

/app/library/Components/Mail.php

that has a namespace of:

MyApp\Components

then the loader, as defined above, does not need to know about the MyApp\Components namespace location, or have it defined in the () declaration.

If the component referenced in the code is MyApp\Components\Mail, it will assume that it is a subdirectory of the root namespace. However, since we specified a different location for the MyApp\Controllers and MyApp\Models namespaces, the loader will look for those namespaces in the specified directories.

The setNamespaces() method also accepts a second parameter merge. By default, it is false. You can however set it to true when having multiple calls to setNamespaces() so that the namespace definitions are merged.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setNamespaces(
    [
       'MyApp'             => 'app/library',
    ]
);

$loader->setNamespaces(
    [
       'MyApp\Controllers' => 'app/controllers',
       'MyApp\Models'      => 'app/models',
    ],
    true
);

$loader->register();

The above example merges the second declaration of setNamespaces() with the previous one.

If you need to check what namespaces are registered in the autoloader, you can use the getNamespaces() getter, which returns the array of the registered namespaces. For the example above, getNamespaces() returns:

[
   'MyApp'             => 'app/library',
   'MyApp\Controllers' => 'app/controllers',
   'MyApp\Models'      => 'app/models',
]

Classes

Another way to let Phalcon\Autoload\Loader know where your components/classes are located, so that the autoloader can load them properly, is by using setClasses().

The method accepts an array, where the key is the namespaced class and the value is the location of the file that contains the class. As expected, this is the fastest way to autoload a class, since the autoloader does not need to do file scans or stats to find the file references.

However, using this method can hinder the maintenance of your application. The more your application grows, the more files are added, and the easier it becomes to make a mistake while maintaining the list of files used in setClasses()

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setClasses(
    [
        'MyApp\Components\Mail'             => 'app/library/Components/Mail.php',
        'MyApp\Controllers\IndexController' => 'app/controllers/IndexController.php',
        'MyApp\Controllers\AdminController' => 'app/controllers/AdminController.php',
        'MyApp\Models\Invoices'             => 'app/models/Invoices.php',
        'MyApp\Models\Users'                => 'app/models/Users.php',
    ]
);

$loader->register();

In the above example, we are defining the relationship between a namespaced class and a file. As you can see, the loader will be as fast as it can be but the list will start growing, the more our application grows, making maintenance difficult. If however, your application does not have that many components, there is no reason why you cannot use this method of autoloading components.

The setClasses() method also accepts a second parameter merge. By default, it is false. You can however set it to true when having multiple calls to setClasses() so that the class definitions are merged.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setClasses(
    [
        'MyApp\Components\Mail'             => 'app/library/Components/Mail.php',
        'MyApp\Controllers\IndexController' => 'app/controllers/IndexController.php',
        'MyApp\Controllers\AdminController' => 'app/controllers/AdminController.php',
    ]
);

$loader->setClasses(
    [
        'MyApp\Models\Invoices'             => 'app/models/Invoices.php',
        'MyApp\Models\Users'                => 'app/models/Users.php',
    ],
    true
);

$loader->register();

The above example merges the second declaration of setClasses() with the previous one.

If you need to check what classes are registered in the autoloader, you can use the getClasses() getter, which returns the array of the registered classes. For the example above, getClasses() returns:

[
    'MyApp\Components\Mail'             => 'app/library/Components/Mail.php',
    'MyApp\Controllers\IndexController' => 'app/controllers/IndexController.php',
    'MyApp\Controllers\AdminController' => 'app/controllers/AdminController.php',
    'MyApp\Models\Invoices'             => 'app/models/Invoices.php',
    'MyApp\Models\Users'                => 'app/models/Users.php',
]

Files

There are times that you might need to require a specific file that contains a class without a namespace or a file that contains some code that you need. An example would be a file that contains handy debugging functions.

Phalcon\Autoload\Loader offers setFiles() which is used to require such files. It accepts an array, containing the file name and location of each file.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setFiles(
    [
        'functions.php',
        'arrayFunctions.php',
    ]
);

$loader->register();

These files are automatically loaded when the register() method is called.

The setFiles() method also accepts a second parameter merge. By default, it is false. You can however set it to true when having multiple calls to setFiles() so that the file definitions are merged.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setFiles(
    [
        'app/functions/functions.php',
    ]
);

$loader->setFiles(
    [
        'app/functions/debug.php',
    ],
    true
);

$loader->register();

The above example merges the second declaration of setFiles() with the previous one.

If you need to check what files are registered in the autoloader, you can use the getFiles() getter, which returns the array of the registered files. For the example above, getFiles() returns:

[
    'app/functions/functions.php',
    'app/functions/debug.php',
]

You also have access to the loadFiles() method, which will traverse all the files registered and if they exist it will require them. This method is automatically called when you call register().

Directories

Another way to let Phalcon\Autoload\Loader know where your application files are is to register directories. When a file needs to be required by the application, the autoloader will scan the registered directories to find the referenced file so that it can require it.

The setDirectories() method accepts an array with each element being a directory in the file system containing the files that will be required by the application.

This type of registration is not recommended for performance. Additionally, the order of declared directories matters, since the autoloader tries to locate the files by searching directories sequentially. As a result, the directory that contains the most referenced files should be declared first, etc.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setDirectories(
    [
        'app/functions',
        'app/controllers',
        'app/models',
    ]
);

$loader->register();

The setDirectories() method also accepts a second parameter merge. By default, it is false. You can however set it to true when having multiple calls to setDirectories() so that the class definitions are merged.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setDirectories(
    [
        'app/functions',
    ]
);

$loader->setDirectories(
    [
        'app/controllers',
        'app/models',
    ],
    true
);

$loader->register();

The above example merges the second declaration of setDirectories() with the previous one.

If you need to check what directories are registered in the autoloader, you can use the getDirs() getter, which returns the array of the registered directories. For the example above, getDirs() returns:

[
    'app/functions',
    'app/controllers',
    'app/models',
]

File Extensions

When you use the setNamespaces() and setDirectories(), Phalcon\Autoload\Loader automatically assumes that your files will have the .php extension. You can change this behavior by using the setExtensions() method. The method accepts an array, where each element is the extension to be checked (without the .):

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setExtensions(
    [
        'php',
        'inc',
        'phb',
    ]
);

$loader->setDirectories(
    [
        'app/functions',
    ]
);

In the example above, when referencing a file Mail, the autoloader will search in app/functions for the following files:

  • Mail.php
  • Mail.inc
  • Mail.phb

Files are checked in the order that each extension is defined.

File Checking Callback

You can speed up the loader by setting a different file-checking callback method using the setFileCheckingCallback() method.

The default behavior uses is_file. However, you can also use null which will not check whether a file exists or not, before loading it, or you can use stream_resolve_include_path which is much faster than is_file but will cause problems if the target file is removed from the file system.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setFileCheckingCallback("is_file");

Default behavior

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setFileCheckingCallback("stream_resolve_include_path");

Faster than is_file(), but introduces issues if the file is removed from the filesystem.

<?php

use Phalcon\Autoload\Loader;

$loader = new Loader();

$loader->setFileCheckingCallback(null);

Do not check file existence.

Events

The [Events Manager][events] component offers hooks that can be implemented to observe or expand the functionality of the loader. The Phalcon\Autoload\Loader implements the Phalcon\Events\EventsAwareInterface, and therefore the getEventsManager() and setEventsManager() methods are available.

The following events are available:

Event Description Can stop operation?
afterCheckClass Fires at the end of the auto-load process when the class has not been found. No
beforeCheckClass Fires at the beginning of the auto-load process, before checking for the class. Yes
beforeCheckPath Fires before checking a directory for a class file. Yes
pathFound Fires when the loader locates a class file or a file in a registered directory Yes

In the following example, the EventsManager is working with the class loader, offering additional information on the operation flow:

<?php

use Phalcon\Events\Event;
use Phalcon\Events\Manager;
use Phalcon\Autoload\Loader;

$eventsManager = new Manager();
$loader        = new Loader();

$loader->setNamespaces(
    [
       'MyApp'        => 'app/library',
       'MyApp\Models' => 'app/models',
    ]
);

$eventsManager->attach(
    'loader:beforeCheckPath',
    function (
        Event $event, 
        Loader $loader
    ) {
        echo $loader->getCheckedPath();
    }
);

$loader->setEventsManager($eventsManager);

$loader->register();

In the above example, we create a new Events Manager object, attach a method to the loader:beforeCheckPath event, and then set it in our autoloader. Every time the loader loops and looks for a particular file in a specific path, the path will be printed on the screen.

The getCheckedPath() holds the path that is scanned during each iteration of the internal loop. Also, you can use the getfoundPath() method, which holds the path of the found file during the internal loop.

For events that can stop operation, all you will need to do is return false in the method that is attached to the particular event:

<?php

use Phalcon\Events\Event;
use Phalcon\Events\Manager;
use Phalcon\Autoload\Loader;

$eventsManager = new Manager();
$loader        = new Loader();

$loader->setNamespaces(
    [
       'MyApp'        => 'app/library',
       'MyApp\Models' => 'app/models',
    ]
);

$eventsManager->attach(
    'loader:beforeCheckPath',
    function (
        Event $event, 
        Loader $loader
    ) {
        if ('app/models' === $loader->getCheckedPath()) {
            return false;
        }
    }
);

$loader->setEventsManager($eventsManager);

$loader->register();

In the above example, when the autoloader starts scanning the app/models folder for the MyApp\Models namespace, it will stop the operation.

Troubleshooting

Some things to keep in mind when using the autoloader:

  • The autoloading process is case-sensitive
  • Strategies based on namespaces/prefixes are faster than the directories strategy
  • If a bytecode cache, such as APCu, is installed, it will be used to get the requested file (an implicit caching of the file is performed)

Debugging

The Phalcon\Autoload\Loader can be instantiated by passing true to the constructor, so that you can enable debug mode. In debug mode, the loader will collect data about searching and finding files that are requested. You can then use the getDebug() method to output the debug messages, to diagnose issues.

<?php

use Phalcon\Autoload\Loader;

$loader    = new Loader(true);
$directory = dataDir('some/directory/');
$loader->addDirectory($directory);

$loader->autoload('Simple');

var_dump($loader->getDebug());

// [
//     'Loading: Simple',
//     'Class: 404: Simple',
//     'Namespace: 404: Simple',
//     'Require: some/directory/Simple.php',
//     'Directories: some/directory/Simple.php',
// ];

Methods

public function __construct(bool $isDebug = false)
Constructor. If $isDebug is true, debugging information will be collected.

public function addClass(string $name, string $file): Loader
Adds a class to the internal collection for the mapping

public function addDirectory(string $directory): Loader
Adds a directory for the loaded files

public function addExtension(string $extension): Loader
Adds an extension for the loaded files

public function addFile(string $file): Loader
Adds a file to be added to the loader

public function addNamespace(
  string $name,
  mixed $directories,
  bool $prepend = false
): Loade
Adds a namespace to the loader, mapping it to different directories. The third parameter allows to prepend the namespace.

public function autoload(string $className): bool
Autoloads the registered classes

public function getCheckedPath(): string | null
Get the path the loader is checking for a path

public function getClasses(): array
Returns the class map registered in the autoloader

public function getDebug(): array
Returns debug information collected

public function getDirectories(): array
Returns the directories registered in the autoloader

public function getExtensions(): array
Returns the file extensions registered in the loader

public function getFiles(): array
Returns the files registered in the autoloader

public function getFoundPath(): string | null
Get the path when a class was found

public function getNamespaces(): array
Returns the namespaces registered in the autoloader

public function loadFiles(): void
Checks if a file exists and then adds the file by doing virtual require

public function register(bool $prepend = false): Loader
Register the autoload method

public function setClasses(
    array $classes, 
    bool $merge = false
): Loader
Register classes and their locations

public function setDirectories(
    array $directories, 
    bool $merge = false
): Loader
Register directories in which "not found" classes could be found

public function setExtensions(
    array $extensions, 
    bool $merge = false
): Loader
Sets an array of file extensions that the loader must try in each attempt to locate the file

public function setFileCheckingCallback(
    mixed $method = null
): Loader
Sets the file check callback.

public function setFiles(
    array $files, 
    bool $merge = false
): Loader
Register files that are "non-classes" and hence need a "require". This is useful for including files that only have functions

public function setNamespaces(
    array namespaces, 
    bool merge = false
): Loader
Register namespaces and their related directories

public function unregister(): Loader
Unregister the autoload method