| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 | <?php/* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */namespace Symfony\Component\Debug\FatalErrorHandler;use Composer\Autoload\ClassLoader as ComposerClassLoader;use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;use Symfony\Component\Debug\DebugClassLoader;use Symfony\Component\Debug\Exception\ClassNotFoundException;use Symfony\Component\Debug\Exception\FatalErrorException;/** * ErrorHandler for classes that do not exist. * * @author Fabien Potencier <fabien@symfony.com> */class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface{    /**     * {@inheritdoc}     */    public function handleError(array $error, FatalErrorException $exception)    {        $messageLen = \strlen($error['message']);        $notFoundSuffix = '\' not found';        $notFoundSuffixLen = \strlen($notFoundSuffix);        if ($notFoundSuffixLen > $messageLen) {            return;        }        if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {            return;        }        foreach (array('class', 'interface', 'trait') as $typeName) {            $prefix = ucfirst($typeName).' \'';            $prefixLen = \strlen($prefix);            if (0 !== strpos($error['message'], $prefix)) {                continue;            }            $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);            if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {                $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);                $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);                $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);                $tail = ' for another namespace?';            } else {                $className = $fullyQualifiedClassName;                $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);                $tail = '?';            }            if ($candidates = $this->getClassCandidates($className)) {                $tail = array_pop($candidates).'"?';                if ($candidates) {                    $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;                } else {                    $tail = ' for "'.$tail;                }            }            $message .= "\nDid you forget a \"use\" statement".$tail;            return new ClassNotFoundException($message, $exception);        }    }    /**     * Tries to guess the full namespace for a given class name.     *     * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer     * autoloader (that should cover all common cases).     *     * @param string $class A class name (without its namespace)     *     * @return array An array of possible fully qualified class names     */    private function getClassCandidates($class)    {        if (!\is_array($functions = spl_autoload_functions())) {            return array();        }        // find Symfony and Composer autoloaders        $classes = array();        foreach ($functions as $function) {            if (!\is_array($function)) {                continue;            }            // get class loaders wrapped by DebugClassLoader            if ($function[0] instanceof DebugClassLoader) {                $function = $function[0]->getClassLoader();                if (!\is_array($function)) {                    continue;                }            }            if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {                foreach ($function[0]->getPrefixes() as $prefix => $paths) {                    foreach ($paths as $path) {                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));                    }                }            }            if ($function[0] instanceof ComposerClassLoader) {                foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {                    foreach ($paths as $path) {                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));                    }                }            }        }        return array_unique($classes);    }    /**     * @param string $path     * @param string $class     * @param string $prefix     *     * @return array     */    private function findClassInPath($path, $class, $prefix)    {        if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {            return array();        }        $classes = array();        $filename = $class.'.php';        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {            if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {                $classes[] = $class;            }        }        return $classes;    }    /**     * @param string $path     * @param string $file     * @param string $prefix     *     * @return string|null     */    private function convertFileToClass($path, $file, $prefix)    {        $candidates = array(            // namespaced class            $namespacedClass = str_replace(array($path.\DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),            // namespaced class (with target dir)            $prefix.$namespacedClass,            // namespaced class (with target dir and separator)            $prefix.'\\'.$namespacedClass,            // PEAR class            str_replace('\\', '_', $namespacedClass),            // PEAR class (with target dir)            str_replace('\\', '_', $prefix.$namespacedClass),            // PEAR class (with target dir and separator)            str_replace('\\', '_', $prefix.'\\'.$namespacedClass),        );        if ($prefix) {            $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });        }        // We cannot use the autoloader here as most of them use require; but if the class        // is not found, the new autoloader call will require the file again leading to a        // "cannot redeclare class" error.        foreach ($candidates as $candidate) {            if ($this->classExists($candidate)) {                return $candidate;            }        }        require_once $file;        foreach ($candidates as $candidate) {            if ($this->classExists($candidate)) {                return $candidate;            }        }    }    /**     * @param string $class     *     * @return bool     */    private function classExists($class)    {        return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);    }}
 |