DebugClassLoader   F
last analyzed

Complexity

Total Complexity 82

Size/Duplication

Total Lines 322
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 170
c 0
b 0
f 0
dl 0
loc 322
rs 2
wmc 82

6 Methods

Rating   Name   Duplication   Size   Complexity  
A findFile() 0 6 2
B __construct() 0 33 11
A getClassLoader() 0 3 2
A disable() 0 16 6
F loadClass() 0 185 55
A enable() 0 20 6

How to fix   Complexity   

Complex Class

Complex classes like DebugClassLoader 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.

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 DebugClassLoader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Debug;
13
14
/**
15
 * Autoloader checking if the class is really defined in the file found.
16
 *
17
 * The ClassLoader will wrap all registered autoloaders
18
 * and will throw an exception if a file is found but does
19
 * not declare the class.
20
 *
21
 * @author Fabien Potencier <[email protected]>
22
 * @author Christophe Coevoet <[email protected]>
23
 * @author Nicolas Grekas <[email protected]>
24
 */
25
class DebugClassLoader
26
{
27
    private $classLoader;
28
    private $isFinder;
29
    private $wasFinder;
30
    private static $caseCheck;
31
    private static $deprecated = array();
32
    private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null');
33
    private static $darwinCache = array('/' => array('/', array()));
34
35
    /**
36
     * Constructor.
37
     *
38
     * @param callable|object $classLoader Passing an object is @deprecated since version 2.5 and support for it will be removed in 3.0
39
     */
40
    public function __construct($classLoader)
41
    {
42
        $this->wasFinder = is_object($classLoader) && method_exists($classLoader, 'findFile');
43
44
        if ($this->wasFinder) {
45
            @trigger_error('The '.__METHOD__.' method will no longer support receiving an object into its $classLoader argument in 3.0.', E_USER_DEPRECATED);
46
            $this->classLoader = array($classLoader, 'loadClass');
47
            $this->isFinder = true;
48
        } else {
49
            $this->classLoader = $classLoader;
50
            $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
51
        }
52
53
        if (!isset(self::$caseCheck)) {
54
            $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR);
55
            $i = strrpos($file, DIRECTORY_SEPARATOR);
56
            $dir = substr($file, 0, 1 + $i);
57
            $file = substr($file, 1 + $i);
58
            $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
59
            $test = realpath($dir.$test);
60
61
            if (false === $test || false === $i) {
62
                // filesystem is case sensitive
63
                self::$caseCheck = 0;
64
            } elseif (substr($test, -strlen($file)) === $file) {
65
                // filesystem is case insensitive and realpath() normalizes the case of characters
66
                self::$caseCheck = 1;
67
            } elseif (false !== stripos(PHP_OS, 'darwin')) {
68
                // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
69
                self::$caseCheck = 2;
70
            } else {
71
                // filesystem case checks failed, fallback to disabling them
72
                self::$caseCheck = 0;
73
            }
74
        }
75
    }
76
77
    /**
78
     * Gets the wrapped class loader.
79
     *
80
     * @return callable|object A class loader. Since version 2.5, returning an object is @deprecated and support for it will be removed in 3.0
81
     */
82
    public function getClassLoader()
83
    {
84
        return $this->wasFinder ? $this->classLoader[0] : $this->classLoader;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->wasFinder ...0] : $this->classLoader also could return the type object which is incompatible with the documented return type callable|object.
Loading history...
85
    }
86
87
    /**
88
     * Wraps all autoloaders.
89
     */
90
    public static function enable()
91
    {
92
        // Ensures we don't hit https://bugs.php.net/42098
93
        class_exists('Symfony\Component\Debug\ErrorHandler');
94
        class_exists('Psr\Log\LogLevel');
95
96
        if (!is_array($functions = spl_autoload_functions())) {
0 ignored issues
show
introduced by
The condition is_array($functions = spl_autoload_functions()) is always true.
Loading history...
97
            return;
98
        }
99
100
        foreach ($functions as $function) {
101
            spl_autoload_unregister($function);
102
        }
103
104
        foreach ($functions as $function) {
105
            if (!is_array($function) || !$function[0] instanceof self) {
106
                $function = array(new static($function), 'loadClass');
107
            }
108
109
            spl_autoload_register($function);
110
        }
111
    }
