| 
<?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$'));
 
 
 
 
 |