views:

1316

answers:

4

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. Edit: To clarify, I'm looking specifically for good techniques to employ for efficient autoloading, and not alternatives to autoloading.

+5  A: 

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.)

Mike Boers
pimp +1 for coolness
jim
I definitely like the underscore approach. It makes class-to-file translation much more efficient.
zombat
+2  A: 

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.

grossvogel
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
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
@grossvogel: FYI, you can use `mklink` to create symlinks in Windows: http://www.howtogeek.com/howto/windows-vista/using-symlinks-in-windows-vista/
therefromhere
@therefromhere: Thanks for the link! Not using Windows much since XP days, but this is definitely worth knowing.
grossvogel
+1  A: 

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.

jmucchiello
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
I agree zombat. Laziness can be a good thing - its also known as working efficiently. As for performance, hardware is cheap.
rick
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.
jmucchiello
Dynamic views: read my code above. What part of it has to be rewritten if a new view is added to the system? If the controller knows where to find views and knows the views' class name, it can load the file and call new. If not, make a factory object to handle it. You should not need to modify controller code when a new view is created with or without use of autoload.
jmucchiello
So how is your load_view function different from autoloading then? That's exactly what autoload is for.
zombat
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.
jmucchiello
A: 

I have a class that takes care of including, instantiating and initializing loadable modules. All the loadable modules inherit from a common base class. A method of my instantiator class takes a module name as argument, and can dynamically include that module's source file, instantiate an object of it and initialize. For example, if the module is a part of the 'model' it is passed the DB object. Then the new object is returned.

From controls.inc.php (yeah, it's a bit PHP4-y).

class controls
{
    function &getcontrol($objecttype)
    {  
     require_once(RESOURCE_DIR . "nodecontrols/$objecttype.inc.php");
     $control = &new $objecttype($this->db);
     return $control;
    }

    ...
}

When I need to instantiate that object, I do

$nodefetcher = $this->controls->getcontrol('nodefetcher');

$controls->getcontrol does the job of including the right file if it hasn't already been (require_once), instantiating it and passing it the DB object.

Things to watch out for:

  • Make sure that user input cannot poison the include path, for instance by including slashes and loading files it shouldn't. If necessary filter $objecttype so it can contain only letters and numbers, and throw an exception if it doesn't.
thomasrutter