a-pompom / Laravel-playground

Laravelを色々試す
0 stars 0 forks source link

Todoリスト機能をつくりたい #3

Closed a-pompom closed 1 year ago

a-pompom commented 1 year ago

Model入門-TODOリスト

理解したいこと

つくってみる

接続設定

config/database.phpにてホストや認証情報を配列形式で記述。

参考

migration作成

php artisan make:migration <migration名>でmigrationファイルを作成。

その後、upメソッドにDBへ加えたい変更内容を・downへ変更を巻き戻す処理を記述。 参考

migration実行

php artisan migrateコマンドでmigrationファイルの変更内容をデータベースへ反映。 --pretendフラグを加えておくと、どういうSQLが実行されるのか確認できる。

なぜModelをもとにmigrationファイルをつくらないのか

Laravelにおいて、Modelはテーブルと一対一に対応するのではなく、拡張可能なオブジェクトであるとみなされているようだ。 考え方の違いからModelのプロパティにテーブルのカラムを直接もたせなかった。

https://stackoverflow.com/questions/51745634/laravel-models-where-are-model-properties


Model作成

php artisan make:model <Model名>コマンドでModelクラスを作成。 子ディレクトリ以下につくりたい場合は、Model名をスラッシュ区切りで記述する。

なぜ日付カラムやキーの考慮が中途半端にModelクラスに漏れ出しているのか

Modelは基本的にmigration情報の知識をもたない。 よって、LaravelではEloquent Modelにおいてキーはデフォルトでauto incrementするものであるなど、暗黙的な挙動を定めている。

このような挙動と異なる場合にデータベースと合わせるために、Modelクラスのプロパティを変更することがあるようだ。 https://laravel.com/docs/10.x/eloquent#primary-keys

Query Builder vs Eloquent Model

Laravelは、DB操作処理としてEloquent以外にもQuery Builderと呼ばれるモジュールを提供している。 これは、パフォーマンスに優れており、かつ、生のSQLも記述できる。

ただし、戻り値がPHPのStdClassとなるので、Domain Modelを扱うには不適。扱う集合が極端に小さい場合以外はEloquent Modelで記述するのが保守性から考えても良さそう。

a-pompom commented 1 year ago

TODOリストの実装

どういう単位で処理を分けるか

CRUDごとにServiceクラスをつくる? →今回は小規模ゆえ過剰に思えるが、テストを書くことを考えると分けた方が扱いやすい。

Model自体にロジックを生やす? →メソッド名が競合しやすいのでNG。

以下、クラスのメソッドの粒度に対するメモ。

1つのpublicメソッド:
・振る舞いが明確
・テストケースも1つのメソッドに対する振る舞いでまとめられる
・変更したいときどこを変えるべきかクラスレベルで特定できる
・複数の振る舞いを組み合わせたい場合、組み合わせるためのクラスが別途必要になる
     ・個々のクラスへの依存は少なくなるので、巨大なクラスよりメンテナンスしやすいはず

複数のpublicメソッド:
・クラス数は減る
・命名が抽象的になるので、どこで何をやっているか見えづらい
・処理が複雑になると実装・テストケースが肥大化してメンテナンスしづらくなる
・複数人で作業するときに競合しやすくなる

今回は実装する処理も少ないが、テストを書くことも考えて複数のクラスを用意しておく。

どう実装するか

機能単位で実装・テストあわせて書いていきたい。 まずはcreateからはじめてread→update→deleteと繋げていくか。

create

Serviceクラスをつくって、入力値の文字列を引数にTask ModelをDBへ登録。

テストはServiceクラスを呼び出した後のDBの状態を検証する。 参考

a-pompom commented 1 year ago

Factoryに入門したい

https://laravel.com/docs/10.x/eloquent-factories

なぜFactoryが必要か

データベースが関係する機能のテストでは、事前にレコードを用意しておかなければならない。 単純な機能であればさほど問題とはならないが、カラム数の多いテーブルのレコードを複数用意するようになると、テストの準備フェーズが複雑になる。

デフォルトのカラム値を設定してシンプルにレコードを組み立てられるよう、Factoryが用意されたと思われる。 また、テスト用途以外にも共通の形式でModelをつくりたい場合にFactoryは有用。

