class TokenParser

Parses a file for namespaces/use/class declarations.

Hierarchy

  • class \Drupal\Component\Annotation\Doctrine\TokenParser

Expanded class hierarchy of TokenParser

File

core/lib/Drupal/Component/Annotation/Doctrine/TokenParser.php, line 49

Namespace

Drupal\Component\Annotation\Doctrine
View source
class TokenParser {
  
  /**
   * The token list.
   *
   * @phpstan-var list<mixed[]>
   */
  private $tokens;
  
  /**
   * The number of tokens.
   *
   * @var int
   */
  private $numTokens;
  
  /**
   * The current array pointer.
   *
   * @var int
   */
  private $pointer = 0;
  public function __construct(string $contents) {
    $this->tokens = token_get_all($contents);
    // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it
    // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored
    // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a
    // docblock. If the first thing in the file is a class without a doc block this would cause calls to
    // getDocBlock() on said class to return our long lost doc_comment.
    // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least
    // it's harmless to us.
    token_get_all("<?php\n/**\n *\n */");
    $this->numTokens = count($this->tokens);
  }
  
  /**
   * Gets the next non whitespace and non comment token.
   *
   * @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
   * If FALSE then only whitespace and normal comments are skipped.
   *
   * @return mixed[]|string|null The token if exists, null otherwise.
   */
  public function next(bool $docCommentIsComment = true) {
    for ($i = $this->pointer; $i < $this->numTokens; $i++) {
      $this->pointer++;
      if ($this->tokens[$i][0] === T_WHITESPACE || $this->tokens[$i][0] === T_COMMENT || $docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT) {
        continue;
      }
      return $this->tokens[$i];
    }
    return null;
  }
  
  /**
   * Parses a single use statement.
   *
   * @return array<string, string> A list with all found class names for a use statement.
   */
  public function parseUseStatement() {
    $groupRoot = '';
    $class = '';
    $alias = '';
    $statements = [];
    $explicitAlias = false;
    while ($token = $this->next()) {
      if (!$explicitAlias && $token[0] === T_STRING) {
        $class .= $token[1];
        $alias = $token[1];
      }
      elseif ($explicitAlias && $token[0] === T_STRING) {
        $alias = $token[1];
      }
      elseif (PHP_VERSION_ID >= 80000 && ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)) {
        $class .= $token[1];
        $classSplit = explode('\\', $token[1]);
        $alias = $classSplit[count($classSplit) - 1];
      }
      elseif ($token[0] === T_NS_SEPARATOR) {
        $class .= '\\';
        $alias = '';
      }
      elseif ($token[0] === T_AS) {
        $explicitAlias = true;
        $alias = '';
      }
      elseif ($token === ',') {
        $statements[strtolower($alias)] = $groupRoot . $class;
        $class = '';
        $alias = '';
        $explicitAlias = false;
      }
      elseif ($token === ';') {
        $statements[strtolower($alias)] = $groupRoot . $class;
        break;

      }
      elseif ($token === '{') {
        $groupRoot = $class;
        $class = '';
      }
      elseif ($token === '}') {
        continue;
      }
      else {
        break;

      }
    }
    return $statements;
  }
  
  /**
   * Gets all use statements.
   *
   * @param string $namespaceName The namespace name of the reflected class.
   *
   * @return array<string, string> A list with all found use statements.
   */
  public function parseUseStatements(string $namespaceName) {
    $statements = [];
    while ($token = $this->next()) {
      if ($token[0] === T_USE) {
        $statements = array_merge($statements, $this->parseUseStatement());
        continue;
      }
      if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) {
        continue;
      }
      // Get fresh array for new namespace. This is to prevent the parser to collect the use statements
      // for a previous namespace with the same name. This is the case if a namespace is defined twice
      // or if a namespace with the same name is commented out.
      $statements = [];
    }
    return $statements;
  }
  
  /**
   * Gets the namespace.
   *
   * @return string The found namespace.
   */
  public function parseNamespace() {
    $name = '';
    while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || PHP_VERSION_ID >= 80000 && ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED))) {
      $name .= $token[1];
    }
    return $name;
  }
  
  /**
   * Gets the class name.
   *
   * @return string The found class name.
   */
  public function parseClass() {
    // Namespaces and class names are tokenized the same: T_STRINGs
    // separated by T_NS_SEPARATOR so we can use one function to provide
    // both.
    return $this->parseNamespace();
  }

}

Members

Title Sort descending Modifiers Object type Summary
TokenParser::$numTokens private property The number of tokens.
TokenParser::$pointer private property The current array pointer.
TokenParser::$tokens private property The token list.
TokenParser::next public function Gets the next non whitespace and non comment token.
TokenParser::parseClass public function Gets the class name.
TokenParser::parseNamespace public function Gets the namespace.
TokenParser::parseUseStatement public function Parses a single use statement.
TokenParser::parseUseStatements public function Gets all use statements.
TokenParser::__construct public function

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.