PHP Parser 掃描應(yīng)用打印輸出結(jié)構(gòu)語句實(shí)例
正文
PHP-Parser 是由 nikic 開發(fā)的一個(gè) PHP 抽象語法樹(AST)解析器,可方便的將代碼與抽象語法樹互相轉(zhuǎn)換。工程上常用來生成模板代碼(如 rector)、生成抽象語法樹進(jìn)行靜態(tài)分析(如 phpstan)。最近學(xué)習(xí)應(yīng)用(靜態(tài)分析)了一下,編寫了一個(gè)簡單的掃描發(fā)現(xiàn)代碼中的打印、輸出結(jié)構(gòu)語句的命令(FindDumpStatementCommand)。
效果
流程概述
- 掃描拿到指定的 PHP 文件結(jié)果集
- 提取文件內(nèi)容轉(zhuǎn)化為抽象語法樹
- 遍歷抽象語法樹節(jié)點(diǎn),匹配符合要求的節(jié)點(diǎn),暫存符合要求的節(jié)點(diǎn)信息
- 輸出節(jié)點(diǎn)結(jié)果集信息
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 掃描應(yīng)用打印輸出結(jié)構(gòu)語句實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于PHP Parser 掃描打印輸出結(jié)構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
php實(shí)現(xiàn)生成code128條形碼的方法詳解
這篇文章主要介紹了php實(shí)現(xiàn)生成code128條形碼的方法,結(jié)合完整實(shí)例形式給出了php條形碼生成類的定義與使用方法,需要的朋友可以參考下2017-07-07php實(shí)現(xiàn)過濾字符串中的中文和數(shù)字實(shí)例
這篇文章主要介紹了php實(shí)現(xiàn)過濾字符串中的中文和數(shù)字的方法,實(shí)例分析了php操作中文和數(shù)字匹配的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07