Best Way to Autoload Classes in PHP

PHP - Autoload only needed classes

All registered autoloader functions will be called when you try to instantiate a new class or until it finally loads the class or throws an error. The way you have it now, you're registering the same autoloader function again and again for each directory, and file.

What you'd want to do is something along the lines of this.

namespace autoloader;

class autoloader
{
public function __construct()
{
spl_autoload_register([$this, 'autoload']);
}

public function autoload($classname)
{
if (! file_exists("{$classname}.class.php")) {
return;
}

include_once "{$classname}.class.php";
}
}

new autoloader();

Every autoloader function gets the class FQCN passed into it, and from there you'll have to parse it and figure out if you can load the file where that class exists. For instance, if I do the following.

use Some\Awesome\ClassFile;

$class = new ClassFile();

The autoloader we've registered will get the string Some\Awesome\ClassFile passed in as an argument, which we can then parse and see if we have a file for that class, if we don't we return out of the function and let the next registered autoloader function try and find the class.

You can read more about autoloaders in the documentation, I also wrote a blog post about it like 2 months ago that might interest you.

Most efficient way to load classes in PHP

If you're using PHP 5.x, you probably want autoloading.

PHP autoloader class to load files from two or three different path

I think your problem basically boils down to not checking if the file exists before requireing it... that will produce a fatal error if the file doesn't exist in the first folder that you attempt to include from.

I don't know your use case, but here are some suggestions:

Can you just use a single autoloader?

function my_autoload($class_name) {
if (is_file('CLASSES/' . $class_name . '.php')) {
require_once 'CLASSES/' . $class_name . '.php';
} else if (is_file('ADMIN/TPL/SOME_FOLDER2/' . $class_name . '.php')) {
require_once 'ADMIN/TPL/SOME_FOLDER2/' . $class_name . '.php';
}
}
spl_autoload_register("my_autoload");

Or if you need to declare them independently (ie. two or more autoloaders):

function classes_autoload($class_name) {
if (is_file('CLASSES/' . $class_name . '.php')) {
require_once 'CLASSES/' . $class_name . '.php';
}
}
spl_autoload_register("classes_autoload");

function admin_autoload($class_name) {
if (is_file('ADMIN/TPL/SOME_FOLDER2/' . $class_name . '.php')) {
require_once 'ADMIN/TPL/SOME_FOLDER2/' . $class_name . '.php';
}
}
spl_autoload_register("admin_autoload");

Or make your autoloader class generic:

class MyAutoLoader {
private $path;
public function __construct($path) {
$this->path = $path;
spl_autoload_register( array($this, 'load') );
}

function load( $file ) {
if (is_file($this->path . '/' . $file . '.php')) {
require_once( $this->path . '/' . $file . '.php' );
}
}
}
$autoloader_classes = new MyAutoLoader('CLASSES');
$autoloader_admin = new MyAutoLoader('ADMIN/TPL/SOME_FOLDER2');

If you don't want to manually keep the list of child folders inside ADMIN/TPL up to date you could even do something like this to autoload from any of them (this obviously assumes that all subfolders of ADMIN/TPL contains classes):

function my_autoload($class_name) {
if (is_file('CLASSES/' . $class_name . '.php')) {
require_once 'CLASSES/' . $class_name . '.php';
} else {
$matching_files = glob('ADMIN/TPL/*/' . $class_name . '.php');
if (count($matching_files) === 1) {
require_once $matching_files[0];
} else if (count($matching_files) === 0) {
trigger_error('Could not find class ' . $class_name . '!', E_USER_ERROR);
} else {
trigger_error('More than one possible match found for class ' . $class_name . '!', E_USER_ERROR);
}
}
}
spl_autoload_register("my_autoload");

PHP cannot autoload classes

Try this autoload function instead :

function __autoload($classname)
{
$filename = str_replace("\\", "/", $classname).".php";
include __DIR__."/$filename";
}
  • It replaces \ with / to match paths
  • It searches from the includes/ directory.

You might also consider adding the includes/ path to your include_path php directive :

set_include_path(get_include_path() . PATH_SEPARATOR . "{project_root}/includes");

PHP: Is AutoLoader able to load multiple class in a single php file?

You would have to have some complex function to autoload those classes from the file named Client.php. The idea is to translate your namespace\classname into a directory\filename.php

