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.
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:
that has a namespace of:
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:
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:
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.
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¶
Constructor. If$isDebug
is true
, debugging information will be collected. Adds a class to the internal collection for the mapping Adds a directory for the loaded files Adds an extension for the loaded files Adds a file to be added to the loader Adds a namespace to the loader, mapping it to different directories. The third parameter allows to prepend the namespace. Autoloads the registered classes Get the path the loader is checking for a path Returns the class map registered in the autoloader Returns debug information collected Returns the directories registered in the autoloader Returns the file extensions registered in the loader Returns the files registered in the autoloader Get the path when a class was found Returns the namespaces registered in the autoloader Checks if a file exists and then adds the file by doing virtual require Register the autoload method Register classes and their locations Register directories in which "not found" classes could be found Sets an array of file extensions that the loader must try in each attempt to locate the file Sets the file check callback. Register files that are "non-classes" and hence need a "require". This is useful for including files that only have functions Register namespaces and their related directories Unregister the autoload method