PHP文件词法分析

PHP文件解析

token_get_all

将输入的PHP源码进行词法解析,获取到解析后的词法数组

 token_get_all("<?php var_dump(1);?>")
 # 输出:
   array(7) {
   [0]=>
   array(3) {
     [0]=>
     int(382) # 词法字典中定义的CODE,每个CODE代表一个语法关键字,使用token_name能看到对应的词法名称
     [1]=>
     string(6) "<?php "
     [2]=>
     int(1)
   }
   [1]=>
   array(3) {
     [0]=>
     int(311)
     [1]=>
     string(8) "var_dump"
     [2]=>
     int(1)
   }
   [2]=>
   string(1) "("
   [3]=>
   array(3) {
     [0]=>
     int(309)
     [1]=>
     string(1) "1"
     [2]=>
     int(1)
   }
   [4]=>
   string(1) ")"
   [5]=>
   string(1) ";"
   [6]=>
   array(3) {
     [0]=>
     int(384)
     [1]=>
     string(2) "?>"
     [2]=>
     int(1)
   }
 }
 

token_name

根据输入的code查看对应的语法代码

 var_dump(token_name(382));
 # 输出: string(10) "T_OPEN_TAG"

总结:根据这两个函数可以对我们的PHP文件进行语法分组,然后可以进行指定性的筛选,下面举一个使用案例

解析PHP文件获取所有的命名空间和所有的use对象

index.php

 <?php
 require "./core/TokenParser.php";
 require "./core/PhpParser.php";
 require "./TestClass.php";
 
 /**
  * 获取PHP文件中的所有命名空间
  */
 $m1 = new \core\TokenParser(file_get_contents('./TestClass.php'));
 var_dump($m1->getNamespaceList());
 
 /**
  * 获取PHP文件中的所有use 对象
  */
 $m2 = new \PQS\TestClass();
 $m = new \core\PhpParser();
 var_dump($m->parseClass(new ReflectionClass($m2)));
 

TestClass.php

 <?php
 namespace PQS;
 use scarecrow;
 use scarecrow2\test2 as test1;
 
 class TestClass{
 
 }
 
 namespace PQS2;

\core\TokenParser.php

 <?php
 namespace core;
 
 /**
  * Parses a file for namespaces/use/class declarations.
  *
  * @author Fabien Potencier <fabien@symfony.com>
  * @author Christian Kaps <christian.kaps@mohiva.com>
  */
 class TokenParser
 {
     /**
      * The token list.
      *
      * @var array
      */
     private $tokens;
 
     /**
      * The number of tokens.
      *
      * @var int
      */
     private $numTokens;
 
     /**
      * The current array pointer.
      *
      * @var int
      */
     private $pointer = 0;
 
     /**
      * @param string $contents
      */
     public function __construct($contents)
     {
         $this->tokens = token_get_all($contents);
         token_get_all("<?php\n/**\n *\n */");
 
         $this->numTokens = count($this->tokens);
     }
 
     /**
      * gets the next non whitespace and non comment token.
      *
      * @param boolean $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
      *                                     If FALSE then only whitespace and normal comments are skipped.
      *
      * @return array|null The token if exists, null otherwise.
      */
     public function next($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 A list with all found class names for a use statement.
      */
     public function parseUseStatement()
     {
 
         $groupRoot = '';
         $class = '';
         $alias = '';
         $statements = [];
         $explicitAlias = false;
         while (($token = $this->next())) {
             $isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR;
             if (!$explicitAlias && $isNameToken) {
                 $class .= $token[1];
                 $alias = $token[1];
             } else if ($explicitAlias && $isNameToken) {
                 $alias .= $token[1];
             } else if ($token[0] === T_AS) {
                 $explicitAlias = true;
                 $alias = '';
             } else if ($token === ',') {
                 $statements[strtolower($alias)] = $groupRoot . $class;
                 $class = '';
                 $alias = '';
                 $explicitAlias = false;
             } else if ($token === ';') {
                 $statements[strtolower($alias)] = $groupRoot . $class;
                 break;
             } else if ($token === '{' ) {
                 $groupRoot = $class;
                 $class = '';
             } else if ($token === '}' ) {
                 continue;
             } else {
                 break;
             }
         }
 
         return $statements;
     }
 
     /**
      * Gets all use statements.
      *
      * @param string $namespaceName The namespace name of the reflected class.
      *
      * @return array A list with all found use statements.
      */
     public function parseUseStatements($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)) {
             $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();
     }
 
  /**
   * 获取文件中的所有命名空间
   * @return array
   */
     public function getNamespaceList() {
      $this->pointer = 0;
      $namespaceList = [];
   while (($token = $this->next())) {
    //T_NAMESPACE 命名空间开始标签
    $token[0] === T_NAMESPACE && $namespaceList[] = $this->parseNamespace();
   }
   return $namespaceList;
  }
 }

\core\PhpParser.php

 <?php
 namespace core;
 
 use SplFileObject;
 
 /**
  * Parses a file for namespaces/use/class declarations.
  *
  * @author Fabien Potencier <fabien@symfony.com>
  * @author Christian Kaps <christian.kaps@mohiva.com>
  */
 final class PhpParser
 {
     /**
      * Parses a class.
      *
      * @param \ReflectionClass $class A <code>ReflectionClass</code> object.
      *
      * @return array A list with use statements in the form (Alias => FQN).
      */
     public function parseClass(\ReflectionClass $class)
     {
         if (method_exists($class, 'getUseStatements')) {
             return $class->getUseStatements();
         }
 
         if (false === $filename = $class->getFileName()) {
             return [];
         }
 
         $content = $this->getFileContent($filename, $class->getStartLine());
         if (null === $content) {
             return [];
         }
 
         $namespace = preg_quote($class->getNamespaceName());
         $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
 
         $tokenizer = new TokenParser('<?php ' . $content);
 
         $statements = $tokenizer->parseUseStatements($class->getNamespaceName());
 
         return $statements;
     }
 
     /**
      * Gets the content of the file right up to the given line number.
      *
      * @param string  $filename   The name of the file to load.
      * @param integer $lineNumber The number of lines to read from file.
      *
      * @return string|null The content of the file or null if the file does not exist.
      */
     private function getFileContent($filename, $lineNumber)
     {
         if ( ! is_file($filename)) {
             return null;
         }
 
         $content = '';
         $lineCnt = 0;
         $file = new SplFileObject($filename);
         while (!$file->eof()) {
             if ($lineCnt++ == $lineNumber) {
                 break;
             }
 
             $content .= $file->fgets();
         }
 
         return $content;
     }
 }
 

运行index.php输出

 E:\PHP\annotations-1.8.0>php index.php
 array(2) {
   [0]=>
   string(3) "PQS"
   [1]=>
   string(4) "PQS2"
 }
 array(2) {
   ["scarecrow"]=>
   string(9) "scarecrow"
   ["test1"]=>
   string(16) "scarecrow2\test2"
 }


阅读数:346
如有疑问请与我联系:点击与我联系