annotate vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php @ 19:fa3358dc1485 tip

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