PHP Classes

File: autoloader.php

Recommend this page to a friend!
  Classes of Richard Keizer   Namespace-aware autoloader   autoloader.php   Download  
File: autoloader.php
Role: Class source
Content type: text/plain
Description: the class
Class: Namespace-aware autoloader
Load classes automatically supporting namespaces
Author: By
Last change: reindented at last
Date: 12 years ago
Size: 6,690 bytes
 

Contents

Class file image Download
<?php
 
 
/**------------------------------------------------------------------------------
   * File: autoloader.php
   * Description: parsing, cacheing and namespace-aware autoloader
   * Version: 1.0
   * Author: Richard Keizer
   * Email: ra dot keizer at gmail dot com
   * ------------------------------------------------------------------------------
   * COPYRIGHT (c) 2011 Richard Keizer
   *
   * The source code included in this package is free software; you can
   * redistribute it and/or modify it under the terms of the GNU General Public
   * License as published by the Free Software Foundation. This license can be
   * read at:
   *
   * http://www.opensource.org/licenses/gpl-license.php
   *
   * This program is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
   * ------------------------------------------------------------------------------
   *
   *
   * Usage (see bottom of this file):
   *
   * Autoloader::create($cachefilename, $excludelist);
   * e.g:
   * Autoloader::create('classes.cache', array('3rdparty', '\.svn', '^images$'));
   *
   *
   * Most autoloaders do nothing more than loading files with names similar to the
   * classnames. This one does more:
   * As soon as a class can't be found this autoloader will parse all .php files
   * in all folders recursively. It extracts a cache containing class/filename pairs.
   * This is done with respect to the namespace the class is in.
   * These pairs are then used to include the correct files.
   *
   *
   *
   */
 
 
 
class ClassCache {
   
    protected
$items = array();
   
    public function
__construct($filename) {
     
$this->filename = $filename;
     
$this->loadFromFile($filename);
    }
   
    public function
reset() {
     
$this->items = array();
    }
   
    public function
isEmpty() {
      return empty(
$this->items);
    }
   
    public function
addClassFilename($classname, $filename) {
     
$classname = strtolower($classname);
     
$this->items[$classname] = $filename; //detect collisions here!
   
}
   
    public function
getClassFilename($classname) {
     
$classname = strtolower($classname);
      return isset(
$this->items[$classname]) ? $this->items[$classname] : null;
    }
   
    public function
loadFromFile() {
     
$this->items = unserialize(@file_get_contents($this->filename));
    }
   
    public function
saveToFile() {
     
file_put_contents($this->filename, serialize($this->items));
    }
  }
 
 
 
 
  class
Autoloader {
    protected
$cache;
    protected
$excludelist = array();
    protected static
$instance;
   
    protected function
__construct($cachefilename, $excludelist) {
     
$this->excludelist = $excludelist;
     
$this->cache = new ClassCache($cachefilename);
      if (
$this->cache->isEmpty()) $this->rebuildCache();
     
$this->register();
    }
   
    public static function
create($cachefilename, array $excludelist = array()) {
      if (!isset(
self::$instance)) {
       
$className = __CLASS__;
       
self::$instance = new $className($cachefilename, $excludelist);
      }
      return
self::$instance;
    }
       
    protected function
register() {
     
spl_autoload_register(array($this, 'autoloadHandler'));
    }
       
    protected function
autoloadHandler($classname) {
      if (!
$this->includeClass($classname)) { //on fail...
       
$this->rebuildCache(); //rebuild cache...
       
$this->includeClass($classname); //and try again...
     
}
    }
       
    protected function
includeClass($classname) {
      if (
$filename = $this->cache->getClassFilename($classname)) {
        include
$filename;
        return
true;
      }
      return
false;
    }
   
    protected function
shouldFollow($fsnode) {
      if (!
is_dir($fsnode) || in_array(basename($fsnode), array('.', '..'))) return false;
      foreach(
$this->excludelist as $item) if (preg_match("/{$item}/i", basename($fsnode))) return false;
      return
true;
    }
       
    protected function
rebuildCache() {
     
$this->cache->reset();
     
     
$stack = array('./'); //push current folder
     
while (!empty($stack)) {
       
       
$folder = array_shift($stack);
       
        if (
$h = opendir($folder)) {
          while((
$child = readdir($h)) !== FALSE) {
           
            if (
$this->shouldFollow($folder.$child)) {
             
array_push($stack, $folder.$child.'/');
            } elseif (
is_file($folder.$child) && preg_match("/\.php$/i", $folder.$child)) {
             
$tokens = token_get_all(php_strip_whitespace($folder.$child));
             
$namespace = ''; //default namespace
             
$status = null;
              for(
$i = 0; $i < count($tokens); $i++) {
                switch (
$tokens[$i][0]) {
                  case
T_NAMESPACE: {
                   
$namespace = '';
                   
$status = T_NAMESPACE;
                    break;
                  }
                  case
T_CLASS: {
                   
$classname = '';
                   
$status = T_CLASS;
                    break;
                  }
                  default: {
                    switch (
$status) {
                      case
T_NAMESPACE: {
                        if (isset(
$tokens[$i][0]) && $tokens[$i][0] == T_STRING) {
                         
$namespace .= $tokens[$i][1].'\\';
                        } elseif (!
is_array($tokens[$i]) && in_array($tokens[$i], array(';', '{'))) {
                         
$status = null;
                        }
                        break;
                      }
                       
                      case
T_CLASS: {
                        if (
is_array($tokens[$i]) && $tokens[$i][0] == T_STRING) {
                         
$this->cache->addClassFilename($namespace.$tokens[$i][1], $folder.$child);
                         
$status = null;
                        }
                        break;
                      }
                    }
//switch status
                 
} //case token default
               
} //switch token
             
} //end for
           
}
          }
        }
      }
     
$this->cache->saveToFile();
    }
  }
 
 
 
Autoloader::create('classes.cache', array('3rdparty', '\.svn', '^images$'));