112
113
    /**
114
     * Disables the wrapping.
115
     */
116
    public static function disable()
117
    {
118
        if (!is_array($functions = spl_autoload_functions())) {
0 ignored issues
show
introduced by
The condition is_array($functions = spl_autoload_functions()) is always true.
Loading history...
119
            return;
120
        }
121
122
        foreach ($functions as $function) {
123
            spl_autoload_unregister($function);
124
        }
125
126
        foreach ($functions as $function) {
127
            if (is_array($function) && $function[0] instanceof self) {
128
                $function = $function[0]->getClassLoader();
0 ignored issues
show
Deprecated Code introduced by
The function Symfony\Component\Debug\...oader::getClassLoader() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

128
                $function = /** @scrutinizer ignore-deprecated */ $function[0]->getClassLoader();
Loading history...
129
            }
130
131
            spl_autoload_register($function);
132
        }
133
    }
134
135
    /**
136
     * Finds a file by class name.
137
     *
138
     * @param string $class A class name to resolve to file
139
     *
140
     * @return string|null
141
     *
142
     * @deprecated since version 2.5, to be removed in 3.0.
143
     */
144
    public function findFile($class)
145
    {
146
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED);
147
148
        if ($this->wasFinder) {
149
            return $this->classLoader[0]->findFile($class);
150
        }
151
    }
152
153
    /**
154
     * Loads the given class or interface.
155
     *
156
     * @param string $class The name of the class
157
     *
158
     * @return bool|null True, if loaded
159
     *
160
     * @throws \RuntimeException
161
     */
162
    public function loadClass($class)
163
    {
164
        ErrorHandler::stackErrors();
165
166
        try {
167
            if ($this->isFinder) {
168
                if ($file = $this->classLoader[0]->findFile($class)) {
169
                    require_once $file;
170
                }
171
            } else {
172
                call_user_func($this->classLoader, $class);
0 ignored issues
show
Bug introduced by
It seems like $this->classLoader can also be of type object and object; however, parameter $callback of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
                call_user_func(/** @scrutinizer ignore-type */ $this->classLoader, $class);
Loading history...
173
                $file = false;
174
            }
175
        } catch (\Exception $e) {
176
            ErrorHandler::unstackErrors();
177
178
            throw $e;
179
        } catch (\Throwable $e) {
180
            ErrorHandler::unstackErrors();
181
182
            throw $e;
183
        }
184
185
        ErrorHandler::unstackErrors();
186
187
        $exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
188
189
        if ($class && '\\' === $class[0]) {
190
            $class = substr($class, 1);
191
        }
192
193
        if ($exists) {
194
            $refl = new \ReflectionClass($class);
195
            $name = $refl->getName();
196
197
            if ($name !== $class && 0 === strcasecmp($name, $class)) {
198
                throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name));
199
            }
200
201
            if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
202
                @trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
203
            } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
204
                self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]);
205
            } else {
206
                if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) {
207
                    $len = 0;
208
                    $ns = '';
209
                } else {
210
                    switch ($ns = substr($name, 0, $len)) {
211
                        case 'Symfony\Bridge\\':
212
                        case 'Symfony\Bundle\\':
213
                        case 'Symfony\Component\\':
214
                            $ns = 'Symfony\\';
215
                            $len = strlen($ns);
216
                            break;
217
                    }
218
                }
219
                $parent = get_parent_class($class);
220
221
                if (!$parent || strncmp($ns, $parent, $len)) {
222
                    if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) {
223
                        @trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
224
                    }
225
226
                    $parentInterfaces = array();
227
                    $deprecatedInterfaces = array();
228
                    if ($parent) {
229
                        foreach (class_implements($parent) as $interface) {
230
                            $parentInterfaces[$interface] = 1;
231
                        }
232
                    }
