You are here

class PHP_Token_Stream in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 vendor/phpunit/php-token-stream/src/Token/Stream.php \PHP_Token_Stream

A stream of PHP tokens.

@author Sebastian Bergmann <sebastian@phpunit.de> @copyright Sebastian Bergmann <sebastian@phpunit.de> @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License @link http://github.com/sebastianbergmann/php-token-stream/tree @since Class available since Release 1.0.0

Hierarchy

Expanded class hierarchy of PHP_Token_Stream

File

vendor/phpunit/php-token-stream/src/Token/Stream.php, line 20

View source
class PHP_Token_Stream implements ArrayAccess, Countable, SeekableIterator {

  /**
   * @var array
   */
  protected static $customTokens = array(
    '(' => 'PHP_Token_OPEN_BRACKET',
    ')' => 'PHP_Token_CLOSE_BRACKET',
    '[' => 'PHP_Token_OPEN_SQUARE',
    ']' => 'PHP_Token_CLOSE_SQUARE',
    '{' => 'PHP_Token_OPEN_CURLY',
    '}' => 'PHP_Token_CLOSE_CURLY',
    ';' => 'PHP_Token_SEMICOLON',
    '.' => 'PHP_Token_DOT',
    ',' => 'PHP_Token_COMMA',
    '=' => 'PHP_Token_EQUAL',
    '<' => 'PHP_Token_LT',
    '>' => 'PHP_Token_GT',
    '+' => 'PHP_Token_PLUS',
    '-' => 'PHP_Token_MINUS',
    '*' => 'PHP_Token_MULT',
    '/' => 'PHP_Token_DIV',
    '?' => 'PHP_Token_QUESTION_MARK',
    '!' => 'PHP_Token_EXCLAMATION_MARK',
    ':' => 'PHP_Token_COLON',
    '"' => 'PHP_Token_DOUBLE_QUOTES',
    '@' => 'PHP_Token_AT',
    '&' => 'PHP_Token_AMPERSAND',
    '%' => 'PHP_Token_PERCENT',
    '|' => 'PHP_Token_PIPE',
    '$' => 'PHP_Token_DOLLAR',
    '^' => 'PHP_Token_CARET',
    '~' => 'PHP_Token_TILDE',
    '`' => 'PHP_Token_BACKTICK',
  );

  /**
   * @var string
   */
  protected $filename;

  /**
   * @var array
   */
  protected $tokens = array();

  /**
   * @var integer
   */
  protected $position = 0;

  /**
   * @var array
   */
  protected $linesOfCode = array(
    'loc' => 0,
    'cloc' => 0,
    'ncloc' => 0,
  );

  /**
   * @var array
   */
  protected $classes;

  /**
   * @var array
   */
  protected $functions;

  /**
   * @var array
   */
  protected $includes;

  /**
   * @var array
   */
  protected $interfaces;

  /**
   * @var array
   */
  protected $traits;

  /**
   * @var array
   */
  protected $lineToFunctionMap = array();

  /**
   * Constructor.
   *
   * @param string $sourceCode
   */
  public function __construct($sourceCode) {
    if (is_file($sourceCode)) {
      $this->filename = $sourceCode;
      $sourceCode = file_get_contents($sourceCode);
    }
    $this
      ->scan($sourceCode);
  }

  /**
   * Destructor.
   */
  public function __destruct() {
    $this->tokens = array();
  }

  /**
   * @return string
   */
  public function __toString() {
    $buffer = '';
    foreach ($this as $token) {
      $buffer .= $token;
    }
    return $buffer;
  }

  /**
   * @return string
   * @since  Method available since Release 1.1.0
   */
  public function getFilename() {
    return $this->filename;
  }

