<?php
require_once('ClassManagerException.php');
class ClassManager {
private static $autoCheckClassFilesExist = false;
private static $defaultClassFileNameTemplate = '{className}.php';
private static $instance = null;
private $paths;
private $lookupPaths;
private $recursiveLookupPaths;
private $basePath;
private $rootPath;
private function __construct() {
$this->paths = array();
$this->lookupPaths = array();
$this->recursiveLookupPaths = array();
}
public function loadDefaultXml($rootPath = null) {
$this->loadXml(dirname(__FILE__).'/classpath.xml', $rootPath);
}
public static function instance() {
if(is_null(self::$instance)) {
self::$instance = new ClassManager();
spl_autoload_register(array(self::$instance, 'autoLoad'));
}
return self::$instance;
}
public function getPath($className) {
if(!array_key_exists($className, $this->paths))
throw new ClassManagerException("Unknown class: " + $className);
return $this->paths[$className];
}
public function addPath($className, $filePath) {
if(array_key_exists($className, $this->paths))
throw new ClassManagerException('Class already defined: ' . $className);
if(ClassManager::$autoCheckClassFilesExist)
$this->validateFileExists($filePath, $className);
$this->paths[$className] = $filePath;
}
public function addLookupPath($lookupPath, $classFileNameTemplate = null) {
$this->addAnyLookupPath($lookupPath, $classFileNameTemplate, $this->lookupPaths);
}
public function addRecursiveLookupPath($lookupPath, $classFileNameTemplate = null) {
$this->addAnyLookupPath($lookupPath, $classFileNameTemplate, $this->recursiveLookupPaths);
}
private function addAnyLookupPath($lookupPath, $classFileNameTemplate = null, &$lookupArr) {
$lookupPath = $this->preparePath($lookupPath);
if(!array_key_exists($lookupPath, $lookupArr)) {
if(is_null($classFileNameTemplate)) {
$lookupArr[$lookupPath] = self::$defaultClassFileNameTemplate;
} else {
if(strpos($classFileNameTemplate, '{className}')===false)
throw new ClassManagerException('File name template doesnt contain {className} tag for lookup path: ' . $lookupPath);
$lookupArr[$lookupPath] = $classFileNameTemplate;
}
} else {
throw new ClassManagerException('Lookup path can\'t be defined twice: ' . $lookupPath);
}
}
public function loadXml($fileName, $rootPath = null) {
$this->rootPath = ($rootPath !== null) ? $this->preparePath($rootPath) : '';
if(!file_exists($fileName))
throw new ClassManagerException('Configuration XML not found: ' . $fileName);
$classDoc = $this->initDocument($fileName);
$repositories = $classDoc->getElementsByTagName("repository");
foreach($repositories as $repository) {
$this->loadRepository($repository);
}
}
public function checkFilesExist() {
foreach($this->paths as $className => $filePath) {
$this->validateFileExists($filePath, $className);
}
}
public function requireAll() {
foreach($this->paths as $className => $filePath) {
$this->validateFileExists($filePath, $className);
require_once($filePath);
}
}
public function autoLoad($className) {
$path = null;
if(!array_key_exists($className, $this->paths)) {
$path = $this->findPathFromLookups($className);
if($path == null) $path = $this->findPathFromRecursiveLookups($className);
} else {
$path = $this->paths[$className];
}
if($path != null) {
require_once($path);
}
else
throw new ClassManagerException("Unknown class: " + $className);
}
private function findPathFromLookups($className) {
$path = null;
foreach($this->lookupPaths as $lookupPath => $classFileNameTemplate) {
$pathToLook = $lookupPath . $this->prepareClassFileName($className , $classFileNameTemplate);
if(file_exists($pathToLook)) {
$path = $pathToLook;
break;
}
}
return $path;
}
private function findPathFromRecursiveLookups($className) {
$path = null;
foreach($this->recursiveLookupPaths as $lookupPath => $classFileNameTemplate) {
$path = $this->findPathFromRecursiveLookup($className, $lookupPath, $classFileNameTemplate);
if($path != null) {
break;
}
}
return $path;
}
private function findPathFromRecursiveLookup($className, $lookupPath, $classFileNameTemplate) {
$path = null;
$pathToLook = $lookupPath . $this->prepareClassFileName($className , $classFileNameTemplate);
if(file_exists($pathToLook)) {
$path = $pathToLook;
}
else if(is_dir($lookupPath)) {
if($handle = opendir($lookupPath)) {
while($path === null && $file = readdir($handle)) {
if((strpos($file, '.') === false))
$path = $this->findPathFromRecursiveLookup($className, $lookupPath . $file . '/', $classFileNameTemplate);
}
closedir($handle);
}
}
return $path;
}
private function prepareClassFileName($className, $classFileNameTemplate) {
return str_replace('{className}', $className, $classFileNameTemplate);
}
private function validateFileExists($filePath, $className) {
if(!file_exists($filePath))
throw new ClassManagerException('File doesn\'t exist: ' . $filePath . ' for class: ' . $className);
}
private function loadRepository(&$repository) {
$this->loadConfig($repository);
$this->loadClasses($repository);
}
private function loadConfig(&$repository) {
if(!$repository->hasAttribute('basePath'))
throw new ClassManagerException('Base path not specified for repository');
$this->basePath = $repository->getAttribute('basePath');
if($repository->getAttribute('autoLookup') == 'true') {
$template = $repository->hasAttribute('lookupTemplate') ? $repository->getAttribute('lookupTemplate') : null;
if($repository->getAttribute('recursive') == 'true')
$this->addRecursiveLookupPath($this->rootPath.$this->basePath, $template);
else
$this->addLookupPath($this->rootPath.$this->basePath, $template);
}
}
private function readConfigTag(&$repository) {
$configs = $repository->getElementsByTagName("config");
if(count($configs) != 1)
$this->validateXmlError('config tag missing');
$config = $configs->item(0);
return $config;
}
private function readBasePath(&$config) {
if(!$config->hasAttribute('basePath'))
$this->validateXmlError('base path not specified in config');
return $config->getAttribute('basePath');
}
private function loadClasses(&$repository) {
$classes = $repository->getElementsByTagName("class");
foreach($classes as $class) {
$this->loadClass($class);
}
}
private function loadClass(&$class) {
$className = $class->getAttribute('name');
$path = $class->getAttribute('path');
$this->validateClass($class);
$this->addClass($class);
}
private function validateClass(&$class) {
if($class->getAttribute('name') == null) $this->validateXmlError('name of class must be specified');
if($class->getAttribute('path') == null) $this->validateXmlError('path must be specified for class: ' . $class->getAttribute('name'));
}
private function validateXmlError($msg) {
throw new ClassManagerException('Error in classpath XML: '.$msg);
}
private function addClass($class) {
$this->addPath($class->getAttribute('name'), $this->rootPath.$this->basePath.$class->getAttribute('path'));
}
private function initDocument($fileName) {
$classDoc = new DOMDocument();
$classDoc->validateOnParse = true;
if(!@$classDoc->load($fileName)) {
$errors = error_get_last();
throw new ClassManagerException('Classpath xml parsing failed: ' . $errors['message']);
}
return $classDoc;
}
public function generatePhpClassPathDump() {
$dumpStr = "";
$dumpStr .= '$cm = ClassManager::instance();'."\n";
foreach($this->paths as $className => $filePath)
$dumpStr .= '$cm->addPath("'.$className.'", "'.$filePath.'");'."\n";
foreach($this->lookupPaths as $lookupPath => $classFileNameTemplate)
$dumpStr .= '$cm->addLookupPath("'.$lookupPath.'", "'.$classFileNameTemplate.'");'."\n";
foreach($this->recursiveLookupPaths as $lookupPath => $classFileNameTemplate)
$dumpStr .= '$cm->addRecursiveLookupPath("'.$lookupPath.'", "'.$classFileNameTemplate.'");'."\n";
return $dumpStr;
}
private function preparePath($path) {
$path = $path === null ? "" : trim($path);
$length = strlen($path);
if($length > 0) {
$lastChar = substr($path, $length-1, 1);
$path = ($lastChar == '/' || $lastChar == '\\') ? $path : $path.'/';
} else {
$path = "/";
}
return $path;
}
}
?>