View source  
  <?php
namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\DebugClassLoader;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface {
  
  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);
    }
  }
  
  private function getClassCandidates($class) {
    if (!is_array($functions = spl_autoload_functions())) {
      return array();
    }
    
    $classes = array();
    foreach ($functions as $function) {
      if (!is_array($function)) {
        continue;
      }
      
      if ($function[0] instanceof DebugClassLoader) {
        $function = $function[0]
          ->getClassLoader();
        
        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);
  }
  
  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;
  }
  
  private function convertFileToClass($path, $file, $prefix) {
    $candidates = array(
      
      $namespacedClass = str_replace(array(
        $path . DIRECTORY_SEPARATOR,
        '.php',
        '/',
      ), array(
        '',
        '',
        '\\',
      ), $file),
      
      $prefix . $namespacedClass,
      
      $prefix . '\\' . $namespacedClass,
      
      str_replace('\\', '_', $namespacedClass),
      
      str_replace('\\', '_', $prefix . $namespacedClass),
      
      str_replace('\\', '_', $prefix . '\\' . $namespacedClass),
    );
    if ($prefix) {
      $candidates = array_filter($candidates, function ($candidate) use ($prefix) {
        return 0 === strpos($candidate, $prefix);
      });
    }
    
    foreach ($candidates as $candidate) {
      if ($this
        ->classExists($candidate)) {
        return $candidate;
      }
    }
    require_once $file;
    foreach ($candidates as $candidate) {
      if ($this
        ->classExists($candidate)) {
        return $candidate;
      }
    }
  }
  
  private function classExists($class) {
    return class_exists($class, false) || interface_exists($class, false) || function_exists('trait_exists') && trait_exists($class, false);
  }
}