PHP-Parser扫描并发现代码中的打印输出结构语句PHP-Parser是nikicConverter开发的PHP抽象语法树(AST)解析器,它可以轻松地将代码和抽象语法树相互转换。在工程中常用于生成模板代码(如rector)和生成抽象语法树供静态分析(如phpstan)。最近在研究应用(静态分析),写了一个简单的命令(FindDumpStatementCommand)扫描查找代码中的打印输出结构语句。效果过程概览扫描获取指定PHP文件结果集将文件内容提取到抽象语法树中遍历抽象语法树节点,匹配符合要求的节点,暂存符合要求的节点信息输出节点结果设置信息FindDumpStatementCommand**此源文件受捆绑的MIT许可证约束。**@seehttps://github.com/guanguans/laravel-skeleton*/namespaceApp\Console\Commands;使用Composer\XdebugHandler\XdebugHandler;使用Illuminate\Console\Command;使用Illuminate\Support\Str;使用Illuminate\Support\Stringable;使用PhpParser\Error;使用PhpParser\Node;使用PhpParser\NodeFinder;使用PhpParser\ParserFactory;使用PhpParser\PrettyPrinter\Standard;使用SebastianBergmann\Timer\ResourceUsageFormatter;使用SebastianBergmann\Timer\Timer;使用Symfony\Component\Console\Input\InputInterface;使用Symfony\Component\Console\Output\OutputInterface;使用Symfony\Component\Finder\Finder;使用Symfony\Component\Finder\SplFileInfo;classFindDumpStatementCommandextendsCommand{/**@varstrng*/protected$signature='find:dump-statement{--dir=*:搜索文件的目录}{--path=*:搜索文件的路径}{--name=*:名字搜索文件}{--not-path=*:要从搜索中排除的路径}{--not-name=*:要从搜索中排除的名称}{--s|struct=*:结构搜索}{--f|func=*:要搜索的函数}{--m|parse-mode=1:用于PHP解析器的模式(1,2,3,4)}{--M|memory-limit=:PHP解析器使用的内存限制}';/**@varstring*/protected$description='在PHP文件中查找转储语句。';/**@var\string[][]*/private$statements=['struct'=>['echo','print','die','exit',],'func'=>['printf','vprintf','var_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;protectedfunctioninitialize(InputInterface$input,OutputInterface$output){$this->checkOptions();$this->initializeEnvs();$this->initializeProperties();}publicfunctionhandle(Timer$timer){$timer->start();$this->withProgressBar($this->fileFinder,函数(SplFileInfo$fileInfo)使用(&$findInfos,&$odd){try{$nodes=$this->parser->parse($fileInfo->getContents());}catch(错误$e){$this->新线();$this->error(sprintf("%s文件解析错误:%s",$fileInfo->getRealPath(),$e->getMessage()));返回;}$dumpNodes=$this->nodeFinder->find($nodes,function(Node$node){if($nodeinstanceofNode\Stmt\Expression&&$node->exprinstanceofNode\Expr\FuncCall&&$node->expr->nameinstanceofNode\Name&&in_array($node->expr->name->toString(),$this->statements['func'])){返回true;}returnStr::of(class_basename(get_class($node)))->lower()->replaceLast('_','')->is($this->statements['struct']);});如果(空($dumpNodes)){返回;}$findInfos[]=array_map(function(Node$dumpNode)use($fileInfo,$odd){if($dumpNodeinstanceofNode\Stmt\Expression&&$dumpNode->exprinstanceofNode\Expr\FuncCall){$name="{$dumpNode->expr->name->parts[0]}>";$type='func>';}else{$name=Str::of(class_basename(get_class($dumpNode)))->lower()->replaceLast('_','')->pipe(function(Stringable$name){return"$name>";});$type='struct>';}$file=Str::of($fileInfo->getRealPath())->replace(base_path().DIRECTORY_SEPARATOR,'')->pipe(function(Stringable$file)use($odd){return$odd?"$file>":"$文件>";});$line=Str::of($dumpNode->getAttribute('startLine'))->pipe(function(Stringable$line)use($odd){return$odd?"$line>":"$line>";});$formattedCode=Str::of($this->prettyPrinter->prettyPrint([$dumpNode]))->pipe(function(Stringable$formattedCode)use($odd){return$odd?"$格式化代码>":"$格式化代码>";});返回['index'=>null,'name'=>$name,'type'=>$type,'file'=>$file,'line'=>$line,'formatted_code'=>$formattedCode,];},$转储节点);$奇数=!$奇数;});$this->newLine();if(empty($findInfos)){$this->info('未找到打印语句。');$this->info($this->resourceUsageFormatter->resourceUsage($timer->stop()));返回静态::无效;}$findInfos=array_map(function($info,$index){$index++;$info['index']="$index>";return$info;},$findInfos=array_merge([],...$findInfos),array_keys($findInfos));$this->table(array_map(function($name){returnStr::of($name)->snake()->replace('_','')->title();},array_keys($findInfos[0])),$findInfos);$this->info($this->resourceUsageFormatter->resourceUsage($timer->stop()));返回自我::成功;}protectedfunctioncheckOptions(){if(!in_array($this->option('parse-mode'),[ParserFactory::PREFER_PHP7,ParserFactory::PREFER_PHP5,ParserFactory::ONLY_PHP7,ParserFactory::ONLY_PHP5])){$this->error('解析模式选项无效(1,2,3,4).');退出(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'));}}protectedfunctioninitializeEnvs(){$xdebug=newXdebugHandler(__CLASS__);$xdebug->检查();取消设置($xdebug);extension_loaded('xdebug')和ini_set('xdebug.max_nesting_level',2048);ini_set('zend.assertions',0);$this->option('memory-limit')和ini_set('memory_limit',$this->option('内存限制'??));}protectedfunctioninitializeProperties(){$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($methodsas$method=>$parameters){$finder->{$method}($parameters);}});$this->parser=(newParserFactory())->create((int)$this->option('parse-mode'));$this->nodeFinder=newNodeFinder();$this->prettyPrinter=newStandard();$this->资源使用Formatter=newResourceUsageFormatter();}}原文链接https://github.com/guanguans/guanguans.github.io/issues/49