首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 正則提取函數體:基於 php_strip_whitespace 與 PCRE 遞歸模式的實現

     編輯:PHP 綜合知識

需求場景

靜態分析、代碼審計、文檔自動生成、重構工具等場景常需從 PHP 源文件中**提取所有函數定義** (函數名、參數列表、函數體)。挑戰在於:函數體包含巢狀大括號 {}、字符串/註釋中的大括號干擾、多行格式不固定。

核心思路:預處理 + 遞歸模式

  1. 預處理:使用 php_strip_whitespace() 移除註釋、空白、換行,**保留字符串字面量**,獲得單行壓縮代碼,極大簡化正則難度。
  2. 遞歸模式匹配大括號:PCRE 支持 (?R) 或 (?1) 遞歸引用子模式,可精確匹配平衡的巢狀大括號。

完整實現代碼

<?php
/**
 * 從 PHP 文件提取所有函數定義
 * @param string $filePath
 * @return array [ ['name'=>..., 'params'=>..., 'body'=>..., 'start'=>..., 'end'=>...], ... ]
 */
function extractFunctions(string $filePath): array {
    $code = php_strip_whitespace($filePath);
    if ($code === '') return [];

    // 正則模式 (x 修飾符忽略空白、允許註釋)
    $pattern = <<<'REGEX'
~
    \b function \s+                    # function 關鍵字
    (?P [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* )  # 函數名
    \s* \( (?P [^)]* ) \) \s*  # 參數列表 (簡略:不處理參數中的括號,實際需遞歸)
    (?P \{ (?: [^{}]++ | (?&body) )* \} )            # 函數體:遞歸匹配平衡大括號
~x
REGEX;

    $flags = PREG_OFFSET_CAPTURE | PREG_SET_ORDER;
    if (!preg_match_all($pattern, $code, $matches, $flags)) {
        return [];
    }

    $functions = [];
    foreach ($matches as $m) {
        $functions[] = [
            'name'   => $m['name'][0],
            'params' => $m['params'][0],
            'body'   => $m['body'][0],
            'start'  => $m['name'][1],           // 在壓縮代碼中的偏移
            'end'    => $m['name'][1] + strlen($m[0][0]),
        ];
    }
    return $functions;
}

// 測試
$testCode = <<<'PHP'
 10) {
        return $sum * 2;
    }
    return $sum;
}
function foo() { echo "bar"; }
PHP;
file_put_contents('/tmp/test.php', $testCode);
print_r(extractFunctions('/tmp/test.php'));

關鍵正則解析

(?P \{ (?: [^{}]++ | (?&body) )* \} )
# 解讀:
# \{          匹配左大括號
# (?:         非捕獲組
#   [^{}]++   佔有量詞:匹配任意非大括號字符 (不回溯)
# |           或
#   (?&body)  遞歸調用名為 body 的子模式 (即自身)
# )*
# \}          匹配右大括號
# 整體匹配一對平衡的大括號及其內容,支持任意層級巢狀。

局限性與改進方向

問題原因解決方案
參數列表中含默認值數組/函數調用 func([1,2]) 導致提前截斷模式 [^)]* 不處理巢狀括號參數部分同樣使用遞歸模式 (?P \( (?: [^()]+ | (?¶ms) )* \) )
箭頭函數 fn($x) => $x+1 無大括號體模式強制要求 {...}增加分支匹配 fn\s*\(...\)\s*=>\s*[^;]+
類方法 (public/protected/private/static/abstract/final)模式未匹配修飾符在 function 前加 (?: \b (?:public|protected|private|static|abstract|final) \b \s+ )*
命名空間、use 引入、屬性 (PHP 8 #[Attr])干擾函數識別預處理階段移除或模式增加容忍
Heredoc/Nowdoc 中包含 function 關鍵字php_strip_whitespace 保留字符串內容字符串內不會有真實函數定義,誤報率極低;嚴謹場景需詞法分析器

生產級替代方案:PHP-Parser (nikic/PHP-Parser)

正則終究是「正則」,無法 100% 覆蓋 PHP 複雜語法。**業界標準做法**是使用 **PHP-Parser** 將源碼解析為 AST (抽象語法樹),再遍歷節點提取信息:

composer require nikic/php-parser

use PhpParser\ParserFactory;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\ClassMethod;

$code = file_get_contents('src/Service.php');
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse($code);

// 可選:解析名稱解析 (use 導入、命名空間)
$traverser = new NodeTraverser;
$traverser->addVisitor(new NameResolver);
$ast = $traverser->traverse($ast);

// 遍歷提取
$functions = [];
foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($ast)) as $node) {
    if ($node instanceof Function_ || $node instanceof ClassMethod) {
        $functions[] = [
            'name'       => $node->name->toString(),
            'params'     => array_map(fn($p) => $p->toString(), $node->params),
            'returnType' => $node->returnType?->toString(),
            'body'       => $node->stmts ? $node->stmts : [],
            'isStatic'   => $node->isStatic(),
            'visibility' => $node->getVisibility(),
            'startLine'  => $node->getStartLine(),
            'endLine'    => $node->getEndLine(),
        ];
    }
}
print_r($functions);

優勢:100% 語法正確、支持所有 PHP 版本特性、可獲取精確行號、易於擴展分析邏輯 (如參數類型、返回類型、屬性、DocBlock)。

總結

php_strip_whitespace + PCRE 遞歸模式可快速實現「函數體提取」原型,適合輕量級腳本、CI 檢查、簡單重構。面對生產級代碼庫、**複雜語法 (PHP 8.1+)、需精確行號與類型信息**的場景,**請直接使用 PHP-Parser**,以 AST 為基礎構建分析工具,避免正則維護地獄。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved