40

Like most web developers these days, I'm thoroughly enjoying the benefits of solid MVC architecture for web apps and sites. When doing MVC with PHP, autoloading obviously comes in extremely handy.

I've become a fan of spl_autoload_register over simply defining a single __autoload() function, as this is obviously more flexible if you are incorporating different base modules that each use their own autoloading. However, I've never felt great about the loading functions that I write. They involve a lot of string checking and directory scanning in order to look for possible classes to load.

For example, let's say I have an app that has a base path defined as PATH_APP, and a simple structure with directories named models, views and controllers. I often employ a naming structure whereby files are named IndexView.php and IndexController.php inside the appropriate directory, and models generally have no particular scheme by default. I might have a loader function for this structure like this that gets registered with spl_autoload_register:

public function MVCLoader($class)
{
    if (file_exists(PATH_APP.'/models/'.$class.'.php')) {
        require_once(PATH_APP.'/models/'.$class.'.php');
        return true;
    }
    else if (strpos($class,'View') !== false) {
        if (file_exists(PATH_APP.'/views/'.$class.'.php')) {
            require_once(PATH_APP.'/views/'.$class.'.php');
            return true;
        }
    }
    else if (strpos($class,'Controller') !== false) {
        if (file_exists(PATH_APP.'/controllers/'.$class.'.php')) {
            require_once(PATH_APP.'/controllers/'.$class.'.php');
            return true;
        }
    }
    return false;
}

If it's not found after that, I might have another function to scan sub-directories in the models directory. However, all the if/else-ing, string checking and directory scanning seems inefficient to me, and I'd like to improve it.

I'm very curious what file naming and autoloading strategies other developers might employ. I'm looking specifically for good techniques to employ for efficient autoloading, and not alternatives to autoloading.

4 Answers 4

29

This is what I have been using in all of my projects (lifted straight from the source of the last one):

public static function loadClass($class)
{
    $files = array(
        $class . '.php',
        str_replace('_', '/', $class) . '.php',
    );
    foreach (explode(PATH_SEPARATOR, ini_get('include_path')) as $base_path)
    {
        foreach ($files as $file)
        {
            $path = "$base_path/$file";
            if (file_exists($path) && is_readable($path))
            {
                include_once $path;
                return;
            }
        }
    }
}

If I look for SomeClass_SeperatedWith_Underscores it will look for SomeClass_SeperatedWith_Underscores.php followed by SomeClass/SeperatedWith/Underscores.php rooted at each directory in the current include path.

EDIT: I just wanted to put out there that I use this for efficiency in development, and not necessarily processing time. If you have PEAR on your path then with this you can just use the classes and don't have to include them when you need them.

I tend to keep my classes in a hierarchy of directories, with underscores breaking up namespaces... This code lets me keep the file structure nice and tidy if I want, or to inject a quick class file without nested directories if I want (for adding a single class or two to a library that it is defendant on, but not part of the project I am currently working on.)

2
  • I definitely like the underscore approach. It makes class-to-file translation much more efficient.
    – zombat
    Apr 27, 2009 at 2:40
  • 3
    Wrap array_unique() around your $files array. If there's no underscore in the class name, you're trying each file twice.
    – mpen
    Aug 31, 2013 at 18:16
13

I landed on this solution:

I created a single script that traverses my class library folder (which contains subfolders for separate modules / systems), and parses the file contents looking for class definitions. If it finds a class definition in a php file (pretty simple regex pattern), it creates a symlink:

class_name.php -> actual/source/file.php

This lets me use a single, simple autoload function that needs only the class name and the path to the main symlink folder, and doesn't have to do any path/string manipulation.

The best part is that I can rearrange my source code completely or add a new subsystem and just run the link generating script to have everything autoloaded.

5
  • That's probably the most creative solution I've ever come across. Good stuff. Just out of curiosity, how cross-platform would that approach be?
    – zombat
    Apr 27, 2009 at 2:38
  • 4
    Ever since I started working with linux, one of my major gripes with Windows has been the lack of symlinks. As far as I know, this solution only works with unixes.
    – grossvogel
    Apr 27, 2009 at 19:29
  • 5
    FYI, you can use mklink to create symlinks in Windows: howtogeek.com/howto/windows-vista/… Oct 22, 2010 at 14:37
  • @therefromhere: Thanks for the link! Not using Windows much since XP days, but this is definitely worth knowing.
    – grossvogel
    Oct 22, 2010 at 15:05
  • 1
    For what it's worth, symlinks have been working on Windows with NTFS since Windows 2000 or so.
    – Brad
    Sep 15, 2012 at 16:36
9

If you want efficiency then you shouldn't be using the autoload feature at all. The autoload feature is for being lazy. You should be providing an explicit path to your include files when you include them. If your autoload function can find these files then you could code to find them explicitly. When you are working on the view part of the code and about to load a new view class, by letting the autoload function handle it, it first assumes your class is a model class? That's inefficient. Instead your code should just be:

include_once $this->views_path . $class . '.php';

If you need multiple "view" paths, make a function that loads views:

public function load_view($class) {
    // perhaps there's a mapping here instead....
    foreach ($this->views_paths as $path) {
        $filename = $path . $class . '.php';
        if (file_exists($filename)) {
            include_once $filename;
        }
    }
    throw ....
}

In any case, at the point where the include occurs, you have the greatest/most accurate information about the class you want to load. Using that information to load the class fully is the only efficient class loading strategy. Yes, you may end up with more class variables or (heaven forbid) some global variables. But that is a better tradeoff than just being lazy and scanning parts of the file system for your class.

8
  • 10
    While you're right about direct loading being the most efficient overall, it makes code harder to maintain. What if you change the name of a class or a file? Or say I have dynamic view segments that can be loaded by the controller, and as a project goes on, more and more view classes get created. Each time I create a view class, I don't want to have to go back and modify a controller to manually include it wherever it might be used. I agree that autoloading is less efficient than direct loading, but I am looking for the most efficient autoloading.
    – zombat
    Apr 27, 2009 at 2:36
  • 3
    If you have to change the name of a class after it hits production, you aren't spending enough time designing before writing code. If efficiency is important, spending more time up front saves infinitely more time in maintenance than lazy, don't-think-about-it-at-all functions like autoload. Apr 27, 2009 at 5:00
  • 2
    So how is your load_view function different from autoloading then? That's exactly what autoload is for.
    – zombat
    Apr 27, 2009 at 18:22
  • 2
    I prefer explicit actions over implicit actions. Also there are several caveats to autoloading right on the documentation page such as "Note: Exceptions thrown in __autoload function cannot be caught in the catch block and results in a fatal error." Robust software doesn't ignore (or in this case mask and die on) exceptions. Apr 27, 2009 at 18:35
  • 2
    @Supericy Just because something should be caught during development doesn't mean it will or that it won't happen in the wild. You don't always have complete control of the install environment, so you should not rely on it not to fail on you. Feb 12, 2013 at 2:57
0

PSR-4 is a standard defined by the PHP Standards Recommendation (PSR) that provides a consistent and interoperable way to autoload classes. In PSR-4 autoloading, you define a mapping between the namespace of a class and its location on the filesystem. The Composer package manager implements this standard by default as it generates a vendor/autoload.php file. You can include this file and start using the classes that those libraries provide without any extra work:

require __DIR__ . '/vendor/autoload.php';

$log = new Monolog\Logger('name');
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING));
$log->warning('Foo');

You can even add your own code to the autoloader by adding an autoload field to composer.json.

{
    "autoload": {
        "psr-4": {"Acme\\": "src/"}
    }
}

Reference: https://getcomposer.org/doc/01-basic-usage.md#autoloading

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.