PHP Parser 扫描应用打印输出结构语句实例

吾爱主题 阅读:218 2022-11-20 16:56:00 评论:0

正文

PHP-Parser 是由 nikic 开发的一个 PHP 抽象语法树(AST)解析器,可方便的将代码与抽象语法树互相转换。工程上常用来生成模板代码(如 rector)、生成抽象语法树进行静态分析(如 phpstan)。最近学习应用(静态分析)了一下,编写了一个简单的扫描发现代码中的打印、输出结构语句的命令(FindDumpStatementCommand)。

效果

流程概述

  • 扫描拿到指定的 PHP 文件结果集
  • 提取文件内容转化为抽象语法树
  • 遍历抽象语法树节点,匹配符合要求的节点,暂存符合要求的节点信息
  • 输出节点结果集信息

FindDumpStatementCommand

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 <?php /**   * This file is part of the guanguans/laravel-skeleton.   *   * (c) guanguans <ityaozm@gmail.com>   *   * This source file is subject to the MIT license that is bundled.   *   * @see https://github.com/guanguans/laravel-skeleton   */ namespace App\Console\Commands; use Composer\XdebugHandler\XdebugHandler; use Illuminate\Console\Command; use Illuminate\Support\Str; use Illuminate\Support\Stringable; use PhpParser\Error; use PhpParser\Node; use PhpParser\NodeFinder; use PhpParser\ParserFactory; use PhpParser\PrettyPrinter\Standard; use SebastianBergmann\Timer\ResourceUsageFormatter; use SebastianBergmann\Timer\Timer; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; class FindDumpStatementCommand extends Command {      /** @var string */      protected $signature = '          find:dump-statement          {--dir=* : The directories to search for files}          {--path=* : The paths to search for files}          {--name=* : The names to search for files}          {--not-path=* : The paths to exclude from the search}          {--not-name=* : The names to exclude from the search}          {--s|struct=* : The structs to search}          {--f|func=* : The functions to search}          {--m|parse-mode=1 : The mode(1,2,3,4) to use for the PHP parser}          {--M|memory-limit= : The memory limit to use for the PHP parser}';      /** @var string */      protected $description = 'Find dump statements in PHP files.' ;      /** @var \string[][] */      private $statements = [          'struct' => [              'echo' ,              'print' ,              'die' ,              'exit' ,          ],          'func' => [              'printf' ,              'vprintf' ,              'var_dump' ,              'dump' ,              'dd' ,              'print_r' ,              'var_export'          ]      ];      /** @var \Symfony\Component\Finder\Finder */      private $fileFinder ;      /** @var \PhpParser\Parser */      private $parser ;      /** @var \PhpParser\NodeFinder */      private $nodeFinder ;      /** @var \PhpParser\PrettyPrinter\Standard */      private $prettyPrinter ;      /** @var \SebastianBergmann\Timer\ResourceUsageFormatter */      private $resourceUsageFormatter ;      protected function initialize(InputInterface $input , OutputInterface $output )      {          $this ->checkOptions();          $this ->initializeEnvs();          $this ->initializeProperties();      }      public function handle(Timer $timer )      {          $timer ->start();          $this ->withProgressBar( $this ->fileFinder, function (SplFileInfo $fileInfo ) use (& $findInfos , & $odd ) {              try {                  $nodes = $this ->parser->parse( $fileInfo ->getContents());              } catch (Error $e ) {                  $this ->newLine();                  $this ->error(sprintf( "The file of %s parse error: %s." , $fileInfo ->getRealPath(), $e ->getMessage()));                  return ;              }              $dumpNodes = $this ->nodeFinder->find( $nodes , function (Node $node ) {                  if (                      $node instanceof Node\Stmt\Expression                      && $node ->expr instanceof Node\Expr\FuncCall                      && $node ->expr->name instanceof Node\Name                      && in_array( $node ->expr->name->toString(), $this ->statements[ 'func' ])                  ) {                      return true;                  }                  return Str::of(class_basename(get_class( $node )))                      ->lower()                      ->replaceLast( '_' , '' )                      ->is( $this ->statements[ 'struct' ]);              });              if ( empty ( $dumpNodes )) {                  return ;              }              $findInfos [] = array_map ( function (Node $dumpNode ) use ( $fileInfo , $odd ) {                  if ( $dumpNode instanceof Node\Stmt\Expression && $dumpNode ->expr instanceof Node\Expr\FuncCall) {                      $name = "<fg=cyan>{$dumpNode->expr->name->parts[0]}</>" ;                      $type = '<fg=cyan>func</>' ;                  } else {                      $name = Str::of(class_basename(get_class( $dumpNode )))->lower()->replaceLast( '_' , '' )->pipe( function (Stringable $name ) {                          return "<fg=red>$name</>" ;                      });                      $type = '<fg=red>struct</>' ;                  }                  $file = Str::of( $fileInfo ->getRealPath())->replace(base_path().DIRECTORY_SEPARATOR, '' )->pipe( function (Stringable $file ) use ( $odd ) {                      return $odd ? "<fg=green>$file</>" : "<fg=blue>$file</>" ;                  });                  $line = Str::of( $dumpNode ->getAttribute( 'startLine' ))->pipe( function (Stringable $line ) use ( $odd ) {                      return $odd ? "<fg=green>$line</>" : "<fg=blue>$line</>" ;                  });                  $formattedCode = Str::of( $this ->prettyPrinter->prettyPrint([ $dumpNode ]))->pipe( function (Stringable $formattedCode ) use ( $odd ) {                      return $odd ? "<fg=green>$formattedCode</>" : "<fg=blue>$formattedCode</>" ;                  });                  return [                      'index' => null,                      'name' => $name ,                      'type' => $type ,                      'file' => $file ,                      'line' => $line ,                      'formatted_code' => $formattedCode ,                  ];              }, $dumpNodes );              $odd = ! $odd ;          });          $this ->newLine();          if ( empty ( $findInfos )) {              $this ->info( 'The print statement was not found.' );              $this ->info( $this ->resourceUsageFormatter->resourceUsage( $timer ->stop()));              return static ::INVALID;          }          $findInfos = array_map ( function ( $info , $index ) {              $index ++;              $info [ 'index' ] = "<fg=yellow>$index</>" ;              return $info ;          }, $findInfos = array_merge ([], ... $findInfos ), array_keys ( $findInfos ));          $this ->table( array_map ( function ( $name ) {              return Str::of( $name )->snake()->replace( '_' , ' ' )->title();          }, array_keys ( $findInfos [0])), $findInfos );          $this ->info( $this ->resourceUsageFormatter->resourceUsage( $timer ->stop()));          return self::SUCCESS;      }      protected function checkOptions()      {          if (! in_array( $this ->option( 'parse-mode' ), [              ParserFactory::PREFER_PHP7,              ParserFactory::PREFER_PHP5,              ParserFactory::ONLY_PHP7,              ParserFactory::ONLY_PHP5])          ) {              $this ->error( 'The parse-mode option is not valid(1,2,3,4).' );              exit (1);          }          if ( $this ->option( 'struct' )) {              $this ->statements[ 'struct' ] = array_intersect ( $this ->statements[ 'struct' ], $this ->option( 'struct' ));          }          if ( $this ->option( 'func' )) {              $this ->statements[ 'func' ] = array_intersect ( $this ->statements[ 'func' ], $this ->option( 'func' ));          }      }      protected function initializeEnvs()      {          $xdebug = new XdebugHandler( __CLASS__ );          $xdebug ->check();          unset( $xdebug );          extension_loaded ( 'xdebug' ) and ini_set ( 'xdebug.max_nesting_level' , 2048);          ini_set ( 'zend.assertions' , 0);          $this ->option( 'memory-limit' ) and ini_set ( 'memory_limit' , $this ->option( 'memory-limit' ));      }      protected function initializeProperties()      {          $this ->fileFinder = tap(Finder::create()->files()->ignoreDotFiles(true)->ignoreVCS(true), function (Finder $finder ) {              $methods = [                  'in' => $this ->option( 'dir' ) ?: [base_path()],                  'path' => $this ->option( 'path' ) ?: [],                  'notPath' => $this ->option( 'not-path' ) ?: [ 'vendor' , 'storage' ],                  'name' => $this ->option( 'name' ) ?: [ '*.php' ],                  'notName' => $this ->option( 'not-name' ) ?: [],              ];              foreach ( $methods as $method => $parameters ) {                  $finder ->{ $method }( $parameters );              }          });          $this ->parser = ( new ParserFactory())->create((int) $this ->option( 'parse-mode' ));          $this ->nodeFinder = new NodeFinder();          $this ->prettyPrinter = new Standard();          $this ->resourceUsageFormatter = new ResourceUsageFormatter();      } }

以上就是PHP Parser 扫描应用打印输出结构语句实例的详细内容,更多关于PHP Parser 扫描打印输出结构的资料请关注服务器之家其它相关文章!

原文链接:https://github.com/guanguans/guanguans.github.io/issues/49

可以去百度分享获取分享代码输入这里。
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

【腾讯云】云服务器产品特惠热卖中
搜索
标签列表
    关注我们

    了解等多精彩内容