You are here

class ClassNotFoundFatalErrorHandler in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php \Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler

ErrorHandler for classes that do not exist.

@author Fabien Potencier <fabien@symfony.com>

Hierarchy

Expanded class hierarchy of ClassNotFoundFatalErrorHandler

2 files declare their use of ClassNotFoundFatalErrorHandler
ClassNotFoundFatalErrorHandlerTest.php in vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
ErrorHandler.php in vendor/symfony/debug/ErrorHandler.php

File

vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php, line 26

Namespace

Symfony\Component\Debug\FatalErrorHandler
View source
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();

        // @deprecated since version 2.5. Returning an object from DebugClassLoader::getClassLoader() is deprecated.
        if (is_object($function)) {
          $function = array(
            $function,
          );
        }
        if (!is_array($function)) {
          continue;
        }
      }
      if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader || $function[0] instanceof SymfonyUniversalClassLoader) {
        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) || function_exists('trait_exists') && trait_exists($class, false);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ClassNotFoundFatalErrorHandler::classExists private function
ClassNotFoundFatalErrorHandler::convertFileToClass private function
ClassNotFoundFatalErrorHandler::findClassInPath private function
ClassNotFoundFatalErrorHandler::getClassCandidates private function Tries to guess the full namespace for a given class name.
ClassNotFoundFatalErrorHandler::handleError public function Attempts to convert an error into an exception. Overrides FatalErrorHandlerInterface::handleError