Completed
Pull Request — master (#2252)
by ྅༻ Ǭɀħ
02:31
created

ClassLoader   C

Complexity

Total Complexity 64

Size/Duplication

Total Lines 393
Duplicated Lines 16.79 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 66
loc 393
rs 5.8364
c 0
b 0
f 0
wmc 64
lcom 1
cbo 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A getPrefixes() 0 8 2
A getPrefixesPsr4() 0 4 1
A getFallbackDirs() 0 4 1
A getFallbackDirsPsr4() 0 4 1
A getClassMap() 0 4 1
A addClassMap() 0 8 2
B add() 26 36 5
B addPsr4() 20 37 6
A set() 0 8 2
A setPsr4() 10 13 3
A setUseIncludePath() 0 4 1
A getUseIncludePath() 0 4 1
A setClassMapAuthoritative() 0 4 1
A isClassMapAuthoritative() 0 4 1
A setApcuPrefix() 0 4 3
A getApcuPrefix() 0 4 1
A register() 0 4 1
A unregister() 0 4 1
A loadClass() 0 8 2
D findFile() 0 34 10
C findFileWithExtension() 10 65 18

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ClassLoader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClassLoader, and based on these observations, apply Extract Interface, too.

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, PSR-4 and classmap class loader.
17
 *
18
 *     $loader = new \Composer\Autoload\ClassLoader();
19
 *
20
 *     // register classes with namespaces
21
 *     $loader->add('Symfony\Component', __DIR__.'/component');
22
 *     $loader->add('Symfony',           __DIR__.'/framework');
23
 *
24
 *     // activate the autoloader
25
 *     $loader->register();
26
 *
27
 *     // to enable searching the include path (eg. for PEAR packages)
28
 *     $loader->setUseIncludePath(true);
29
 *
30
 * In this example, if you try to use a class in the Symfony\Component
31
 * namespace or one of its children (Symfony\Component\Console for instance),
32
 * the autoloader will first look for the class under the component/
33
 * directory, and it will then fallback to the framework/ directory if not
34
 * found before giving up.
35
 *
36
 * This class is loosely based on the Symfony UniversalClassLoader.
37
 *
38
 * @author Fabien Potencier <[email protected]>
39
 * @author Jordi Boggiano <[email protected]>
40
 * @see    http://www.php-fig.org/psr/psr-0/
41
 * @see    http://www.php-fig.org/psr/psr-4/
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
    private $classMapAuthoritative = false;
57
    private $missingClasses = array();
58
    private $apcuPrefix;
59
60
    public function getPrefixes()
61
    {
62
        if (!empty($this->prefixesPsr0)) {
63
            return call_user_func_array('array_merge', $this->prefixesPsr0);
64
        }
65
66
        return array();
67
    }
68
69
    public function getPrefixesPsr4()
70
    {
71
        return $this->prefixDirsPsr4;
72
    }
73
74
    public function getFallbackDirs()
75
    {
76
        return $this->fallbackDirsPsr0;
77
    }
78
79
    public function getFallbackDirsPsr4()
80
    {
81
        return $this->fallbackDirsPsr4;
82
    }
83
84
    public function getClassMap()
85
    {
86
        return $this->classMap;
87
    }
88
89
    /**
90
     * @param array $classMap Class to filename map
91
     */
92
    public function addClassMap(array $classMap)
93
    {
94
        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...
95
            $this->classMap = array_merge($this->classMap, $classMap);
96
        } else {
97
            $this->classMap = $classMap;
98
        }
99
    }
100
101
    /**
102
     * Registers a set of PSR-0 directories for a given prefix, either
103
     * appending or prepending to the ones previously set for this prefix.
104
     *
105
     * @param string       $prefix  The prefix
106
     * @param array|string $paths   The PSR-0 root directories
107
     * @param bool         $prepend Whether to prepend the directories
108
     */
109
    public function add($prefix, $paths, $prepend = false)
110
    {
111 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...
112
            if ($prepend) {
113
                $this->fallbackDirsPsr0 = array_merge(
114
                    (array) $paths,
115
                    $this->fallbackDirsPsr0
116
                );
117
            } else {
118
                $this->fallbackDirsPsr0 = array_merge(
119
                    $this->fallbackDirsPsr0,
120
                    (array) $paths
121
                );
122
            }
123
124
            return;
125
        }
126
127
        $first = $prefix[0];
