├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── AutoloaderFactory.php ├── ClassMapAutoloader.php ├── Exception ├── BadMethodCallException.php ├── DomainException.php ├── ExceptionInterface.php ├── InvalidArgumentException.php ├── InvalidPathException.php ├── MissingResourceNamespaceException.php ├── PluginLoaderException.php ├── RuntimeException.php └── SecurityException.php ├── ModuleAutoloader.php ├── PluginClassLoader.php ├── PluginClassLocator.php ├── ShortNameLocator.php ├── SplAutoloader.php └── StandardAutoloader.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 2.6.2 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 2.6.1 - 2019-09-04 28 | 29 | ### Added 30 | 31 | - [#18](https://github.com/zendframework/zend-loader/pull/18) adds support for PHP 7.3. 32 | 33 | ### Changed 34 | 35 | - Nothing. 36 | 37 | ### Deprecated 38 | 39 | - Nothing. 40 | 41 | ### Removed 42 | 43 | - Nothing. 44 | 45 | ### Fixed 46 | 47 | - Nothing. 48 | 49 | ## 2.6.0 - 2018-04-30 50 | 51 | ### Added 52 | 53 | - [#16](https://github.com/zendframework/zend-loader/pull/16) adds support for PHP 7.1 and 7.2. 54 | 55 | - [#8](https://github.com/zendframework/zend-loader/pull/8) adds documentation at https://docs.zendframework.com/zend-loader/ 56 | 57 | ### Changed 58 | 59 | - Nothing. 60 | 61 | ### Deprecated 62 | 63 | - Nothing. 64 | 65 | ### Removed 66 | 67 | - [#16](https://github.com/zendframework/zend-loader/pull/16) removes support for PHP 5.5. 68 | 69 | - [#16](https://github.com/zendframework/zend-loader/pull/16) removes support for HHVM. 70 | 71 | ### Fixed 72 | 73 | - Nothing. 74 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2019, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zend-loader 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [laminas/laminas-loader](https://github.com/laminas/laminas-loader). 6 | 7 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-loader.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-loader) 8 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-loader/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-loader?branch=master) 9 | 10 | zend-loader provides different strategies for autoloading PHP classes. 11 | 12 | - File issues at https://github.com/zendframework/zend-loader/issues 13 | - Documentation is at https://docs.zendframework.com/zend-loader/ 14 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zendframework/zend-loader", 3 | "description": "Autoloading and plugin loading strategies", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zf", 7 | "zendframework", 8 | "loader" 9 | ], 10 | "support": { 11 | "docs": "https://docs.zendframework.com/zend-loader/", 12 | "issues": "https://github.com/zendframework/zend-loader/issues", 13 | "source": "https://github.com/zendframework/zend-loader", 14 | "rss": "https://github.com/zendframework/zend-loader/releases.atom", 15 | "chat": "https://zendframework-slack.herokuapp.com", 16 | "forum": "https://discourse.zendframework.com/c/questions/components" 17 | }, 18 | "require": { 19 | "php": "^5.6 || ^7.0" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", 23 | "zendframework/zend-coding-standard": "~1.0.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Zend\\Loader\\": "src/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "ZendTest\\Loader\\": "test/" 33 | } 34 | }, 35 | "config": { 36 | "sort-packages": true 37 | }, 38 | "extra": { 39 | "branch-alias": { 40 | "dev-master": "2.6.x-dev", 41 | "dev-develop": "2.7.x-dev" 42 | } 43 | }, 44 | "scripts": { 45 | "check": [ 46 | "@cs-check", 47 | "@test" 48 | ], 49 | "cs-check": "phpcs", 50 | "cs-fix": "phpcbf", 51 | "test": "phpunit --colors=always", 52 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/AutoloaderFactory.php: -------------------------------------------------------------------------------- 1 | 36 | * array( 37 | * '' => $autoloaderOptions, 38 | * ) 39 | * 40 | * 41 | * The factory will then loop through and instantiate each autoloader with 42 | * the specified options, and register each with the spl_autoloader. 43 | * 44 | * You may retrieve the concrete autoloader instances later using 45 | * {@link getRegisteredAutoloaders()}. 46 | * 47 | * Note that the class names must be resolvable on the include_path or via 48 | * the Zend library, using PSR-0 rules (unless the class has already been 49 | * loaded). 50 | * 51 | * @param array|Traversable $options (optional) options to use. Defaults to Zend\Loader\StandardAutoloader 52 | * @return void 53 | * @throws Exception\InvalidArgumentException for invalid options 54 | * @throws Exception\InvalidArgumentException for unloadable autoloader classes 55 | * @throws Exception\DomainException for autoloader classes not implementing SplAutoloader 56 | */ 57 | public static function factory($options = null) 58 | { 59 | if (null === $options) { 60 | if (! isset(static::$loaders[static::STANDARD_AUTOLOADER])) { 61 | $autoloader = static::getStandardAutoloader(); 62 | $autoloader->register(); 63 | static::$loaders[static::STANDARD_AUTOLOADER] = $autoloader; 64 | } 65 | 66 | // Return so we don't hit the next check's exception (we're done here anyway) 67 | return; 68 | } 69 | 70 | if (! is_array($options) && ! ($options instanceof Traversable)) { 71 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 72 | throw new Exception\InvalidArgumentException( 73 | 'Options provided must be an array or Traversable' 74 | ); 75 | } 76 | 77 | foreach ($options as $class => $autoloaderOptions) { 78 | if (! isset(static::$loaders[$class])) { 79 | $autoloader = static::getStandardAutoloader(); 80 | if (! class_exists($class) && ! $autoloader->autoload($class)) { 81 | require_once 'Exception/InvalidArgumentException.php'; 82 | throw new Exception\InvalidArgumentException( 83 | sprintf('Autoloader class "%s" not loaded', $class) 84 | ); 85 | } 86 | 87 | if (! is_subclass_of($class, 'Zend\Loader\SplAutoloader')) { 88 | require_once 'Exception/InvalidArgumentException.php'; 89 | throw new Exception\InvalidArgumentException( 90 | sprintf('Autoloader class %s must implement Zend\\Loader\\SplAutoloader', $class) 91 | ); 92 | } 93 | 94 | if ($class === static::STANDARD_AUTOLOADER) { 95 | $autoloader->setOptions($autoloaderOptions); 96 | } else { 97 | $autoloader = new $class($autoloaderOptions); 98 | } 99 | $autoloader->register(); 100 | static::$loaders[$class] = $autoloader; 101 | } else { 102 | static::$loaders[$class]->setOptions($autoloaderOptions); 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Get a list of all autoloaders registered with the factory 109 | * 110 | * Returns an array of autoloader instances. 111 | * 112 | * @return array 113 | */ 114 | public static function getRegisteredAutoloaders() 115 | { 116 | return static::$loaders; 117 | } 118 | 119 | /** 120 | * Retrieves an autoloader by class name 121 | * 122 | * @param string $class 123 | * @return SplAutoloader 124 | * @throws Exception\InvalidArgumentException for non-registered class 125 | */ 126 | public static function getRegisteredAutoloader($class) 127 | { 128 | if (! isset(static::$loaders[$class])) { 129 | require_once 'Exception/InvalidArgumentException.php'; 130 | throw new Exception\InvalidArgumentException(sprintf('Autoloader class "%s" not loaded', $class)); 131 | } 132 | return static::$loaders[$class]; 133 | } 134 | 135 | /** 136 | * Unregisters all autoloaders that have been registered via the factory. 137 | * This will NOT unregister autoloaders registered outside of the fctory. 138 | * 139 | * @return void 140 | */ 141 | public static function unregisterAutoloaders() 142 | { 143 | foreach (static::getRegisteredAutoloaders() as $class => $autoloader) { 144 | spl_autoload_unregister([$autoloader, 'autoload']); 145 | unset(static::$loaders[$class]); 146 | } 147 | } 148 | 149 | /** 150 | * Unregister a single autoloader by class name 151 | * 152 | * @param string $autoloaderClass 153 | * @return bool 154 | */ 155 | public static function unregisterAutoloader($autoloaderClass) 156 | { 157 | if (! isset(static::$loaders[$autoloaderClass])) { 158 | return false; 159 | } 160 | 161 | $autoloader = static::$loaders[$autoloaderClass]; 162 | spl_autoload_unregister([$autoloader, 'autoload']); 163 | unset(static::$loaders[$autoloaderClass]); 164 | return true; 165 | } 166 | 167 | /** 168 | * Get an instance of the standard autoloader 169 | * 170 | * Used to attempt to resolve autoloader classes, using the 171 | * StandardAutoloader. The instance is marked as a fallback autoloader, to 172 | * allow resolving autoloaders not under the "Zend" namespace. 173 | * 174 | * @return SplAutoloader 175 | */ 176 | protected static function getStandardAutoloader() 177 | { 178 | if (null !== static::$standardAutoloader) { 179 | return static::$standardAutoloader; 180 | } 181 | 182 | 183 | if (! class_exists(static::STANDARD_AUTOLOADER)) { 184 | // Extract the filename from the classname 185 | $stdAutoloader = substr(strrchr(static::STANDARD_AUTOLOADER, '\\'), 1); 186 | require_once __DIR__ . "/$stdAutoloader.php"; 187 | } 188 | $loader = new StandardAutoloader(); 189 | static::$standardAutoloader = $loader; 190 | return static::$standardAutoloader; 191 | } 192 | 193 | /** 194 | * Checks if the object has this class as one of its parents 195 | * 196 | * @see https://bugs.php.net/bug.php?id=53727 197 | * @see https://github.com/zendframework/zf2/pull/1807 198 | * 199 | * @deprecated since zf 2.3 requires PHP >= 5.3.23 200 | * 201 | * @param string $className 202 | * @param string $type 203 | * @return bool 204 | */ 205 | protected static function isSubclassOf($className, $type) 206 | { 207 | return is_subclass_of($className, $type); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/ClassMapAutoloader.php: -------------------------------------------------------------------------------- 1 | setOptions($options); 45 | } 46 | } 47 | 48 | /** 49 | * Configure the autoloader 50 | * 51 | * Proxies to {@link registerAutoloadMaps()}. 52 | * 53 | * @param array|Traversable $options 54 | * @return ClassMapAutoloader 55 | */ 56 | public function setOptions($options) 57 | { 58 | $this->registerAutoloadMaps($options); 59 | return $this; 60 | } 61 | 62 | /** 63 | * Register an autoload map 64 | * 65 | * An autoload map may be either an associative array, or a file returning 66 | * an associative array. 67 | * 68 | * An autoload map should be an associative array containing 69 | * classname/file pairs. 70 | * 71 | * @param string|array $map 72 | * @throws Exception\InvalidArgumentException 73 | * @return ClassMapAutoloader 74 | */ 75 | public function registerAutoloadMap($map) 76 | { 77 | if (is_string($map)) { 78 | $location = $map; 79 | if ($this === ($map = $this->loadMapFromFile($location))) { 80 | return $this; 81 | } 82 | } 83 | 84 | if (! is_array($map)) { 85 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 86 | throw new Exception\InvalidArgumentException(sprintf( 87 | 'Map file provided does not return a map. Map file: "%s"', 88 | (isset($location) && is_string($location) ? $location : 'unexpected type: ' . gettype($map)) 89 | )); 90 | } 91 | 92 | $this->map = $map + $this->map; 93 | 94 | if (isset($location)) { 95 | $this->mapsLoaded[] = $location; 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Register many autoload maps at once 103 | * 104 | * @param array $locations 105 | * @throws Exception\InvalidArgumentException 106 | * @return ClassMapAutoloader 107 | */ 108 | public function registerAutoloadMaps($locations) 109 | { 110 | if (! is_array($locations) && ! ($locations instanceof Traversable)) { 111 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 112 | throw new Exception\InvalidArgumentException('Map list must be an array or implement Traversable'); 113 | } 114 | foreach ($locations as $location) { 115 | $this->registerAutoloadMap($location); 116 | } 117 | return $this; 118 | } 119 | 120 | /** 121 | * Retrieve current autoload map 122 | * 123 | * @return array 124 | */ 125 | public function getAutoloadMap() 126 | { 127 | return $this->map; 128 | } 129 | 130 | /** 131 | * {@inheritDoc} 132 | */ 133 | public function autoload($class) 134 | { 135 | if (isset($this->map[$class])) { 136 | require_once $this->map[$class]; 137 | 138 | return $class; 139 | } 140 | 141 | return false; 142 | } 143 | 144 | /** 145 | * Register the autoloader with spl_autoload registry 146 | * 147 | * @return void 148 | */ 149 | public function register() 150 | { 151 | spl_autoload_register([$this, 'autoload'], true, true); 152 | } 153 | 154 | /** 155 | * Load a map from a file 156 | * 157 | * If the map has been previously loaded, returns the current instance; 158 | * otherwise, returns whatever was returned by calling include() on the 159 | * location. 160 | * 161 | * @param string $location 162 | * @return ClassMapAutoloader|mixed 163 | * @throws Exception\InvalidArgumentException for nonexistent locations 164 | */ 165 | protected function loadMapFromFile($location) 166 | { 167 | if (! file_exists($location)) { 168 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 169 | throw new Exception\InvalidArgumentException(sprintf( 170 | 'Map file provided does not exist. Map file: "%s"', 171 | (is_string($location) ? $location : 'unexpected type: ' . gettype($location)) 172 | )); 173 | } 174 | 175 | if (! $path = static::realPharPath($location)) { 176 | $path = realpath($location); 177 | } 178 | 179 | if (in_array($path, $this->mapsLoaded)) { 180 | // Already loaded this map 181 | return $this; 182 | } 183 | 184 | $map = include $path; 185 | 186 | return $map; 187 | } 188 | 189 | /** 190 | * Resolve the real_path() to a file within a phar. 191 | * 192 | * @see https://bugs.php.net/bug.php?id=52769 193 | * @param string $path 194 | * @return string 195 | */ 196 | public static function realPharPath($path) 197 | { 198 | if (! preg_match('|^phar:(/{2,3})|', $path, $match)) { 199 | return; 200 | } 201 | 202 | $prefixLength = 5 + strlen($match[1]); 203 | $parts = explode('/', str_replace(['/', '\\'], '/', substr($path, $prefixLength))); 204 | $parts = array_values(array_filter($parts, function ($p) { 205 | return ($p !== '' && $p !== '.'); 206 | })); 207 | 208 | array_walk($parts, function ($value, $key) use (&$parts) { 209 | if ($value === '..') { 210 | unset($parts[$key], $parts[$key - 1]); 211 | $parts = array_values($parts); 212 | } 213 | }); 214 | 215 | if (file_exists($realPath = str_pad('phar:', $prefixLength, '/') . implode('/', $parts))) { 216 | return $realPath; 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/Exception/BadMethodCallException.php: -------------------------------------------------------------------------------- 1 | path 28 | */ 29 | protected $explicitPaths = []; 30 | 31 | /** 32 | * @var array An array of namespaceName => namespacePath 33 | */ 34 | protected $namespacedPaths = []; 35 | 36 | /** 37 | * @var string Will contain the absolute phar:// path to the executable when packaged as phar file 38 | */ 39 | protected $pharBasePath = ""; 40 | 41 | /** 42 | * @var array An array of supported phar extensions (filled on constructor) 43 | */ 44 | protected $pharExtensions = []; 45 | 46 | /** 47 | * @var array An array of module classes to their containing files 48 | */ 49 | protected $moduleClassMap = []; 50 | 51 | /** 52 | * Constructor 53 | * 54 | * Allow configuration of the autoloader via the constructor. 55 | * 56 | * @param null|array|Traversable $options 57 | */ 58 | public function __construct($options = null) 59 | { 60 | if (extension_loaded('phar')) { 61 | $this->pharBasePath = Phar::running(true); 62 | $this->pharExtensions = [ 63 | 'phar', 64 | 'phar.tar', 65 | 'tar', 66 | ]; 67 | 68 | // ext/zlib enabled -> phar can read gzip & zip compressed files 69 | if (extension_loaded('zlib')) { 70 | $this->pharExtensions[] = 'phar.gz'; 71 | $this->pharExtensions[] = 'phar.tar.gz'; 72 | $this->pharExtensions[] = 'tar.gz'; 73 | 74 | $this->pharExtensions[] = 'phar.zip'; 75 | $this->pharExtensions[] = 'zip'; 76 | } 77 | 78 | // ext/bzip2 enabled -> phar can read bz2 compressed files 79 | if (extension_loaded('bzip2')) { 80 | $this->pharExtensions[] = 'phar.bz2'; 81 | $this->pharExtensions[] = 'phar.tar.bz2'; 82 | $this->pharExtensions[] = 'tar.bz2'; 83 | } 84 | } 85 | 86 | if (null !== $options) { 87 | $this->setOptions($options); 88 | } 89 | } 90 | 91 | /** 92 | * Configure the autoloader 93 | * 94 | * In most cases, $options should be either an associative array or 95 | * Traversable object. 96 | * 97 | * @param array|Traversable $options 98 | * @return ModuleAutoloader 99 | */ 100 | public function setOptions($options) 101 | { 102 | $this->registerPaths($options); 103 | return $this; 104 | } 105 | 106 | /** 107 | * Retrieves the class map for all loaded modules. 108 | * 109 | * @return array 110 | */ 111 | public function getModuleClassMap() 112 | { 113 | return $this->moduleClassMap; 114 | } 115 | 116 | /** 117 | * Sets the class map used to speed up the module autoloading. 118 | * 119 | * @param array $classmap 120 | * @return ModuleAutoloader 121 | */ 122 | public function setModuleClassMap(array $classmap) 123 | { 124 | $this->moduleClassMap = $classmap; 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * Autoload a class 131 | * 132 | * @param $class 133 | * @return mixed 134 | * False [if unable to load $class] 135 | * get_class($class) [if $class is successfully loaded] 136 | */ 137 | public function autoload($class) 138 | { 139 | // Limit scope of this autoloader 140 | if (substr($class, -7) !== '\Module') { 141 | return false; 142 | } 143 | 144 | if (isset($this->moduleClassMap[$class])) { 145 | require_once $this->moduleClassMap[$class]; 146 | return $class; 147 | } 148 | 149 | $moduleName = substr($class, 0, -7); 150 | if (isset($this->explicitPaths[$moduleName])) { 151 | $classLoaded = $this->loadModuleFromDir($this->explicitPaths[$moduleName], $class); 152 | if ($classLoaded) { 153 | return $classLoaded; 154 | } 155 | 156 | $classLoaded = $this->loadModuleFromPhar($this->explicitPaths[$moduleName], $class); 157 | if ($classLoaded) { 158 | return $classLoaded; 159 | } 160 | } 161 | 162 | if (count($this->namespacedPaths) >= 1) { 163 | foreach ($this->namespacedPaths as $namespace => $path) { 164 | if (false === strpos($moduleName, $namespace)) { 165 | continue; 166 | } 167 | 168 | $moduleNameBuffer = str_replace($namespace . "\\", "", $moduleName); 169 | $path .= DIRECTORY_SEPARATOR . $moduleNameBuffer . DIRECTORY_SEPARATOR; 170 | 171 | $classLoaded = $this->loadModuleFromDir($path, $class); 172 | if ($classLoaded) { 173 | return $classLoaded; 174 | } 175 | 176 | $classLoaded = $this->loadModuleFromPhar($path, $class); 177 | if ($classLoaded) { 178 | return $classLoaded; 179 | } 180 | } 181 | } 182 | 183 | $moduleClassPath = str_replace('\\', DIRECTORY_SEPARATOR, $moduleName); 184 | 185 | $pharSuffixPattern = null; 186 | if ($this->pharExtensions) { 187 | $pharSuffixPattern = '(' . implode('|', array_map('preg_quote', $this->pharExtensions)) . ')'; 188 | } 189 | 190 | foreach ($this->paths as $path) { 191 | $path = $path . $moduleClassPath; 192 | 193 | if ($path == '.' || substr($path, 0, 2) == './' || substr($path, 0, 2) == '.\\') { 194 | if (! $basePath = $this->pharBasePath) { 195 | $basePath = realpath('.'); 196 | } 197 | 198 | if (false === $basePath) { 199 | $basePath = getcwd(); 200 | } 201 | 202 | $path = rtrim($basePath, '\/\\') . substr($path, 1); 203 | } 204 | 205 | $classLoaded = $this->loadModuleFromDir($path, $class); 206 | if ($classLoaded) { 207 | return $classLoaded; 208 | } 209 | 210 | // No directory with Module.php, searching for phars 211 | if ($pharSuffixPattern) { 212 | foreach (new GlobIterator($path . '.*') as $entry) { 213 | if ($entry->isDir()) { 214 | continue; 215 | } 216 | 217 | if (! preg_match('#.+\.' . $pharSuffixPattern . '$#', $entry->getPathname())) { 218 | continue; 219 | } 220 | 221 | $classLoaded = $this->loadModuleFromPhar($entry->getPathname(), $class); 222 | if ($classLoaded) { 223 | return $classLoaded; 224 | } 225 | } 226 | } 227 | } 228 | 229 | return false; 230 | } 231 | 232 | /** 233 | * loadModuleFromDir 234 | * 235 | * @param string $dirPath 236 | * @param string $class 237 | * @return mixed 238 | * False [if unable to load $class] 239 | * get_class($class) [if $class is successfully loaded] 240 | */ 241 | protected function loadModuleFromDir($dirPath, $class) 242 | { 243 | $modulePath = $dirPath . '/Module.php'; 244 | if (substr($modulePath, 0, 7) === 'phar://') { 245 | $file = new PharFileInfo($modulePath); 246 | } else { 247 | $file = new SplFileInfo($modulePath); 248 | } 249 | 250 | if (($file->isReadable() && $file->isFile())) { 251 | // Found directory with Module.php in it 252 | $absModulePath = $this->pharBasePath ? (string) $file : $file->getRealPath(); 253 | require_once $absModulePath; 254 | if (class_exists($class)) { 255 | $this->moduleClassMap[$class] = $absModulePath; 256 | return $class; 257 | } 258 | } 259 | return false; 260 | } 261 | 262 | /** 263 | * loadModuleFromPhar 264 | * 265 | * @param string $pharPath 266 | * @param string $class 267 | * @return mixed 268 | * False [if unable to load $class] 269 | * get_class($class) [if $class is successfully loaded] 270 | */ 271 | protected function loadModuleFromPhar($pharPath, $class) 272 | { 273 | $pharPath = static::normalizePath($pharPath, false); 274 | $file = new SplFileInfo($pharPath); 275 | if (! $file->isReadable() || ! $file->isFile()) { 276 | return false; 277 | } 278 | 279 | $fileRealPath = $file->getRealPath(); 280 | 281 | // Phase 0: Check for executable phar with Module class in stub 282 | if (strpos($fileRealPath, '.phar') !== false) { 283 | // First see if the stub makes the Module class available 284 | require_once $fileRealPath; 285 | if (class_exists($class)) { 286 | $this->moduleClassMap[$class] = $fileRealPath; 287 | return $class; 288 | } 289 | } 290 | 291 | // Phase 1: Not executable phar, no stub, or stub did not provide Module class; try Module.php directly 292 | $moduleClassFile = 'phar://' . $fileRealPath . '/Module.php'; 293 | $moduleFile = new SplFileInfo($moduleClassFile); 294 | if ($moduleFile->isReadable() && $moduleFile->isFile()) { 295 | require_once $moduleClassFile; 296 | if (class_exists($class)) { 297 | $this->moduleClassMap[$class] = $moduleClassFile; 298 | return $class; 299 | } 300 | } 301 | 302 | // Phase 2: Check for nested module directory within archive 303 | // Checks for /path/to/MyModule.tar/MyModule/Module.php 304 | // (shell-integrated zip/tar utilities wrap directories like this) 305 | $pharBaseName = $this->pharFileToModuleName($fileRealPath); 306 | $moduleClassFile = 'phar://' . $fileRealPath . '/' . $pharBaseName . '/Module.php'; 307 | $moduleFile = new SplFileInfo($moduleClassFile); 308 | if ($moduleFile->isReadable() && $moduleFile->isFile()) { 309 | require_once $moduleClassFile; 310 | if (class_exists($class)) { 311 | $this->moduleClassMap[$class] = $moduleClassFile; 312 | return $class; 313 | } 314 | } 315 | 316 | return false; 317 | } 318 | 319 | /** 320 | * Register the autoloader with spl_autoload registry 321 | * 322 | * @return void 323 | */ 324 | public function register() 325 | { 326 | spl_autoload_register([$this, 'autoload']); 327 | } 328 | 329 | /** 330 | * Unregister the autoloader with spl_autoload registry 331 | * 332 | * @return void 333 | */ 334 | public function unregister() 335 | { 336 | spl_autoload_unregister([$this, 'autoload']); 337 | } 338 | 339 | /** 340 | * registerPaths 341 | * 342 | * @param array|Traversable $paths 343 | * @throws \InvalidArgumentException 344 | * @return ModuleAutoloader 345 | */ 346 | public function registerPaths($paths) 347 | { 348 | if (! is_array($paths) && ! $paths instanceof Traversable) { 349 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 350 | throw new Exception\InvalidArgumentException( 351 | 'Parameter to \\Zend\\Loader\\ModuleAutoloader\'s ' 352 | . 'registerPaths method must be an array or ' 353 | . 'implement the Traversable interface' 354 | ); 355 | } 356 | 357 | foreach ($paths as $module => $path) { 358 | if (is_string($module)) { 359 | $this->registerPath($path, $module); 360 | } else { 361 | $this->registerPath($path); 362 | } 363 | } 364 | 365 | return $this; 366 | } 367 | 368 | /** 369 | * registerPath 370 | * 371 | * @param string $path 372 | * @param bool|string $moduleName 373 | * @throws \InvalidArgumentException 374 | * @return ModuleAutoloader 375 | */ 376 | public function registerPath($path, $moduleName = false) 377 | { 378 | if (! is_string($path)) { 379 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 380 | throw new Exception\InvalidArgumentException(sprintf( 381 | 'Invalid path provided; must be a string, received %s', 382 | gettype($path) 383 | )); 384 | } 385 | if ($moduleName) { 386 | if (in_array(substr($moduleName, -2), ['\\*', '\\%'])) { 387 | $this->namespacedPaths[substr($moduleName, 0, -2)] = static::normalizePath($path); 388 | } else { 389 | $this->explicitPaths[$moduleName] = static::normalizePath($path); 390 | } 391 | } else { 392 | $this->paths[] = static::normalizePath($path); 393 | } 394 | return $this; 395 | } 396 | 397 | /** 398 | * getPaths 399 | * 400 | * This is primarily for unit testing, but could have other uses. 401 | * 402 | * @return array 403 | */ 404 | public function getPaths() 405 | { 406 | return $this->paths; 407 | } 408 | 409 | /** 410 | * Returns the base module name from the path to a phar 411 | * 412 | * @param string $pharPath 413 | * @return string 414 | */ 415 | protected function pharFileToModuleName($pharPath) 416 | { 417 | do { 418 | $pathinfo = pathinfo($pharPath); 419 | $pharPath = $pathinfo['filename']; 420 | } while (isset($pathinfo['extension'])); 421 | return $pathinfo['filename']; 422 | } 423 | 424 | /** 425 | * Normalize a path for insertion in the stack 426 | * 427 | * @param string $path 428 | * @param bool $trailingSlash Whether trailing slash should be included 429 | * @return string 430 | */ 431 | public static function normalizePath($path, $trailingSlash = true) 432 | { 433 | $path = rtrim($path, '/'); 434 | $path = rtrim($path, '\\'); 435 | if ($trailingSlash) { 436 | $path .= DIRECTORY_SEPARATOR; 437 | } 438 | return $path; 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /src/PluginClassLoader.php: -------------------------------------------------------------------------------- 1 | class name pairs 21 | * @var array 22 | */ 23 | protected $plugins = []; 24 | 25 | /** 26 | * Static map allow global seeding of plugin loader 27 | * @var array 28 | */ 29 | protected static $staticMap = []; 30 | 31 | /** 32 | * Constructor 33 | * 34 | * @param null|array|Traversable $map If provided, seeds the loader with a map 35 | */ 36 | public function __construct($map = null) 37 | { 38 | // Merge in static overrides 39 | if (! empty(static::$staticMap)) { 40 | $this->registerPlugins(static::$staticMap); 41 | } 42 | 43 | // Merge in constructor arguments 44 | if ($map !== null) { 45 | $this->registerPlugins($map); 46 | } 47 | } 48 | 49 | /** 50 | * Add a static map of plugins 51 | * 52 | * A null value will clear the static map. 53 | * 54 | * @param null|array|Traversable $map 55 | * @throws Exception\InvalidArgumentException 56 | * @return void 57 | */ 58 | public static function addStaticMap($map) 59 | { 60 | if (null === $map) { 61 | static::$staticMap = []; 62 | return; 63 | } 64 | 65 | if (! is_array($map) && ! $map instanceof Traversable) { 66 | throw new Exception\InvalidArgumentException('Expects an array or Traversable object'); 67 | } 68 | foreach ($map as $key => $value) { 69 | static::$staticMap[$key] = $value; 70 | } 71 | } 72 | 73 | /** 74 | * Register a class to a given short name 75 | * 76 | * @param string $shortName 77 | * @param string $className 78 | * @return PluginClassLoader 79 | */ 80 | public function registerPlugin($shortName, $className) 81 | { 82 | $this->plugins[strtolower($shortName)] = $className; 83 | return $this; 84 | } 85 | 86 | /** 87 | * Register many plugins at once 88 | * 89 | * If $map is a string, assumes that the map is the class name of a 90 | * Traversable object (likely a ShortNameLocator); it will then instantiate 91 | * this class and use it to register plugins. 92 | * 93 | * If $map is an array or Traversable object, it will iterate it to 94 | * register plugin names/classes. 95 | * 96 | * For all other arguments, or if the string $map is not a class or not a 97 | * Traversable class, an exception will be raised. 98 | * 99 | * @param string|array|Traversable $map 100 | * @return PluginClassLoader 101 | * @throws Exception\InvalidArgumentException 102 | */ 103 | public function registerPlugins($map) 104 | { 105 | if (is_string($map)) { 106 | if (! class_exists($map)) { 107 | throw new Exception\InvalidArgumentException('Map class provided is invalid'); 108 | } 109 | $map = new $map; 110 | } 111 | if (is_array($map)) { 112 | $map = new ArrayIterator($map); 113 | } 114 | if (! $map instanceof Traversable) { 115 | throw new Exception\InvalidArgumentException('Map provided is invalid; must be traversable'); 116 | } 117 | 118 | // iterator_apply doesn't work as expected with IteratorAggregate 119 | if ($map instanceof IteratorAggregate) { 120 | $map = $map->getIterator(); 121 | } 122 | 123 | foreach ($map as $name => $class) { 124 | if (is_int($name) || is_numeric($name)) { 125 | if (! is_object($class) && class_exists($class)) { 126 | $class = new $class(); 127 | } 128 | 129 | if ($class instanceof Traversable) { 130 | $this->registerPlugins($class); 131 | continue; 132 | } 133 | } 134 | 135 | $this->registerPlugin($name, $class); 136 | } 137 | 138 | return $this; 139 | } 140 | 141 | /** 142 | * Unregister a short name lookup 143 | * 144 | * @param mixed $shortName 145 | * @return PluginClassLoader 146 | */ 147 | public function unregisterPlugin($shortName) 148 | { 149 | $lookup = strtolower($shortName); 150 | if (array_key_exists($lookup, $this->plugins)) { 151 | unset($this->plugins[$lookup]); 152 | } 153 | return $this; 154 | } 155 | 156 | /** 157 | * Get a list of all registered plugins 158 | * 159 | * @return array|Traversable 160 | */ 161 | public function getRegisteredPlugins() 162 | { 163 | return $this->plugins; 164 | } 165 | 166 | /** 167 | * Whether or not a plugin by a specific name has been registered 168 | * 169 | * @param string $name 170 | * @return bool 171 | */ 172 | public function isLoaded($name) 173 | { 174 | $lookup = strtolower($name); 175 | return isset($this->plugins[$lookup]); 176 | } 177 | 178 | /** 179 | * Return full class name for a named helper 180 | * 181 | * @param string $name 182 | * @return string|false 183 | */ 184 | public function getClassName($name) 185 | { 186 | return $this->load($name); 187 | } 188 | 189 | /** 190 | * Load a helper via the name provided 191 | * 192 | * @param string $name 193 | * @return string|false 194 | */ 195 | public function load($name) 196 | { 197 | if (! $this->isLoaded($name)) { 198 | return false; 199 | } 200 | return $this->plugins[strtolower($name)]; 201 | } 202 | 203 | /** 204 | * Defined by IteratorAggregate 205 | * 206 | * Returns an instance of ArrayIterator, containing a map of 207 | * all plugins 208 | * 209 | * @return ArrayIterator 210 | */ 211 | public function getIterator() 212 | { 213 | return new ArrayIterator($this->plugins); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/PluginClassLocator.php: -------------------------------------------------------------------------------- 1 | 57 | * spl_autoload_register(array($this, 'autoload')); 58 | * 59 | * 60 | * @return void 61 | */ 62 | public function register(); 63 | } 64 | -------------------------------------------------------------------------------- /src/StandardAutoloader.php: -------------------------------------------------------------------------------- 1 | setOptions($options); 53 | } 54 | } 55 | 56 | /** 57 | * Configure autoloader 58 | * 59 | * Allows specifying both "namespace" and "prefix" pairs, using the 60 | * following structure: 61 | * 62 | * array( 63 | * 'namespaces' => array( 64 | * 'Zend' => '/path/to/Zend/library', 65 | * 'Doctrine' => '/path/to/Doctrine/library', 66 | * ), 67 | * 'prefixes' => array( 68 | * 'Phly_' => '/path/to/Phly/library', 69 | * ), 70 | * 'fallback_autoloader' => true, 71 | * ) 72 | * 73 | * 74 | * @param array|\Traversable $options 75 | * @throws Exception\InvalidArgumentException 76 | * @return StandardAutoloader 77 | */ 78 | public function setOptions($options) 79 | { 80 | if (! is_array($options) && ! ($options instanceof \Traversable)) { 81 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 82 | throw new Exception\InvalidArgumentException('Options must be either an array or Traversable'); 83 | } 84 | 85 | foreach ($options as $type => $pairs) { 86 | switch ($type) { 87 | case self::AUTOREGISTER_ZF: 88 | if ($pairs) { 89 | $this->registerNamespace('Zend', dirname(__DIR__)); 90 | $this->registerNamespace( 91 | 'ZendXml', 92 | dirname(dirname((__DIR__))) . DIRECTORY_SEPARATOR . 'ZendXml' 93 | ); 94 | } 95 | break; 96 | case self::LOAD_NS: 97 | if (is_array($pairs) || $pairs instanceof \Traversable) { 98 | $this->registerNamespaces($pairs); 99 | } 100 | break; 101 | case self::LOAD_PREFIX: 102 | if (is_array($pairs) || $pairs instanceof \Traversable) { 103 | $this->registerPrefixes($pairs); 104 | } 105 | break; 106 | case self::ACT_AS_FALLBACK: 107 | $this->setFallbackAutoloader($pairs); 108 | break; 109 | default: 110 | // ignore 111 | } 112 | } 113 | return $this; 114 | } 115 | 116 | /** 117 | * Set flag indicating fallback autoloader status 118 | * 119 | * @param bool $flag 120 | * @return StandardAutoloader 121 | */ 122 | public function setFallbackAutoloader($flag) 123 | { 124 | $this->fallbackAutoloaderFlag = (bool) $flag; 125 | return $this; 126 | } 127 | 128 | /** 129 | * Is this autoloader acting as a fallback autoloader? 130 | * 131 | * @return bool 132 | */ 133 | public function isFallbackAutoloader() 134 | { 135 | return $this->fallbackAutoloaderFlag; 136 | } 137 | 138 | /** 139 | * Register a namespace/directory pair 140 | * 141 | * @param string $namespace 142 | * @param string $directory 143 | * @return StandardAutoloader 144 | */ 145 | public function registerNamespace($namespace, $directory) 146 | { 147 | $namespace = rtrim($namespace, self::NS_SEPARATOR) . self::NS_SEPARATOR; 148 | $this->namespaces[$namespace] = $this->normalizeDirectory($directory); 149 | return $this; 150 | } 151 | 152 | /** 153 | * Register many namespace/directory pairs at once 154 | * 155 | * @param array $namespaces 156 | * @throws Exception\InvalidArgumentException 157 | * @return StandardAutoloader 158 | */ 159 | public function registerNamespaces($namespaces) 160 | { 161 | if (! is_array($namespaces) && ! $namespaces instanceof \Traversable) { 162 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 163 | throw new Exception\InvalidArgumentException('Namespace pairs must be either an array or Traversable'); 164 | } 165 | 166 | foreach ($namespaces as $namespace => $directory) { 167 | $this->registerNamespace($namespace, $directory); 168 | } 169 | return $this; 170 | } 171 | 172 | /** 173 | * Register a prefix/directory pair 174 | * 175 | * @param string $prefix 176 | * @param string $directory 177 | * @return StandardAutoloader 178 | */ 179 | public function registerPrefix($prefix, $directory) 180 | { 181 | $prefix = rtrim($prefix, self::PREFIX_SEPARATOR). self::PREFIX_SEPARATOR; 182 | $this->prefixes[$prefix] = $this->normalizeDirectory($directory); 183 | return $this; 184 | } 185 | 186 | /** 187 | * Register many namespace/directory pairs at once 188 | * 189 | * @param array $prefixes 190 | * @throws Exception\InvalidArgumentException 191 | * @return StandardAutoloader 192 | */ 193 | public function registerPrefixes($prefixes) 194 | { 195 | if (! is_array($prefixes) && ! $prefixes instanceof \Traversable) { 196 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 197 | throw new Exception\InvalidArgumentException('Prefix pairs must be either an array or Traversable'); 198 | } 199 | 200 | foreach ($prefixes as $prefix => $directory) { 201 | $this->registerPrefix($prefix, $directory); 202 | } 203 | return $this; 204 | } 205 | 206 | /** 207 | * Defined by Autoloadable; autoload a class 208 | * 209 | * @param string $class 210 | * @return false|string 211 | */ 212 | public function autoload($class) 213 | { 214 | $isFallback = $this->isFallbackAutoloader(); 215 | if (false !== strpos($class, self::NS_SEPARATOR)) { 216 | if ($this->loadClass($class, self::LOAD_NS)) { 217 | return $class; 218 | } elseif ($isFallback) { 219 | return $this->loadClass($class, self::ACT_AS_FALLBACK); 220 | } 221 | return false; 222 | } 223 | if (false !== strpos($class, self::PREFIX_SEPARATOR)) { 224 | if ($this->loadClass($class, self::LOAD_PREFIX)) { 225 | return $class; 226 | } elseif ($isFallback) { 227 | return $this->loadClass($class, self::ACT_AS_FALLBACK); 228 | } 229 | return false; 230 | } 231 | if ($isFallback) { 232 | return $this->loadClass($class, self::ACT_AS_FALLBACK); 233 | } 234 | return false; 235 | } 236 | 237 | /** 238 | * Register the autoloader with spl_autoload 239 | * 240 | * @return void 241 | */ 242 | public function register() 243 | { 244 | spl_autoload_register([$this, 'autoload']); 245 | } 246 | 247 | /** 248 | * Transform the class name to a filename 249 | * 250 | * @param string $class 251 | * @param string $directory 252 | * @return string 253 | */ 254 | protected function transformClassNameToFilename($class, $directory) 255 | { 256 | // $class may contain a namespace portion, in which case we need 257 | // to preserve any underscores in that portion. 258 | $matches = []; 259 | preg_match('/(?P.+\\\)?(?P[^\\\]+$)/', $class, $matches); 260 | 261 | $class = (isset($matches['class'])) ? $matches['class'] : ''; 262 | $namespace = (isset($matches['namespace'])) ? $matches['namespace'] : ''; 263 | 264 | return $directory 265 | . str_replace(self::NS_SEPARATOR, '/', $namespace) 266 | . str_replace(self::PREFIX_SEPARATOR, '/', $class) 267 | . '.php'; 268 | } 269 | 270 | /** 271 | * Load a class, based on its type (namespaced or prefixed) 272 | * 273 | * @param string $class 274 | * @param string $type 275 | * @return bool|string 276 | * @throws Exception\InvalidArgumentException 277 | */ 278 | protected function loadClass($class, $type) 279 | { 280 | if (! in_array($type, [self::LOAD_NS, self::LOAD_PREFIX, self::ACT_AS_FALLBACK])) { 281 | require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 282 | throw new Exception\InvalidArgumentException(); 283 | } 284 | 285 | // Fallback autoloading 286 | if ($type === self::ACT_AS_FALLBACK) { 287 | // create filename 288 | $filename = $this->transformClassNameToFilename($class, ''); 289 | $resolvedName = stream_resolve_include_path($filename); 290 | if ($resolvedName !== false) { 291 | return include $resolvedName; 292 | } 293 | return false; 294 | } 295 | 296 | // Namespace and/or prefix autoloading 297 | foreach ($this->$type as $leader => $path) { 298 | if (0 === strpos($class, $leader)) { 299 | // Trim off leader (namespace or prefix) 300 | $trimmedClass = substr($class, strlen($leader)); 301 | 302 | // create filename 303 | $filename = $this->transformClassNameToFilename($trimmedClass, $path); 304 | if (file_exists($filename)) { 305 | return include $filename; 306 | } 307 | } 308 | } 309 | return false; 310 | } 311 | 312 | /** 313 | * Normalize the directory to include a trailing directory separator 314 | * 315 | * @param string $directory 316 | * @return string 317 | */ 318 | protected function normalizeDirectory($directory) 319 | { 320 | $last = $directory[strlen($directory) - 1]; 321 | if (in_array($last, ['/', '\\'])) { 322 | $directory[strlen($directory) - 1] = DIRECTORY_SEPARATOR; 323 | return $directory; 324 | } 325 | $directory .= DIRECTORY_SEPARATOR; 326 | return $directory; 327 | } 328 | } 329 | --------------------------------------------------------------------------------