  /**
   * Scans the source for sequences of characters and converts them into a
   * stream of tokens.
   *
   * @param string $sourceCode
   */
  protected function scan($sourceCode) {
    $line = 1;
    $tokens = token_get_all($sourceCode);
    $numTokens = count($tokens);
    $lastNonWhitespaceTokenWasDoubleColon = false;
    for ($i = 0; $i < $numTokens; ++$i) {
      $token = $tokens[$i];
      unset($tokens[$i]);
      if (is_array($token)) {
        $name = substr(token_name($token[0]), 2);
        $text = $token[1];
        if ($lastNonWhitespaceTokenWasDoubleColon && $name == 'CLASS') {
          $name = 'CLASS_NAME_CONSTANT';
        }
        $tokenClass = 'PHP_Token_' . $name;
      }
      else {
        $text = $token;
        $tokenClass = self::$customTokens[$token];
      }
      $this->tokens[] = new $tokenClass($text, $line, $this, $i);
      $lines = substr_count($text, "\n");
      $line += $lines;
      if ($tokenClass == 'PHP_Token_HALT_COMPILER') {
        break;
      }
      elseif ($tokenClass == 'PHP_Token_COMMENT' || $tokenClass == 'PHP_Token_DOC_COMMENT') {
        $this->linesOfCode['cloc'] += $lines + 1;
      }
      if ($name == 'DOUBLE_COLON') {
        $lastNonWhitespaceTokenWasDoubleColon = true;
      }
      elseif ($name != 'WHITESPACE') {
        $lastNonWhitespaceTokenWasDoubleColon = false;
      }
    }
    $this->linesOfCode['loc'] = substr_count($sourceCode, "\n");
    $this->linesOfCode['ncloc'] = $this->linesOfCode['loc'] - $this->linesOfCode['cloc'];
  }

  /**
   * @return integer
   */
  public function count() {
    return count($this->tokens);
  }

  /**
   * @return PHP_Token[]
   */
  public function tokens() {
    return $this->tokens;
  }

  /**
   * @return array
   */
  public function getClasses() {
    if ($this->classes !== null) {
      return $this->classes;
    }
    $this
      ->parse();
    return $this->classes;
  }

  /**
   * @return array
   */
  public function getFunctions() {
    if ($this->functions !== null) {
      return $this->functions;
    }
    $this
      ->parse();
    return $this->functions;
  }

  /**
   * @return array
   */
  public function getInterfaces() {
    if ($this->interfaces !== null) {
      return $this->interfaces;
    }
    $this
      ->parse();
    return $this->interfaces;
  }

  /**
   * @return array
   * @since  Method available since Release 1.1.0
   */
  public function getTraits() {
    if ($this->traits !== null) {
      return $this->traits;
    }
    $this
      ->parse();
    return $this->traits;
  }

  /**
   * Gets the names of all files that have been included
   * using include(), include_once(), require() or require_once().
   *
   * Parameter $categorize set to TRUE causing this function to return a
   * multi-dimensional array with categories in the keys of the first dimension
   * and constants and their values in the second dimension.
   *
   * Parameter $category allow to filter following specific inclusion type
   *
   * @param bool   $categorize OPTIONAL
   * @param string $category   OPTIONAL Either 'require_once', 'require',
   *                                           'include_once', 'include'.
   * @return array
   * @since  Method available since Release 1.1.0
   */
  public function getIncludes($categorize = false, $category = null) {
    if ($this->includes === null) {
      $this->includes = array(
        'require_once' => array(),
        'require' => array(),
        'include_once' => array(),
        'include' => array(),
      );
      foreach ($this->tokens as $token) {
        switch (get_class($token)) {
          case 'PHP_Token_REQUIRE_ONCE':
          case 'PHP_Token_REQUIRE':
          case 'PHP_Token_INCLUDE_ONCE':
          case 'PHP_Token_INCLUDE':
            $this->includes[$token
              ->getType()][] = $token
              ->getName();
            break;
        }
      }
    }
    if (isset($this->includes[$category])) {
      $includes = $this->includes[$category];
    }
    elseif ($categorize === false) {
      $includes = array_merge($this->includes['require_once'], $this->includes['require'], $this->includes['include_once'], $this->includes['include']);
    }
    else {
      $includes = $this->includes;
    }
    return $includes;
  }

