3

I've been working on a method of including files recursively with the __autoload() function in php. This way, You could throw your class anywhere in the "classes" folder, and have them organized by sub directory, But the __autoload function would still be able to find them. This is what I've gotten so far, And was wondering if anyone might be able to help me simplify it so that it isn't so lengthy. It is completely functional currently, And works like a charm. I'm just trying to make it shorter.

<?php
function readRecursive($path){
    if(!is_dir($path)){
        return false;
    }
    $dir = glob($path ."/*");
    $retArr = array();
    foreach($dir as $f){
        if(is_dir($f)){
            $m = readRecursive($f);
            foreach($m as $n){
                $retArr[] = $n;
            }
        }else{
            $retArr[] = $f;
        }
    }
    return $retArr;
}

function endsWith($haystack, $needle){
    return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
}

/* Set up AutoLoading for object classes */
function __autoload($class_name){
    $classes = readRecursive("classes");
    foreach($classes as $class){
        if(endsWith(strtolower($class), strtolower($class_name.".class.php"))){
            include_once ($class);
        }
    }
}

?>

Nathan F.
  • 3,250
  • 3
  • 35
  • 69
  • For one idea, see http://stackoverflow.com/questions/3226519/use-php-scandirdir-and-get-only-images – L0j1k May 14 '14 at 06:23
  • Your function will load each file which ends with `$name.".class.php"` - no matter how many such files would exist in whole directory tree. If this is intended, then it's very odd and unclear (to those who'll use) behavior – Alma Do May 14 '14 at 06:28
  • Why do not use composer autoloader? Classmap for begin. – sectus May 14 '14 at 06:31
  • @AlmaDo No, It would include only the specific file being required by the __autoload function. – Nathan F. May 14 '14 at 06:38
  • @sectus I'm not quite sure what you were trying to say. – Nathan F. May 14 '14 at 06:39
  • @thefiscster510 what if you have `"/dir/subdir/foo.class.php"` and `/dir/foo.class.php` and you'll pass `"foo"` to your function? – Alma Do May 14 '14 at 06:39
  • Even if this works, it is very slow. You parse all directories each time you include a file! At least cache the $classes array, and break the loop when you find the correct one. – gontrollez May 14 '14 at 06:40
  • This question might be better suited for http://codereview.stackexchange.com/ – Syjin May 14 '14 at 06:41
  • 1
    @AlmaDo It will include both foo classes. However, Why would any developer have more than one class with the same name?.. And if that truly ever became an issue, there could always be a break in the for loop to only include the first one the function comes accross. – Nathan F. May 14 '14 at 06:41
  • @thefiscster510 I will. Because there are many cases when classes should have same name, but belong to different naming spaces (yes, `namespace` and their proper naming + proper class naming is a key to making your function "shorter") – Alma Do May 14 '14 at 06:42
  • @Syjin I wasn't even aware that that site existed, That will definitely come in handy. And I will probably move this question over there. – Nathan F. May 14 '14 at 06:42

2 Answers2

4

Here's my attempt at this autoload for you. I've slightly modified Emil Condrea's Answer.

To start, I'll show you the file structure of my classes:

Classes structure

As you can see above, the classes are set into seperate files and such to show.

Now taking Emil's answer and slightly changing it: (Provided the file name is something like "Class.php" as seen above in the file structure)

function getClasses($path) {
    $files = array();
    $dir_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
    foreach ($dir_iterator as $item) {
        $pathname = $item->getPathName();
        $filename = $item->getFileName();
        if ($item->isDir()) {
            getClasses($item);
        } else {
            $files[$filename] = $pathname;
        }
    }
    return $files;
}

Will warrant a return array of files like the following [FILE_NAME] => [PATH_NAME]:

