. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package PHP_TokenStream * @author Sebastian Bergmann * @copyright 2009-2012 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @since File available since Release 1.0.0 */ /** * A stream of PHP tokens. * * @author Sebastian Bergmann * @copyright 2009-2012 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @version Release: @package_version@ * @link http://github.com/sebastianbergmann/php-token-stream/tree * @since Class available since Release 1.0.0 */ 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; /** * 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); for ($i = 0; $i < $numTokens; ++$i) { $token = $tokens[$i]; unset($tokens[$i]); if (is_array($token)) { $text = $token[1]; $tokenClass = 'PHP_Token_' . substr(token_name($token[0]), 2); } 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; } else if ($tokenClass == 'PHP_Token_COMMENT' || $tokenClass == 'PHP_Token_DOC_COMMENT') { $this->linesOfCode['cloc'] += $lines + 1; } } $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]; } else if ($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; } protected function parse() { $this->interfaces = array(); $this->classes = array(); $this->traits = array(); $this->functions = array(); $class = FALSE; $classEndLine = FALSE; $trait = FALSE; $traitEndLine = FALSE; $interface = FALSE; $interfaceEndLine = FALSE; foreach ($this->tokens as $token) { switch (get_class($token)) { case 'PHP_Token_HALT_COMPILER': { return; } break; 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(); $this->classes[$class] = $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 ($class === FALSE && $trait === FALSE && $interface === FALSE) { $this->functions[$name] = $tmp; } else if ($class !== FALSE) { $this->classes[$class]['methods'][$name] = $tmp; } else if ($trait !== FALSE) { $this->traits[$trait]['methods'][$name] = $tmp; } else { $this->interfaces[$interface]['methods'][$name] = $tmp; } } break; case 'PHP_Token_CLOSE_CURLY': { if ($classEndLine !== FALSE && $classEndLine == $token->getLine()) { $class = FALSE; $classEndLine = FALSE; } else if ($traitEndLine !== FALSE && $traitEndLine == $token->getLine()) { $trait = FALSE; $traitEndLine = FALSE; } else if ($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 mixed $offset */ public function offsetExists($offset) { return isset($this->tokens[$offset]); } /** * @param mixed $offset * @return mixed */ public function offsetGet($offset) { return $this->tokens[$offset]; } /** * @param mixed $offset * @param mixed $value */ public function offsetSet($offset, $value) { $this->tokens[$offset] = $value; } /** * @param mixed $offset */ public function offsetUnset($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('Invalid seek position'); } } }