  /**
   * Returns the name of the function or method a line belongs to.
   *
   * @return string or null if the line is not in a function or method
   * @since  Method available since Release 1.2.0
   */
  public function getFunctionForLine($line) {
    $this
      ->parse();
    if (isset($this->lineToFunctionMap[$line])) {
      return $this->lineToFunctionMap[$line];
    }
  }
  protected function parse() {
    $this->interfaces = array();
    $this->classes = array();
    $this->traits = array();
    $this->functions = array();
    $class = array();
    $classEndLine = array();
    $trait = false;
    $traitEndLine = false;
    $interface = false;
    $interfaceEndLine = false;
    foreach ($this->tokens as $token) {
      switch (get_class($token)) {
        case 'PHP_Token_HALT_COMPILER':
          return;
        case 'PHP_Token_INTERFACE':
          $interface = $token
            ->getName();
          $interfaceEndLine = $token
            ->getEndLine();
          $this->interfaces[$interface] = array(
            'methods' => array(),
            'parent' => $token
              ->getParent(),
            'keywords' => $token
              ->getKeywords(),
            'docblock' => $token
              ->getDocblock(),
            'startLine' => $token
              ->getLine(),
            'endLine' => $interfaceEndLine,
            'package' => $token
              ->getPackage(),
            'file' => $this->filename,
          );
          break;
        case 'PHP_Token_CLASS':
        case 'PHP_Token_TRAIT':
          $tmp = array(
            'methods' => array(),
            'parent' => $token
              ->getParent(),
            'interfaces' => $token
              ->getInterfaces(),
            'keywords' => $token
              ->getKeywords(),
            'docblock' => $token
              ->getDocblock(),
            'startLine' => $token
              ->getLine(),
            'endLine' => $token
              ->getEndLine(),
            'package' => $token
              ->getPackage(),
            'file' => $this->filename,
          );
          if ($token instanceof PHP_Token_CLASS) {
            $class[] = $token
              ->getName();
            $classEndLine[] = $token
              ->getEndLine();
            if ($class[count($class) - 1] != 'anonymous class') {
              $this->classes[$class[count($class) - 1]] = $tmp;
            }
          }
          else {
            $trait = $token
              ->getName();
            $traitEndLine = $token
              ->getEndLine();
            $this->traits[$trait] = $tmp;
          }
          break;
        case 'PHP_Token_FUNCTION':
          $name = $token
            ->getName();
          $tmp = array(
            'docblock' => $token
              ->getDocblock(),
            'keywords' => $token
              ->getKeywords(),
            'visibility' => $token
              ->getVisibility(),
            'signature' => $token
              ->getSignature(),
            'startLine' => $token
              ->getLine(),
            'endLine' => $token
              ->getEndLine(),
            'ccn' => $token
              ->getCCN(),
            'file' => $this->filename,
          );
          if (empty($class) && $trait === false && $interface === false) {
            $this->functions[$name] = $tmp;
            $this
              ->addFunctionToMap($name, $tmp['startLine'], $tmp['endLine']);
          }
          elseif (!empty($class) && $class[count($class) - 1] != 'anonymous class') {
            $this->classes[$class[count($class) - 1]]['methods'][$name] = $tmp;
            $this
              ->addFunctionToMap($class[count($class) - 1] . '::' . $name, $tmp['startLine'], $tmp['endLine']);
          }
          elseif ($trait !== false) {
            $this->traits[$trait]['methods'][$name] = $tmp;
            $this
              ->addFunctionToMap($trait . '::' . $name, $tmp['startLine'], $tmp['endLine']);
          }
          else {
            $this->interfaces[$interface]['methods'][$name] = $tmp;
          }
          break;
        case 'PHP_Token_CLOSE_CURLY':
          if (!empty($classEndLine) && $classEndLine[count($classEndLine) - 1] == $token
            ->getLine()) {
            array_pop($classEndLine);
            array_pop($class);
          }
          elseif ($traitEndLine !== false && $traitEndLine == $token
            ->getLine()) {
            $trait = false;
            $traitEndLine = false;
          }
          elseif ($interfaceEndLine !== false && $interfaceEndLine == $token
            ->getLine()) {
            $interface = false;
            $interfaceEndLine = false;
          }
          break;
      }
    }
  }