128 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...
129
            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
130
131
            return;
132
        }
133
        if ($prepend) {
134
            $this->prefixesPsr0[$first][$prefix] = array_merge(
135
                (array) $paths,
136
                $this->prefixesPsr0[$first][$prefix]
137
            );
138 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...
139
            $this->prefixesPsr0[$first][$prefix] = array_merge(
140
                $this->prefixesPsr0[$first][$prefix],
141
                (array) $paths
142
            );
143
        }
144
    }
145
146
    /**
147
     * Registers a set of PSR-4 directories for a given namespace, either
148
     * appending or prepending to the ones previously set for this namespace.
149
     *
150
     * @param string       $prefix  The prefix/namespace, with trailing '\\'
151
     * @param array|string $paths   The PSR-4 base directories
152
     * @param bool         $prepend Whether to prepend the directories
153
     *
154
     * @throws \InvalidArgumentException
155
     */
156
    public function addPsr4($prefix, $paths, $prepend = false)
157
    {
158
        if (!$prefix) {
159
            // Register directories for the root namespace.
160 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...
161
                $this->fallbackDirsPsr4 = array_merge(
162
                    (array) $paths,
163
                    $this->fallbackDirsPsr4
164
                );
165
            } else {
166
                $this->fallbackDirsPsr4 = array_merge(
167
                    $this->fallbackDirsPsr4,
168
                    (array) $paths
169
                );
170
            }
171 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...
172
            // Register directories for a new namespace.
173
            $length = strlen($prefix);
174
            if ('\\' !== $prefix[$length - 1]) {
175
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
176
            }
177
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
178
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
179
        } elseif ($prepend) {
180
            // Prepend directories for an already registered namespace.
181
            $this->prefixDirsPsr4[$prefix] = array_merge(
182
                (array) $paths,
183
                $this->prefixDirsPsr4[$prefix]
184
            );
185
        } else {
186
            // Append directories for an already registered namespace.
187
            $this->prefixDirsPsr4[$prefix] = array_merge(
188
                $this->prefixDirsPsr4[$prefix],
189
                (array) $paths
190
            );
191
        }
192
    }
193
194
    /**
195
     * Registers a set of PSR-0 directories for a given prefix,
196
     * replacing any others previously set for this prefix.
197
     *
198
     * @param string       $prefix The prefix
199
     * @param array|string $paths  The PSR-0 base directories
200
     */
201
    public function set($prefix, $paths)
202
    {
203
        if (!$prefix) {
204
            $this->fallbackDirsPsr0 = (array) $paths;
205
        } else {
206
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
207
        }
208
    }
209
210
    /**
211
     * Registers a set of PSR-4 directories for a given namespace,
212
     * replacing any others previously set for this namespace.
213
     *
214
     * @param string       $prefix The prefix/namespace, with trailing '\\'
215
     * @param array|string $paths  The PSR-4 base directories
216
     *
217
     * @throws \InvalidArgumentException
218
     */
219
    public function setPsr4($prefix, $paths)
220
    {
221 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...
222
            $this->fallbackDirsPsr4 = (array) $paths;
223
        } else {
224
            $length = strlen($prefix);
225
            if ('\\' !== $prefix[$length - 1]) {
226
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
227
            }
228
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
229
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
230
        }
231
    }
232
233
    /**
234
     * Turns on searching the include path for class files.
235
     *
236
     * @param bool $useIncludePath
237
     */
238
    public function setUseIncludePath($useIncludePath)
239
    {
240
        $this->useIncludePath = $useIncludePath;
241
    }
242
243
    /**
244
     * Can be used to check if the autoloader uses the include path to check
245
     * for classes.
246
     *
247
     * @return bool
248
     */
249
    public function getUseIncludePath()
250
    {
251
        return $this->useIncludePath;
252
    }
253
254
    /**
255
     * Turns off searching the prefix and fallback directories for classes
256
     * that have not been registered with the class map.
257
     *
258
     * @param bool $classMapAuthoritative
259
     */
260
    public function setClassMapAuthoritative($classMapAuthoritative)
261
    {
262
        $this->classMapAuthoritative = $classMapAuthoritative;
263
    }
264
265
    /**
266
     * Should class lookup fail if not found in the current class map?
267
     *
268
     * @return bool
269
     */
270
    public function isClassMapAuthoritative()
271
    {
272
        return $this->classMapAuthoritative;
273
    }
