学習記録:12月9日(日):【輪読会まとめ 後編パート1】Laravel Webアプリケーション開発 Chapter5 データベース
これは 俺のインプットアウトプット記録 Advent Calendar 2018 の9日目のエントリーです。
昨日に引き続きです。分量増えちゃった。しかも後編はパート1と2に分かれるほどの分量になってしまいました(汗)
というわけでクエリビルダから。僕にとってはEloquentよりこちらの方が馴染みがありますし、他のフレームワークを使っての開発もたくさんありますので、Laravelにロックインされないためにも、クエリビルダを使いたいなあと思ったりします。
が、昨日も書いた通りですので両方使ってみようと思ってます。
データベースを取り扱う場合に、可能な限り抽象化されているというのはやはり利点の方が勝るとも思うので、どうにかしてEloquent、クエリビルダの両方を使ってみて、結果を検証したいなって思いました。
ちなみに、クエリビルダの章については、全てのSQLとコードを実際に実行したので、その結果も掲載している部分があります。冗長な部分がありますがご容赦くださいませ。あと、この章のSQL、コードは全部動くはずです。(実行し直したので大丈夫…のはず)
前置き
Laravelには tinker
っていう便利なやつがあります。DBを操作するコードの実行には全てtinkerを使っています。一応、tinkerを使うところはコマンドから記載しているので大丈夫かとは思いますが…。
ちなみに、tinkerを実行する時は、Dockerコンテナの中に入り、artisanがある場所で実行しています。
$ docker-compose up -d nginx mysql workspace (中略) $ docker-compose exec --user=laradock workspace bash $ pwd /var/www laradock@0f043864bf49:/var/www$ ls -la artisan -rw-r--r-- 1 laradock laradock 1686 Oct 16 16:35 artisan laradock@0f043864bf49:/var/www$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>>
5-4 クエリビルダ
リスト5.4.0.1:データ検索のSQL文
- 価格が1000円以上、かつ出版日が
2011-01-01
以降 - これをクエリビルダで書き換えていく
SELECT bookdetails.isbn, books.name, authors.name, bookdetails.price FROM books LEFT JOIN bookdetails ON books.bookdetail_id = bookdetails.id LEFT JOIN authors ON books.author_id = authors.id WHERE bookdetails.price >= 1000 AND bookdetails.published_date >= '2011-01-01' ORDER BY bookdetails.published_date DESC;
リスト5.4.0.2:データ検索のSQL文をクエリビルダで組み立てる
- ①ベースのbooksテーブルのクエリビルダインスタンスを取得
- ②取得カラムを指定
- ③テーブル結合
- ④〜⑤条件指定
- ⑥ソート
- ここまでの各メソッドの戻り値は
Illuminate\Database\Query\Builder
- つまりクエリビルダを戻り値に受け取り続けることでメソッドチェーンが可能
- ここまでの各メソッドの戻り値は
- ⑦SQL実行して結果のオブジェクトを取得
- ここが呼ばれるまでDB接続もSQL実行もされない
- 実行結果は
stdClass
オブジェクトのコレクションで取り扱いも容易- 各カラムを操作するのも簡単
<?php $results = DB::table('books') //① ->select(['bookdetails.isbn','books.name','authors.name','bookdetails.price']) //② ->leftjoin('bookdetails', 'books.bookdetail_id', '=', 'bookdetails.id') //③ ->leftjoin('authors', 'books.author_id', '=', 'authors.id') //③ ->where('bookdetails.price', '>=', 1000) //④ ->where('bookdetails.published_date', '>=', '2011-01-01') //⑤ ->orderby('bookdetails.published_date', 'desc') //⑥ ->get(); //⑦
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('books')->select(['bookdetails.isbn', 'books.name', 'authors.name', 'bookdetails.price'])->leftjoin('bookdetails', 'books.bookdetail_id', '=', 'bookdetails.id')->leftjoin('authors', 'books.author_id', '=', 'authors.id')->where('bookdetails.price', '>=', 1000)->where('bookdetails.published_date', '>=', '2011-01-01')->orderby('bookdetails.published_date', 'desc')->get(); => Illuminate\Support\Collection {#2917 all: [ {#2918 +"isbn": "9784204176365", +"name": "著者名9", +"price": 3755, }, {#2920 +"isbn": "9793433402596", +"name": "著者名2", +"price": 3962, }, ], } >>>
5-4-1 クエリビルダの書式
- ①ベースとなるクエリビルダオブジェクト
- 処理対象のテーブルのクエリビルダインスタンス取得
- ②〜⑥メソッドチェーンによる処理対象や内容の特定
- ①のインスタンスにメソッドチェーンで色々指定する
- ⑦クエリ実行
- この書式は検索、更新、削除でも共通
tinkerによるクエリ実行
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('books')->select(['bookdetails.isbn', 'books.name', 'authors.name', 'bookdetails.price', 'bookdetails.published_date'])->leftjoin('bookdetails', 'books.bookdetail_id', '=', 'bookdetails.id')->leftjoin('authors', 'book desc')->get(); => Illuminate\Support\Collection {#2884 all: [ {#2922 +"isbn": "9784204176365", +"name": "著者名9", +"price": 3755, +"published_date": "2018-08-07", }, {#2926 +"isbn": "9793433402596", +"name": "著者名2", +"price": 3962, +"published_date": "2015-05-16", }, {#2924 +"isbn": "9784238339682", +"name": "著者名2", +"price": 9908, +"published_date": "2009-02-24", }, {#2928 +"isbn": "9790930291296", +"name": "著者名7", +"price": 7689, +"published_date": "2006-05-11", }, {#2879 +"isbn": "9788521682677", +"name": "著者名7", +"price": 9093, +"published_date": "2000-11-14", }, {#2874 +"isbn": "9787518986811", +"name": "著者名3", +"price": 1634, +"published_date": "2000-05-10", }, ], }
5-4-2- クエリビルダの取得
リスト5.4.2.1:DBファサードを利用したクエリビルダの取得
<?php // 書籍テーブルのクエリビルダ取得 $query = DB::table('books');
リスト5.4.2.2:Connectionクラスからクエリビルダを取得
- ①DatabaseManagerクラスのインスタンスをサービスコンテナから取得
- ②connection()メソッドでConnectionのインスタンス取得
- ③tableメソッドでクエリビルダのインスタンスを取得
<?php // ①サービスコンテナからDatabaseManagerクラスのインスタンス取得 $db = \Illuminate\Foundation\Application::getInstance()->make('db'); // ②上記インスタンスからConnectionクラスのインスタンスを取得 $connection = $db->connection(); // ③Connectionクラスのインスタンスからクエリビルダを取得 $query = $connection->table('books');
リスト5.4.2.3:データ操作専用クラスを作ってクエリビルダを利用
- コンストラクタインジェクションを利用
- クエリビルダ提供元のクラスを外から与える
- 拡張性やテスト容易性を保つことが可能
<?php declare(strict_types=1); namespace App\DataAccess; use Illuminate\Database\DatabaseManager; class BookDataAccessObject { /** @var DatabaseManager */ protected $db; /** @var string */ protected $table = 'books'; public function __construct(DatabaseManager $db) { $this->db = $db; } public function find($id) { $query = $this->db->connection() ->table($this->table); (以下略) } }
5-4-3 処理対象や内容の特定
- 各種メソッドの紹介と実装例
Select系メソッド
表5.4.3.1:Select系メソッド
メソッド | 機能 |
---|---|
select(カラム名の配列) | 取得対象のカラム名を指定する |
selectRaw(SQL文) | select文の中身をSQLで直接指定する |
リスト5.4.3.2:Select系メソッドの利用
<?php $result = DB::table('books')->select('id', 'name as title')->get(); $result = DB::table('books')->selectRaw('id, name as title')->get();
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('books')->select('id', 'name as title')->get(); => Illuminate\Support\Collection {#2881 all: [ {#2872 +"id": 11, +"title": "Quae sunt libero adipisci.", }, {#2859 +"id": 12, +"title": "Est nihil ut quo.", }, {#2868 +"id": 13, +"title": "Quas maiores sit aut.", }, {#2867 +"id": 14, +"title": "Quibusdam ut eius omnis.", }, {#2874 +"id": 15, +"title": "Animi et sed culpa.", }, {#2869 +"id": 16, +"title": "Dignissimos sunt est autem ea.", }, {#2882 +"id": 17, +"title": "Minus cum ut est.", }, {#2860 +"id": 18, +"title": "At voluptates numquam magni.", }, {#2853 +"id": 19, +"title": "Ratione dolore exercitationem.", }, {#2852 +"id": 20, +"title": "Repellat molestiae provident nisi.", }, ], } >>> DB::table('books')->selectRaw('id, name as title')->get(); => Illuminate\Support\Collection {#2879 all: [ {#2840 +"id": 11, +"title": "Quae sunt libero adipisci.", }, {#2856 +"id": 12, +"title": "Est nihil ut quo.", }, {#2858 +"id": 13, +"title": "Quas maiores sit aut.", }, {#2871 +"id": 14, +"title": "Quibusdam ut eius omnis.", }, {#2865 +"id": 15, +"title": "Animi et sed culpa.", }, {#2850 +"id": 16, +"title": "Dignissimos sunt est autem ea.", }, {#2866 +"id": 17, +"title": "Minus cum ut est.", }, {#2870 +"id": 18, +"title": "At voluptates numquam magni.", }, {#2861 +"id": 19, +"title": "Ratione dolore exercitationem.", }, {#2855 +"id": 20, +"title": "Repellat molestiae provident nisi.", }, ], } >>>
Where系メソッド
- 連続して呼ぶとAND条件
- ORにしたければ
orWhere
orWhereBetween
など先頭に or をつける
表5.4.3.3:Where系メソッド
メソッド | 機能 |
---|---|
where(`カラム名', ''比較演算子, '条件値') | whereを使用した一般的な条件指定。比較演算子を省略すると等価判定(=)となる |
whereBetween('カラム名', '範囲') | betweenを使用した範囲指定 |
whereNotBetween('カラム名', '範囲') | not betweenを使用した範囲指定 |
whereIn('カラム名', '条件値') | inを使用 |
whereNotIn('カラム名', '条件値') | not inを使用 |
whereNull(カラム名) | is nullを使用 |
whereNotNull(カラム名) | is not nullを使用 |
リスト5.4.3.4:Where系メソッドの利用
<?php $results = DB::table('books') ->where('id', '>=', '30') ->orWhere(`created_at`, '>=', '2018-01-01') ->get();
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('books')->where('id', '>=', '30')->orWhere('created_at', '>=', '2018-01-01')->get(); => Illuminate\Support\Collection {#2857 all: [ {#2858 +"id": 11, +"name": "Quae sunt libero adipisci.", +"bookdetail_id": 17, +"author_id": 23, +"publisher_id": 12, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2856 +"id": 12, +"name": "Est nihil ut quo.", +"bookdetail_id": 30, +"author_id": 11, +"publisher_id": 12, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2849 +"id": 13, +"name": "Quas maiores sit aut.", +"bookdetail_id": 25, +"author_id": 32, +"publisher_id": 9, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2879 +"id": 14, +"name": "Quibusdam ut eius omnis.", +"bookdetail_id": 10, +"author_id": 8, +"publisher_id": 15, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2840 +"id": 15, +"name": "Animi et sed culpa.", +"bookdetail_id": 49, +"author_id": 17, +"publisher_id": 10, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2851 +"id": 16, +"name": "Dignissimos sunt est autem ea.", +"bookdetail_id": 38, +"author_id": 39, +"publisher_id": 5, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2861 +"id": 17, +"name": "Minus cum ut est.", +"bookdetail_id": 32, +"author_id": 1, +"publisher_id": 8, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2880 +"id": 18, +"name": "At voluptates numquam magni.", +"bookdetail_id": 28, +"author_id": 37, +"publisher_id": 27, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2855 +"id": 19, +"name": "Ratione dolore exercitationem.", +"bookdetail_id": 43, +"author_id": 22, +"publisher_id": 18, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2864 +"id": 20, +"name": "Repellat molestiae provident nisi.", +"bookdetail_id": 50, +"author_id": 24, +"publisher_id": 10, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, ], } >>>
Limit, Offsetメソッド
表5.4.3.5:LimitとOffsetメソッド
メソッド | 機能 |
---|---|
limit(数値) または take(数値) | limit句に置き換わる |
offset(数値) または skip(数値) | offset句に置き換わる |
リスト5.4.3.6:Limit, Offsetメソッドの利用
<?php $results = DB::table('books') ->limit(10) ->offset(6) ->get();
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('books')->limit(10)->offset(6)->get(); => Illuminate\Support\Collection {#2865 all: [ {#2872 +"id": 17, +"name": "Minus cum ut est.", +"bookdetail_id": 32, +"author_id": 1, +"publisher_id": 8, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2871 +"id": 18, +"name": "At voluptates numquam magni.", +"bookdetail_id": 28, +"author_id": 37, +"publisher_id": 27, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2866 +"id": 19, +"name": "Ratione dolore exercitationem.", +"bookdetail_id": 43, +"author_id": 22, +"publisher_id": 18, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2870 +"id": 20, +"name": "Repellat molestiae provident nisi.", +"bookdetail_id": 50, +"author_id": 24, +"publisher_id": 10, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, ], } >>>
集計系メソッド
SQLを生で書くことが多い僕にはお馴染みのメソッドたちです。
表5.4.3.7:集計系メソッド
メソッド | 機能 |
---|---|
orderBy(カラム名, 方向) | order by句に置き換わる |
groupBy(カラム名) | group by句に置き換わる |
having(`カラム名’, '比較演算子', '条件値') | havingを利用した絞り込み |
havingRaw(SQL文) | having句の中身をSQLで直接指定 |
リスト5.4.3.8:集計系の中から、orderByメソッドによる複数カラムのソート
<?php $results = DB::table('books') ->orderBy('id') ->orderBy('updated_at', 'desc') ->get();
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('books')->orderBy('id')->orderBy('updated_at', 'desc')->get(); => Illuminate\Support\Collection {#2879 all: [ {#2861 +"id": 11, +"name": "Quae sunt libero adipisci.", +"bookdetail_id": 17, +"author_id": 23, +"publisher_id": 12, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2880 +"id": 12, +"name": "Est nihil ut quo.", +"bookdetail_id": 30, +"author_id": 11, +"publisher_id": 12, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2849 +"id": 13, +"name": "Quas maiores sit aut.", +"bookdetail_id": 25, +"author_id": 32, +"publisher_id": 9, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2859 +"id": 14, +"name": "Quibusdam ut eius omnis.", +"bookdetail_id": 10, +"author_id": 8, +"publisher_id": 15, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2858 +"id": 15, +"name": "Animi et sed culpa.", +"bookdetail_id": 49, +"author_id": 17, +"publisher_id": 10, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2857 +"id": 16, +"name": "Dignissimos sunt est autem ea.", +"bookdetail_id": 38, +"author_id": 39, +"publisher_id": 5, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2856 +"id": 17, +"name": "Minus cum ut est.", +"bookdetail_id": 32, +"author_id": 1, +"publisher_id": 8, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2851 +"id": 18, +"name": "At voluptates numquam magni.", +"bookdetail_id": 28, +"author_id": 37, +"publisher_id": 27, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2840 +"id": 19, +"name": "Ratione dolore exercitationem.", +"bookdetail_id": 43, +"author_id": 22, +"publisher_id": 18, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, {#2847 +"id": 20, +"name": "Repellat molestiae provident nisi.", +"bookdetail_id": 50, +"author_id": 24, +"publisher_id": 10, +"created_at": "2018-11-18 07:08:48", +"updated_at": "2018-11-18 07:08:48", }, ], } >>>
JOINメソッド
フレームワークにより記法が異なりますが、パッと見でわかる感じですね。
表5.4.3.9:JOINメソッド
メソッド | 機能 |
---|---|
join('対象テーブル', '結合対象カラム', '演算子', '結合対象カラム') | テーブル間の内部結合、inner joinに置き換わる |
leftJoin('対象テーブル', '結合対象カラム', '演算子', '結合対象カラム') | テーブル間の外部結合、left joinに置き換わる |
rightJoin('対象テーブル', '結合対象カラム', '演算子', '結合対象カラム') | テーブル間の外部結合、right joinに置き換わる |
リスト5.4.3.10:連続したJOINメソッドによる結合
tinkerでお手軽に実行します。ワンライナーにしてるので実際にコードに書く場合と分けてみます。
<?php $results = DB::table('books') ->leftJoin('authors', 'books.author_id', '=', 'authors.id') ->leftJoin('publishers', 'books.publisher_id', '=', 'publishers.id') ->get();
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('books')->leftJoin('authors', 'books.author_id', '=', 'authors.id')->leftJoin('publishers', 'books.publisher_id', '=', 'publishers.id')->get(); => Illuminate\Support\Collection {#2872 all: [ {#2869 +"id": 12, +"name": "株式会社 青田出版", +"bookdetail_id": 17, +"author_id": 23, +"publisher_id": 12, +"created_at": "2018-11-14 16:36:06", +"updated_at": "2018-11-14 16:36:06", +"kana": "チョシャメイ3", +"deleted_at": null, +"address": "4586817 群馬県青山市南区中津川町松本1-5-2", }, {#2874 +"id": 12, +"name": "株式会社 青田出版", +"bookdetail_id": 30, +"author_id": 11, +"publisher_id": 12, +"created_at": "2018-11-14 16:36:06", +"updated_at": "2018-11-14 16:36:06", +"kana": "チョシャメイ1", +"deleted_at": null, +"address": "4586817 群馬県青山市南区中津川町松本1-5-2", }, {#2867 +"id": 9, +"name": "有限会社 佐々木出版", +"bookdetail_id": 25, +"author_id": 32, +"publisher_id": 9, +"created_at": "2018-11-14 15:56:28", +"updated_at": "2018-11-14 15:56:28", +"kana": "チョシャメイ2", +"deleted_at": null, +"address": "8481973 群馬県山田市北区宇野町喜嶋6-2-10", }, {#2868 +"id": 15, +"name": "株式会社 山田出版", +"bookdetail_id": 10, +"author_id": 8, +"publisher_id": 15, +"created_at": "2018-11-14 16:36:06", +"updated_at": "2018-11-14 16:36:06", +"kana": "チョシャメイ8", +"deleted_at": null, +"address": "7517039 山梨県工藤市北区小林町三宅2-7-10", }, {#2870 +"id": 10, +"name": "株式会社 田辺出版", +"bookdetail_id": 49, +"author_id": 17, +"publisher_id": 10, +"created_at": "2018-11-14 15:56:28", +"updated_at": "2018-11-14 15:56:28", +"kana": "チョシャメイ7", +"deleted_at": null, +"address": "3881619 高知県津田市中央区喜嶋町宮沢9-1-7 ハイツ藤本110号", }, {#2866 +"id": 5, +"name": "株式会社 青山出版", +"bookdetail_id": 38, +"author_id": 39, +"publisher_id": 5, +"created_at": "2018-11-14 15:56:28", +"updated_at": "2018-11-14 15:56:28", +"kana": "チョシャメイ9", +"deleted_at": null, +"address": "2219948 千葉県西之園市東区井上町佐々木4-5-8 ハイツ井高110号", }, {#2871 +"id": 8, +"name": "株式会社 吉本出版", +"bookdetail_id": 32, +"author_id": 1, +"publisher_id": 8, +"created_at": "2018-11-14 15:56:28", +"updated_at": "2018-11-14 15:56:28", +"kana": "チョシャメイ1", +"deleted_at": null, +"address": "9972025 福井県廣川市東区吉本町笹田5-4-2 ハイツ青山104号", }, {#2865 +"id": 27, +"name": "有限会社 井高出版", +"bookdetail_id": 28, +"author_id": 37, +"publisher_id": 27, +"created_at": "2018-11-14 16:36:18", +"updated_at": "2018-11-14 16:36:18", +"kana": "チョシャメイ7", +"deleted_at": null, +"address": "7757641 秋田県宇野市東区西之園町小泉9-4-4 ハイツ野村107号", }, {#2864 +"id": 18, +"name": "有限会社 西之園出版", +"bookdetail_id": 43, +"author_id": 22, +"publisher_id": 18, +"created_at": "2018-11-14 16:36:06", +"updated_at": "2018-11-14 16:36:06", +"kana": "チョシャメイ2", +"deleted_at": null, +"address": "8728464 岐阜県鈴木市西区近藤町江古田1-3-8", }, {#2863 +"id": 10, +"name": "株式会社 田辺出版", +"bookdetail_id": 50, +"author_id": 24, +"publisher_id": 10, +"created_at": "2018-11-14 15:56:28", +"updated_at": "2018-11-14 15:56:28", +"kana": "チョシャメイ4", +"deleted_at": null, +"address": "3881619 高知県津田市中央区喜嶋町宮沢9-1-7 ハイツ藤本110号", }, ], } >>>
5-4-4 クエリの実行
- クエリビルダの最後に指定するSQL実行について
5-4-4-1 データの取得
こちらはお馴染みな感じがしますね。
表5.4.4.1:クエリを実行し結果を取得するメソッド
メソッド | 機能 | 結果 |
---|---|---|
get() | 全てのデータを取得 | stdClassオブジェクトのコレクション |
first() | 最初の1行 | オブジェクト単体 |
リスト5.4.4.2:tinkerによるgetメソッド実行例
Seederを使ってbooksテーブルにダミーデータを入れてから実行しています。tinker便利ですね。
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('books')->select('id', 'name')->get(); => Illuminate\Support\Collection {#2847 all: [ {#2849 +"id": 11, +"name": "Quae sunt libero adipisci.", }, {#2846 +"id": 12, +"name": "Est nihil ut quo.", }, {#2845 +"id": 13, +"name": "Quas maiores sit aut.", }, {#2844 +"id": 14, +"name": "Quibusdam ut eius omnis.", }, {#2843 +"id": 15, +"name": "Animi et sed culpa.", }, {#2842 +"id": 16, +"name": "Dignissimos sunt est autem ea.", }, {#2855 +"id": 17, +"name": "Minus cum ut est.", }, {#2857 +"id": 18, +"name": "At voluptates numquam magni.", }, {#2858 +"id": 19, +"name": "Ratione dolore exercitationem.", }, {#2859 +"id": 20, +"name": "Repellat molestiae provident nisi.", }, ], } >>> DB::table('books')->select('id', 'name')->first(); => {#2862 +"id": 11, +"name": "Quae sunt libero adipisci.", } >>> DB::table('books')->select('id', 'name')->find(11); => {#2858 +"id": 11, +"name": "Quae sunt libero adipisci.", }
実際のクエリ
-- getメソッド select `id`, `name` from `books` -- firstメソッド select `id`, `name` from `books` limit 1 -- findメソッド(なぜかlimitがつく) select `id`, `name` from `books` where `id` = 11 limit 1
表5.4.4.3:レコード数取得や計算を行うメソッド
集約関数と呼ばれるやつですね。
メソッド | 機能 |
---|---|
count() | 件数取得 |
max(カラム名) | 最大数取得 |
min(カラム名) | 最小値 |
avg(カラム名) | 平均値 |
リスト5.4.4.4:tinkerによる各メソッド実行例
こちらもtinkerで実行します。
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('bookdetails')->count(); => 100 >>> DB::table('bookdetails')->max('price'); => 9918 >>> DB::table('bookdetails')->min('price'); => 66 >>> DB::table('bookdetails')->avg('price'); => "5193.3600"
5-4-4-2 データの登録、更新、削除
こちらもお馴染みです。
表5.4.4.4:データ更新を行うメソッド
メソッド | 機能 |
---|---|
insert(['カラム'=>'値', ...]) | データ登録 |
update(['カラム'=>'値', ...]) | データ更新 |
delete() | データ削除 |
truncate() | 全行削除 |
リスト5.4.4.5:データ更新
こちらもtinkerで実行してみます。
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::table('bookdetails')->where('id', 1)->update(['price' => 10000]); => 1 >>> DB::table('bookdetails')->max('price'); => 10000
5-4-5 トランザクションとテーブルロック
トランザクション、テーブルロックといった操作も可能です。残念ながら僕は未だ ロックを意図的に実施する必要があるシステム を経験したことがないのですが、想定されるおおよそのSQLがメソッドとして用意されているのはパワフルだな、と思いました。
表5.4.5.1:トランザクション系メソッド
メソッド | 機能 |
---|---|
DB::beginTransaction() | 手動でトランザクション開始 |
DB::rollback() | 手動ロールバック |
DB::commit() | 手動コミット |
DB::transaction(クロージャ) | クロージャの中でトランザクションを実施 |
手動トランザクションの実例
せっかくなので、トランザクションの動きをなぞって検証してみます。
$ php artisan tinker Psy Shell v0.9.9 (PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> DB::beginTransaction(); => null
トランザクション直後のデータ
mysql> select * from bookdetails where id=100; +-----+---------------+----------------+-------+---------------------+---------------------+ | id | isbn | published_date | price | created_at | updated_at | +-----+---------------+----------------+-------+---------------------+---------------------+ | 100 | 9780493063348 | 1998-02-06 | 3265 | 2018-11-18 07:00:03 | 2018-11-18 07:00:03 | +-----+---------------+----------------+-------+---------------------+---------------------+ 1 row in set (0.00 sec)
UPDATE実施(未コミット)
>>> DB::table('bookdetails')->where('id', 100)->update(['price' => 100000]); => 1
UPDATEしたが、commitしていない状態、値段は変わっていない
mysql> select * from bookdetails where id=100; +-----+---------------+----------------+-------+---------------------+---------------------+ | id | isbn | published_date | price | created_at | updated_at | +-----+---------------+----------------+-------+---------------------+---------------------+ | 100 | 9780493063348 | 1998-02-06 | 3265 | 2018-11-18 07:00:03 | 2018-11-18 07:00:03 | +-----+---------------+----------------+-------+---------------------+---------------------+ 1 row in set (0.00 sec)
commit実施
>>> DB::commit(); => null
UPDATEし、commitした結果、値段が変わっている
mysql> select * from bookdetails where id=100; +-----+---------------+----------------+--------+---------------------+---------------------+ | id | isbn | published_date | price | created_at | updated_at | +-----+---------------+----------------+--------+---------------------+---------------------+ | 100 | 9780493063348 | 1998-02-06 | 100000 | 2018-11-18 07:00:03 | 2018-11-18 07:00:03 | +-----+---------------+----------------+--------+---------------------+---------------------+ 1 row in set (0.00 sec)
表5.4.5.2:悲観的ロックのメソッド
こちらはなんとなく利用シーンが思い浮かぶ気がするんですが、じゃあ具体的にどんな時?と言われるとパッと出てきません。まだまだだな、と思うと以前の僕ならこの章のように悲観的になったりもしたのですが、今の僕は「楽しみだな」と思たりはします。
ここではメソッドの紹介に止まり、実際に動かすところまでは書いていませんでした。 この本は2週目を考えているので、その際にシチュエーションと実際の実装をやりたいですね。
メソッド | 機能 |
---|---|
sharedLock() | selectされた行に共有ロックをかけ、トランザクションコミットまで更新を禁止する |
lockForUpdate() | selectされた行に排他ロックをかけ、トランザクションコミットまで読み書き両方を禁止する |
5-4-6 ベーシックなデータ操作
- EloquentもクエリビルダもSQLに変換されているよね
- コードから生成されるSQLが常に一定とは限らない
- 個人的にこれは困るなあ…
- クエリが長いとメソッドチェーンも長くなるので手軽さや可読性が失われる
- SQL直接書きたい!
- ありますよ。
表5.4.6.1:ベーシックなSQL実行メソッド
あらゆるSQLが書けるようなメソッドが用意されてます。プリペアドステートメントもPDOで親しんだのと同じなので、速度を求められる場合や複雑な分析系のSQLを書く際には重宝しそうです。(SQLが複雑な時点で設計が…という話は置いといて)
メソッド名 | 説明 |
---|---|
DB::select('selectクエリ', [クエリに結合する引数]) | select文によるデータ抽出 |
DB::insert('insertクエリ', [クエリに結合する引数]) | insert文によるデータ登録 |
DB::update('updateクエリ', [クエリに結合する引数]) | update文によるデータ更新。更新された行数が返る |
DB::delete('deleteクエリ', [クエリに結合する引数]) | delete文によるデータ削除。削除された行数が返る |
DB::statement('SQL', [クエリに結合する引数]) | 上記以外のSQLを実行する場合 |
コード5.4.6.1:DB:selectを使用したデータ抽出
以下のSQLを実行します。
mysql> SELECT -> bookdetails.isbn, books.name -> FROM -> books -> LEFT JOIN -> bookdetails -> ON books.bookdetail_id = bookdetails.id -> WHERE -> bookdetails.price >= '1000' -> AND bookdetails.published_date >= '2011-01-01' -> ORDER BY -> bookdetails.published_date DESC; +---------------+--------------------------------+ | isbn | name | +---------------+--------------------------------+ | 9784204176365 | Dignissimos sunt est autem ea. | | 9793433402596 | Quas maiores sit aut. | +---------------+--------------------------------+ 2 rows in set (0.00 sec)
コードはこちら。
ちなみに //利用方法
の部分、配列の中は stdClassオブジェクト
なのでアロー演算子じゃないとエラーになります。ので修正してます。
<?php $sql = ' SELECT bookdetails.isbn, books.name ' . ' FROM books' . ' LEFT JOIN bookdetails ON books.bookdetail_id = bookdetails.id' . ' WHERE bookdetails.price >= ? AND bookdetails.published_date >= ?' . ' ORDER BY bookdetails.published_date DESC'; $results = DB::select($sql, [1000, '2011-01-01']); // 僕はこっち派 $sql = ' SELECT bookdetails.isbn, books.name ' . ' FROM books' . ' LEFT JOIN bookdetails ON books.bookdetail_id = bookdetails.id' . ' WHERE bookdetails.price >= :price AND bookdetails.published_date >= :published_date' . ' ORDER BY bookdetails.published_date DESC'; $results = DB::select($sql, ['price' => 1000, 'published_date' => '2011-01-01']); // 利用方法、$bookはstdClassオブジェクトなのでアロー演算子だよ foreach ($results as $book) { echo $book->name; echo $book->isbn; }
tinkerで実行します。 tinkerは改行を入れるとエラーになりますが、パッと確認できる最小単位なので便利なので一貫して使ってます。
>>> $sql = 'SELECT bookdetails.isbn, books.name FROM books LEFT JOIN bookdetails ON books.bookdetail_id = bookdetails.id WHERE bookdetails.price >= :price AND bookdetails.published_date >= :published_date ORDER BY bookdetails.published_date DESC'; => "SELECT bookdetails.isbn, books.name FROM books LEFT JOIN bookdetails ON books.bookdetail_id = bookdetails.id WHERE bookdetails.price >= :price AND bookdetails.published_date >= :published_date ORDER BY bookdetails.published_date DESC" >>> $results = DB::select($sql, ['price' => 1000, 'published_date' => '2011-01-01']); => [ {#2864 +"isbn": "9784204176365", +"name": "Dignissimos sunt est autem ea.", }, {#2863 +"isbn": "9793433402596", +"name": "Quas maiores sit aut.", }, ] >>> foreach ($results as $book) { echo $book->name; echo $book->isbn; } Dignissimos sunt est autem ea.9784204176365Quas maiores sit aut.9793433402596 >>>
foreachでの stdClass
オブジェクトへのアクセスも確認できます。
コード5.4.6.2:PDOオブジェクトを直接使用する
ほぼ同じなので写経して動かしたものを貼っておきます。
<?php $sql = ' SELECT bookdetails.isbn, books.name ' . ' FROM books' . ' LEFT JOIN bookdetails ON books.bookdetail_id = bookdetails.id' . ' WHERE bookdetails.price >= :price AND bookdetails.published_date >= :published_date' . ' ORDER BY bookdetails.published_date DESC'; $pdo = DB::connect()->getPdo(); $statement = $pdo->prepare($sql); $statement->execute(['price' => 1000, 'published_date' => '2011-01-01']); $results = $statement->fetchAll(PDO::FETCH_ASSOC); // 利用方法、PDOだと配列 foreach ($results as $book) { echo $book['name']; echo $book['isbn']; }
>>> $sql = 'SELECT bookdetails.isbn, books.name FROM books LEFT JOIN bookdetails ON books.bookdetail_id = bookdetails.id WHERE bookdetails.price >= :price AND bookdetails.published_date >= :published_date ORDER BY bookdetails.published_date DESC'; => "SELECT bookdetails.isbn, books.name FROM books LEFT JOIN bookdetails ON books.bookdetail_id = bookdetails.id WHERE bookdetails.price >= :price AND bookdetails.published_date >= :published_date ORDER BY bookdetails.published_date DESC" >>> $pdo = DB::connection()->getPdo(); => PDO {#2850 inTransaction: false, attributes: { CASE: NATURAL, ERRMODE: EXCEPTION, AUTOCOMMIT: 1, PERSISTENT: false, DRIVER_NAME: "mysql", SERVER_INFO: "Uptime: 10737 Threads: 2 Questions: 77 Slow queries: 0 Opens: 125 Flush tables: 1 Open tables: 118 Queries per second avg: 0.007", ORACLE_NULLS: NATURAL, CLIENT_VERSION: "mysqlnd 5.0.12-dev - 20150407 - $Id: 38fea24f2847fa7519001be390c98ae0acafe387 $", SERVER_VERSION: "5.7.24", STATEMENT_CLASS: [ "PDOStatement", ], EMULATE_PREPARES: 0, CONNECTION_STATUS: "mysql via TCP/IP", DEFAULT_FETCH_MODE: BOTH, }, } >>> $statement = $pdo->prepare($sql); => PDOStatement {#2844 +queryString: "SELECT bookdetails.isbn, books.name FROM books LEFT JOIN bookdetails ON books.bookdetail_id = bookdetails.id WHERE bookdetails.price >= :price AND bookdetails.published_date >= :published_date ORDER BY bookdetails.published_date DESC", } >>> $statement->execute(['price' => 1000, 'published_date' => '2011-01-01']); => true >>> $results = $statement->fetchAll(PDO::FETCH_ASSOC); => [ [ "isbn" => "9784204176365", "name" => "Dignissimos sunt est autem ea.", ], [ "isbn" => "9793433402596", "name" => "Quas maiores sit aut.", ], ] >>> foreach ($results as $book) { echo $book['isbn']; echo $book['name']; } 9784204176365Dignissimos sunt est autem ea.9793433402596Quas maiores sit aut. >>>
対話型でそれぞれ処理が実行され、無事に動いていることがわかります。
やってみて
実際にtinkerを使ってコードを実行していくと、変数や配列、オブジェクトに何が設定され、何が返ってくるのかがはっきりとわかりました。
コードを実行していて、これほど動きを実感できることってそうそうないよなって思います。
ずーっと昔、VisualCで業務システムのコードを書いていた時に、デバッガを使ってメモリ確保の中身まで追いかけた日々を思い出します。
そしてEloquent(ORM)とクエリビルダ、どっちがいいか悪いかはないと思うに至りました。Laravelに特化してサービスを構築して、効率的にリリースを継続するなら、フレームワークの力を最大限に引き出すべきだろうと思いました。
が、そうではないレベルにサービスが成長したのなら、それはケースバイケース。その時に採り得る最良の方法で課題を解決していくために、技術を、道具を、選択していくべきだよな、と改めて思わせてくれました。
データベースの章だけでもこんな風に思わせてくれるんですから、通読すれば得られることはすごく多いと思います。Laravel本、と言いますけど、Laravelを通して設計やOOPについて基礎からしっかり学べる書籍になっていると思います。
もし興味があって、購入を迷われているかたがおられましたら、ぜひ購入してみてください。得られることはものすごくたくさんあると思います!
そして後半パート2へ続くのじゃ…(汗)
そしてなんと…ここまでで約3.4万文字。後編を2つに分けることに相成りました!
というわけで、リポジトリパターンは後編パート2に続く!!!