Projekt

Obecné

Profil

Stáhnout (11.7 KB) Statistiky
| Větev: | Revize:
1
<?php
2

    
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
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 <fabien@symfony.com>
22
 * @author Christophe Coevoet <stof@notk.org>
23
 * @author Nicolas Grekas <p@tchwork.com>
24
 */
25
class DebugClassLoader
26
{
27
    private $classLoader;
28
    private $isFinder;
29
    private static $caseCheck;
30
    private static $deprecated = array();
31
    private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null');
32
    private static $darwinCache = array('/' => array('/', array()));
33

    
34
    /**
35
     * Constructor.
36
     *
37
     * @param callable $classLoader A class loader
38
     */
39
    public function __construct(callable $classLoader)
40
    {
41
        $this->classLoader = $classLoader;
42
        $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
43

    
44
        if (!isset(self::$caseCheck)) {
45
            $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR);
46
            $i = strrpos($file, DIRECTORY_SEPARATOR);
47
            $dir = substr($file, 0, 1 + $i);
48
            $file = substr($file, 1 + $i);
49
            $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
50
            $test = realpath($dir.$test);
51

    
52
            if (false === $test || false === $i) {
53
                // filesystem is case sensitive
54
                self::$caseCheck = 0;
55
            } elseif (substr($test, -strlen($file)) === $file) {
56
                // filesystem is case insensitive and realpath() normalizes the case of characters
57
                self::$caseCheck = 1;
58
            } elseif (false !== stripos(PHP_OS, 'darwin')) {
59
                // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
60
                self::$caseCheck = 2;
61
            } else {
62
                // filesystem case checks failed, fallback to disabling them
63
                self::$caseCheck = 0;
64
            }
65
        }
66
    }
67

    
68
    /**
69
     * Gets the wrapped class loader.
70
     *
71
     * @return callable The wrapped class loader
72
     */
73
    public function getClassLoader()
74
    {
75
        return $this->classLoader;
76
    }
77

    
78
    /**
79
     * Wraps all autoloaders.
80
     */
81
    public static function enable()
82
    {
83
        // Ensures we don't hit https://bugs.php.net/42098
84
        class_exists('Symfony\Component\Debug\ErrorHandler');
85
        class_exists('Psr\Log\LogLevel');
86

    
87
        if (!is_array($functions = spl_autoload_functions())) {
88
            return;
89
        }
90

    
91
        foreach ($functions as $function) {
92
            spl_autoload_unregister($function);
93
        }
94

    
95
        foreach ($functions as $function) {
96
            if (!is_array($function) || !$function[0] instanceof self) {
97
                $function = array(new static($function), 'loadClass');
98
            }
99

    
100
            spl_autoload_register($function);
101
        }
102
    }
103

    
104
    /**
105
     * Disables the wrapping.
106
     */
107
    public static function disable()
108
    {
109
        if (!is_array($functions = spl_autoload_functions())) {
110
            return;
111
        }
112

    
113
        foreach ($functions as $function) {
114
            spl_autoload_unregister($function);
115
        }
116

    
117
        foreach ($functions as $function) {
118
            if (is_array($function) && $function[0] instanceof self) {
119
                $function = $function[0]->getClassLoader();
120
            }
121

    
122
            spl_autoload_register($function);
123
        }
124
    }
125

    
126
    /**
127
     * Loads the given class or interface.
128
     *
129
     * @param string $class The name of the class
130
     *
131
     * @return bool|null True, if loaded
132
     *
133
     * @throws \RuntimeException
134
     */
135
    public function loadClass($class)
136
    {
137
        ErrorHandler::stackErrors();
138

    
139
        try {
140
            if ($this->isFinder) {
141
                if ($file = $this->classLoader[0]->findFile($class)) {
142
                    require_once $file;
143
                }
144
            } else {
145
                call_user_func($this->classLoader, $class);
146
                $file = false;
147
            }
148
        } finally {
149
            ErrorHandler::unstackErrors();
150
        }
151

    
152
        $exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
153

    
154
        if ('\\' === $class[0]) {
155
            $class = substr($class, 1);
156
        }
157

    
158
        if ($exists) {
159
            $refl = new \ReflectionClass($class);
160
            $name = $refl->getName();
161

    
162
            if ($name !== $class && 0 === strcasecmp($name, $class)) {
163
                throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name));
164
            }
165

    
166
            if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
167
                @trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
168
            } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
169
                self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]);
