メタプログラミングやコードのスタブを作っているとしばしば後付けでコードを書き換えたい時があります。この記事ではそういった時に使える手法を Laravel のモデルクラスを題材に紹介します。
自分がよくやりたくなる書き換えはプロパティをメソッドに置き換えるものです。これは次の様なコードへの書き換えで、プロパティと異なって処理をその場に書ける点で読みやすいコードです。
<?php
namespace App\Models\Eloquents;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public $table = 'users';
// ↓このrulesは静的プロパティで定義されており式(特に new HogeRule の様なルールのインスタンス化定義)を初期値で含められません。
public static $rules = [
'name' => 'string|max:255',
'email' => 'string|max:255',
'email_verified_at' => 'nullable|date',
'password' => 'string|max:255',
'remember_token' => 'nullable|string|max:100'
];
}
<?php
namespace App\Models\Eloquents;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\Rules\In;
class User extends Model
{
public $table = 'users';
// ↓このrulesは静的メソッドで定義されており、初期値で式を書けるのみでなく、処理も挟めます。
// 外部からの変更も後付けのみでこのメソッドを壊すことは容易ではありません(多分リフレクションの何かしらを使えば壊せます)。
public static function rules(): array
{
$this->何かメソッド();
return [
'name' => ['string', 'max:255', new In([])],
'email' => ['string', 'max:255'],
'email_verified_at' => ['nullable', 'date'],
'password' => ['string', 'max:255'],
'remember_token' => ['nullable', 'string', 'max:100'],
];
}
}
この様な書き換えは構文を書き換えるのみで決まりきった作業で置換しやすいです。これを正規表現で実現します。具体的には次です。
/**
* rules プロパティを rules メソッドに置き換え
* @param string $classSrcCode クラスのソースコード
* @return string メソッドに置き換えた後のソースコード
*/
private function replacer(string $classSrcCode): string
{
// マッチされた部分に限って色々操作するために preg_replace_callback を使用
// preg_replace_callback はマッチ部を引数に取る関数と正規表現を用いて文字列を置換する
return preg_replace_callback(
'/\$rules = (\[[\S\s]*?\]);/', // $rulesの値だけをグループ化。\S\sによって[から始まり、改行コードも含めた任意文字が続いて];で終わると表現
static function ($matches) {
// メソッドの大枠をヒアドキュメントで定義してグループ化した元々の$rulesの値をリターンにセット
$functionCode = <<<EOF
function rules(): array
{
return {$matches[1]};
}
EOF;
// Laravel の rule 定義はパイプ区切り文字列よりも配列にしたほうが好きな派なので配列化
// これは配列化の前準備としてパイプ区切りをカンマ区切りに変えている
$functionCode = str_replace('|', "','", $functionCode);
// カンマ区切りの文字列が並んでいるだけのPHPとして不正な構文を配列にする
return preg_replace('/=> (.*?)[\r\n]*$/m', '=> [$1],', $functionCode);
},
$classSrcCode
);
}
これを実行すると上述したプロパティからメソッドへの置き換えができます。この様にプロパティ、メソッド定義部のみを正規表現で抜き出して preg_replace_callback 内部でなんやかんやすれば、クラス内の他の部分に影響することなくソースコードを置換できます。