【Laravel】Eloquent から対応するテーブルのメタ情報を引っ張る

 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 を用いて便利なコードを実現しています。その様なライブラリのコードを読むとメタプログラミングの技術を吸収でき、手打ちやコピペ抜きにコーディングの大半を済ませられるようになります。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG