I feel like it can be simplified and/or that the method names are a bit "off". What do you think?
Code: Select all
<?php
/**
* High performance class resource locator
*
* Recursively scans multiple directories for *.class.php files. The naming
* convention dictates the filename, minus extensions, is the name of the class.
* The scan results are then saved to disk in a serialized array for instant
* access.
*
* The file system scan is only performed once, eliminating hundreds of system
* calls to file_exists() and the like for every page request.
*
* A rescan is automatically performed when a class is not found in the cached
* array. Manually clearing the cache when adding/renaming classes is not
* necessary.
*
* @todo Create a file writer class for writing the cache file.
* @todo Research caching the class list into shared memory
*/
class adfClassLocator implements adfAutoLoaderInterface {
/**
* An array of classes, with class names as the keys and their associated
* path as the values.
*
* @access protected
* @var array
*/
protected static $_classList = null;
/**
* Indicates whether a system scan has been performed during the life of the
* current instance.
*
* @access protected
* @var bool
*/
protected static $_hasScanned = false;
/**
* Public Constructor
*
* @param string $cacheFile An optional absolute path to the cache file.
* @todo Move cache file path into central configuration
*/
public function __construct($cacheFile = null) {
$this->cacheFilePath = ($cacheFile !== null) ? $cacheFile : ABSOLUTE_SYS_PATH . 'lib/cache/system/classCache';
}
/**
* Attempts to find the file system path to a class.
*
* This method will load the class cache, if it hasn't already been loaded,
* then check if the requested class exists. If found, true is returned.
*
* If the class is not found in the class cache a file system scan will be
* performed in an attempt to locate it. A scan is only performed once
* during the life of each instance. e.g. A maximum of once per page
* request and only if a class is not found in the cache.
*
* If the class is found after the rescan, true is returned, otherwise
* false.
*
* @param string $className The name of the class
* @return bool True or False
* @todo Look into optimizing this algorithm for speed
*/
public function canFind($className) {
$className = strtolower($className);
if (self::$_classList === null) {
if (!file_exists($this->cacheFilePath)) {
$this->scanPaths($this->getPaths());
}
self::$_classList = unserialize(file_get_contents($this->cacheFilePath));
}
if (isset(self::$_classList[$className])) {
return true;
} elseif (!self::$_hasScanned) {
$this->scanPaths($this->getPaths());
}
return isset(self::$_classList[$className]);
}
/**
* Get the path to a class
*
* Returns an absolute path to the given class name.
*
* <b>NOTE:</b> canLocate() should be called first to ensure the class is in
* the $_classList array. Existence of the class is not tested and will
* result in a PHP Notice being thrown if it's not in the array.
*
* @param string $className The name of the class.
* @return string The absolute path to the class.
*/
public function getPath($className) {
return self::$_classList[strtolower($className)];
}
/**
* Populates the $_classList array from an array of file paths.
*
* Each path in the provided array matching the naming convention of
* className.class.php will be added to the $_classList array.
*
* <b>NOTE: </b> This method resets $_classList to an empty array before
* processing the file paths.
*
* @param array $filePaths An array of paths to files
* @param bool $doWriteCacheFile Optionally set whether the cache file should
* be saved.
*/
protected function scanPaths($filePaths, $doWriteCacheFile = true) {
self::$_hasScanned = true;
self::$_classList = array();
foreach ($filePaths as $path) {
if (preg_match('#^..*/([^/\.]+)\.class.php$#i', $path, $matches)) {
self::$_classList[strtolower($matches[1])] = $path;
}
}
if ($doWriteCacheFile) {
$this->writeCacheFile();
}
}
/**
* Caches the class list
*
* Saves the contents of $_classList on disk as a serialized array.
*/
protected function writeCacheFile() {
$pathInfo = pathinfo($this->cacheFilePath);
if (!is_writable($pathInfo['dirname']) || !file_put_contents($this->cacheFilePath, serialize(self::$_classList))) {
trigger_error("Cannot write class cache to '" . $this->cacheFilePath . "'.", E_USER_WARNING);
}
}
/**
* Iterates through each folder path, gathering all PHP files in each path.
*
* @return array An array of files
*/
protected function getPaths() {
$scanDirs = array(
ABSOLUTE_SYS_PATH . 'lib/controllers',
ABSOLUTE_SYS_PATH . 'lib/core/classes',
ABSOLUTE_SYS_PATH . 'lib/models',
ABSOLUTE_SYS_PATH . 'lib/vendor/classes',
);
$this->files = array();
foreach ($scanDirs as $path) {
$this->_scanDirectory($path);
}
return $this->files;
}
/**
* Finds all PHP files in a specific path.
*
* @return array An array of files
*/
private function _scanDirectory($path = null) {
if (!is_dir($path) || !is_readable($path)) {
return false;
}
$dh = opendir($path);
while (false !== ($file = readdir($dh))) {
if (is_dir("$path/$file") && !preg_match('#^(?:\.svn|\.{1,2}).*$#', $file)) {
$this->_scanDirectory("$path/$file");
} elseif (preg_match('#^.*\.class\.php$#i', $file)) {
$this->files[] = "$path/$file";
}
}
closedir($dh);
return $this->files;
}
}