Chris@0: Chris@0: * Jordi Boggiano Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Composer\Autoload; Chris@0: Chris@0: /** Chris@0: * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. Chris@0: * Chris@0: * $loader = new \Composer\Autoload\ClassLoader(); Chris@0: * Chris@0: * // register classes with namespaces Chris@0: * $loader->add('Symfony\Component', __DIR__.'/component'); Chris@0: * $loader->add('Symfony', __DIR__.'/framework'); Chris@0: * Chris@0: * // activate the autoloader Chris@0: * $loader->register(); Chris@0: * Chris@0: * // to enable searching the include path (eg. for PEAR packages) Chris@0: * $loader->setUseIncludePath(true); Chris@0: * Chris@0: * In this example, if you try to use a class in the Symfony\Component Chris@0: * namespace or one of its children (Symfony\Component\Console for instance), Chris@0: * the autoloader will first look for the class under the component/ Chris@0: * directory, and it will then fallback to the framework/ directory if not Chris@0: * found before giving up. Chris@0: * Chris@0: * This class is loosely based on the Symfony UniversalClassLoader. Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: * @author Jordi Boggiano Chris@0: * @see http://www.php-fig.org/psr/psr-0/ Chris@0: * @see http://www.php-fig.org/psr/psr-4/ Chris@0: */ Chris@0: class ClassLoader Chris@0: { Chris@0: // PSR-4 Chris@0: private $prefixLengthsPsr4 = array(); Chris@0: private $prefixDirsPsr4 = array(); Chris@0: private $fallbackDirsPsr4 = array(); Chris@0: Chris@0: // PSR-0 Chris@0: private $prefixesPsr0 = array(); Chris@0: private $fallbackDirsPsr0 = array(); Chris@0: Chris@0: private $useIncludePath = false; Chris@0: private $classMap = array(); Chris@0: private $classMapAuthoritative = false; Chris@0: private $missingClasses = array(); Chris@0: private $apcuPrefix; Chris@0: Chris@0: public function getPrefixes() Chris@0: { Chris@0: if (!empty($this->prefixesPsr0)) { Chris@0: return call_user_func_array('array_merge', $this->prefixesPsr0); Chris@0: } Chris@0: Chris@0: return array(); Chris@0: } Chris@0: Chris@0: public function getPrefixesPsr4() Chris@0: { Chris@0: return $this->prefixDirsPsr4; Chris@0: } Chris@0: Chris@0: public function getFallbackDirs() Chris@0: { Chris@0: return $this->fallbackDirsPsr0; Chris@0: } Chris@0: Chris@0: public function getFallbackDirsPsr4() Chris@0: { Chris@0: return $this->fallbackDirsPsr4; Chris@0: } Chris@0: Chris@0: public function getClassMap() Chris@0: { Chris@0: return $this->classMap; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param array $classMap Class to filename map Chris@0: */ Chris@0: public function addClassMap(array $classMap) Chris@0: { Chris@0: if ($this->classMap) { Chris@0: $this->classMap = array_merge($this->classMap, $classMap); Chris@0: } else { Chris@0: $this->classMap = $classMap; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Registers a set of PSR-0 directories for a given prefix, either Chris@0: * appending or prepending to the ones previously set for this prefix. Chris@0: * Chris@0: * @param string $prefix The prefix Chris@0: * @param array|string $paths The PSR-0 root directories Chris@0: * @param bool $prepend Whether to prepend the directories Chris@0: */ Chris@0: public function add($prefix, $paths, $prepend = false) Chris@0: { Chris@0: if (!$prefix) { Chris@0: if ($prepend) { Chris@0: $this->fallbackDirsPsr0 = array_merge( Chris@0: (array) $paths, Chris@0: $this->fallbackDirsPsr0 Chris@0: ); Chris@0: } else { Chris@0: $this->fallbackDirsPsr0 = array_merge( Chris@0: $this->fallbackDirsPsr0, Chris@0: (array) $paths Chris@0: ); Chris@0: } Chris@0: Chris@0: return; Chris@0: } Chris@0: Chris@0: $first = $prefix[0]; Chris@0: if (!isset($this->prefixesPsr0[$first][$prefix])) { Chris@0: $this->prefixesPsr0[$first][$prefix] = (array) $paths; Chris@0: Chris@0: return; Chris@0: } Chris@0: if ($prepend) { Chris@0: $this->prefixesPsr0[$first][$prefix] = array_merge( Chris@0: (array) $paths, Chris@0: $this->prefixesPsr0[$first][$prefix] Chris@0: ); Chris@0: } else { Chris@0: $this->prefixesPsr0[$first][$prefix] = array_merge( Chris@0: $this->prefixesPsr0[$first][$prefix], Chris@0: (array) $paths Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Registers a set of PSR-4 directories for a given namespace, either Chris@0: * appending or prepending to the ones previously set for this namespace. Chris@0: * Chris@0: * @param string $prefix The prefix/namespace, with trailing '\\' Chris@0: * @param array|string $paths The PSR-4 base directories Chris@0: * @param bool $prepend Whether to prepend the directories Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: */ Chris@0: public function addPsr4($prefix, $paths, $prepend = false) Chris@0: { Chris@0: if (!$prefix) { Chris@0: // Register directories for the root namespace. Chris@0: if ($prepend) { Chris@0: $this->fallbackDirsPsr4 = array_merge( Chris@0: (array) $paths, Chris@0: $this->fallbackDirsPsr4 Chris@0: ); Chris@0: } else { Chris@0: $this->fallbackDirsPsr4 = array_merge( Chris@0: $this->fallbackDirsPsr4, Chris@0: (array) $paths Chris@0: ); Chris@0: } Chris@0: } elseif (!isset($this->prefixDirsPsr4[$prefix])) { Chris@0: // Register directories for a new namespace. Chris@0: $length = strlen($prefix); Chris@0: if ('\\' !== $prefix[$length - 1]) { Chris@0: throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); Chris@0: } Chris@0: $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; Chris@0: $this->prefixDirsPsr4[$prefix] = (array) $paths; Chris@0: } elseif ($prepend) { Chris@0: // Prepend directories for an already registered namespace. Chris@0: $this->prefixDirsPsr4[$prefix] = array_merge( Chris@0: (array) $paths, Chris@0: $this->prefixDirsPsr4[$prefix] Chris@0: ); Chris@0: } else { Chris@0: // Append directories for an already registered namespace. Chris@0: $this->prefixDirsPsr4[$prefix] = array_merge( Chris@0: $this->prefixDirsPsr4[$prefix], Chris@0: (array) $paths Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Registers a set of PSR-0 directories for a given prefix, Chris@0: * replacing any others previously set for this prefix. Chris@0: * Chris@0: * @param string $prefix The prefix Chris@0: * @param array|string $paths The PSR-0 base directories Chris@0: */ Chris@0: public function set($prefix, $paths) Chris@0: { Chris@0: if (!$prefix) { Chris@0: $this->fallbackDirsPsr0 = (array) $paths; Chris@0: } else { Chris@0: $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Registers a set of PSR-4 directories for a given namespace, Chris@0: * replacing any others previously set for this namespace. Chris@0: * Chris@0: * @param string $prefix The prefix/namespace, with trailing '\\' Chris@0: * @param array|string $paths The PSR-4 base directories Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: */ Chris@0: public function setPsr4($prefix, $paths) Chris@0: { Chris@0: if (!$prefix) { Chris@0: $this->fallbackDirsPsr4 = (array) $paths; Chris@0: } else { Chris@0: $length = strlen($prefix); Chris@0: if ('\\' !== $prefix[$length - 1]) { Chris@0: throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); Chris@0: } Chris@0: $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; Chris@0: $this->prefixDirsPsr4[$prefix] = (array) $paths; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Turns on searching the include path for class files. Chris@0: * Chris@0: * @param bool $useIncludePath Chris@0: */ Chris@0: public function setUseIncludePath($useIncludePath) Chris@0: { Chris@0: $this->useIncludePath = $useIncludePath; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Can be used to check if the autoloader uses the include path to check Chris@0: * for classes. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function getUseIncludePath() Chris@0: { Chris@0: return $this->useIncludePath; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Turns off searching the prefix and fallback directories for classes Chris@0: * that have not been registered with the class map. Chris@0: * Chris@0: * @param bool $classMapAuthoritative Chris@0: */ Chris@0: public function setClassMapAuthoritative($classMapAuthoritative) Chris@0: { Chris@0: $this->classMapAuthoritative = $classMapAuthoritative; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Should class lookup fail if not found in the current class map? Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function isClassMapAuthoritative() Chris@0: { Chris@0: return $this->classMapAuthoritative; Chris@0: } Chris@0: Chris@0: /** Chris@0: * APCu prefix to use to cache found/not-found classes, if the extension is enabled. Chris@0: * Chris@0: * @param string|null $apcuPrefix Chris@0: */ Chris@0: public function setApcuPrefix($apcuPrefix) Chris@0: { Chris@0: $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; Chris@0: } Chris@0: Chris@0: /** Chris@0: * The APCu prefix in use, or null if APCu caching is not enabled. Chris@0: * Chris@0: * @return string|null Chris@0: */ Chris@0: public function getApcuPrefix() Chris@0: { Chris@0: return $this->apcuPrefix; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Registers this instance as an autoloader. Chris@0: * Chris@0: * @param bool $prepend Whether to prepend the autoloader or not Chris@0: */ Chris@0: public function register($prepend = false) Chris@0: { Chris@0: spl_autoload_register(array($this, 'loadClass'), true, $prepend); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Unregisters this instance as an autoloader. Chris@0: */ Chris@0: public function unregister() Chris@0: { Chris@0: spl_autoload_unregister(array($this, 'loadClass')); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Loads the given class or interface. Chris@0: * Chris@0: * @param string $class The name of the class Chris@0: * @return bool|null True if loaded, null otherwise Chris@0: */ Chris@0: public function loadClass($class) Chris@0: { Chris@0: if ($file = $this->findFile($class)) { Chris@0: includeFile($file); Chris@0: Chris@0: return true; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Finds the path to the file where the class is defined. Chris@0: * Chris@0: * @param string $class The name of the class Chris@0: * Chris@0: * @return string|false The path if found, false otherwise Chris@0: */ Chris@0: public function findFile($class) Chris@0: { Chris@0: // class map lookup Chris@0: if (isset($this->classMap[$class])) { Chris@0: return $this->classMap[$class]; Chris@0: } Chris@0: if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { Chris@0: return false; Chris@0: } Chris@0: if (null !== $this->apcuPrefix) { Chris@0: $file = apcu_fetch($this->apcuPrefix.$class, $hit); Chris@0: if ($hit) { Chris@0: return $file; Chris@0: } Chris@0: } Chris@0: Chris@0: $file = $this->findFileWithExtension($class, '.php'); Chris@0: Chris@0: // Search for Hack files if we are running on HHVM Chris@0: if (false === $file && defined('HHVM_VERSION')) { Chris@0: $file = $this->findFileWithExtension($class, '.hh'); Chris@0: } Chris@0: Chris@0: if (null !== $this->apcuPrefix) { Chris@0: apcu_add($this->apcuPrefix.$class, $file); Chris@0: } Chris@0: Chris@0: if (false === $file) { Chris@0: // Remember that this class does not exist. Chris@0: $this->missingClasses[$class] = true; Chris@0: } Chris@0: Chris@0: return $file; Chris@0: } Chris@0: Chris@0: private function findFileWithExtension($class, $ext) Chris@0: { Chris@0: // PSR-4 lookup Chris@0: $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; Chris@0: Chris@0: $first = $class[0]; Chris@0: if (isset($this->prefixLengthsPsr4[$first])) { Chris@0: $subPath = $class; Chris@0: while (false !== $lastPos = strrpos($subPath, '\\')) { Chris@0: $subPath = substr($subPath, 0, $lastPos); Chris@0: $search = $subPath.'\\'; Chris@0: if (isset($this->prefixDirsPsr4[$search])) { Chris@16: $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); Chris@0: foreach ($this->prefixDirsPsr4[$search] as $dir) { Chris@16: if (file_exists($file = $dir . $pathEnd)) { Chris@0: return $file; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // PSR-4 fallback dirs Chris@0: foreach ($this->fallbackDirsPsr4 as $dir) { Chris@0: if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { Chris@0: return $file; Chris@0: } Chris@0: } Chris@0: Chris@0: // PSR-0 lookup Chris@0: if (false !== $pos = strrpos($class, '\\')) { Chris@0: // namespaced class name Chris@0: $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) Chris@0: . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); Chris@0: } else { Chris@0: // PEAR-like class name Chris@0: $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; Chris@0: } Chris@0: Chris@0: if (isset($this->prefixesPsr0[$first])) { Chris@0: foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { Chris@0: if (0 === strpos($class, $prefix)) { Chris@0: foreach ($dirs as $dir) { Chris@0: if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { Chris@0: return $file; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // PSR-0 fallback dirs Chris@0: foreach ($this->fallbackDirsPsr0 as $dir) { Chris@0: if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { Chris@0: return $file; Chris@0: } Chris@0: } Chris@0: Chris@0: // PSR-0 include paths. Chris@0: if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { Chris@0: return $file; Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Scope isolated include. Chris@0: * Chris@0: * Prevents access to $this/self from included files. Chris@0: */ Chris@0: function includeFile($file) Chris@0: { Chris@0: include $file; Chris@0: }