274
275
    /**
276
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
277
     *
278
     * @param string|null $apcuPrefix
279
     */
280
    public function setApcuPrefix($apcuPrefix)
281
    {
282
        $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
283
    }
284
285
    /**
286
     * The APCu prefix in use, or null if APCu caching is not enabled.
287
     *
288
     * @return string|null
289
     */
290
    public function getApcuPrefix()
291
    {
292
        return $this->apcuPrefix;
293
    }
294
295
    /**
296
     * Registers this instance as an autoloader.
297
     *
298
     * @param bool $prepend Whether to prepend the autoloader or not
299
     */
300
    public function register($prepend = false)
301
    {
302
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
303
    }
304
305
    /**
306
     * Unregisters this instance as an autoloader.
307
     */
308
    public function unregister()
309
    {
310
        spl_autoload_unregister(array($this, 'loadClass'));
311
    }
312
313
    /**
314
     * Loads the given class or interface.
315
     *
316
     * @param  string    $class The name of the class
317
     * @return bool|null True if loaded, null otherwise
318
     */
319
    public function loadClass($class)
320
    {
321
        if ($file = $this->findFile($class)) {
322
            includeFile($file);
323
324
            return true;
325
        }
326
    }
327
328
    /**
329
     * Finds the path to the file where the class is defined.
330
     *
331
     * @param string $class The name of the class
332
     *
333
     * @return string|false The path if found, false otherwise
334
     */
335
    public function findFile($class)
336
    {
337
        // class map lookup
338
        if (isset($this->classMap[$class])) {
339
            return $this->classMap[$class];
340
        }
341
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
342
            return false;
343
        }
344
        if (null !== $this->apcuPrefix) {
345
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
1 ignored issue
show
Bug introduced by
The variable $hit does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
346
            if ($hit) {
347
                return $file;
348
            }
349
        }
350
351
        $file = $this->findFileWithExtension($class, '.php');
352
353
        // Search for Hack files if we are running on HHVM
354
        if (false === $file && defined('HHVM_VERSION')) {
355
            $file = $this->findFileWithExtension($class, '.hh');
356
        }
357
358
        if (null !== $this->apcuPrefix) {
359
            apcu_add($this->apcuPrefix.$class, $file);
360
        }
361
362
        if (false === $file) {
363
            // Remember that this class does not exist.
364
            $this->missingClasses[$class] = true;
365
        }
366
367
        return $file;
368
    }
369
370
    private function findFileWithExtension($class, $ext)
371
    {
372
        // PSR-4 lookup
373
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
374
375
        $first = $class[0];
376
        if (isset($this->prefixLengthsPsr4[$first])) {
377
            $subPath = $class;
378
            while (false !== $lastPos = strrpos($subPath, '\\')) {
379
                $subPath = substr($subPath, 0, $lastPos);
380
                $search = $subPath.'\\';
381
                if (isset($this->prefixDirsPsr4[$search])) {
382
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
383
                        $length = $this->prefixLengthsPsr4[$first][$search];
384
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
385
                            return $file;
386
                        }
387
                    }
388
                }
389
            }
390
        }
391
392
        // PSR-4 fallback dirs
393
        foreach ($this->fallbackDirsPsr4 as $dir) {
394
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
395
                return $file;
396
            }
397
        }
398
399
        // PSR-0 lookup
400
        if (false !== $pos = strrpos($class, '\\')) {
401
            // namespaced class name
402
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
403
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
404
        } else {
405
            // PEAR-like class name
406
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
407
        }
408
409
        if (isset($this->prefixesPsr0[$first])) {
410
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
411
                if (0 === strpos($class, $prefix)) {
412 View Code Duplication
                    foreach ($dirs as $dir) {
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...
413
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
414
                            return $file;
415
                        }
416
                    }
417
                }
418
            }
419
        }
420
421
        // PSR-0 fallback dirs
422 View Code Duplication
        foreach ($this->fallbackDirsPsr0 as $dir) {
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...
423
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
424
                return $file;
425
            }
426
        }
427
428
        // PSR-0 include paths.
429
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
430
            return $file;
431
        }
432
433
        return false;
434
    }
435
}
436
437
/**
438
 * Scope isolated include.
439
 *
440
 * Prevents access to $this/self from included files.
441
 */
442
function includeFile($file)
443
{
444
    include $file;
445
}
446