170
            } else {
171
                if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) {
172
                    $len = 0;
173
                    $ns = '';
174
                } else {
175
                    switch ($ns = substr($name, 0, $len)) {
176
                        case 'Symfony\Bridge\\':
177
                        case 'Symfony\Bundle\\':
178
                        case 'Symfony\Component\\':
179
                            $ns = 'Symfony\\';
180
                            $len = strlen($ns);
181
                            break;
182
                    }
183
                }
184
                $parent = get_parent_class($class);
185

    
186
                if (!$parent || strncmp($ns, $parent, $len)) {
187
                    if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) {
188
                        @trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
189
                    }
190

    
191
                    $parentInterfaces = array();
192
                    $deprecatedInterfaces = array();
193
                    if ($parent) {
194
                        foreach (class_implements($parent) as $interface) {
195
                            $parentInterfaces[$interface] = 1;
196
                        }
197
                    }
198

    
199
                    foreach ($refl->getInterfaceNames() as $interface) {
200
                        if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
201
                            $deprecatedInterfaces[] = $interface;
202
                        }
203
                        foreach (class_implements($interface) as $interface) {
204
                            $parentInterfaces[$interface] = 1;
205
                        }
206
                    }
207

    
208
                    foreach ($deprecatedInterfaces as $interface) {
209
                        if (!isset($parentInterfaces[$interface])) {
210
                            @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);
211
                        }
212
                    }
213
                }
214
            }
215
        }
216

    
217
        if ($file) {
218
            if (!$exists) {
219
                if (false !== strpos($class, '/')) {
220
                    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));
221
                }
222

    
223
                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));
224
            }
225
            if (self::$caseCheck) {
226
                $real = explode('\\', $class.strrchr($file, '.'));
227
                $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file));
228

    
229
                $i = count($tail) - 1;
230
                $j = count($real) - 1;
231

    
232
                while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
233
                    --$i;
234
                    --$j;
235
                }
236

    
237
                array_splice($tail, 0, $i + 1);
238
            }
239
            if (self::$caseCheck && $tail) {
240
                $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail);
241
                $tailLen = strlen($tail);
242
                $real = $refl->getFileName();
243

    
244
                if (2 === self::$caseCheck) {
245
                    // realpath() on MacOSX doesn't normalize the case of characters
246

    
247
                    $i = 1 + strrpos($real, '/');
248
                    $file = substr($real, $i);
249
                    $real = substr($real, 0, $i);
250

    
251
                    if (isset(self::$darwinCache[$real])) {
252
                        $kDir = $real;
253
                    } else {
254
                        $kDir = strtolower($real);
255

    
256
                        if (isset(self::$darwinCache[$kDir])) {
257
                            $real = self::$darwinCache[$kDir][0];
258
                        } else {
259
                            $dir = getcwd();
260
                            chdir($real);
261
                            $real = getcwd().'/';
262
                            chdir($dir);
263

    
264
                            $dir = $real;
265
                            $k = $kDir;
266
                            $i = strlen($dir) - 1;
267
                            while (!isset(self::$darwinCache[$k])) {
268
                                self::$darwinCache[$k] = array($dir, array());
269
                                self::$darwinCache[$dir] = &self::$darwinCache[$k];
270

    
271
                                while ('/' !== $dir[--$i]) {
272
                                }
273
                                $k = substr($k, 0, ++$i);
274
                                $dir = substr($dir, 0, $i--);
275
                            }
276
                        }
277
                    }
278

    
279
                    $dirFiles = self::$darwinCache[$kDir][1];
280

    
281
                    if (isset($dirFiles[$file])) {
282
                        $kFile = $file;
283
                    } else {
284
                        $kFile = strtolower($file);
285

    
286
                        if (!isset($dirFiles[$kFile])) {
287
                            foreach (scandir($real, 2) as $f) {
288
                                if ('.' !== $f[0]) {
289
                                    $dirFiles[$f] = $f;
290
                                    if ($f === $file) {
291
                                        $kFile = $k = $file;
292
                                    } elseif ($f !== $k = strtolower($f)) {
293
                                        $dirFiles[$k] = $f;
294
                                    }
295
                                }
296
                            }
297
                            self::$darwinCache[$kDir][1] = $dirFiles;
298
                        }
299
                    }
300

    
301
                    $real .= $dirFiles[$kFile];
302
                }
303

    
304
                if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
305
                  && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
306
                ) {
307
                    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)));
308
                }
309
            }
310

    
311
            return true;
312
        }
313
    }
314
}
(5-5/11)