LoginSignup
33
39

More than 1 year has passed since last update.

今更聞けないLaravelで利用できるデータ操作

Last updated at Posted at 2021-09-23

Laravelのデータ操作方法について

今回調べてみようと思ったきっかけはドメイン駆動設計アーキテクチャについて、学んでいる最中なのですが、「EloaquentはActiveRecord型のためDDDには不向き」的な意見が多く、Laravelで利用できるデータ操作方法が取得する側のクラスにどう影響を与えるのか整理しておきたいと思ったため。
また、Eloaquentを使わない場合に代替案としてどんな方法があるのか(メリデメを知りたい)と思ったため。

DoctrineはLaravelで標準で使えるものではないがライブラリで簡単に導入できることと、SymfonyはDDDに向いているという話を聞いたことがあったので、Symfonyで標準利用できるDoctrineも検証内容に含ました。

結論をまとめておく

後で見返せるように各取得方法を羅列して行ったところ、かなり冗長になったので最初に纏めておきます。
自身の整理用の基本的な内容が続くので、考察とか諸々の段落まではサラッと読み流して大丈夫だと思います。

全件取得 id指定(find) 検索 リレーション
PDO 配列[※連想配列] - 配列[※1 連想配列] 配列[※1 連想配列]
QueryBuilder Collectionオブジェクト[stdClassのオブジェクト] stdClassのオブジェクト Collectionオブジェクト[stdClass] Collectionオブジェクト[stdClassのオブジェクト]
Eloaquent Collectionオブジェクト[※2 Modelオブジェクト] ※2 Modelオブジェクト Collectionオブジェクト[※2 Modelオブジェクト] ※2 Modelオブジェクト
Doctrine 配列[※4 Entityオブジェクト] Entityオブジェクト Entityオブジェクト Entityオブジェクト[PersistentCollection]

※1 連想配列を指定した場合
※2 ModelオブジェクトはEloaquent由来
※3 PersistentCollectionのオブジェクトはDoctrine由来
※4 Entityクラスのメンバは自由に操作可能

使いやすい便利機能を利用すると依存度は高くなる

特定の技術を用いた便利機能をいろんな箇所で使っていると依存度は高くなります。
Eloaquentはデータベース構造と密結合のModelオブジェクトを返していることがわかります。

実際に取得方法とそのデータについて見ていきます。

PDO

PDO(PHP Data Object)とは、PHP標準(5.1.0以降)で利用することのできるデータベース接続クラスのことです。

今回の記事では本筋ではないのでサラッと流しますが、詳しくおさらいしておきたい方はこちらの記事をどうぞ→【PHP超入門】クラス~例外処理~PDOの基礎

Laravelで利用する場合はDBクラスを用いることで簡単に利用できます。

      $pdo = DB::connection()->getPdo();
      $users = $pdo->query('select * from users');

