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