  /**
   * @return array
   */
  public function getLinesOfCode() {
    return $this->linesOfCode;
  }

  /**
   */
  public function rewind() {
    $this->position = 0;
  }

  /**
   * @return boolean
   */
  public function valid() {
    return isset($this->tokens[$this->position]);
  }

  /**
   * @return integer
   */
  public function key() {
    return $this->position;
  }

  /**
   * @return PHP_Token
   */
  public function current() {
    return $this->tokens[$this->position];
  }

  /**
   */
  public function next() {
    $this->position++;
  }

  /**
   * @param  integer $offset
   * @return boolean
   */
  public function offsetExists($offset) {
    return isset($this->tokens[$offset]);
  }

  /**
   * @param  integer $offset
   * @return mixed
   * @throws OutOfBoundsException
   */
  public function offsetGet($offset) {
    if (!$this
      ->offsetExists($offset)) {
      throw new OutOfBoundsException(sprintf('No token at position "%s"', $offset));
    }
    return $this->tokens[$offset];
  }

  /**
   * @param integer $offset
   * @param mixed   $value
   */
  public function offsetSet($offset, $value) {
    $this->tokens[$offset] = $value;
  }

  /**
   * @param  integer $offset
   * @throws OutOfBoundsException
   */
  public function offsetUnset($offset) {
    if (!$this
      ->offsetExists($offset)) {
      throw new OutOfBoundsException(sprintf('No token at position "%s"', $offset));
    }
    unset($this->tokens[$offset]);
  }

  /**
   * Seek to an absolute position.
   *
   * @param  integer $position
   * @throws OutOfBoundsException
   */
  public function seek($position) {
    $this->position = $position;
    if (!$this
      ->valid()) {
      throw new OutOfBoundsException(sprintf('No token at position "%s"', $this->position));
    }
  }

  /**
   * @param string  $name
   * @param integer $startLine
   * @param integer $endLine
   */
  private function addFunctionToMap($name, $startLine, $endLine) {
    for ($line = $startLine; $line <= $endLine; $line++) {
      $this->lineToFunctionMap[$line] = $name;
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
PHP_Token_Stream::$classes protected property
PHP_Token_Stream::$customTokens protected static property
PHP_Token_Stream::$filename protected property
PHP_Token_Stream::$functions protected property
PHP_Token_Stream::$includes protected property
PHP_Token_Stream::$interfaces protected property
PHP_Token_Stream::$linesOfCode protected property
PHP_Token_Stream::$lineToFunctionMap protected property
PHP_Token_Stream::$position protected property
PHP_Token_Stream::$tokens protected property
PHP_Token_Stream::$traits protected property
PHP_Token_Stream::addFunctionToMap private function
PHP_Token_Stream::count public function
PHP_Token_Stream::current public function
PHP_Token_Stream::getClasses public function
PHP_Token_Stream::getFilename public function @since Method available since Release 1.1.0
PHP_Token_Stream::getFunctionForLine public function Returns the name of the function or method a line belongs to.
PHP_Token_Stream::getFunctions public function
PHP_Token_Stream::getIncludes public function Gets the names of all files that have been included using include(), include_once(), require() or require_once().
PHP_Token_Stream::getInterfaces public function
PHP_Token_Stream::getLinesOfCode public function
PHP_Token_Stream::getTraits public function @since Method available since Release 1.1.0
PHP_Token_Stream::key public function
PHP_Token_Stream::next public function
PHP_Token_Stream::offsetExists public function
PHP_Token_Stream::offsetGet public function
PHP_Token_Stream::offsetSet public function
PHP_Token_Stream::offsetUnset public function
PHP_Token_Stream::parse protected function
PHP_Token_Stream::rewind public function
PHP_Token_Stream::scan protected function Scans the source for sequences of characters and converts them into a stream of tokens.
PHP_Token_Stream::seek public function Seek to an absolute position.
PHP_Token_Stream::tokens public function
PHP_Token_Stream::valid public function
PHP_Token_Stream::__construct public function Constructor.
PHP_Token_Stream::__destruct public function Destructor.
PHP_Token_Stream::__toString public function