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