Completed
Push — master ( 93f6e0...a7ea34 )
by Aydin
27:25 queued 18:55
created

ClassLoader::findFile()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 29
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 29
rs 6.7273
cc 7
eloc 13
nc 12
nop 1
1
<?php
2
3
/*
4
 * This file is part of Composer.
5
 *
6
 * (c) Nils Adermann <[email protected]>
7
 *     Jordi Boggiano <[email protected]>
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Composer\Autoload;
14
15
/**
16
 * ClassLoader implements a PSR-0 class loader
17
 *
18
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
19
 *
20
 *     $loader = new \Composer\Autoload\ClassLoader();
21
 *
22
 *     // register classes with namespaces
23
 *     $loader->add('Symfony\Component', __DIR__.'/component');
24
 *     $loader->add('Symfony',           __DIR__.'/framework');
25
 *
26
 *     // activate the autoloader
27
 *     $loader->register();
28
 *
29
 *     // to enable searching the include path (eg. for PEAR packages)
30
 *     $loader->setUseIncludePath(true);
31
 *
32
 * In this example, if you try to use a class in the Symfony\Component
33
 * namespace or one of its children (Symfony\Component\Console for instance),
34
 * the autoloader will first look for the class under the component/
35
 * directory, and it will then fallback to the framework/ directory if not
36
 * found before giving up.
37
 *
38
 * This class is loosely based on the Symfony UniversalClassLoader.
39
 *
40
 * @author Fabien Potencier <[email protected]>
41
 * @author Jordi Boggiano <[email protected]>
42
 */
43
class ClassLoader
44
{
45
    // PSR-4
46
    private $prefixLengthsPsr4 = array();
47
    private $prefixDirsPsr4 = array();
48
    private $fallbackDirsPsr4 = array();
49
50
    // PSR-0
51
    private $prefixesPsr0 = array();
52
    private $fallbackDirsPsr0 = array();
53
54
    private $useIncludePath = false;
55
    private $classMap = array();
56
57
    private $classMapAuthoritative = false;
58
59
    public function getPrefixes()
60
    {
61
        if (!empty($this->prefixesPsr0)) {
62
            return call_user_func_array('array_merge', $this->prefixesPsr0);
63
        }
64
65
        return array();
66
    }
67
68
    public function getPrefixesPsr4()
69
    {
70
        return $this->prefixDirsPsr4;
71
    }
72
73
    public function getFallbackDirs()
74
    {
75
        return $this->fallbackDirsPsr0;
76
    }
77
78
    public function getFallbackDirsPsr4()
79
    {
80
        return $this->fallbackDirsPsr4;
81
    }
82
83
    public function getClassMap()
84
    {
85
        return $this->classMap;
86
    }
87
88
    /**
89
     * @param array $classMap Class to filename map
90
     */
91
    public function addClassMap(array $classMap)
92
    {
93
        if ($this->classMap) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->classMap of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
94
            $this->classMap = array_merge($this->classMap, $classMap);
95
        } else {
96
            $this->classMap = $classMap;
97
        }
98
    }
99
100
    /**
101
     * Registers a set of PSR-0 directories for a given prefix, either
102
     * appending or prepending to the ones previously set for this prefix.
103
     *
104
     * @param string       $prefix  The prefix
105
     * @param array|string $paths   The PSR-0 root directories
106
     * @param bool         $prepend Whether to prepend the directories
107
     */
108
    public function add($prefix, $paths, $prepend = false)
109
    {
110 View Code Duplication
        if (!$prefix) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
            if ($prepend) {
112
                $this->fallbackDirsPsr0 = array_merge(
113
                    (array) $paths,
114
                    $this->fallbackDirsPsr0
115
                );
116
            } else {
117
                $this->fallbackDirsPsr0 = array_merge(
118
                    $this->fallbackDirsPsr0,
119
                    (array) $paths
120
                );
121
            }
122
123
            return;
124
        }
125
126
        $first = $prefix[0];
