You are here

class RegistryAutoloadSearcher in Registry Autoload 7

RegistryAutoloadSearcher helper class.

Note: This class is not registered via the registry so it is available while the registry is being rebuild.

Hierarchy

Expanded class hierarchy of RegistryAutoloadSearcher

File

./registry_autoload.module, line 138
Main module for enabling core registry to support namespaced files.

View source
class RegistryAutoloadSearcher {

  // @todo Make configurable.

  /**
   * The namespace $map maps the namespaces provided to subdirectories.
   *
   * @var array
   */
  protected $formatMap = array(
    'PSR-0' => 'lib',
    'PSR-4' => 'src',
    'PHPUnit' => 'tests/src',
    'PSR-0/absolute' => '',
    'PSR-4/absolute' => '',
    'PHPUnit/absolute' => '',
  );

  /**
   * The current content of the {registry_file} table.
   *
   * @see registry_get_parsed_files()
   *
   * @var array $parsedFiles
   */
  protected $parsedFiles = array();

  /**
   * Constructs a RegistryAutoloadSearcher object.
   *
   * @param array $parsed_files
   *   The current content of the {registry_file} table.
   */
  public function __construct(array $parsed_files) {
    $this->parsedFiles = $parsed_files;
  }

  /**
   * Finds class files in the given module for the given format.
   *
   * @param string $path
   *   The path to search class files in.
   * @param string $format
   *   The format to scan for as specified in the $formatMap.
   *
   * @return array
   *   Returns all found php files for the given module and format.
   */
  public function findClassFiles($path, $format) {
    if (!isset($this->formatMap[$format])) {
      return array();
    }
    $directory = $path;
    $subdir = $this->formatMap[$format];
    if (!empty($subdir)) {
      $directory .= '/' . $subdir;
    }
    $class_files = file_scan_directory($directory, '/.*.php$/');
    return array_keys($class_files);
  }

  /**
   * Processes files containing classes for the given module.
   *
   * @param array $class_files
   *   The files to scan for classes.
   * @param object $module
   *   The module object as given to hook_registry_files_alter().
   *
   * @return array
   *   An associative array keyed by filename with object values.
   *   The objects have the following properties:
   *   - classes: An associative array keyed by namespace+name with properties:
   *     - type: Type of the class, can be 'interface' or 'class'.
   *     - name: Name of the class or interface.
   *     This can be empty if needs_update below is FALSE.
   *   - filename: The filename of the file.
   *   - module: The module this file belongs to.
   *   - weight: The weight of the module this file belongs to.
   *   - hash: The file_hash() of the filename.
   *   - needs_update: Whether the registry needs to be updated or not.
   */
  public function processFiles(array $class_files, $module) {
    $registry = array();
    foreach ($class_files as $filename) {

      // Check if file exists.
      if (!file_exists($filename)) {
        continue;
      }
      $hash = NULL;
      $needs_update = $this
        ->checkFileNeedsUpdate($filename, $hash);
      $registry[$filename] = (object) array(
        'classes' => array(),
        'filename' => $filename,
        'module' => $module->name,
        'weight' => $module->weight,
        // We create a unique structure to populate both registry tables.
        'hash' => $hash,
        'needs_update' => $needs_update,
      );
      if ($needs_update) {
        $this
          ->parseFile($registry[$filename], $filename);
      }
    }
    return $registry;
  }

  /**
   * Checks if the is in need of an update.
   *
   * @param string $filename
   *   The filename to check against the {registry_file} table.
   * @param string $hash
   *   The hash passed by reference for later usage.
   *
   * @return bool
   *   TRUE if the file needs an update, FALSE otherwise.
   */
  protected function checkFileNeedsUpdate($filename, &$hash) {

    // Check if hash matches still.
    $stored_hash = !empty($this->parsedFiles[$filename]['hash']) ? $this->parsedFiles[$filename]['hash'] : NULL;
    $hash = hash_file('sha256', $filename);
    if (!empty($stored_hash) && $stored_hash == $hash) {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Parses a file for classes and interfaces, including the namespace.
   *
   * This lifts the core limitation of not supporting namespaces.
   *
   * This code is now based on
   * \Symfony\Component\ClassLoader\ClassMapGenerator::findClasses()
   *
   * @param object $entry
   *   The registry entry as defined in processFiles(). The classes key will be
   *   updated in the entry if classes can be found.
   * @param string $filename
   *   The filename to parse.
   *
   * @return bool
   *   TRUE if classes could be found, FALSE otherwise.
   *
   * @see _registry_parse_file()
   */
  protected function parseFile($entry, $filename) {
    $contents = file_get_contents($filename);
    $namespace = '';

    // Check if this version of PHP supports traits.
    $traits = version_compare(PHP_VERSION, '5.4', '<') ? '' : '|trait';

    // Return early if there is no chance of matching anything in this file.
    if (!preg_match('{\\b(?:class|interface' . $traits . ')\\s}i', $contents)) {
      return array();
    }
    $tokens = token_get_all($contents);
    for ($i = 0, $max = count($tokens); $i < $max; $i++) {
      $token = $tokens[$i];
      if (is_string($token)) {
        continue;
      }
      $class = '';
      $token_name = token_name($token[0]);
      switch ($token_name) {
        case "T_NAMESPACE":
          $namespace = '';

          // If there is a namespace, extract it
          while (($t = $tokens[++$i]) && is_array($t)) {
            if (in_array($t[0], array(
              T_STRING,
              T_NS_SEPARATOR,
            ))) {
              $namespace .= $t[1];
            }
          }
          $namespace .= '\\';
          break;
        case "T_CLASS":
        case "T_INTERFACE":
        case "T_TRAIT":

          // Find the classname
          while (($t = $tokens[++$i]) && is_array($t)) {
            if (T_STRING === $t[0]) {
              $class .= $t[1];
            }
            elseif ($class !== '' && T_WHITESPACE == $t[0]) {
              break;
            }
          }
          $class_name = ltrim($namespace . $class, '\\');
          $type = 'class';
          if ($token[0] == T_INTERFACE) {
            $type = 'interface';
          }
          $entry->classes[$class_name] = array(
            'name' => $class_name,
            'type' => $type,
          );
          break;
        default:
          break;
      }
    }
    return TRUE;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
RegistryAutoloadSearcher::$formatMap protected property The namespace $map maps the namespaces provided to subdirectories.
RegistryAutoloadSearcher::$parsedFiles protected property The current content of the {registry_file} table.
RegistryAutoloadSearcher::checkFileNeedsUpdate protected function Checks if the is in need of an update.
RegistryAutoloadSearcher::findClassFiles public function Finds class files in the given module for the given format.
RegistryAutoloadSearcher::parseFile protected function Parses a file for classes and interfaces, including the namespace.
RegistryAutoloadSearcher::processFiles public function Processes files containing classes for the given module.
RegistryAutoloadSearcher::__construct public function Constructs a RegistryAutoloadSearcher object.