Completed
Push — master ( d1be6d...8440b6 )
by Andrey
07:58
created

DebugClassLoader   F

Complexity

Total Complexity 82

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 325
rs 2
c 0
b 0
f 0
wmc 82
lcom 1
cbo 1

6 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 36 11
A getClassLoader() 0 4 2
B enable() 0 22 6
A disable() 0 18 6
A findFile() 0 8 2
F loadClass() 0 187 55

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. 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 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);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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;
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())) {
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())) {
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();
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);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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);
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();
0 ignored issues
show
Bug introduced by
Consider using $refl->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
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);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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) {
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);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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
Bug Best Practice introduced by
The expression $tail 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...
275
                $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail);
0 ignored issues
show
Bug introduced by
The variable $tail does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
276
                $tailLen = strlen($tail);
277
                $real = $refl->getFileName();
0 ignored issues
show
Bug introduced by
The variable $refl does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
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
$k is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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