tags:

views:

962

answers:

7

Hi

I've just been looking at php's autoload() function. Seems a nice idea, but I'm not sure how it handles multiple directories. My current development basically has a library directory structure grouping classes into subdirectories by operation. I'm wondering I have to declare a include() for each directory ... which I really hope I don't have to do.

Can you advise - thanks

A: 

Unfortunately, you do have to explicitly add each directory. This can either be done programmatically in a script that recursively traverses your directories, or you can specify a list.

Probably the most efficient way is to specify a list of directories and subdirectories to search, and add these to your 'include_path' using ini_set().

Lucas Oman
+4  A: 

Hi,

You might want to take a look at the PEAR Convention for class names, which is really great for autoloading.

Basically, it states that :

The PEAR class hierarchy is also reflected in the class name, each level of the hierarchy separated with a single underscore.

Which means finding the file to include for a classe name HTML_Upload_Error is just a matter of replacing '_' by '/' ; giving you HTML/Upload/Error.php

For more explanations, and a couple of examples, you can take a look at the articles :

BTW : this convention is used by many big Frameworks / libraries ;-)
For instance, Zend Framework uses this convention -- and it's really helpful !

Pascal MARTIN
I've found this method to work very well. Another method I've used is to use a static package to folder map, which points to root-directories under which the above method is then applied.
nikc
@Pascal MARTIN: +1 for the great explanation link that explains __autoload very well: http://blog.straylightrun.net/2009/05/06/autoload-magic/
Marco Demajo
A: 

You seem confused :) Or maybe I am confused by your question.

It is completely up to you to write a function that locates and loads the class, PHP does not care where/how many levels deep it is.

And, look into SPL autoload too, it has the same base functionality, but you can write multiple autoload functions and then chain them. Can be useful, if you want to use some external libraries, that define their own autoloaders that could possibly conflict with yours.

Anti Veeranna
A: 

I assume you are talking about PHP's SPL autoload ability - where you write you're own function and then register it with the SPL.

How you do it depends on how you create your include function(s). It is possible to declare multiple include functions and then register them with PHP: how many is up to you. The SPL autoload ability simply allows you to create your own function and then tell PHP to run that function each time a class needs including.

One benefit of creating multiple is the ability to register them in their order of use, the most used directory first to the least used last. Also, if a directory is changed or deleted, then you simple change and/or delete the responsible function.

You can write one function that will go through you're entire folder structure too (though I wouldn't recommend it for ease of administration and code decoupling). There is no "technically right" way to do it :)

Robert DeBoer
A: 

many thanks guys - got it now. thanks for your help Giles

giles
+3  A: 

Here is a class I wrote a while ago for a similar purpose. That time I was still in the learning phase, so there might be stupid ideas involved; it worked nevertheless.

The basic idea is that it scans the source directory once, and creates an array mapping classes to their source files. The class is registered as an autoloader, and when invoked, it includes the required file. If not found, it tries to rebuild the array on the fly.

/* register ClassLoader as class loader */
spl_autoload_register(array(ClassLoader::getInstance(), 'loadClass'));


class ClassLoader {

    private static $SAVE_FILE = 'ClassLoader.save.php';

    /* singleton */
    private static $instance;

    /* stores a className -> filePath map */
    private $classList;
    /* tells whether working from saved file */
    private $refreshed;


    public static function getInstance() {
        if (!isset(self::$instance)) {
            self::$instance = new ClassLoader();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->initClassList();
    }

    public function loadClass($className) {
        if ( !array_key_exists($className, $this->classList) && !$this->refreshed ) {
            $this->refreshClassList();
        }
        require_once($this->classList[$className]);
    }

    private function initClassList() {
        if (file_exists(INCLUDES_DIR . self::$SAVE_FILE)) {
            require_once(INCLUDES_DIR . self::$SAVE_FILE);
            $this->refreshed = FALSE;
        } else {
            $this->refreshClassList();
        } 
    }

    private function refreshClassList() {
        $this->classList = $this->scanDirectory(INCLUDES_DIR);
        $this->refreshed = TRUE;

        $this->saveClassList();
    }

    private function saveClassList() {
        $handle = fopen(INCLUDES_DIR . self::$SAVE_FILE, 'w');
        fwrite($handle, "<?php\r\n");

        foreach($this->classList as $class => $path) {
            $line = '$this->classList' . "['" . $class . "'] = '" . $path . "';\r\n";
            fwrite($handle, $line);
        }

        fwrite($handle, '?>');
        fclose($handle);
    }

    private function scanDirectory ($directory) {
        // strip closing '/'
        if (substr($directory, -1) == '/') {
         $directory = substr($directory, 0, -1);
        }

        if (!file_exists($directory) || !is_dir($directory) || !is_readable($directory)) {
            return array();
        }

        $dirH = opendir($directory);
        $scanRes = array();

        while(($file = readdir($dirH)) !== FALSE) {

            // skip pointers
            if ( strcmp($file , '.') == 0 || strcmp($file , '..') == 0) {
                continue;
            }

            $path = $directory . '/' . $file;

            if (!is_readable($path)) {
                continue;
            }

            // recursion
            if (is_dir($path)) {
                $scanRes = array_merge($scanRes, $this->scanDirectory($path));

            } elseif (is_file($path)) {
                $className = explode('.', $file);
                if ( strcmp($className[1], 'class') == 0 && strcmp($className[2], 'php') == 0 ) {
                    $scanRes[$className[0]] = $path; 
                }
            }
        }

        return $scanRes;
    }

}
Zed
A: 

As mentioned already, SPL autoloading is functionally a structure onto which you have to graft practical implementation - directory traversal and naming conventions are part of those considerations.

Take a practical example in the form of the Zend Loader: at its basis, this is a singleton which uses a convention of correlating namespaces to directories that are registered with PHP's include path. Practical Example:

set_include_path(get_include_path(). PATH_SEPARATOR. 'App/'); //Concat the "App" directory onto the existing include paths
$loader = Zend_Loader::getInstance(); //because the autoloader is a singleton, we get a reference to it without assuming we need to first create it
$loader->registerNamespace('App_'); //Tell autoloader it can look in the app directory to find classes if it can't find them in the default Zend directory.

Obviously specific implementation concerns will vary from project to project, but it may be best, both as an exercise in understanding and for code reuse, to try your hand at programming an autoloader that can parse a specific class format (e.g. 'directory_classname') into a directory map, then load and validate the class.

Dan Ingraham