127 View Code Duplication
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
128
            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
129
130
            return;
131
        }
132
        if ($prepend) {
133
            $this->prefixesPsr0[$first][$prefix] = array_merge(
134
                (array) $paths,
135
                $this->prefixesPsr0[$first][$prefix]
136
            );
137 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138
            $this->prefixesPsr0[$first][$prefix] = array_merge(
139
                $this->prefixesPsr0[$first][$prefix],
140
                (array) $paths
141
            );
142
        }
143
    }
144
145
    /**
146
     * Registers a set of PSR-4 directories for a given namespace, either
147
     * appending or prepending to the ones previously set for this namespace.
148
     *
149
     * @param string       $prefix  The prefix/namespace, with trailing '\\'
150
     * @param array|string $paths   The PSR-0 base directories
151
     * @param bool         $prepend Whether to prepend the directories
152
     *
153
     * @throws \InvalidArgumentException
154
     */
155
    public function addPsr4($prefix, $paths, $prepend = false)
156
    {
157
        if (!$prefix) {
158
            // Register directories for the root namespace.
159 View Code Duplication
            if ($prepend) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
160
                $this->fallbackDirsPsr4 = array_merge(
161
                    (array) $paths,
162
                    $this->fallbackDirsPsr4
163
                );
164
            } else {
165
                $this->fallbackDirsPsr4 = array_merge(
166
                    $this->fallbackDirsPsr4,
167
                    (array) $paths
168
                );
169
            }
170 View Code Duplication
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
            // Register directories for a new namespace.
172
            $length = strlen($prefix);
173
            if ('\\' !== $prefix[$length - 1]) {
174
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
175
            }
176
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
177
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
178
        } elseif ($prepend) {
179
            // Prepend directories for an already registered namespace.
180
            $this->prefixDirsPsr4[$prefix] = array_merge(
181
                (array) $paths,
182
                $this->prefixDirsPsr4[$prefix]
183
            );
184
        } else {
185
            // Append directories for an already registered namespace.
186
            $this->prefixDirsPsr4[$prefix] = array_merge(
187
                $this->prefixDirsPsr4[$prefix],
188
                (array) $paths
189
            );
190
        }
191
    }
192
193
    /**
194
     * Registers a set of PSR-0 directories for a given prefix,
195
     * replacing any others previously set for this prefix.
196
     *
197
     * @param string       $prefix The prefix
198
     * @param array|string $paths  The PSR-0 base directories
199
     */
200
    public function set($prefix, $paths)
201
    {
202
        if (!$prefix) {
203
            $this->fallbackDirsPsr0 = (array) $paths;
204
        } else {
205
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
206
        }
207
    }
208
209
    /**
210
     * Registers a set of PSR-4 directories for a given namespace,
211
     * replacing any others previously set for this namespace.
212
     *
213
     * @param string       $prefix The prefix/namespace, with trailing '\\'
214
     * @param array|string $paths  The PSR-4 base directories
215
     *
216
     * @throws \InvalidArgumentException
217
     */
218
    public function setPsr4($prefix, $paths)
219
    {
220 View Code Duplication
        if (!$prefix) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
            $this->fallbackDirsPsr4 = (array) $paths;
222
        } else {
223
            $length = strlen($prefix);
224
            if ('\\' !== $prefix[$length - 1]) {
225
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
226
            }
227
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
228
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
229
        }
230
    }
231
232
    /**
233
     * Turns on searching the include path for class files.
234
     *
235
     * @param bool $useIncludePath
236
     */
237
    public function setUseIncludePath($useIncludePath)
238
    {
239
        $this->useIncludePath = $useIncludePath;
240
    }
241
242
    /**
243
     * Can be used to check if the autoloader uses the include path to check
244
     * for classes.
245
     *
246
     * @return bool
247
     */
248
    public function getUseIncludePath()
249
    {
250
        return $this->useIncludePath;
251
    }