Factory vs Fixture

さまざまなテストで参照されるデータは、Fixtureとして表現することもできる。 しかし、データベースのテーブル・値のバリエーションごとにFixtureを用意していると、どこに何があるか見えづらくなる。

また、テーブルの構造が変わると個々のFixtureをメンテナンスしなければならない。 メンテナンスを容易にしてかつ、テストコードの準備フェーズをシンプルに保ちたい場合、Factoryの方が適している。

Factoryは生成処理をシンプルにすると共に、各テーブルのデータを表すものをどこに配置すればよいか明確になる。 具体的には、database/factoriesディレクトリにまとめてしまえば、レコードの生成方法やバリエーションに関する知識を閉じ込められる。

まとめると、データベースのレコードを事前に準備する用途においては、Factoryの方がシンプルかつメンテナンスしやすい形で実現できる。

Factoryクラスのつくり方

database/factories以下のディレクトリへ配置。生成するクラスは、Factoryクラスを継承しておく。

Factoryオブジェクトの生成方法

LaravelがModelにたくさんのtraitを生やす設計としているからなのか、FactoryオブジェクトはModel::factory()を呼び出してつくられる。 ※ 実体は結局Factoryクラスの静的メソッドを呼び出してオブジェクトを構築。

https://laravel.com/docs/10.x/eloquent-factories#factory-and-model-discovery-conventions

Factoryオブジェクトを介してModelオブジェクトを生成

Factory::make()を呼び出せばよい。内部ではModelオブジェクトをインスタンス化しているようだ。 引数としてキーをカラム名・値を設定したいカラム値とした配列を渡せば、個別にModelを組み立てられる。

初期値

Factory::definition()でカラムと値を表す配列を返すことで、Modelの各カラムの初期値を設定できる。

state

Modelオブジェクトを組み立てるとき、ある程度は共通化しておいて、一部だけ切り替えたい場合はFactory::state()が有用。 例えば管理者の属性値を設定したユーザをつくりたい場合、UserFactory::admin()のようなメソッドでFactory::state()を呼び出すとよい。

a-pompom commented 1 year ago

CRUD

Create

Eloquent Modelのインスタンスをつくって属性値を配列から設定。 Model::save()からデータベースへレコードが登録される。

https://laravel.com/docs/10.x/eloquent#inserts

Read

全件読み出し

Model::all()で全件を読み出した結果を詰め込んだCollectionオブジェクトが得られる。

ただし、Collectionオブジェクトにはクエリビルダとしての機能は無い。 よって、全件をソートしてから読み出したい場合は、Builder::get()を介して読み出すしかないようだ。

参考

https://laravel.com/docs/10.x/eloquent#retrieving-models

絞り込み

Builder::where()などで絞り込んでBuilder::get()からCollectionを取得。 その他、Collectionを介さず直接単体のModelを取得するようなメソッドも用意されているようだ。

ただ、情報が充実しているとは言えないので、もう少し色々試して理解していった方が良さそう...。

https://laravel.com/docs/10.x/eloquent#retrieving-single-models

クエリメソッドをどう実装するべきか

Modelにはメソッドを実装しない。

基本的にはドメインのServiceクラスでEloquent Builderを介して取得。 共通化したいのであれば、知識を閉じ込めたServiceクラスを実装し、Service Containerから受け取るべき。

結論を出すに至ったメモ書きも残しておく。 

Modelにクエリメソッドをたくさん生やす:
・処理を共通化はできる
・Eloquent Modelはtraitまみれでメソッドがたくさん実装されているので、名前が衝突する可能性がある
・ドメインロジックに限定しても、ドメイン知識がModel層に分散されて処理を追いづらくなる
・どうしても汎用的な処理が必要なのであれば、Eloquent Builderを内包したServiceクラスつくってService Containerから受け取った方がメンテナンスしやすい
    ・単純な絞り込み条件であれば、都度書いても機能テストで振る舞いを検証することで問題ないはず

Update

単体はModel::save()・複数はModel::update()と使い分けるようだ。 https://laravel.com/docs/10.x/eloquent#updates

Delete

Modelオブジェクトに対してdeleteメソッドを呼び出すだけでよい。 https://laravel.com/docs/10.x/eloquent#deleting-models