PHP-Parser 应用之扫描发现代码中的打印、输出结构语句
PHP-Parser 应用之扫描发现代码中的打印、输出结构语句
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();
}
}
原文链接
推荐阅读
如何使用 PHPStorm 进行优雅的项目开发?
PHP Storm 这个开发工具,很多 phper 应该有所耳闻,甚至也有不少人使用其作为生产工具,但是很多人都没有最大限度的使用它,本文就来总结一些优雅开发的小技巧。
唯一丶赞 45阅读 4.8k评论 7
怎样用 PHP 来实现枚举?
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,...
唯一丶赞 25阅读 6.4k评论 4
PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。
王中阳Go赞 10阅读 2k评论 3
图片防盗链破解 解决图片防盗链问题 反向代理
当客户端(浏览器)向服务器请求内容的时候,会提交一个header,这个header中包含了如:浏览器信息、cookie等内容,那么有一个叫referer的东东,也包含在这里面。
TANKING赞 7阅读 11.3k评论 5
Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...
王中阳Go赞 5阅读 2.3k评论 2
Hyperf 3.0 发布,PHP 新时代
在过去的一年半时间里,Hyperf 2.2 共发布了 35 个小版本,使 Hyperf 达到了一个前所未有的高度,这里也获得了一些不错的数据反馈。
huangzhhui赞 4阅读 1.1k评论 1
微信公众号开发:自动回复文本/图片/图文消息/关键词回复/上传素材/自定义菜单
对接流程1、申请微信公众号测试账号URL:[链接]2、登录,配置开发者服务器URL和Token开发者服务器配置代码:config.php {代码...} URL是config.php在你服务器的URLToken是上面代码自己设置的Token搞定之后,就能完...
TANKING赞 2阅读 10.1k
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。