Array
(
    [Error.php] => /home2/DERP/public_html/something.com/watch/model/Site/Error.php
    [Form.php] => /home2/DERP/public_html/something.com/watch/model/Site/Form.php
    [Site.php] => /home2/DERP/public_html/something.com/watch/model/Site/Site.php
    [Db.php] => /home2/DERP/public_html/something.com/watch/model/Database/Db.php
    [Db_pdo.php] => /home2/DERP/public_html/something.com/watch/model/Database/Db_pdo.php
    [Session.php] => /home2/DERP/public_html/something.com/watch/model/Security/Session.php
    [Auth.php] => /home2/DERP/public_html/something.com/watch/model/Security/Auth.php
    [Input.php] => /home2/DERP/public_html/something.com/watch/model/Security/Input.php
    [Postcode.php] => /home2/DERP/public_html/something.com/watch/model/Postcode.php
    [Rep.php] => /home2/DERP/public_html/something.com/watch/model/User/Rep.php
    [User.php] => /home2/DERP/public_html/something.com/watch/model/User/User.php
    [Notifications.php] => /home2/DERP/public_html/something.com/watch/model/User/Notifications.php
    [Log.php] => /home2/DERP/public_html/something.com/watch/model/Log/Log.php
    [Hook.php] => /home2/DERP/public_html/something.com/watch/model/Hook.php
)

Now that would've been called by something like the following:

getClasses(realpath(dirname(__FILE__)) . '/model')

Allowing us to run an __autoload() like the following:

$model_classes = getClasses(realpath(dirname(__FILE__)) . '/model');

function __autoload($class_name) {
    global $model_classes;
    $filename = ucfirst($class_name) . '.php';
    $model = $filename;

    if (!isset($model_classes[$model])) {
        // dead
        return false;
    } else {
        // include first file (model)
        include($model_classes[$model]);
    }
}

Now

Obviously you shouldn't use global but to me it seemed a far better alternative to running the getClasses() function every single time within the __autoload() function.

If someone else has anything to add, feel free! I just tried my own little method on this and it works without fault!

Note:

I was using file_exists() before and the above method is, in my opinion; a lot faster.

UPDATE

I had a brain wave just the other night and thought; "Why not scan the application root and fetch all the php files then run a function to check if said file actually contains a class to make this as universal as possible.."

So I did a little research and found this nifty little function from php: token_get_all()

Now after a little digging through SO I found this answer: Determine class in file...

and after some modification, the getClasses() function now looks like this:

function getClasses($path) {
            $files = array();
            $dir_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
            foreach ($dir_iterator as $item) {
                $pathname = $item->getPathName();
                $filename = $item->getFileName();
                if ($item->isDir()) {
                    get_classes($item);
                } else {
                    if (substr($filename, -4) === '.php') {
                        if (get_php_classes(file_get_contents($pathname))) {
                            $files[$filename] = $pathname;
                        }
                    }
                }
            }
            return $files;
        }

With the addition of this new function from the above question:

function get_php_classes($php_code) {
            $tokens = token_get_all($php_code);
            $class_token = false;
            foreach ($tokens as $token) {
                if (is_array($token)) {
                    if ($token[0] == T_CLASS) {
                        $class_token = true;
                    } else if ($class_token && $token[0] == T_STRING) {
                        $classes[] = $token[1];
//                        $class_token = false;
                    }
                }
            }
            return $class_token;
        }

Now that allows you to simply run a $classes = getClasses(ROOTPATH) and itterate through them.

DOWNFALL: each of the classes will have to have unique class names and/or file names. Unless somebody could lend their hand at a modification to allow.

Community
  • 1
  • 1
Darren
  • 13,050
  • 4
  • 41
  • 79
0

You can use RecursiveDirectoryIterator to iterate through the directory recursively. This might simplify your function.

function getRecursive($path){
    $files = array();
    $dir_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),RecursiveIteratorIterator::SELF_FIRST);
    foreach ($dir_iterator as $item) {
            $subPath = $dir_iterator->getSubPathName();
            if($item->isDir())
                    $files[$subPath] = array();
            else
                    $files[$subPath][] = $subPath;
    }
    return $files;
}
Emil Condrea
  • 9,705
  • 7
  • 33
  • 52