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';
14 * PSR-0 compliant autoloader
16 * Allows autoloading both namespaced and vendor-prefixed classes. Class
17 * lookups are performed on the filesystem. If a class file for the referenced
18 * class is not found, a PHP warning will be raised by include().
20 class StandardAutoloader
implements SplAutoloader
22 const NS_SEPARATOR
= '\\';
23 const PREFIX_SEPARATOR
= '_';
24 const LOAD_NS
= 'namespaces';
25 const LOAD_PREFIX
= 'prefixes';
26 const ACT_AS_FALLBACK
= 'fallback_autoloader';
27 const AUTOREGISTER_ZF
= 'autoregister_zf';
30 * @var array Namespace/directory pairs to search; ZF library added by default
32 protected $namespaces = [];
35 * @var array Prefix/directory pairs to search
37 protected $prefixes = [];
40 * @var bool Whether or not the autoloader should also act as a fallback autoloader
42 protected $fallbackAutoloaderFlag = false;
47 * @param null|array|\Traversable $options
49 public function __construct($options = null)
51 if (null !== $options) {
52 $this->setOptions($options);
57 * Configure autoloader
59 * Allows specifying both "namespace" and "prefix" pairs, using the
60 * following structure:
63 * 'namespaces' => array(
64 * 'Zend' => '/path/to/Zend/library',
65 * 'Doctrine' => '/path/to/Doctrine/library',
67 * 'prefixes' => array(
68 * 'Phly_' => '/path/to/Phly/library',
70 * 'fallback_autoloader' => true,
74 * @param array|\Traversable $options
75 * @throws Exception\InvalidArgumentException
76 * @return StandardAutoloader
78 public function setOptions($options)
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');
85 foreach ($options as $type => $pairs) {
87 case self
::AUTOREGISTER_ZF
:
89 $this->registerNamespace('Zend', dirname(__DIR__
));
90 $this->registerNamespace(
92 dirname(dirname((__DIR__
))) . DIRECTORY_SEPARATOR
. 'ZendXml'
97 if (is_array($pairs) ||
$pairs instanceof \Traversable
) {
98 $this->registerNamespaces($pairs);
101 case self
::LOAD_PREFIX
:
102 if (is_array($pairs) ||
$pairs instanceof \Traversable
) {
103 $this->registerPrefixes($pairs);
106 case self
::ACT_AS_FALLBACK
:
107 $this->setFallbackAutoloader($pairs);
117 * Set flag indicating fallback autoloader status
120 * @return StandardAutoloader
122 public function setFallbackAutoloader($flag)
124 $this->fallbackAutoloaderFlag
= (bool) $flag;
129 * Is this autoloader acting as a fallback autoloader?
133 public function isFallbackAutoloader()
135 return $this->fallbackAutoloaderFlag
;
139 * Register a namespace/directory pair
141 * @param string $namespace
142 * @param string $directory
143 * @return StandardAutoloader
145 public function registerNamespace($namespace, $directory)
147 $namespace = rtrim($namespace, self
::NS_SEPARATOR
) . self
::NS_SEPARATOR
;
148 $this->namespaces
[$namespace] = $this->normalizeDirectory($directory);
153 * Register many namespace/directory pairs at once
155 * @param array $namespaces
156 * @throws Exception\InvalidArgumentException
157 * @return StandardAutoloader
159 public function registerNamespaces($namespaces)
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');
166 foreach ($namespaces as $namespace => $directory) {
167 $this->registerNamespace($namespace, $directory);
173 * Register a prefix/directory pair
175 * @param string $prefix
176 * @param string $directory
177 * @return StandardAutoloader
179 public function registerPrefix($prefix, $directory)
181 $prefix = rtrim($prefix, self
::PREFIX_SEPARATOR
). self
::PREFIX_SEPARATOR
;
182 $this->prefixes
[$prefix] = $this->normalizeDirectory($directory);
187 * Register many namespace/directory pairs at once
189 * @param array $prefixes
190 * @throws Exception\InvalidArgumentException
191 * @return StandardAutoloader
193 public function registerPrefixes($prefixes)
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');
200 foreach ($prefixes as $prefix => $directory) {
201 $this->registerPrefix($prefix, $directory);
207 * Defined by Autoloadable; autoload a class
209 * @param string $class
210 * @return false|string
212 public function autoload($class)
214 $isFallback = $this->isFallbackAutoloader();
215 if (false !== strpos($class, self
::NS_SEPARATOR
)) {
216 if ($this->loadClass($class, self
::LOAD_NS
)) {
218 } elseif ($isFallback) {
219 return $this->loadClass($class, self
::ACT_AS_FALLBACK
);
223 if (false !== strpos($class, self
::PREFIX_SEPARATOR
)) {
224 if ($this->loadClass($class, self
::LOAD_PREFIX
)) {
226 } elseif ($isFallback) {
227 return $this->loadClass($class, self
::ACT_AS_FALLBACK
);
232 return $this->loadClass($class, self
::ACT_AS_FALLBACK
);
238 * Register the autoloader with spl_autoload
242 public function register()
244 spl_autoload_register([$this, 'autoload']);
248 * Transform the class name to a filename
250 * @param string $class
251 * @param string $directory
254 protected function transformClassNameToFilename($class, $directory)
256 // $class may contain a namespace portion, in which case we need
257 // to preserve any underscores in that portion.
259 preg_match('/(?P<namespace>.+\\\)?(?P<class>[^\\\]+$)/', $class, $matches);
261 $class = (isset($matches['class'])) ?
$matches['class'] : '';
262 $namespace = (isset($matches['namespace'])) ?
$matches['namespace'] : '';
265 . str_replace(self
::NS_SEPARATOR
, '/', $namespace)
266 . str_replace(self
::PREFIX_SEPARATOR
, '/', $class)
271 * Load a class, based on its type (namespaced or prefixed)
273 * @param string $class
274 * @param string $type
275 * @return bool|string
276 * @throws Exception\InvalidArgumentException
278 protected function loadClass($class, $type)
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();
285 // Fallback autoloading
286 if ($type === self
::ACT_AS_FALLBACK
) {
288 $filename = $this->transformClassNameToFilename($class, '');
289 $resolvedName = stream_resolve_include_path($filename);
290 if ($resolvedName !== false) {
291 return include $resolvedName;
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));
303 $filename = $this->transformClassNameToFilename($trimmedClass, $path);
304 if (file_exists($filename)) {
305 return include $filename;
313 * Normalize the directory to include a trailing directory separator
315 * @param string $directory
318 protected function normalizeDirectory($directory)
320 $last = $directory[strlen($directory) - 1];
321 if (in_array($last, ['/', '\\'])) {
322 $directory[strlen($directory) - 1] = DIRECTORY_SEPARATOR
;
325 $directory .= DIRECTORY_SEPARATOR
;