In this instance you would need to name your file A.php then when you call new Protobuf\A() it will find it. Otherwise you will have to create an overly-complex autoloader.

Let's say you do create the autoloader so it finds the A class, then you can have B on the same file, but only if you have already autoloaded A otherwise you have to make some algorythm to know that A and B are on the same page.

I would do the above pattern or the pattern adopted by apps like Magento that turn class names into directory paths by replacing underscores:

$class = new Core_Classes_MyClass_Client();

Your autoloader would replace the underscores and will load:

Core/Classes/MyClass/Client.php //or similar scheme

This to me is an easy way to do it, but I prefer using namespace and class. The above method is not in favor at the moment and from a naming standpoint, very easy to get mixed up since a lot of classes may be in the same folder or nested really deep into sub folders. You could get some really long naming for classes.

Autoload classes from different folders

You should name your classes so the underscore (_) translates to the directory separator (/). A few PHP frameworks do this, such as Zend and Kohana.

So, you name your class Model_Article and place the file in classes/model/article.php and then your autoload does...

function __autoload($class_name) 
{
$filename = str_replace('_', DIRECTORY_SEPARATOR, strtolower($class_name)).'.php';

$file = AP_SITE.$filename;

if ( ! file_exists($file))
{
return FALSE;
}
include $file;
}

Also note you can use spl_autoload_register() to make any function an autoloading function. It is also more flexible, allowing you to define multiple autoload type functions.

If there must be multiple autoload functions, spl_autoload_register() allows for this. It effectively creates a queue of autoload functions, and runs through each of them in the order they are defined. By contrast, __autoload() may only be defined once.

Edit

Note : __autoload has been DEPRECATED as of PHP 7.2.0. Relying on this feature is highly discouraged. Please refer to PHP documentation for more details. http://php.net/manual/en/function.autoload.php

PHP Autoload Classes Is Not Working With Namespaces

As already pointed out in the comments, you'll need to strip everything besides the class name, like so:

$classname = substr($classname, strrpos($classname, "\\") + 1);

Within the context of your autoloading function:

spl_autoload_register(function($classname){

$classname = substr($classname, strrpos($classname, "\\") + 1);
require_once "src/{$classname}.php";
});

Let's take this a step further by making use of the fact that an autoload function always receives the qualified namespace as opposed to, for example, the relative namespace:

<?php

namespace Acme;

$foo = new \Acme\Foo(); // Fully qualified namespace
$foo = new Acme\Foo(); // Qualified namespace
$foo = new Foo(); // Relative namespace

In all three instances, our autoload function is always given Acme\Foo as argument. With this in mind, it's fairly easy to implement an autoloader strategy that maps a namespace and any sub-namespaces to a file system path - especially if we include the top-level namespace (Acme, in this case) in the filesystem hierarchy.

For example, given these two classes within some project of ours...

<?php

namespace Acme;

class Foo {}

Foo.php

<?php

namespace Acme\Bar;

class Bar {}

Bar.php

...within this file system layout...

my-project
`-- library
`-- Acme
|-- Bar
| `-- Bar.php
`-- Foo.php

...we could implement a simple mapping between a namespaced class and its physical location like so:

<?php

namespace Acme;

const LIBRARY_DIR = __DIR__.'/lib'; // Where our classes reside

/**
* Autoload classes within the current namespace
*/
spl_autoload_register(function($qualified_class_name) {

$filepath = str_replace(

'\\', // Replace all namespace separators...
'/', // ...with their file system equivalents
LIBRARY_DIR."/{$qualified_class_name}.php"
);

if (is_file($filepath)) {

require_once $filepath;
}
});

new Foo();
new Bar\Bar();

Also note that you can register multiple autoloading functions, for example, to handle different top-level namespaces in different physical locations. In a real-word project, though, you might want to get yourself acquainted with Composer's autoloading mechanism:

  • https://getcomposer.org/doc/01-basic-usage.md#autoloading
  • https://getcomposer.org/doc/04-schema.md#autoload
  • https://getcomposer.org/doc/articles/autoloader-optimization.md

At some point, you might also want to have a look into PHP's autoloading specification:

  • https://www.php-fig.org/psr/psr-4/


Related Topics



Leave a reply



Submit