252
253
    /**
254
     * Turns off searching the prefix and fallback directories for classes
255
     * that have not been registered with the class map.
256
     *
257
     * @param bool $classMapAuthoritative
258
     */
259
    public function setClassMapAuthoritative($classMapAuthoritative)
260
    {
261
        $this->classMapAuthoritative = $classMapAuthoritative;
262
    }
263
264
    /**
265
     * Should class lookup fail if not found in the current class map?
266
     *
267
     * @return bool
268
     */
269
    public function isClassMapAuthoritative()
270
    {
271
        return $this->classMapAuthoritative;
272
    }
273
274
    /**
275
     * Registers this instance as an autoloader.
276
     *
277
     * @param bool $prepend Whether to prepend the autoloader or not
278
     */
279
    public function register($prepend = false)
280
    {
281
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
282
    }
283
284
    /**
285
     * Unregisters this instance as an autoloader.
286
     */
287
    public function unregister()
288
    {
289
        spl_autoload_unregister(array($this, 'loadClass'));
290
    }
291
292
    /**
293
     * Loads the given class or interface.
294
     *
295
     * @param  string    $class The name of the class
296
     * @return bool|null True if loaded, null otherwise
297
     */
298
    public function loadClass($class)
299
    {
300
        if ($file = $this->findFile($class)) {
301
            includeFile($file);
302
303
            return true;
304
        }
305
    }
306
307
    /**
308
     * Finds the path to the file where the class is defined.
309
     *
310
     * @param string $class The name of the class
311
     *
312
     * @return string|false The path if found, false otherwise
313
     */
314
    public function findFile($class)
315
    {
316
        // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
317
        if ('\\' == $class[0]) {
318
            $class = substr($class, 1);
319
        }
320
321
        // class map lookup
322
        if (isset($this->classMap[$class])) {
323
            return $this->classMap[$class];
324
        }
325
        if ($this->classMapAuthoritative) {
326
            return false;
327
        }
328
329
        $file = $this->findFileWithExtension($class, '.php');
330
331
        // Search for Hack files if we are running on HHVM
332
        if ($file === null && defined('HHVM_VERSION')) {
333
            $file = $this->findFileWithExtension($class, '.hh');
334
        }
335
336
        if ($file === null) {
337
            // Remember that this class does not exist.
338
            return $this->classMap[$class] = false;
339
        }
340
341
        return $file;
342
    }
343
344
    private function findFileWithExtension($class, $ext)
345
    {
346
        // PSR-4 lookup
347
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
348
349
        $first = $class[0];
350 View Code Duplication
        if (isset($this->prefixLengthsPsr4[$first])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
351
            foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
352
                if (0 === strpos($class, $prefix)) {
353
                    foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
354
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
355
                            return $file;
356
                        }
357
                    }
358
                }
359
            }
360
        }
361
362
        // PSR-4 fallback dirs
363
        foreach ($this->fallbackDirsPsr4 as $dir) {
364
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
365
                return $file;
366
            }
367
        }
368
369
        // PSR-0 lookup
370
        if (false !== $pos = strrpos($class, '\\')) {
371
            // namespaced class name
372
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
373
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
374
        } else {
375
            // PEAR-like class name
376
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
377
        }
378
379 View Code Duplication
        if (isset($this->prefixesPsr0[$first])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
380
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
381
                if (0 === strpos($class, $prefix)) {
382
                    foreach ($dirs as $dir) {
383
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
384
                            return $file;
385
                        }
386
                    }
387
                }
388
            }
389
        }
390
391
        // PSR-0 fallback dirs
392
        foreach ($this->fallbackDirsPsr0 as $dir) {
393
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
394
                return $file;
395
            }
396
        }
397
398
        // PSR-0 include paths.
399
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
400
            return $file;
401
        }
402
    }
403
}
404
405
/**
406
 * Scope isolated include.
407
 *
408
 * Prevents access to $this/self from included files.
409
 */
410
function includeFile($file)
411
{
412
    include $file;
413
}
414