Subversion Repositories PHPX

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
40 PointedEar 1
<?php
2
/**
3
 * Zend Framework (http://framework.zend.com/)
4
 *
5
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7
 * @license   http://framework.zend.com/license/new-bsd New BSD License
8
 */
9
 
10
namespace Zend\Loader;
11
 
12
// Grab SplAutoloader interface
13
require_once __DIR__ . '/SplAutoloader.php';
14
 
15
/**
16
 * PSR-0 compliant autoloader
17
 *
18
 * Allows autoloading both namespaced and vendor-prefixed classes. Class
19
 * lookups are performed on the filesystem. If a class file for the referenced
20
 * class is not found, a PHP warning will be raised by include().
21
 */
22
class StandardAutoloader implements SplAutoloader
23
{
24
    const NS_SEPARATOR     = '\\';
25
    const PREFIX_SEPARATOR = '_';
26
    const LOAD_NS          = 'namespaces';
27
    const LOAD_PREFIX      = 'prefixes';
28
    const ACT_AS_FALLBACK  = 'fallback_autoloader';
29
    const AUTOREGISTER_ZF  = 'autoregister_zf';
30
 
31
    /**
32
     * @var array Namespace/directory pairs to search; ZF library added by default
33
     */
34
    protected $namespaces = array();
35
 
36
    /**
37
     * @var array Prefix/directory pairs to search
38
     */
39
    protected $prefixes = array();
40
 
41
    /**
42
     * @var bool Whether or not the autoloader should also act as a fallback autoloader
43
     */
44
    protected $fallbackAutoloaderFlag = false;
45
 
46
    /**
47
     * Constructor
48
     *
49
     * @param  null|array|\Traversable $options
50
     */
51
    public function __construct($options = null)
52
    {
53
        if (null !== $options) {
54
            $this->setOptions($options);
55
        }
56
    }
57
 
58
    /**
59
     * Configure autoloader
60
     *
61
     * Allows specifying both "namespace" and "prefix" pairs, using the
62
     * following structure:
63
     * <code>
64
     * array(
65
     *     'namespaces' => array(
66
     *         'Zend'     => '/path/to/Zend/library',
67
     *         'Doctrine' => '/path/to/Doctrine/library',
68
     *     ),
69
     *     'prefixes' => array(
70
     *         'Phly_'     => '/path/to/Phly/library',
71
     *     ),
72
     *     'fallback_autoloader' => true,
73
     * )
74
     * </code>
75
     *
76
     * @param  array|\Traversable $options
77
     * @throws Exception\InvalidArgumentException
78
     * @return StandardAutoloader
79
     */
80
    public function setOptions($options)
81
    {
82
        if (!is_array($options) && !($options instanceof \Traversable)) {
83
            require_once __DIR__ . '/Exception/InvalidArgumentException.php';
84
            throw new Exception\InvalidArgumentException('Options must be either an array or Traversable');
85
        }
86
 
87
        foreach ($options as $type => $pairs) {
88
            switch ($type) {
89
                case self::AUTOREGISTER_ZF:
90
                    if ($pairs) {
91
                        $this->registerNamespace('Zend', dirname(__DIR__));
92
                    }
93
                    break;
94
                case self::LOAD_NS:
95
                    if (is_array($pairs) || $pairs instanceof \Traversable) {
96
                        $this->registerNamespaces($pairs);
97
                    }
98
                    break;
99
                case self::LOAD_PREFIX:
100
                    if (is_array($pairs) || $pairs instanceof \Traversable) {
101
                        $this->registerPrefixes($pairs);
102
                    }
103
                    break;
104
                case self::ACT_AS_FALLBACK:
105
                    $this->setFallbackAutoloader($pairs);
106
                    break;
107
                default:
108
                    // ignore
109
            }
110
        }
111
        return $this;
112
    }
113
 
114
    /**
115
     * Set flag indicating fallback autoloader status
116
     *
117
     * @param  bool $flag
118
     * @return StandardAutoloader
119
     */
120
    public function setFallbackAutoloader($flag)
121
    {
122
        $this->fallbackAutoloaderFlag = (bool) $flag;
123
        return $this;
124
    }
125
 
126
    /**
127
     * Is this autoloader acting as a fallback autoloader?
128
     *
129
     * @return bool
130
     */
131
    public function isFallbackAutoloader()
132
    {
133
        return $this->fallbackAutoloaderFlag;
134
    }
135
 
136
    /**
137
     * Register a namespace/directory pair
138
     *
139
     * @param  string $namespace
140
     * @param  string $directory
141
     * @return StandardAutoloader
142
     */
143
    public function registerNamespace($namespace, $directory)
144
    {
145
        $namespace = rtrim($namespace, self::NS_SEPARATOR) . self::NS_SEPARATOR;
146
        $this->namespaces[$namespace] = $this->normalizeDirectory($directory);
147
        return $this;
148
    }
149
 
150
    /**
151
     * Register many namespace/directory pairs at once
152
     *
153
     * @param  array $namespaces
154
     * @throws Exception\InvalidArgumentException
155
     * @return StandardAutoloader
156
     */
157
    public function registerNamespaces($namespaces)
158
    {
159
        if (!is_array($namespaces) && !$namespaces instanceof \Traversable) {
160
            require_once __DIR__ . '/Exception/InvalidArgumentException.php';
161
            throw new Exception\InvalidArgumentException('Namespace pairs must be either an array or Traversable');
162
        }
163
 
164
        foreach ($namespaces as $namespace => $directory) {
165
            $this->registerNamespace($namespace, $directory);
166
        }
167
        return $this;
168
    }
169
 
170
    /**
171
     * Register a prefix/directory pair
172
     *
173
     * @param  string $prefix
174
     * @param  string $directory
175
     * @return StandardAutoloader
176
     */
177
    public function registerPrefix($prefix, $directory)
178
    {
179
        $prefix = rtrim($prefix, self::PREFIX_SEPARATOR). self::PREFIX_SEPARATOR;
180
        $this->prefixes[$prefix] = $this->normalizeDirectory($directory);
181
        return $this;
182
    }
183
 
184
    /**
185
     * Register many namespace/directory pairs at once
186
     *
187
     * @param  array $prefixes
188
     * @throws Exception\InvalidArgumentException
189
     * @return StandardAutoloader
190
     */
191
    public function registerPrefixes($prefixes)
192
    {
193
        if (!is_array($prefixes) && !$prefixes instanceof \Traversable) {
194
            require_once __DIR__ . '/Exception/InvalidArgumentException.php';
195
            throw new Exception\InvalidArgumentException('Prefix pairs must be either an array or Traversable');
196
        }
197
 
198
        foreach ($prefixes as $prefix => $directory) {
199
            $this->registerPrefix($prefix, $directory);
200
        }
201
        return $this;
202
    }
203
 
204
    /**
205
     * Defined by Autoloadable; autoload a class
206
     *
207
     * @param  string $class
208
     * @return false|string
209
     */
210
    public function autoload($class)
211
    {
212
        $isFallback = $this->isFallbackAutoloader();
213
        if (false !== strpos($class, self::NS_SEPARATOR)) {
214
            if ($this->loadClass($class, self::LOAD_NS)) {
215
                return $class;
216
            } elseif ($isFallback) {
217
                return $this->loadClass($class, self::ACT_AS_FALLBACK);
218
            }
219
            return false;
220
        }
221
        if (false !== strpos($class, self::PREFIX_SEPARATOR)) {
222
            if ($this->loadClass($class, self::LOAD_PREFIX)) {
223
                return $class;
224
            } elseif ($isFallback) {
225
                return $this->loadClass($class, self::ACT_AS_FALLBACK);
226
            }
227
            return false;
228
        }
229
        if ($isFallback) {
230
            return $this->loadClass($class, self::ACT_AS_FALLBACK);
231
        }
232
        return false;
233
    }
234
 
235
    /**
236
     * Register the autoloader with spl_autoload
237
     *
238
     * @return void
239
     */
240
    public function register()
241
    {
242
        spl_autoload_register(array($this, 'autoload'));
243
    }
244
 
245
    /**
246
     * Transform the class name to a filename
247
     *
248
     * @param  string $class
249
     * @param  string $directory
250
     * @return string
251
     */
252
    protected function transformClassNameToFilename($class, $directory)
253
    {
254
        // $class may contain a namespace portion, in  which case we need
255
        // to preserve any underscores in that portion.
256
        $matches = array();
257
        preg_match('/(?P<namespace>.+\\\)?(?P<class>[^\\\]+$)/', $class, $matches);
258
 
259
        $class     = (isset($matches['class'])) ? $matches['class'] : '';
260
        $namespace = (isset($matches['namespace'])) ? $matches['namespace'] : '';
261
 
262
        return $directory
263
             . str_replace(self::NS_SEPARATOR, '/', $namespace)
264
             . str_replace(self::PREFIX_SEPARATOR, '/', $class)
265
             . '.php';
266
    }
267
 
268
    /**
269
     * Load a class, based on its type (namespaced or prefixed)
270
     *
271
     * @param  string $class
272
     * @param  string $type
273
     * @return bool|string
274
     * @throws Exception\InvalidArgumentException
275
     */
276
    protected function loadClass($class, $type)
277
    {
278
        if (!in_array($type, array(self::LOAD_NS, self::LOAD_PREFIX, self::ACT_AS_FALLBACK))) {
279
            require_once __DIR__ . '/Exception/InvalidArgumentException.php';
280
            throw new Exception\InvalidArgumentException();
281
        }
282
 
283
        // Fallback autoloading
284
        if ($type === self::ACT_AS_FALLBACK) {
285
            // create filename
286
            $filename     = $this->transformClassNameToFilename($class, '');
287
            $resolvedName = stream_resolve_include_path($filename);
288
            if ($resolvedName !== false) {
289
                return include $resolvedName;
290
            }
291
            return false;
292
        }
293
 
294
        // Namespace and/or prefix autoloading
295
        foreach ($this->$type as $leader => $path) {
296
            if (0 === strpos($class, $leader)) {
297
                // Trim off leader (namespace or prefix)
298
                $trimmedClass = substr($class, strlen($leader));
299
 
300
                // create filename
301
                $filename = $this->transformClassNameToFilename($trimmedClass, $path);
302
                if (file_exists($filename)) {
303
                    return include $filename;
304
                }
305
                return false;
306
            }
307
        }
308
        return false;
309
    }
310
 
311
    /**
312
     * Normalize the directory to include a trailing directory separator
313
     *
314
     * @param  string $directory
315
     * @return string
316
     */
317
    protected function normalizeDirectory($directory)
318
    {
319
        $last = $directory[strlen($directory) - 1];
320
        if (in_array($last, array('/', '\\'))) {
321
            $directory[strlen($directory) - 1] = DIRECTORY_SEPARATOR;
322
            return $directory;
323
        }
324
        $directory .= DIRECTORY_SEPARATOR;
325
        return $directory;
326
    }
327
}