5

I have been experimenting with autoloader class directory mapping techniques, and it has been a bit of a struggle. I managed to come up with a fairly straightforward solution (on the surface), but I'm totally mystified that it works at all, while other, "more obvious" solutions failed. Below are some code snippets that illustrate my confusion.

Here's the working code:

<?php
    spl_autoload_register('my_autoloader');

    function my_autoloader($class) {
        $classMap = array(
            'classes/',
            'classes/sites/',
            'classes/data/'
        );
        foreach ($classMap as $location) {
            if (!@include_once($location . $class . '.php')) { // @ SUPPRESSES include_once WARNINGS
                // CLASS DOESN'T EXIST IN THIS DIRECTORY
                continue;
            } else {
                // CLASS IS AUTO-LOADED
                break;
            }
        }
    }
?>

Here's a snippet that I felt should work, but doesn't:

<?php
    spl_autoload_register('my_autoloader');

    function my_autoloader($class) {
        $classMap = array(
            'classes/',
            'classes/sites/',
            'classes/data/'
        );
        foreach ($classMap as $location) {
            if (file_exists($location . $class . '.php')) {
                require_once ($location . $class . '.php');
            }
        }
    }
?>

The latter makes more sense to me because while these two versions work:

<?php
    spl_autoload_register('my_autoloader');

    function my_autoloader($class) {
        require_once ('classes/sites/' . $class . '.php');
    }
?>

<?php
    spl_autoload_register('my_autoloader');

    function my_autoloader($class) {                
        $location = 'classes/sites/';                
        require_once ($location . $class . '.php');
    }
?>

This one throws "No such file or directory..." (note the lack of "sites/" in the path.

<?php
    spl_autoload_register('my_autoloader');

    function my_autoloader($class) {
        require_once ('classes/' . $class . '.php');
    }
?>

The "No such file or directory..." error made me think I could simply check for a class's supporting file and, if (file_exists()) {require_once(); break;} else {continue;}

Why doesn't that work? And, why DOES the first snippet work? The supporting path/file is never explicitly included or required.

Eric Fuller
  • 159
  • 1
  • 8
  • is the parent directory of classes path in your include_paths? – Orangepill Jul 23 '13 at 16:01
  • The supporting file IS included, it's just combined into the if statement: if (!@include_once($location . $class . '.php')) – John Sparwasser Jul 23 '13 at 16:08
  • Yeah, I was kind of thinking that, john500, but was hoping for confirmation, so thanks for that. It feels like a very sloppy way to achieve what I need, though, so this just redoubles my itch to improve the solution. Orangepill, I'm not sure how to do that...I haven't worked with PHP in awhile, and when I last did, I wasn't half as good at programming as I am now...so I'm basically hacking at PHP trying to do things I already know how to do in Java and ColdFusion ;). – Eric Fuller Jul 23 '13 at 16:22
  • Sorry, lost my ability to edit the last post and it has too much fluff... Orangepill, I should've said, I'm not sure how to leverage include_path for this, but I definitely think I'm incorrectly referencing the /classes directory. It's off of the root, while the site I'm working on is in a subdirectory, and I kinda/sorta think I'm not building the path right programmatically. – Eric Fuller Jul 23 '13 at 16:30

1 Answers1

1

OK, I figured it out. My issue was indeed not setting up the path correctly; using the __DIR__ constant was the ::ahem:: path to success. Here's the working code I'm now using:

<?php
    spl_autoload_register('my_autoloader');

    function my_autoloader($class) {
        $classMap = array(
            'classes/',
            'classes/sites/',
            'classes/data/'
        );
        foreach ($classMap as $location) {
            if (file_exists(__DIR__ . '/' . $location . $class . '.php')) {
                require_once(__DIR__ . '/' . $location . $class . '.php');
                break;
            }
        }
    }
?>

It turns out __DIR__ returns the directory location of the actual file in which it is called (as opposed to, say, a file that is including it), which means this will work so long as this file remains in the root of the directory with my /classes directory. And, it exists exclusively for defining these kinds of settings...

Hope this helps someone else down the line! And, of course, if anyone can shed light on why this solution is sub-optimal, I'm all eyes...

EDIT: One optimization I will perform will be to convert the $classMap array() to an associative array (e.g,. $classMap = array('root' => 'classes/', 'sites' => 'classes/sites/'); so I can do a lookup rather than loop through all the directories I create over time. But, I suppose that's for another thread.

EDIT2: If anyone's interested, here's the solution I came up with for doing this as an associative array. Basically, I set up an associative array in $GLOBALS and use that to store a map of Classes -> Packages. Then, in the autoloader, I map Packages -> Locations and then call in the file necessary for the instantiated class.

This is the global config file:

<?php
    // INITIALIZE GLOBAL Class -> Package map
    if (!array_key_exists('packageMap',$GLOBALS)) {
        $GLOBALS['packageMap'] = array();
    }

    spl_autoload_register('my_autoloader');

    function my_autoloader($class) {
        $classMap = array(
            'root'=>'/classes/',
            'sites'=>'/classes/sites/',
            'data'=>'/classes/data/'
        );

        // RESOLVE MAPPINGS TO AN ACTUAL LOCATION
        $classPackage = $GLOBALS['packageMap'][$class];
        $location = $classMap[$classPackage];
        if (file_exists(__DIR__ . $location . $class . '.php')) {
            require_once(__DIR__ . $location . $class . '.php');
        }
    }
?>

Here's a specific site config file that uses the autoloader:

<?php   
    // LOAD GLOBAL CONFIG FILE      
    require_once('../config/init.php');

    // CREATE LOCAL REFERENCE TO GLOBAL PACKAGE MAP (MOSTLY FOR READABILITY)
    $packageMap = $GLOBALS['packageMap'];

    // ADD Class -> Package MAPPINGS
    $packageMap['DAO'] = 'data';
    $packageMap['SomeSite'] = 'sites';
    $packageMap['PDOQuery'] = 'data';

    // INSTANTIATE SOMETHING IN ONE OF THE PACKAGES
    $someSite = new SomeSite();
?>

Hopefully this is useful to others...if it raises any red flags for anyone, please chime in. Ultimately, I'd like to replace the use of $GLOBALS with apc, but it's not installed on my hosted server :/. I might consider memcached, but I've heard mixed reviews...

Eric Fuller
  • 159
  • 1
  • 8
  • You can also use [`__DIR__`](http://php.net/manual/en/language.constants.predefined.php) as of PHP 5.3 – Raekye Jul 23 '13 at 17:20