日記

日々のことと、Python/Django/PHP/Laravel/nodejs などソフトウェア開発のことを書き綴ります

Laravel 5.1 入門記 その10 (Eloquent SELECT 編)

超久々に書きます。

Eloquent: Getting Started - Laravel - The PHP Framework For Web Artisans

題材はこれ。

Eloquent

Eloquent は Laravel の OR Mapper で、Rails ActiveRecordPHP 版みたいな位置づけで、モデルがデータベースのテーブルに 1 対 1 で紐付く感じですね。

Eloquent モデルの定義

Eloquent のモデルクラスを作る時には、 artisan のサポートコマンドを実行すれば、ベースな部分は勝手に .php ファイルに出力してくれる。

php artisan make:model User

これを実行すると app/User.php が作成される。(と言ってもプロジェクトを作った時点で User クラスは既に存在しているのだけど…。)

Laravel のドキュメントはこういうところがなぁ…。ということで、次のセクションに合わせたモデルクラスを生成!

php artisan make:model Flight

これで生成されるクラスは

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    //
}

こんな感じ。このモデルクラスに対応するデータベース上のテーブルは fights となります。

必ずしも Laravel のお作法に沿って、データベースのテーブルが設計されるわけじゃないので、対応するテーブル名を変更することも可能で、次のようにします。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    protected $table = 'my_flights';
}

他にもプライマリキーのカラム名を変更するときは $primaryKey を定義したり、Eloquent モデルでは標準でサポートされるタイムスタンプの更新 (updated_at / created_at) を止める $timestamps などがあります。

さらにタイムスタンプのカラム名も変更でき、その場合は定数を作ります。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    const CREATED_AT = 'created';
    const UPDATED_AT = 'updated';

    protected $table = 'my_flights';
}

こんな感じ。クラス変数ではない点に注意。

Eloquent モデルからクエリの発行

モデルを定義したので、モデルクラス経由でクエリを発行してみます。

以下のコマンドで、phpインタラクティブコンソールを起動。

$ php artisan tinker

これで、Laravel の機能はロードされた状態で試せます。 まず、Flight クラスで全データ取得。

$flights = App\Flight::all();

これで、全データを取得する形になります。$flights を foreach 分などでループ処理させて使います。この $flights は Laravel の Collection クラスで、便利なメソッドが用意されてます。詳しくは以下を参照。

Eloquent: Collections - Laravel - The PHP Framework For Web Artisans

実際のところ、プロダクションなアプリケーションでは、条件を指定した上で件数を絞ることが多いので、次のようにします。

$flights = App\Flight::where('active', 1)
    ->orderBy('id', 'asc')
    ->take(10)
    ->get();

これで active カラムが "1" なもので、id カラムの昇順にソートして先頭から10件のデータを取得できます。

メソッドチェインで次々に処理を行っていますが、 take までは、 QueryBuilder というクラスのインスタンスに対して処理を行っています。最後の get で、前述の Collection クラスインスタンスが返っている感じ。

必ずしもメソッドチェインで呼ぶ必要は無いため、次のように入力値に応じて条件を指定することもできます。

$query = App\Flight::where('active', 1);

if (\Input::has('name')) {
    $query = $query->where('name', 'like' \Input::get('name') . '%');
}

$flights = $query->orderBy('id', 'asc')
    ->take(10)
    ->get();

ここまでの例は Collection で複数のデータが返ってくる想定のクエリでしたが、あるデータの詳細ページの処理では 1件だけのデータが取得できればよいケースもあります。その場合は以下の様にします。

$flight = App\Flight::find(1);

$flight = App\Flight::where('active', 1)->first();

まず find は、プライマリキーでデータを取得するケースです。プライマリキーのみで指定ができます。 first は、複数返ってくるかもしれない中の一つ目のデータを取得する方法です。

この2つのメソッドは、データが見つからない場合は null を返します。データが見つからない場合は、例外を発生させて Handler に処理させたい場合もあるでしょう。その場合は変わりに次のようにします。

$flight = App\Flight::findOrFail(1);

$flight = App\Flight::where('active', 1)->firstOrFail();

findOrFail / firstOrFail は、データが見つからない場合に Illuminate\Database\Eloquent\ModelNotFoundException の例外が発生します。

Controller 上でこの ModelNotFoundException が発生した場合は、自動的に例外ハンドラーで処理されて HTTP Status 404 エラーとして処理が行われます。

QueryBuilder の集計系処理

QueryBuilder インスタンスに対して count / min / max なども用意されてます。まんま、その処理なので省略。以下のURLを参考に。

Eloquent: Getting Started - Laravel - The PHP Framework For Web Artisans

Database: Query Builder - Laravel - The PHP Framework For Web Artisans

テーブルの JOIN

テーブルを結合して結果を取得したいケースは良くあると思いますが、 Eloquent の解説ページには詳細が無くて、やり方を探すのに割と苦労したんですが、ポイントは Query Builder であるということで、下記のページに紹介が色々とありました。

Database: Query Builder - Laravel - The PHP Framework For Web Artisans

Eloquent のモデルについてだけ調べてもダメで、このページを読んでおかないと Laravel の OR Mapper は使いこなせないので注意。

では、簡単に JOIN する方法を試しますが、Eloquent のモデルでは、hasOne / hasMany などの定義は書けるみたいですが、JOIN してデータを取得したい時は、Query Builder の join を使用してクエリを発行する必要があるようです。hasMany 等は基本的に lazy なアクセスを期待しているぽい。

まず上記のドキュメントのサンプルを見てみます。

$users = DB::table('users')
            ->join('contacts', 'users.id', '=', 'contacts.user_id')
            ->join('orders', 'users.id', '=', 'orders.user_id')
            ->select('users.*', 'contacts.phone', 'orders.price')
            ->get();

DB ファサードを使用して、users テーブルを指定しています。これを Eloquent モデルに置き換えると

$users = User::join('contacts', 'users.id', '=', 'contacts.user_id')
            ->join('orders', 'users.id', '=', 'orders.user_id')
            ->get();

こんな感じに。select は省略してしまっていますが。この使い方は、一覧画面で結合先のテーブルの状態 (この場合は contacts や orders テーブルの状態) で条件指定をすることができるようになります。

$users = User::join('contacts', 'users.id', '=', 'contacts.user_id')
            ->join('orders', 'users.id', '=', 'orders.user_id')
            ->where('orders.status', Order::PRE_ORDER)
            ->get();

あくまで例ですが、注文が Order::PRE_ORDER のものだけを表示する例です。

この例を見ると、DB ファサードを使って書けば良いんじゃ無いか?と思うかもしれません。 しかし、Query Scope と組み合わせることで、宣言的にクエリを書けるようになります。

Query Scope

Eloquent モデルに用意されている条件を宣言的に書く仕組みです。例えば、Userが男性なのか女性なのかを指定するときに以下の様に書くことができます。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function scopeMale($query)
    {
        return $query->where('gender', 1);
    }

    public function scopeFemail($query)
    {
        return $query->where('gender', 2);
    }

}

scope で始まっているメソッドが Query Scope 用のメソッドで、引数に Query Builder を受け取ります。 クエリを発行する時には以下の様にします。

$maleUsers = App\User::male()->orderBy('id', 'asc');

$femaleUsers = App\User::female()->orderBy('id', 'asc');

Query Scope を使用しない場合は、以下の様になります。

$maleUsers = App\User::where('gender', 1)->orderBy('id', 'asc');

性別を判断しているのだと推測できますが、右辺側だけを見ると 男性女性のどちらを取得しようとしているのか曖昧です。 Query Scope を使うとこういったコード上での分かりやすさが期待できます。

Query Scope を使うときにパラメータを固定にしてしまいましたが、パラメータを受け取って動的に設定することもできます。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    const MALE = 1;
    const FEMALE = 2;

    public function scopeGender($query, $gender)
    {
        return $query->where('gender', $gender);
    }

}
$maleUsers = App\User::gender(App\User::MALE)->orderBy('id', 'asc');

$femaleUsers = App\User::gender(App\User::FEMALE)->orderBy('id', 'asc');

このやり方であれば、画面の入力値をそのまま Query Scope に利用でき、検索フォームを実装するときなどは、こちらの方が扱いやすいかもしれません。

今回はここまで。次は insert / update / delete の操作について書きます。