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

目录

正文

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

效果

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

流程概述

  • 扫描拿到指定的 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
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容