目录
正文
PHP-Parser 是由 nikic 开发的一个 PHP 抽象语法树(AST)解析器,可方便的将代码与抽象语法树互相转换。工程上常用来生成模板代码(如 rector)、生成抽象语法树进行静态分析(如 phpstan)。最近学习应用(静态分析)了一下,编写了一个简单的扫描发现代码中的打印、输出结构语句的命令(FindDumpStatementCommand)。
效果
流程概述
- 扫描拿到指定的 PHP 文件结果集
- 提取文件内容转化为抽象语法树
- 遍历抽象语法树节点,匹配符合要求的节点,暂存符合要求的节点信息
- 输出节点结果集信息
FindDumpStatementCommand
<?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 扫描打印输出结构的资料请关注其它相关文章!
© 版权声明
THE END
暂无评论内容