Laravel は PHP のフレームワークで Eloquent は Laravel 内で使われているデータベースのモデルクラスです。Laravel を使っている時、全テーブルに対して何かしたい、全モデル、全 Eloquent 定義に対して……、という時があります。例えば、プログラム内で使っているテーブルのみの ER 図を用意したい、Eloquent と1対1で存在するべきクラスのスケルトンを用意したい、といった時です。そういった時はまず Eloquent クラスをまとめて引っ張ってくる必要があります。これは次でできます。
function getEloquentClassNames(): array { // プロジェクトの Eloquents を置いてあるディレクトリ以下の PHP ファイルを探索 // この探索は再帰的に行われる // @see https://symfony.com/doc/current/components/finder.html $files = (new Finder())->in(app_path('Models/Eloquents')) ->files() ->name('*.php'); // 見つかったファイルパスを元にクラス名一覧を作る $classNames = []; /** @var SplFileInfo $fileInfo */ foreach($files->getIterator() as $fileInfo) { $classNames[] = str_replace( // ファイルパスを名前空間に入れ替え、拡張子を除去 [app_path('Models/Eloquents'), '/', ".php"], ['App\\Models\\Eloquents', '\\', ''], $fileInfo->getRealPath() ); } // クラス名の中から Eloquent を継承したクラスのみを抜き出す $eloquentClassNames = []; foreach($classNames as $className){ try { // クラス名からインスタンスを作成。 Eloquent を継承しているか確認 $instance = new $className(); if($instance instanceof \Illuminate\Database\Eloquent\Model) { $eloquentClassNames[] = $className; } } catch(\Error $exception) { // インスタンス化できない対象について new を行った際のエラーを握りつぶす // abstract class や trait が引っかかりやすい } } return $eloquentClassNames; }
流れとしては以下です。
- あらかじめ Eloquent を格納しているディレクトリのパスを上記コード内に用意
- Symfony の Finder コンポーネントで Eloquent のあるディレクトリ以下の PHP ファイルを探索
- 見つかった PHP ファイルを元にクラス名を作成
- クラス名を使ってインスタンスの生成を試み Eloqent クラスが生成されるか確認
- Eloquent クラスであることが確認できたクラス名の配列を返す
例では app/Models/Eloquents 以下しか検索していませんが Symfony の Finder コンポーネントは複数ディレクトリも探索できます。これによりモジュール的に Eloquent ファイルが分断されていても全然対応できます。
The Finder Component (Symfony Docs)
このコードの注意点としてコンストラクタを実行している点があります。なかなかないとは思いますが、コンストラクタの実行時にデータベース書き込み等の副作用を持つコードを動作させると予期せぬ副作用の発火を招きます。コンストラクタに悪意あるコードを埋め込んだ PHP クラスファイルを置かれても同様に予期せぬ副作用の発火で事故が起きます。簡単に書けるため try new catch と書きましたが、開発時以外にこの方法によってクラスがあるクラスを継承しているか調べるのは危険です。
もし安全に処理をしたいのであれば PHP Parser などの構文から判断するライブラリを使うか、ファイル内に abstract class の様なインスタンス化不能であることを示す文字列がないか確かめるなどしてテキストファイルの状態のままクラスを識別する方が良いです。
nikic/PHP-Parser: A PHP parser written in PHP