Browse Source

Changed to PSR-4 class loader.

ADmad 12 years ago
parent
commit
4a3a6a68ce
4 changed files with 90 additions and 71 deletions
  1. 77 64
      Cake/Core/ClassLoader.php
  2. 3 1
      Cake/Core/Plugin.php
  3. 1 1
      Test/TestCase/Core/PluginTest.php
  4. 9 5
      Test/init.php

+ 77 - 64
Cake/Core/ClassLoader.php

@@ -22,100 +22,113 @@ namespace Cake\Core;
 class ClassLoader {
 
 /**
- * File extension
+ * An associative array where the key is a namespace prefix and the value
+ * is an array of base directories for classes in that namespace.
  *
- * @var string
+ * @var array
  */
-	protected $_fileExtension;
+	protected $_prefixes = [];
 
 /**
- * The path a given namespace maps to.
+ * Register loader with SPL autoloader stack.
  *
- * @var string
+ * @return void
  */
-	protected $_path;
+	public function register() {
+		spl_autoload_register([$this, 'loadClass']);
+	}
 
 /**
- * Registered namespace
+ * Adds a base directory for a namespace prefix.
  *
- * @var string
+ * @param string $prefix The namespace prefix.
+ * @param string $baseDir A base directory for class files in the
+ * namespace.
+ * @param bool $prepend If true, prepend the base directory to the stack
+ * instead of appending it; this causes it to be searched first rather
+ * than last.
+ * @return void
  */
-	protected $_namespace;
+	public function addNamespace($prefix, $baseDir, $prepend = false) {
+		$prefix = trim($prefix, '\\') . '\\';
 
-/**
- * Store the namespace length for performance
- *
- * @var integer
- */
-	protected $_namespaceLength;
+		$baseDir = rtrim($baseDir, '/') . DIRECTORY_SEPARATOR;
+		$baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
 
-/**
- * Constructor
- *
- * @param string $ns The _namespace to use.
- */
-	public function __construct($ns = null, $path = null, $fileExtension = '.php') {
-		$this->_namespace = rtrim($ns, '\\') . '\\';
-		$this->_namespaceLength = strlen($this->_namespace);
-		$this->_path = $path;
-		$this->_fileExtension = '.php';
-	}
+		if (!isset($this->_prefixes[$prefix])) {
+			$this->_prefixes[$prefix] = [];
+		}
 
-/**
- * Gets the base include path for all class files in the _namespace of this class loader.
- *
- * @return string
- */
-	public function getIncludePath() {
-		return $this->_includePath;
+		if ($prepend) {
+			array_unshift($this->_prefixes[$prefix], $baseDir);
+		} else {
+			array_push($this->_prefixes[$prefix], $baseDir);
+		}
 	}
 
 /**
- * Gets the file extension of class files in the _namespace of this class loader.
+ * Loads the class file for a given class name.
  *
- * @return string
+ * @param string $class The fully-qualified class name.
+ * @return mixed The mapped file name on success, or boolean false on
+ * failure.
  */
-	public function getFileExtension() {
-		return $this->_fileExtension;
-	}
+	public function loadClass($class) {
+		$prefix = $class;
 
-/**
- * Installs this class loader on the SPL autoload stack.
- *
- * @return void
- */
-	public function register() {
-		spl_autoload_register([$this, 'loadClass']);
+		while (($pos = strrpos($prefix, '\\')) !== false) {
+			$prefix = substr($class, 0, $pos + 1);
+			$relativeClass = substr($class, $pos + 1);
+
+			$mappedFile = $this->_loadMappedFile($prefix, $relativeClass);
+			if ($mappedFile) {
+				return $mappedFile;
+			}
+
+			$prefix = rtrim($prefix, '\\');
+		}
+
+		return false;
 	}
 
 /**
- * Uninstalls this class loader from the SPL autoloader stack.
+ * Load the mapped file for a namespace prefix and relative class.
  *
- * @return void
+ * @param string $prefix The namespace prefix.
+ * @param string $relativeClass The relative class name.
+ * @return mixed Boolean false if no mapped file can be loaded, or the
+ * name of the mapped file that was loaded.
  */
-	public function unregister() {
-		spl_autoload_unregister([$this, 'loadClass']);
+	protected function _loadMappedFile($prefix, $relativeClass) {
+		if (!isset($this->_prefixes[$prefix])) {
+			return false;
+		}
+
+		foreach ($this->_prefixes[$prefix] as $baseDir) {
+			$file = $baseDir .
+				str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php';
+			$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
+
+			if ($this->_requireFile($file)) {
+				return $file;
+			}
+		}
+
+		return false;
 	}
 
 /**
- * Loads the given class or interface.
+ * If a file exists, require it from the file system.
  *
- * @param string $className The name of the class to load.
- * @return boolean
+ * @param string $file The file to require.
+ * @return bool True if the file exists, false if not.
  */
-	public function loadClass($className) {
-		if (strpos($className, 'Cake\\Test\\') === 0) {
-			$className = substr($className, 5);
-		} elseif (strpos($className, 'App\\Test\\') === 0) {
-			$className = substr($className, 4);
-		} elseif (substr($className, 0, $this->_namespaceLength) !== $this->_namespace) {
-			return false;
-		}
-		$path = $this->_path . DS . str_replace('\\', DS, $className) . $this->_fileExtension;
-		if (!file_exists($path)) {
-			return false;
+	protected function _requireFile($file) {
+		if (file_exists($file)) {
+			require $file;
+			return true;
 		}
-		return require $path;
+		return false;
 	}
 
 }

+ 3 - 1
Cake/Core/Plugin.php

@@ -142,7 +142,9 @@ class Plugin {
 		}
 
 		if ($config['autoload'] === true) {
-			(new ClassLoader($config['namespace'], dirname($config['path'])))->register();
+			$loader = new ClassLoader;
+			$loader->register();
+			$loader->addNamespace($config['namespace'], $config['path']);
 		}
 	}
 

+ 1 - 1
Test/TestCase/Core/PluginTest.php

@@ -104,7 +104,7 @@ class PluginTest extends TestCase {
 		$this->assertFalse(class_exists('Company\TestPluginThree\Utility\Hello'));
 		Plugin::load('TestPluginThree', [
 			'namespace' => 'Company\TestPluginThree',
-			'path' => TEST_APP . 'Plugin/TestPluginThree',
+			'path' => TEST_APP . 'Plugin/Company/TestPluginThree',
 			'autoload' => true,
 		]);
 		$this->assertTrue(

+ 9 - 5
Test/init.php

@@ -51,11 +51,15 @@ define('TEST_APP', ROOT . '/Test/TestApp/');
 
 require CORE_PATH . 'Cake/Core/ClassLoader.php';
 
-(new Cake\Core\ClassLoader('Cake', ROOT))->register();
-(new Cake\Core\ClassLoader('TestApp', ROOT . '/Test'))->register();
-(new Cake\Core\ClassLoader('TestPlugin', CORE_TESTS . 'TestApp/Plugin/'))->register();
-(new Cake\Core\ClassLoader('TestPluginTwo', CORE_TESTS . 'TestApp/Plugin/'))->register();
-(new Cake\Core\ClassLoader('PluginJs', CORE_TESTS . 'TestApp/Plugin/'))->register();
+$loader = new Cake\Core\ClassLoader;
+$loader->register();
+
+$loader->addNamespace('Cake', ROOT . '/Cake');
+$loader->addNamespace('Cake\Test', ROOT . '/Test');
+$loader->addNamespace('TestApp', ROOT . '/Test/TestApp');
+$loader->addNamespace('TestPlugin', CORE_TESTS . 'TestApp/Plugin/TestPlugin');
+$loader->addNamespace('TestPluginTwo', CORE_TESTS . 'TestApp/Plugin/TestPluginTwo');
+$loader->addNamespace('PluginJs', CORE_TESTS . 'TestApp/Plugin/PluginJs');
 
 require CORE_PATH . 'Cake/bootstrap.php';