<?php /** * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace Zend\Loader; // Grab SplAutoloader interface require_once __DIR__ . '/SplAutoloader.php'; use GlobIterator; use SplFileInfo; use Traversable; class ModuleAutoloader implements SplAutoloader { /** * @var array An array of module paths to scan */ protected $paths = array(); /** * @var array An array of modulename => path */ protected $explicitPaths = array(); /** * @var array An array of namespaceName => namespacePath */ protected $namespacedPaths = array(); /** * @var array An array of supported phar extensions (filled on constructor) */ protected $pharExtensions = array(); /** * @var array An array of module classes to their containing files */ protected $moduleClassMap = array(); /** * Constructor * * Allow configuration of the autoloader via the constructor. * * @param null|array|Traversable $options */ public function __construct($options = null) { if (extension_loaded('phar')) { $this->pharExtensions = array( 'phar', 'phar.tar', 'tar', ); // ext/zlib enabled -> phar can read gzip & zip compressed files if (extension_loaded('zlib')) { $this->pharExtensions[] = 'phar.gz'; $this->pharExtensions[] = 'phar.tar.gz'; $this->pharExtensions[] = 'tar.gz'; $this->pharExtensions[] = 'phar.zip'; $this->pharExtensions[] = 'zip'; } // ext/bzip2 enabled -> phar can read bz2 compressed files if (extension_loaded('bzip2')) { $this->pharExtensions[] = 'phar.bz2'; $this->pharExtensions[] = 'phar.tar.bz2'; $this->pharExtensions[] = 'tar.bz2'; } } if (null !== $options) { $this->setOptions($options); } } /** * Configure the autoloader * * In most cases, $options should be either an associative array or * Traversable object. * * @param array|Traversable $options * @return ModuleAutoloader */ public function setOptions($options) { $this->registerPaths($options); return $this; } /** * Retrieves the class map for all loaded modules. * * @return array */ public function getModuleClassMap() { return $this->moduleClassMap; } /** * Sets the class map used to speed up the module autoloading. * * @param array $classmap * @return ModuleAutoloader */ public function setModuleClassMap(array $classmap) { $this->moduleClassMap = $classmap; return $this; } /** * Autoload a class * * @param $class * @return mixed * False [if unable to load $class] * get_class($class) [if $class is successfully loaded] */ public function autoload($class) { // Limit scope of this autoloader if (substr($class, -7) !== '\Module') { return false; } if (isset($this->moduleClassMap[$class])) { require_once $this->moduleClassMap[$class]; return $class; } $moduleName = substr($class, 0, -7); if (isset($this->explicitPaths[$moduleName])) { $classLoaded = $this->loadModuleFromDir($this->explicitPaths[$moduleName], $class); if ($classLoaded) { return $classLoaded; } $classLoaded = $this->loadModuleFromPhar($this->explicitPaths[$moduleName], $class); if ($classLoaded) { return $classLoaded; } } if (count($this->namespacedPaths) >= 1) { foreach ($this->namespacedPaths as $namespace => $path) { if (false === strpos($moduleName, $namespace)) { continue; } $moduleNameBuffer = str_replace($namespace . "\\", "", $moduleName ); $path .= DIRECTORY_SEPARATOR . $moduleNameBuffer . DIRECTORY_SEPARATOR; $classLoaded = $this->loadModuleFromDir($path, $class); if ($classLoaded) { return $classLoaded; } $classLoaded = $this->loadModuleFromPhar($path, $class); if ($classLoaded) { return $classLoaded; } } } $moduleClassPath = str_replace('\\', DIRECTORY_SEPARATOR, $moduleName); $pharSuffixPattern = null; if ($this->pharExtensions) { $pharSuffixPattern = '(' . implode('|', array_map('preg_quote', $this->pharExtensions)) . ')'; } foreach ($this->paths as $path) { $path = $path . $moduleClassPath; if ($path == '.' || substr($path, 0, 2) == './' || substr($path, 0, 2) == '.\\') { $basePath = realpath('.'); if (false === $basePath) { $basePath = getcwd(); } $path = rtrim($basePath, '\/\\') . substr($path, 1); } $classLoaded = $this->loadModuleFromDir($path, $class); if ($classLoaded) { return $classLoaded; } // No directory with Module.php, searching for phars if ($pharSuffixPattern) { foreach (new GlobIterator($path . '.*') as $entry) { if ($entry->isDir()) { continue; } if (!preg_match('#.+\.' . $pharSuffixPattern . '$#', $entry->getPathname())) { continue; } $classLoaded = $this->loadModuleFromPhar($entry->getPathname(), $class); if ($classLoaded) { return $classLoaded; } } } } return false; } /** * loadModuleFromDir * * @param string $dirPath * @param string $class * @return mixed * False [if unable to load $class] * get_class($class) [if $class is successfully loaded] */ protected function loadModuleFromDir($dirPath, $class) { $file = new SplFileInfo($dirPath . '/Module.php'); if ($file->isReadable() && $file->isFile()) { // Found directory with Module.php in it require_once $file->getRealPath(); if (class_exists($class)) { $this->moduleClassMap[$class] = $file->getRealPath(); return $class; } } return false; } /** * loadModuleFromPhar * * @param string $pharPath * @param string $class * @return mixed * False [if unable to load $class] * get_class($class) [if $class is successfully loaded] */ protected function loadModuleFromPhar($pharPath, $class) { $pharPath = static::normalizePath($pharPath, false); $file = new SplFileInfo($pharPath); if (!$file->isReadable() || !$file->isFile()) { return false; } $fileRealPath = $file->getRealPath(); // Phase 0: Check for executable phar with Module class in stub if (strpos($fileRealPath, '.phar') !== false) { // First see if the stub makes the Module class available require_once $fileRealPath; if (class_exists($class)) { $this->moduleClassMap[$class] = $fileRealPath; return $class; } } // Phase 1: Not executable phar, no stub, or stub did not provide Module class; try Module.php directly $moduleClassFile = 'phar://' . $fileRealPath . '/Module.php'; $moduleFile = new SplFileInfo($moduleClassFile); if ($moduleFile->isReadable() && $moduleFile->isFile()) { require_once $moduleClassFile; if (class_exists($class)) { $this->moduleClassMap[$class] = $moduleClassFile; return $class; } } // Phase 2: Check for nested module directory within archive // Checks for /path/to/MyModule.tar/MyModule/Module.php // (shell-integrated zip/tar utilities wrap directories like this) $pharBaseName = $this->pharFileToModuleName($fileRealPath); $moduleClassFile = 'phar://' . $fileRealPath . '/' . $pharBaseName . '/Module.php'; $moduleFile = new SplFileInfo($moduleClassFile); if ($moduleFile->isReadable() && $moduleFile->isFile()) { require_once $moduleClassFile; if (class_exists($class)) { $this->moduleClassMap[$class] = $moduleClassFile; return $class; } } return false; } /** * Register the autoloader with spl_autoload registry * * @return void */ public function register() { spl_autoload_register(array($this, 'autoload')); } /** * Unregister the autoloader with spl_autoload registry * * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'autoload')); } /** * registerPaths * * @param array|Traversable $paths * @throws \InvalidArgumentException * @return ModuleAutoloader */ public function registerPaths($paths) { if (!is_array($paths) && !$paths instanceof Traversable) { require_once __DIR__ . '/Exception/InvalidArgumentException.php'; throw new Exception\InvalidArgumentException( 'Parameter to \\Zend\\Loader\\ModuleAutoloader\'s ' . 'registerPaths method must be an array or ' . 'implement the Traversable interface' ); } foreach ($paths as $module => $path) { if (is_string($module)) { $this->registerPath($path, $module); } else { $this->registerPath($path); } } return $this; } /** * registerPath * * @param string $path * @param bool|string $moduleName * @throws \InvalidArgumentException * @return ModuleAutoloader */ public function registerPath($path, $moduleName = false) { if (!is_string($path)) { require_once __DIR__ . '/Exception/InvalidArgumentException.php'; throw new Exception\InvalidArgumentException(sprintf( 'Invalid path provided; must be a string, received %s', gettype($path) )); } if ($moduleName) { if (in_array( substr($moduleName, -2), array('\\*', '\\%'))) { $this->namespacedPaths[substr($moduleName, 0, -2)] = static::normalizePath($path); } else { $this->explicitPaths[$moduleName] = static::normalizePath($path); } } else { $this->paths[] = static::normalizePath($path); } return $this; } /** * getPaths * * This is primarily for unit testing, but could have other uses. * * @return array */ public function getPaths() { return $this->paths; } /** * Returns the base module name from the path to a phar * * @param string $pharPath * @return string */ protected function pharFileToModuleName($pharPath) { do { $pathinfo = pathinfo($pharPath); $pharPath = $pathinfo['filename']; } while (isset($pathinfo['extension'])); return $pathinfo['filename']; } /** * Normalize a path for insertion in the stack * * @param string $path * @param bool $trailingSlash Whether trailing slash should be included * @return string */ public static function normalizePath($path, $trailingSlash = true) { $path = rtrim($path, '/'); $path = rtrim($path, '\\'); if ($trailingSlash) { $path .= DIRECTORY_SEPARATOR; } return $path; } }