PHPにPHPを埋め込みさせる

この記事は,OIT Advent Calendar 2018の2日目の記事です。

また,本記事は雑に書いてます.自己責任で読んでください.

何をしたいか

下記のようなPHPファイルがあるとします.

<?php
$hoge = "Hello World!";

それに"代入演算子がある次の行"になにか任意の処理を埋め込みたい.

<?php
$hoge = "Hello World!";
# ここに下のコードを追加したい
echo("ここに代入演算子があったぞおおおおお!!");

もちろん手で書き換えるのもいいが,3000近くのファイルに対し同様のことを行いたいと考えると厳しい.

解決策

PHP-Parserというライブラリを利用する.

github.com

(composerで導入するので,composerの知識はちょっと必要)

このライブラリはPHPソースコード構文木の状態にして解析しやすいようにするものである.(多分)

構文木の状態にせずに直接ソースコードの状態でやろうとすると色々とダメなパターンが出てきて無駄に苦労する.

<?php
// composerで管理してるライブラリをrequire(各自ファイルパスが違う気がする)
require_once 'vendor/autoload.php';

use PhpParser\ParserFactory;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;

$code = <<<'CODE'
<?php
$hoge = "Hello World!";
CODE;

// array_insert関数の中身は略してます.
function array_insert(&$base_array, $insert_value, $position=null) {..}

class MyNodeVisitor extends NodeVisitorAbstract
{
    public function enterNode(Node $node) {
        for($i = 0 ; $i < count($node->stmts) ; $i++){
            $value = $node->stmts[$i];
            $valueType = $value->getType();
            if($valueType == "Expr_Assign"){
                // ここで自ら埋め込みたいソースコードの構文木を構築する
                $echoStr = new \PhpParser\Node\Scalar\String_("ここに代入演算子があったぞおおおおお!!")
                $echoArg = new \PhpParser\Node\Arg($echoStr); 
                $echoArgs = array($echoArg);
                $echoName = new \PhpParser\Node\Name("echo");
                $echoFunc = new \PhpParser\Node\Expr\FuncCall($echoName, $echoArgs);
                $i++;
                // array_insert関数は自作関数
                array_insert($node->stmts, $echoFunc, $i);
            }
        }
    }
}

$parser        = (new ParserFactory)->create(ParserFactory::PREFER_PHP5);
$traverser     = new NodeTraverser;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;

$traverser->addVisitor(new MyNodeVisitor);

// parse
$stmts = $parser->parse($code);

// traverse
$stmts = $traverser->traverse($stmts);

// pretty print
$code = $prettyPrinter->prettyPrintFile($stmts);

echo($code);

array_insert関数は以下のサイトから持ってきた.

qiita.com

構文木構築パートは,事前に構築したいソースコード構文木がどうなるか確認しておく必要がある.

構文木$parser->parse($code)した後の$stmts変数をvar_dump()あたりで出力すると確認できる.

その後は頑張って構文木パズルをするんだよ.

なんでこんなことをしてたか

埋め込みたい気分になった.(詳細は省く)

PHP-Parserの情報が少なくて結局ライブラリのコードを読んでこの方法に辿り着いた.

多分正しい使い方ではなさそうなので自己責任で.(これしたい人が存在するかわからないけど)