Eloquent は Laravel の用意したデータベースに対応するクラスの枠組みです。その特性上、データベース中のテーブルに対する操作を色々やりやすい他、テーブル中のメタ情報を元に様々なことができます。このメタ情報は Eloquent に生えている getConnection メソッドから呼び出せる Doctrine によってアクセスでき、これを元に色々できます。
Doctrine: PHP Open Source Project
【PHP】DBを直接扱うためのパッケージDoctrineの紹介 – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発
この Doctrine を使うとあるEloquentの指すテーブルに対応したコードを編集しやすく、メタプログラミングの大きな助けになります。
例えばdeleted_at カラムの有無で SoftDelete トレイトの使用を自動で記述できます。
モデル中の deleted_at カラムに対応する softDelete trait の use を追加する Artisan コマンドのソースコード
<?php
namespace App\Console\Commands\ForDevelop;
use App\Console\BaseCommand;
use App\Models\Eloquents\BaseEloquent;
class AppendSoftDeleteToModel extends BaseCommand
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dev:append-model-soft-delete {classNamePath}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'モデル中の deleted_at カラムに対応する softDelete trait の use を追加';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle(): void
{
/** @var BaseEloquent $modelNamePath */
$modelNamePath = $this->argument('classNamePath');
/** @var BaseEloquent $modelInstance */
$modelInstance = new $modelNamePath();
$deletedAtColumn = $modelInstance->getConnection()->getDoctrineColumn($modelInstance->getTable(), 'deleted_at');
if (! $deletedAtColumn) {
$this->error($modelInstance->getTable().'.deleted_at カラムが見つかりませんでした。');
return;
}
if (in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses($modelInstance), true)) {
$this->error('既に SoftDeletes は use されています');
return;
}
$modelFilePath = config('infyom.laravel_generator.path.model').DIRECTORY_SEPARATOR.class_basename($modelNamePath).'.php';
$content = file_get_contents($modelFilePath);
$replacedContent = $this->appender($content);
file_put_contents($modelFilePath, $replacedContent);
}
private function appender(string $content): string
{
$newContent = preg_replace('/(class [^{]*{)/', "$1\nuse SoftDeletes;", $content);
return preg_replace('/(namespace \S+;\n)/', "$1\nuse Illuminate\Database\Eloquent\SoftDeletes;", $newContent);
}
}
例えば各カラムの型とコメントを元にルールとルールに対応するラベルを作れます。
モデル中の public static function rules() メソッドの返り値のキーに対応する rules 用のラベルメソッド rulesAttributes を追加する Artisan コマンドのソースコード
<?php
namespace App\Console\Commands\ForDevelop;
use App\Console\BaseCommand;
use App\Models\Eloquents\BaseEloquent;
class AppendRuleAttributesToModel extends BaseCommand
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dev:append-model-rule-attributes {classNamePath}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'モデル中の public static function rules() メソッドの返り値のキーに対応する attributes メソッドを追加';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle(): void
{
/** @var BaseEloquent $modelNamePath */
$modelNamePath = $this->argument('classNamePath');
$rules = $modelNamePath::rules();
$ruleKeys = array_keys($rules);
$nameToCommentMap = $this->getCommentMapFromTable(new $modelNamePath());
$attributeMap = [];
foreach ($ruleKeys as $ruleKey) {
$attributeMap[$ruleKey] = $nameToCommentMap[$ruleKey] ?? '';
}
$modelFilePath = config('infyom.laravel_generator.path.model').DIRECTORY_SEPARATOR.class_basename($modelNamePath).'.php';
$content = file_get_contents($modelFilePath);
$replacedContent = $this->replacer($content, $attributeMap);
file_put_contents($modelFilePath, $replacedContent);
}
/**
* @param BaseEloquent $model
* @return array {columnName => comment}[]
*/
protected function getCommentMapFromTable(BaseEloquent $model): ?array
{
$table = $model->getConnection()->getTablePrefix().$model->getTable();
$schema = $model->getConnection()->getDoctrineSchemaManager();
$database = null;
if (strpos($table, '.')) {
[$database, $table] = explode('.', $table);
}
$columns = $schema->listTableColumns($table, $database);
$nameToCommentMap = [];
foreach ($columns as $column) {
$nameToCommentMap[$column->getName()] = $column->getComment();
}
return $nameToCommentMap;
}
protected function replacer(string $content, array $attributeMap): string
{
$attributeMapStr = '';
foreach ($attributeMap as $key => $attribute) {
$attributeMapStr .= " '{$key}' => '{$attribute}',\n";
}
$appendFunctionStr = <<<EOF
public static function ruleAttributes(): array
{
return [
{$attributeMapStr}
];
}
EOF;
return preg_replace('/}\s*\z/', $appendFunctionStr."\n}\n", $content);
}
}
例の様なオレオレコードジェネレータをサクッと作れる他、 hoge-generator の様な名前を冠したコード生成ライブラリ多くのがこの Doctrine を用いて便利なコードを実現しています。その様なライブラリのコードを読むとメタプログラミングの技術を吸収でき、手打ちやコピペ抜きにコーディングの大半を済ませられるようになります。