PDOにquery()を実行すると返り値はPDOStatement クラスとなります。(ドキュメンはこちら

スクリーンショット 2021-09-19 14.58.46.png

一つずつ結果を見ていきます。

[PDO] 全件取得

        //全件取得
       $pdo = DB::connection()->getPdo();
       $users = $pdo->query('select * from users')->fetchAll(PDO::FETCH_ASSOC); //キーを連想配列に指定した場合

スクリーンショット 2021-09-19 15.07.06.png

  • FETCH_ASSOCを指定しているため3件分のユーザーの一覧があり、その中に連想配列という形になっています。

[PDO] id指定(find)

省略

[PDO] 検索

        //検索
        $pdo = DB::connection()->getPdo();
        $users = $pdo->query('select * from users where name = "モーリー"')
                     ->fetchAll(PDO::FETCH_ASSOC);

スクリーンショット 2021-09-19 15.12.29.png

  • 検索に該当したusersテーブルのデータが配列形式で取得(今回は1件分)でその中に連想配列が入っています。

[PDO]リレーション(posts)

        //リレーション(posts)
        $pdo = DB::connection()->getPdo();
        $users = $pdo->query('select * from users inner join posts on posts.user_id = users.id where users.name = "モーリー"')
                     ->fetchAll(PDO::FETCH_ASSOC);

スクリーンショット 2021-09-19 15.16.39.png

  • SQLの実行結果が配列データとなるので、同じユーザーのデータで計2件の場合は後述するORMで取得した場合よりも、データ取得方法よりも冗長な形で取得しています
  • usersテーブルのカラムとpostsテーブルのカラムが区別なく同列に扱われていることも実際のデータを見てみることでわかるかと思います。

QueryBuilder

クエリビルダはLaravelがサポートするすべてのデータベースシステムで機能します。つまりデータベースがMySQL・PostgreSQLといった種類が変わっても抽象化してくれます。
またPDOパラメーターバインディングを利用することでSQLインジェクション攻撃の対策も行ってくれます。
クエリビルダのドキュメントはこちら

PDOを利用した記述に比べると、記述が直感的になっています。発行されるSQLの条件を増やしたい場合はチェーンメソッドを増やしていく形になるため、SQLが組める人にとっては複雑な条件の場合にもイメージしやすい取得方法になります。

        $users = DB::table('users');

まずテーブルの情報を取得してみます。

スクリーンショット 2021-09-19 16.14.18.png

こちらの中身を見てみるとIlluminate\Database\Query\Builderクラスであるとこがわかります。こちらに用意されている便利なメソッドを使って、冒頭の一覧にあるデータを取得していきます。(各メソッドについてはこちらのドキュメントをどうぞ)

[クエリビルダ] 全件取得

        // 全件取得
        $users = DB::table('users')->get();

スクリーンショット 2021-09-19 16.17.32.png

  • ユーザーテーブルにある3レコード分が配列となっていますが、Illuminate\Support\Collectionで囲っているインスタンスが帰っていることがわかります。
  • Collectionは配列データを操作しやすくするためのラッパーです。(Collectionのドキュメントはこちら
  • さらに配列の中の1レコード分のデータに着目してみるとオブジェクトになっています。(PDOの際は配列の中に連想配列が入っていました。)

  • クエリビルダを用いた場合はオブジェクトの中身は構造をしていて、これをPHPのstdClassと言って、プロパティやメソッドを一切持たない標準クラスになります。(stdClassの説明は【PHP】stdClassについて

  • 型として扱える他にオブジェクトにすることで、$obj->hogeのように表記することができます。

[クエリビルダ] id指定(find)


        // id指定(find)
        $users = DB::table('users')->find(1);

スクリーンショット 2021-09-19 16.32.30.png

  • PDOの時と違い、クエリビルダのfind()を利用することで該当する1つのデータのみをstdClassのオブジェクトとして取得できることになります
  • 型を持つデータとして扱いやすくなりました。

[クエリビルダ] 検索

        //検索
        $users = DB::table('users')->where('name', 'モーリー')->get();

スクリーンショット 2021-09-19 16.38.17.png

  • データの形式としては全件取得の時と同じ形式で、該当しないレコード分が取り除かれた状態になる

[クエリビルダ] リレーション(posts)

        //リレーション(posts)
        $users = DB::table('users')->join('posts', 'users.id', '=', 'posts.user_id')->where('users.name', 'モーリー')->get();

スクリーンショット 2021-09-19 16.41.47.png

  • データの形としては検索の時の状態に、子テーブルであるpostsのカラムが追加された形。
  • postsテーブルのカラムにアクセスする際は$users[0]->contentとして取得することになる。

Eloquent

EloquentはLaravelで標準的に備えられているオブジェクトリレーショナルマッパー(ORM)です。
モデルクラスを利用して各データベーステーブルのデータの扱いを簡単にする機能です。

Eloquentについて仕組みを詳しく知りたい方はこちらの記事を呼んでみるのがいいと思います。(バージョンによる違いはあるかもしれないので最新のソースを追いつつ読むのがいいかと)→ 【Laravel】 第1回 Eloquent ソースコードリーディング - モデルの取得

[Eloaquent]全件取得

        // 全件取得
        $user = User::all();

スクリーンショット 2021-09-19 17.00.41.png

  • これまでのクエリビルダやPDOで取得できるデータに比べるとかなり多くの情報が付与されていることがわかります。
  • Collectionオブジェクトとして取得した中のデータに関してはEloquent由来のモデルクラスであるIlluminate\Database\Eloquent\Modelを継承するApp\Models\Postのオブジェクトが返りました。
  • これにより、Eloquentの持つ多機能かつ強力な機能を利用することができるようになります(Eloquentのドキュメントはこちら)
  • 後述しますが、ドキュメントに記載のある便利な機能をControllerやServiceクラスで利用した場合に激しくこの機能をもつクラスに依存することになります。

[Eloaquent] id指定(find)


        // id指定(find)
        $users = User::find(1);

スクリーンショット 2021-09-19 17.14.15.png

  • find()を利用した場合はApp\Models\Userのオブジェクトが取得できます。
  • こちらもEloaquent由来なので操作しやすい便利な機能を利用できますね。

[Eloaquent] 検索

        //検索
        $users = User::where('name', 'モーリー')->get();

スクリーンショット 2021-09-23 16.28.18.png

  • 該当したusersテーブルのレコード分がCollection形式で取得できます。
  • 各レコード単位ではApp\Models\Userのオブジェクトが取得できます。

リレーション(posts)

        //リレーション(posts)
        $user = User::find(1);
        $posts = $user->posts;

スクリーンショット 2021-09-23 16.38.24.png

  • usersのid=1に紐つく、postsテーブルのレコードがCollection形式で取得できます。
  • 各postsレコード単位ではApp\Model\Postが取得できます。
  • これはモデルにusersとposts間でリレーションを定義しているからであり、関係を定義すると、Eloquentの動的プロパティを使用して関連レコードを取得できます。(Laravel 8.x Eloquent:リレーション

Doctrine

DoctrineはSymfonyでデフォルト導入されているデータマッパー型のORMでEntityクラスとRepositoryクラスを用いてデータを取得しオブジェクト化します

Entityとは、データベースにおける表の個別の行に対応するオブジェクトでメンバを自由にカスタムすることができます(データベース構造に依存しないオブジェクトを生成できる)
ちなみにDDDのEntityやクリーンアーキテクチャのEntityとは名前が被っているだけで、役割としてはいずれも異なる。
Repositoryとはデータの永続化や取得を担うクラスです。

Laravelでは標準的に使うことはできませんがライブラリを用いると簡単に導入できるため、比較対象として個人的に気になったので、今回は取り上げました。
Laravelでの取り入れ方はこちらの記事を参考にしました→laravelでdoctrine

全件取得


    $users = $repository->findAll();

スクリーンショット 2021-09-23 22.27.32.png

  • Userテーブルの各レコードが配列に格納されている形
  • レコード単位ではAPP\Entities\Userのクラス由来のオブジェクトが返る
  • Entities以下は自分で定義しているのでクラスのメンバーは自由に編集可能(findAll()はdoctrineの機能だけどデータベース構造由来のオブジェクトが返るわけではない)

id指定(find)

    $user = $repository->find(1);

スクリーンショット 2021-09-23 22.35.48.png

  • APP\Entities\Userのクラス由来のオブジェクトが返る

検索

    $users = $repository->findBy(["name" => 'モーリー']);
  • Userテーブルの検索条件に該当する各レコードが配列に格納されている形
  • レコード単位ではAPP\Entities\Userのクラス由来のオブジェクトが返る
  • データの構造は全件取得と同じ

リレーション

    $user = $repository->find(1);
APP\Entities\User
    // ...
    /**
     * @OneToMany(targetEntity="Post", mappedBy="user")
     */
    protected $posts;
    // ...

スクリーンショット 2021-09-23 22.58.15.png

  • App\Entities\Userが返るがEntitiesにアノテーションでリレーションを表現しUserクラスのメンバーに$postsを加えることでPostクラス由来のオブジェクトをメンバーのように扱うことができるようになる。アソシエーションの詳しいマッピング方法はこちら
  • postsのレコード単位ではPersistentCollectionというDoctrineに依存したクラスのオブジェクトが返るために、データベースの関連情報が利用箇所に漏れる

考察とか諸々

  • 当たり前ですが多機能なORMを使うとその技術に対する依存度は高くなる
  • 他のレイヤーとドメインモデルを疎結合な状態に保つためRepository層でデータ操作について隠蔽するドメイン駆動設計と、データベース設計と戦略的に密結合なオブジェクトを使うことでコードの記述量を減らし開発速度を上げるActiveRecord型のFWはアプローチが真逆なので不向きという文脈が理解できた。「結局妥協するなら落とし所がどこなのか」という問題と「厳密にやろうとするとRepository層でEloaquent由来のオブジェクトを詰め替える必要があり高コスト」という点。
  • Doctrineは単一のオブジェクトの場合、Entityは自由に設定できるのでデータベース構造に依存しない。しかし複数のテーブルの関係をアノテーションで表現するとデータベースの関連情報にオブジェクトが引きずられる。
  • Laravel x Doctrineにすることでお行儀のいいRepositoryパターンを実装できる
    • 実際にプロジェクト導入したことないので細かい問題がでてくるのかはわからない
    • しかしRepositoryクラスで完全に隠蔽しようとすると結局それなりに詰め替えや記述コストがかかる。

まとめ

Laravelを技術選定するということは便利なEloaquentを使いたいということだと思うので、ORM依存はある程度受け入れるのが吉なのかなと思いました。(PHPを選定している時点で、ある程度開発速度が求められるプロジェクトだと思いますし...)
EloaquentをRepository層でのみ利用し、詰め替えするのはコストが大きいと感じました。
RDB以外(API経由など)からデータリソースを受け取ることになったならば、大人しく別のクラスとして定義する方がプロジェクト全体のコスト感は低いのかなとも思いました。
リプレイスのフェーズだけど、何某の事情で「データベースの設計は変えられず、その設計自体が微妙」みたいな特殊事情があれば、データリソースに依存せず実装できる意味でDoctrineを用いてもいいのかと思いました。
開発に関わるメンバーがSymfonyに精通しているけれど、Laravelの機能を使いたい事情があるみたいな時もLaravel x Doctrineを選択肢に入れてもいい気はします。
実際にプロジェクトで用いたことがないのでわかりませんがLaravel x DoctrineはSymfonyベースの開発だけれど、Laravelの機能を足したみたいなイメージになるのかなと思いました。
プロジェクトメンバーの入れ替わりが激しいと、独自ルールを浸透させるのが難しそうだなと思いました。

PS

調べながら書いたので間違っているところが結構あるかもしれません。

33
39
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
39