PluginProbe
UpStream: a Project Management Plugin for WordPress / trunk
UpStream: a Project Management Plugin for WordPress vtrunk
trunk 1.39.0 1.39.1 1.39.2 1.39.3 2.0.7 2.1.0
upstream / vendor / symfony / debug / FatalErrorHandler / ClassNotFoundFatalErrorHandler.php

ClassNotFoundFatalErrorHandler.php in UpStream: a Project Management Plugin for WordPress trunk, at vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php

199 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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\FatalErrorHandler;
13
14 use Composer\Autoload\ClassLoader as ComposerClassLoader;
15 use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
16 use Symfony\Component\Debug\DebugClassLoader;
17 use Symfony\Component\Debug\Exception\ClassNotFoundException;
18 use Symfony\Component\Debug\Exception\FatalErrorException;
19
20 /**
21 * ErrorHandler for classes that do not exist.
22 *
23 * @author Fabien Potencier <fabien@symfony.com>
24 */
25 class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
26 {
27 /**
28 * {@inheritdoc}
29 */
30 public function handleError(array $error, FatalErrorException $exception)
31 {
32 if (!preg_match('/^(Class|Interface|Trait) [\'"]([^\'"]+)[\'"] not found$/', $error['message'], $matches)) {
33 return null;
34 }
35 $typeName = strtolower($matches[1]);
36 $fullyQualifiedClassName = $matches[2];
37
38 if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
39 $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
40 $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
41 $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
42 $tail = ' for another namespace?';
43 } else {
44 $className = $fullyQualifiedClassName;
45 $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
46 $tail = '?';
47 }
48
49 if ($candidates = $this->getClassCandidates($className)) {
50 $tail = array_pop($candidates).'"?';
51 if ($candidates) {
52 $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
53 } else {
54 $tail = ' for "'.$tail;
55 }
56 }
57 $message .= "\nDid you forget a \"use\" statement".$tail;
58
59 return new ClassNotFoundException($message, $exception);
60 }
61
62 /**
63 * Tries to guess the full namespace for a given class name.
64 *
65 * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
66 * autoloader (that should cover all common cases).
67 *
68 * @param string $class A class name (without its namespace)
69 *
70 * @return array An array of possible fully qualified class names
71 */
72 private function getClassCandidates($class)
73 {
74 if (!\is_array($functions = spl_autoload_functions())) {
75 return [];
76 }
77
78 // find Symfony and Composer autoloaders
79 $classes = [];
80
81 foreach ($functions as $function) {
82 if (!\is_array($function)) {
83 continue;
84 }
85 // get class loaders wrapped by DebugClassLoader
86 if ($function[0] instanceof DebugClassLoader) {
87 $function = $function[0]->getClassLoader();
88
89 if (!\is_array($function)) {
90 continue;
91 }
92 }
93
94 if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
95 foreach ($function[0]->getPrefixes() as $prefix => $paths) {
96 foreach ($paths as $path) {
97 $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
98 }
99 }
100 }
101 if ($function[0] instanceof ComposerClassLoader) {
102 foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
103 foreach ($paths as $path) {
104 $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
105 }
106 }
107 }
108 }
109
110 return array_unique($classes);
111 }
112
113 /**
114 * @param string $path
115 * @param string $class
116 * @param string $prefix
117 *
118 * @return array
119 */
120 private function findClassInPath($path, $class, $prefix)
121 {
122 if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
123 return [];
124 }
125
126 $classes = [];
127 $filename = $class.'.php';
128 foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
129 if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
130 $classes[] = $class;
131 }
132 }
133
134 return $classes;
135 }
136
137 /**
138 * @param string $path
139 * @param string $file
140 * @param string $prefix
141 *
142 * @return string|null
143 */
144 private function convertFileToClass($path, $file, $prefix)
145 {
146 $candidates = [
147 // namespaced class
148 $namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file),
149 // namespaced class (with target dir)
150 $prefix.$namespacedClass,
151 // namespaced class (with target dir and separator)
152 $prefix.'\\'.$namespacedClass,
153 // PEAR class
154 str_replace('\\', '_', $namespacedClass),
155 // PEAR class (with target dir)
156 str_replace('\\', '_', $prefix.$namespacedClass),
157 // PEAR class (with target dir and separator)
158 str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
159 ];
160
161 if ($prefix) {
162 $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
163 }
164
165 // We cannot use the autoloader here as most of them use require; but if the class
166 // is not found, the new autoloader call will require the file again leading to a
167 // "cannot redeclare class" error.
168 foreach ($candidates as $candidate) {
169 if ($this->classExists($candidate)) {
170 return $candidate;
171 }
172 }
173
174 try {
175 require_once $file;
176 } catch (\Throwable $e) {
177 return null;
178 }
179
180 foreach ($candidates as $candidate) {
181 if ($this->classExists($candidate)) {
182 return $candidate;
183 }
184 }
185
186 return null;
187 }
188
189 /**
190 * @param string $class
191 *
192 * @return bool
193 */
194 private function classExists($class)
195 {
196 return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
197 }
198 }
199