3 * @see https://github.com/zendframework/zend-loader for the canonical source repository
4 * @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (https://www.zend.com)
5 * @license https://github.com/zendframework/zend-loader/blob/master/LICENSE.md New BSD License
10 // Grab SplAutoloader interface
11 require_once __DIR__
. '/SplAutoloader.php';
19 class ModuleAutoloader
implements SplAutoloader
22 * @var array An array of module paths to scan
24 protected $paths = [];
27 * @var array An array of modulename => path
29 protected $explicitPaths = [];
32 * @var array An array of namespaceName => namespacePath
34 protected $namespacedPaths = [];
37 * @var string Will contain the absolute phar:// path to the executable when packaged as phar file
39 protected $pharBasePath = "";
42 * @var array An array of supported phar extensions (filled on constructor)
44 protected $pharExtensions = [];
47 * @var array An array of module classes to their containing files
49 protected $moduleClassMap = [];
54 * Allow configuration of the autoloader via the constructor.
56 * @param null|array|Traversable $options
58 public function __construct($options = null)
60 if (extension_loaded('phar')) {
61 $this->pharBasePath
= Phar
::running(true);
62 $this->pharExtensions
= [
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';
74 $this->pharExtensions
[] = 'phar.zip';
75 $this->pharExtensions
[] = 'zip';
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';
86 if (null !== $options) {
87 $this->setOptions($options);
92 * Configure the autoloader
94 * In most cases, $options should be either an associative array or
97 * @param array|Traversable $options
98 * @return ModuleAutoloader
100 public function setOptions($options)
102 $this->registerPaths($options);
107 * Retrieves the class map for all loaded modules.
111 public function getModuleClassMap()
113 return $this->moduleClassMap
;
117 * Sets the class map used to speed up the module autoloading.
119 * @param array $classmap
120 * @return ModuleAutoloader
122 public function setModuleClassMap(array $classmap)
124 $this->moduleClassMap
= $classmap;
134 * False [if unable to load $class]
135 * get_class($class) [if $class is successfully loaded]
137 public function autoload($class)
139 // Limit scope of this autoloader
140 if (substr($class, -7) !== '\Module') {
144 if (isset($this->moduleClassMap
[$class])) {
145 require_once $this->moduleClassMap
[$class];
149 $moduleName = substr($class, 0, -7);
150 if (isset($this->explicitPaths
[$moduleName])) {
151 $classLoaded = $this->loadModuleFromDir($this->explicitPaths
[$moduleName], $class);
156 $classLoaded = $this->loadModuleFromPhar($this->explicitPaths
[$moduleName], $class);
162 if (count($this->namespacedPaths
) >= 1) {
163 foreach ($this->namespacedPaths
as $namespace => $path) {
164 if (false === strpos($moduleName, $namespace)) {
168 $moduleNameBuffer = str_replace($namespace . "\\", "", $moduleName);
169 $path .= DIRECTORY_SEPARATOR
. $moduleNameBuffer . DIRECTORY_SEPARATOR
;
171 $classLoaded = $this->loadModuleFromDir($path, $class);
176 $classLoaded = $this->loadModuleFromPhar($path, $class);
183 $moduleClassPath = str_replace('\\', DIRECTORY_SEPARATOR
, $moduleName);
185 $pharSuffixPattern = null;
186 if ($this->pharExtensions
) {
187 $pharSuffixPattern = '(' . implode('|', array_map('preg_quote', $this->pharExtensions
)) . ')';
190 foreach ($this->paths
as $path) {
191 $path = $path . $moduleClassPath;
193 if ($path == '.' ||
substr($path, 0, 2) == './' ||
substr($path, 0, 2) == '.\\') {
194 if (! $basePath = $this->pharBasePath
) {
195 $basePath = realpath('.');
198 if (false === $basePath) {
199 $basePath = getcwd();
202 $path = rtrim($basePath, '\/\\') . substr($path, 1);
205 $classLoaded = $this->loadModuleFromDir($path, $class);
210 // No directory with Module.php, searching for phars
211 if ($pharSuffixPattern) {
212 foreach (new GlobIterator($path . '.*') as $entry) {
213 if ($entry->isDir()) {
217 if (! preg_match('#.+\.' . $pharSuffixPattern . '$#', $entry->getPathname())) {
221 $classLoaded = $this->loadModuleFromPhar($entry->getPathname(), $class);
235 * @param string $dirPath
236 * @param string $class
238 * False [if unable to load $class]
239 * get_class($class) [if $class is successfully loaded]
241 protected function loadModuleFromDir($dirPath, $class)
243 $modulePath = $dirPath . '/Module.php';
244 if (substr($modulePath, 0, 7) === 'phar://') {
245 $file = new PharFileInfo($modulePath);
247 $file = new SplFileInfo($modulePath);
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;
265 * @param string $pharPath
266 * @param string $class
268 * False [if unable to load $class]
269 * get_class($class) [if $class is successfully loaded]
271 protected function loadModuleFromPhar($pharPath, $class)
273 $pharPath = static::normalizePath($pharPath, false);
274 $file = new SplFileInfo($pharPath);
275 if (! $file->isReadable() ||
! $file->isFile()) {
279 $fileRealPath = $file->getRealPath();
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;
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;
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;
320 * Register the autoloader with spl_autoload registry
324 public function register()
326 spl_autoload_register([$this, 'autoload']);
330 * Unregister the autoloader with spl_autoload registry
334 public function unregister()
336 spl_autoload_unregister([$this, 'autoload']);
342 * @param array|Traversable $paths
343 * @throws \InvalidArgumentException
344 * @return ModuleAutoloader
346 public function registerPaths($paths)
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'
357 foreach ($paths as $module => $path) {
358 if (is_string($module)) {
359 $this->registerPath($path, $module);
361 $this->registerPath($path);
371 * @param string $path
372 * @param bool|string $moduleName
373 * @throws \InvalidArgumentException
374 * @return ModuleAutoloader
376 public function registerPath($path, $moduleName = false)
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',
386 if (in_array(substr($moduleName, -2), ['\\*', '\\%'])) {
387 $this->namespacedPaths
[substr($moduleName, 0, -2)] = static::normalizePath($path);
389 $this->explicitPaths
[$moduleName] = static::normalizePath($path);
392 $this->paths
[] = static::normalizePath($path);
400 * This is primarily for unit testing, but could have other uses.
404 public function getPaths()
410 * Returns the base module name from the path to a phar
412 * @param string $pharPath
415 protected function pharFileToModuleName($pharPath)
418 $pathinfo = pathinfo($pharPath);
419 $pharPath = $pathinfo['filename'];
420 } while (isset($pathinfo['extension']));
421 return $pathinfo['filename'];
425 * Normalize a path for insertion in the stack
427 * @param string $path
428 * @param bool $trailingSlash Whether trailing slash should be included
431 public static function normalizePath($path, $trailingSlash = true)
433 $path = rtrim($path, '/');
434 $path = rtrim($path, '\\');
435 if ($trailingSlash) {
436 $path .= DIRECTORY_SEPARATOR
;