233
234
                    foreach ($refl->getInterfaceNames() as $interface) {
235
                        if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
236
                            $deprecatedInterfaces[] = $interface;
237
                        }
238
                        foreach (class_implements($interface) as $interface) {
0 ignored issues
show
Comprehensibility Bug introduced by
$interface is overwriting a variable from outer foreach loop.
Loading history...
239
                            $parentInterfaces[$interface] = 1;
240
                        }
241
                    }
242
243
                    foreach ($deprecatedInterfaces as $interface) {
244
                        if (!isset($parentInterfaces[$interface])) {
245
                            @trigger_error(sprintf('The %s %s %s that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED);
246
                        }
247
                    }
248
                }
249
            }
250
        }
251
252
        if ($file) {
253
            if (!$exists) {
254
                if (false !== strpos($class, '/')) {
255
                    throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
256
                }
257
258
                throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
259
            }
260
            if (self::$caseCheck) {
261
                $real = explode('\\', $class.strrchr($file, '.'));
262
                $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file));
263
264
                $i = count($tail) - 1;
265
                $j = count($real) - 1;
266
267
                while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
268
                    --$i;
269
                    --$j;
270
                }
271
272
                array_splice($tail, 0, $i + 1);
273
            }
274
            if (self::$caseCheck && $tail) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tail does not seem to be defined for all execution paths leading up to this point.
Loading history...
275
                $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail);
276
                $tailLen = strlen($tail);
277
                $real = $refl->getFileName();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $refl does not seem to be defined for all execution paths leading up to this point.
Loading history...
278
279
                if (2 === self::$caseCheck) {
280
                    // realpath() on MacOSX doesn't normalize the case of characters
281
282
                    $i = 1 + strrpos($real, '/');
283
                    $file = substr($real, $i);
284
                    $real = substr($real, 0, $i);
285
286
                    if (isset(self::$darwinCache[$real])) {
287
                        $kDir = $real;
288
                    } else {
289
                        $kDir = strtolower($real);
290
291
                        if (isset(self::$darwinCache[$kDir])) {
292
                            $real = self::$darwinCache[$kDir][0];
293
                        } else {
294
                            $dir = getcwd();
295
                            chdir($real);
296
                            $real = getcwd().'/';
297
                            chdir($dir);
298
299
                            $dir = $real;
300
                            $k = $kDir;
301
                            $i = strlen($dir) - 1;
302
                            while (!isset(self::$darwinCache[$k])) {
303
                                self::$darwinCache[$k] = array($dir, array());
304
                                self::$darwinCache[$dir] = &self::$darwinCache[$k];
305
306
                                while ('/' !== $dir[--$i]) {
307
                                }
308
                                $k = substr($k, 0, ++$i);
309
                                $dir = substr($dir, 0, $i--);
310
                            }
311
                        }
312
                    }
313
314
                    $dirFiles = self::$darwinCache[$kDir][1];
315
316
                    if (isset($dirFiles[$file])) {
317
                        $kFile = $file;
318
                    } else {
319
                        $kFile = strtolower($file);
320
321
                        if (!isset($dirFiles[$kFile])) {
322
                            foreach (scandir($real, 2) as $f) {
323
                                if ('.' !== $f[0]) {
324
                                    $dirFiles[$f] = $f;
325
                                    if ($f === $file) {
326
                                        $kFile = $k = $file;
0 ignored issues
show
Unused Code introduced by
The assignment to $k is dead and can be removed.
Loading history...
327
                                    } elseif ($f !== $k = strtolower($f)) {
328
                                        $dirFiles[$k] = $f;
329
                                    }
330
                                }
331
                            }
332
                            self::$darwinCache[$kDir][1] = $dirFiles;
333
                        }
334
                    }
335
336
                    $real .= $dirFiles[$kFile];
337
                }
338
339
                if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
340
                  && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
341
                ) {
342
                    throw new \RuntimeException(sprintf('Case mismatch between class and real file names: %s vs %s in %s', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)));
343
                }
344
            }
345
346
            return true;
347
        }
348
    }
349
}
350