データベースモデル Modelの使い方 (Laravelスタイル)

webmanモデルはEloquent ORMに基づいています。各データベーステーブルには、そのテーブルと対話するための「モデル」があります。モデルを通じて、データベーステーブルからデータをクエリしたり、テーブルに新しいレコードを挿入したりできます。

始める前に、config/database.phpでデータベース接続が設定されていることを確認してください。

注意:Eloquent ORMがモデルオブザーバーをサポートするには、composer require "illuminate/events"を追加でインポートする必要があります。

データベースモデルの例

<?php
namespace app\model;

use support\Model;

class User extends Model
{
    /**
     * モデルに関連付けられたテーブル名
     *
     * @var string
     */
    protected $table = 'user';

    /**
     * 主キーを再定義する。デフォルトはid
     *
     * @var string
     */
    protected $primaryKey = 'uid';

    /**
     * タイムスタンプを自動で管理するかどうかを示す
     *
     * @var bool
     */
    public $timestamps = false;
}

テーブル名

モデル上にtableプロパティを定義することで、カスタムデータテーブルを指定できます:

class User extends Model
{
    /**
     * モデルに関連付けられたテーブル名
     *
     * @var string
     */
    protected $table = 'user';
}

主キー

Eloquentは、各データベーステーブルにidという名の主キー列があると仮定します。保護された$primaryKeyプロパティを定義することで、この規約を再定義できます。

class User extends Model
{
    /**
     * 主キーを再定義する。デフォルトはid
     *
     * @var string
     */
    protected $primaryKey = 'uid';
}

Eloquentは、主キーが自動増分の整数値であると仮定します。つまり、デフォルトでは主キーは自動的にint型に変換されます。非増分または非数値の主キーを使用する場合は、$incrementingプロパティをfalseに設定する必要があります。

class User extends Model
{
    /**
     * モデルの主キーが自動増分かどうかを示す
     *
     * @var bool
     */
    public $incrementing = false;
}

主キーが整数でない場合、モデル上の保護された$keyTypeプロパティをstringに設定する必要があります:

class User extends Model
{
    /**
     * 自動増分IDの「型」。
     *
     * @var string
     */
    protected $keyType = 'string';
}

タイムスタンプ

デフォルトでは、Eloquentはデータテーブルにcreated_atとupdated_atの列が存在することを予期します。Eloquentにこれらの列を自動管理させたくない場合は、モデルの$timestampsプロパティをfalseに設定してください:

class User extends Model
{
    /**
     * タイムスタンプを自動で管理するかどうかを示す
     *
     * @var bool
     */
    public $timestamps = false;
}

タイムスタンプのフォーマットをカスタマイズする必要がある場合は、モデル内で$dateFormatプロパティを設定します。このプロパティは、日付属性がデータベースにどのように保存されるか、またモデルが配列またはJSONにシリアライズされる形式を決定します:

class User extends Model
{
    /**
     * タイムスタンプの保存フォーマット
     *
     * @var string
     */
    protected $dateFormat = 'U';
}

タイムスタンプのフィールド名をカスタマイズする必要がある場合は、モデル内でCREATED_ATとUPDATED_AT定数の値を設定することで実現できます:

class User extends Model
{
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'last_update';
}

データベース接続

通常、Eloquentモデルはアプリケーションで設定されたデフォルトのデータベース接続を使用します。モデルに異なる接続を指定したい場合は、$connectionプロパティを設定します:

class User extends Model
{
    /**
     * モデルの接続名
     *
     * @var string
     */
    protected $connection = 'connection-name';
}

デフォルト属性値

モデルのいくつかの属性にデフォルト値を定義したい場合は、モデル上で$attributesプロパティを定義できます:

class User extends Model
{
    /**
     * モデルのデフォルト属性値。
     *
     * @var array
     */
    protected $attributes = [
        'delayed' => false,
    ];
}

モデルの取得

モデルとそれに関連するデータベーステーブルを作成したら、データベースからデータをクエリできます。各Eloquentモデルを強力なクエリビルダーと考えて、関連データテーブルを迅速にクエリできます。例えば:

$users = app\model\User::all();

foreach ($users as $user) {
    echo $user->name;
}

ヒント:Eloquentモデルもクエリビルダーの一部であるため、クエリビルダーで利用可能な全てのメソッドを確認することもお勧めします。これらのメソッドはEloquentクエリの中でも使用できます。

追加制約

Eloquentのallメソッドは、モデル内の全ての結果を返します。各Eloquentモデルはクエリビルダーの役割を果たすため、クエリ条件を追加し、getメソッドを使用して結果を取得できます:

$users = app\model\User::where('name', 'like', '%tom')
               ->orderBy('uid', 'desc')
               ->limit(10)
               ->get();

モデルの再読み込み

freshおよびrefreshメソッドを使用してモデルを再読み込みできます。freshメソッドはデータベースからモデルを再取得します。既存のモデルインスタンスには影響を与えません:

$user = app\model\User::where('name', 'tom')->first();

$fresh_user = $user->fresh();

refreshメソッドは、データベース内の新しいデータを使用して既存のモデルに値を再設定します。さらに、既に読み込まれているリレーションも再読み込まれます:

$user = app\model\User::where('name', 'tom')->first();

$user->name = 'jerry';

$user = $user->fresh();

$user->name; // "tom"

コレクション

Eloquentのallおよびgetメソッドは複数の結果をクエリし、Illuminate\Database\Eloquent\Collectionインスタンスを返します。CollectionクラスはEloquent結果を扱うための多くの補助関数を提供します:

$users = $users->reject(function ($user) {
    return $user->disabled;
});

カーソルの使用

cursorメソッドを使用すると、カーソルを使用してデータベースを反復処理でき、クエリは1回だけ実行されます。大量のデータを処理する際、cursorメソッドはメモリの使用量を大幅に減らすことができます:

foreach (app\model\User::where('sex', 1)->cursor() as $user) {
    //
}

cursorはIlluminate\Support\LazyCollectionインスタンスを返します。Lazy collectionsでは、Laravelコレクションのほとんどの集合メソッドを使用でき、毎回単一のモデルがメモリに読み込まれます:

$users = app\model\User::cursor()->filter(function ($user) {
    return $user->id > 500;
});

foreach ($users as $user) {
    echo $user->id;
}

Selects サブクエリ

Eloquentは高度なサブクエリサポートを提供し、関連テーブルから情報を単一のクエリ文で抽出できます。例えば、目的地テーブルdestinationsと、目的地へのフライトテーブルflightsがあると仮定します。flightsテーブルには、フライトが目的地に到着する時刻を示すarrival_atフィールドがあります。

サブクエリ機能により提供されるselectおよびaddSelectメソッドを使用して、すべての目的地destinationsを単一の文でクエリし、各目的地に最も近く到着するフライトの名前を取得できます:

use app\model\Destination;
use app\model\Flight;

return Destination::addSelect(['last_flight' => Flight::select('name')
    ->whereColumn('destination_id', 'destinations.id')
    ->orderBy('arrived_at', 'desc')
    ->limit(1)
])->get();

サブクエリによるソート

さらに、クエリビルダーのorderBy関数もサブクエリをサポートしています。この機能を使用して、最後のフライトが目的地に到着する時刻に基づいてすべての目的地をソートできます。この操作もデータベースに対して単一のクエリを実行します:

return Destination::orderByDesc(
    Flight::select('arrived_at')
        ->whereColumn('destination_id', 'destinations.id')
        ->orderBy('arrived_at', 'desc')
        ->limit(1)
)->get();

単一モデル/集合の取得

指定したデータテーブルからすべてのレコードを取得するのに加えて、find、firstまたはfirstWhereメソッドを使用して単一のレコードを取得することもできます。これらのメソッドは、モデルの集合ではなく、単一のモデルインスタンスを返します:

// 主キーでモデルを見つける...
$flight = app\model\Flight::find(1);

// 条件に合う最初のモデルを見つける...
$flight = app\model\Flight::where('active', 1)->first();

// 条件に合う最初のモデルを見つけるための短縮実装...
$flight = app\model\Flight::firstWhere('active', 1);

主キーの配列をパラメータとしてfindメソッドを呼び出すこともでき、マッチするレコードの集合が返されます:

$flights = app\model\Flight::find([1, 2, 3]);

時には、最初の結果を見つけるが値が見つからない場合に別のアクションを実行したい場合があります。firstOrメソッドは、結果を見つけた場合、最初の結果を返しますが、結果が見つからない場合は、指定されたコールバックを実行します。コールバックの戻り値は、firstOrメソッドの戻り値となります:

$model = app\model\Flight::where('legs', '>', 100)->firstOr(function () {
        // ...
});

firstOrメソッドは、クエリを行うためにカラムの配列を受け取ることもできます:

$model = app\model\Flight::where('legs', '>', 100)
            ->firstOr(['id', 'legs'], function () {
                // ...
            });

「未発見」例外

時には、モデルが見つからなかった場合に例外を投げたいことがあります。これはコントローラーやルーティングで非常に便利です。findOrFailおよびfirstOrFailメソッドは、クエリの最初の結果を取得し、見つからなかった場合にはIlluminate\Database\Eloquent\ModelNotFoundException例外を投げます:

$model = app\model\Flight::findOrFail(1);
$model = app\model\Flight::where('legs', '>', 100)->firstOrFail();

コレクションの取得

クエリビルダーで提供されるcount、sumおよびmaxメソッド、その他のコレクション関数を使用して、コレクションを操作することもできます。これらのメソッドは、モデルインスタンスではなく適切なスカラ値を返します:

$count = app\model\Flight::where('active', 1)->count();

$max = app\model\Flight::where('active', 1)->max('price');

挿入

データベースに新しいレコードを追加するには、まず新しいモデルインスタンスを作成し、インスタンスのプロパティを設定してからsaveメソッドを呼び出します:

<?php

namespace app\controller;

use app\model\User;
use support\Request;
use support\Response;

class FooController
{
    /**
     * ユーザーテーブルに新しいレコードを追加
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        // リクエストの検証

        $user = new User;

        $user->name = $request->get('name');

        $user->save();
    }
}

created_atおよびupdated_atのタイムスタンプは自動的に設定されます(モデルの$timestampsプロパティがtrueの時)、手動で値を割り当てる必要はありません。

更新

saveメソッドは、既にデータベースに存在するモデルを更新するためにも使用できます。モデルを更新するには、まず取得し、更新するプロパティを設定してからsaveメソッドを呼び出します。同様に、updated_atタイムスタンプは自動的に更新されるため、手動で値を設定する必要はありません:

$user = app\model\User::find(1);
$user->name = 'jerry';
$user->save();

バルク更新

app\model\User::where('uid', '>', 10)
          ->update(['name' => 'tom']);

プロパティの変更をチェック

Eloquentは、模型の内部状態をチェックし、属性が最初に読み込まれた時からどのように変化したかを決定するために、isDirty、isClean、wasChangedメソッドを提供します。
isDirtyメソッドは、モデルが読み込まれて以来属性が変更されたかどうかを確認します。特定の属性名を渡すことで、特定の属性が変更されたかどうかを確認できます。isCleanメソッドはisDirtyの逆で、オプションの属性パラメータも受け取ります:

$user = User::create([
    'first_name' => 'Taylor',
    'last_name' => 'Otwell',
    'title' => 'Developer',
]);

$user->title = 'Painter';

$user->isDirty(); // true
$user->isDirty('title'); // true
$user->isDirty('first_name'); // false

$user->isClean(); // false
$user->isClean('title'); // false
$user->isClean('first_name'); // true

$user->save();

$user->isDirty(); // false
$user->isClean(); // true

wasChangedメソッドは、現在のリクエストサイクル内で最後にモデルを保存した時に属性が変更されたかどうかを決定します。特定の属性が変更されたかどうかを確認するために属性名を渡すこともできます:

$user = User::create([
    'first_name' => 'Taylor',
    'last_name' => 'Otwell',
    'title' => 'Developer',
]);

$user->title = 'Painter';
$user->save();

$user->wasChanged(); // true
$user->wasChanged('title'); // true
$user->wasChanged('first_name'); // false

バルク割り当て

新しいモデルを保存するためにcreateメソッドを使用することもできます。このメソッドはモデルインスタンスを返します。ただし、使用する前に、モデル上でfillableまたはguardedプロパティを指定する必要があります。すべてのEloquentモデルはデフォルトでバルク割り当てができません。

ユーザーがリクエストを通じて予期しないHTTPパラメーターを渡し、そのパラメーターがデータベースで変更する必要がないフィールドを変更した場合、バルク割り当ての脆弱性が発生します。たとえば、悪意のあるユーザーがHTTPリクエストを通じてis_adminパラメーターを渡し、それをcreateメソッドに渡すことで、ユーザーを管理者に昇格させることができます。

したがって、始める前にモデル上でどの属性がバルク割り当てできるかを定義する必要があります。モデルの$fillableプロパティを使用して実現できます。たとえば、Flightモデルのname属性をバルク割り当てできるようにします:

<?php

namespace app\model;

use support\Model;

class Flight extends Model
{
    /**
     * バルク割り当てできる属性。
     *
     * @var array
     */
    protected $fillable = ['name'];
}

一度バルク割り当て可能な属性を設定すれば、createメソッドを通じて新しいデータをデータベースに挿入できます。createメソッドは保存されたモデルインスタンスを返します:

$flight = app\model\Flight::create(['name' => 'Flight 10']);

すでにモデルインスタンスを持っている場合は、fillメソッドに配列を渡して値を割り当てることができます:

$flight->fill(['name' => 'Flight 22']);

$fillableはバルク割り当ての「ホワイトリスト」として考えることができます。$guardedプロパティを使用することもできます。$guardedプロパティには、バルク割り当てが許可されていない配列が含まれます。つまり、$guardedは機能的に「ブラックリスト」に近いです。注意:$fillableまたは$guardedのどちらか一方のみを使用できます。同時に使用することはできません。以下の例では、price属性以外のすべての属性がバルク割り当て可能です:

<?php

namespace app\model;

use support\Model;

class Flight extends Model
{
    /**
     * バルク割り当てが許可されていない属性。
     *
     * @var array
     */
    protected $guarded = ['price'];
}

すべての属性がバルク割り当て可能にしたい場合は、$guardedを空の配列として定義できます:

/**
 * バルク割り当てが許可されていない属性。
 *
 * @var array
 */
protected $guarded = [];

その他の作成メソッド

firstOrCreate/firstOrNew
バルク割り当てに使用できる二つのメソッドがあります:firstOrCreateとfirstOrNew。firstOrCreateメソッドは、与えられたキー/値ペアを使ってデータベース内のデータを一致させます。もしデータベース内にモデルが見つからない場合、最初のパラメータの属性とオプションの第二パラメータの属性を持つレコードが挿入されます。

firstOrNewメソッドは、firstOrCreateメソッドと同様に与えられた属性を使用してデータベース内のレコードを検索します。しかし、firstOrNewメソッドが対応するモデルを見つけられない場合は、新しいモデルインスタンスを返します。注意点:firstOrNewで返されるモデルインスタンスは、まだデータベースに保存されていないため、手動でsaveメソッドを呼び出す必要があります:

// nameを使用してフライトを取得し、存在しない場合は作成...
$flight = app\model\Flight::firstOrCreate(['name' => 'Flight 10']);

// nameを使用してフライトを取得し、またはnameとdelayed属性およびarrival_time属性を使用して作成...
$flight = app\model\Flight::firstOrCreate(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

// nameを使用してフライトを取得し、存在しない場合はインスタンスを作成...
$flight = app\model\Flight::firstOrNew(['name' => 'Flight 10']);

// nameを使用してフライトを取得し、またはnameとdelayed属性およびarrival_time属性を使用してモデルインスタンスを作成...
$flight = app\model\Flight::firstOrNew(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

既存のモデルを更新し、存在しない場合に新しいモデルを作成するシナリオにも出くわすかもしれません。updateOrCreateメソッドを使用すると、一度に実現できます。firstOrCreateメソッドと同様に、updateOrCreateはモデルを永続化させるため、save()を呼び出す必要はありません:

// オークランドからサンディエゴへのフライトがあれば、価格は99ドル。
// 一致するモデルが見つからない場合は、新しいものを作成します。
$flight = app\model\Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);

モデルの削除

モデルインスタンスのdeleteメソッドを呼び出してインスタンスを削除できます:

$flight = app\model\Flight::find(1);
$flight->delete();

主キーを使用してモデルを削除

app\model\Flight::destroy(1);

app\model\Flight::destroy(1, 2, 3);

app\model\Flight::destroy([1, 2, 3]);

app\model\Flight::destroy(collect([1, 2, 3]));

クエリを使用してモデルを削除

$deletedRows = app\model\Flight::where('active', 0)->delete();

モデルのコピー

replicateメソッドを使用して、新しい未保存のモデルインスタンスをコピーできます。モデルインスタンスが多くの同じ属性を共有している場合にこのメソッドは非常に便利です。

$shipping = App\Address::create([
    'type' => 'shipping',
    'line_1' => '123 Example Street',
    'city' => 'Victorville',
    'state' => 'CA',
    'postcode' => '90001',
]);

$billing = $shipping->replicate()->fill([
    'type' => 'billing'
]);

$billing->save();

モデルの比較

時には、二つのモデルが「同じ」かどうかを判断する必要があるかもしれません。isメソッドを使用して、二つのモデルが同じ主キー、テーブル、データベース接続を持っているかを迅速に確認できます:

if ($post->is($anotherPost)) {
    //
}

モデルオブザーバー

参考:Laravelのモデルイベントとオブザーバー

注意:Eloquent ORMがモデルオブザーバーをサポートするには、composer require "illuminate/events"を追加でインポートする必要があります。

<?php
namespace app\model;

use support\Model;
use app\observer\UserObserver;

class User extends Model
{
    public static function boot()
    {
        parent::boot();
        static::observe(UserObserver::class);
    }
}

トランザクション

参照:データベーストランザクション