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