RitoLabo

LaravelのEloquentORMの基本から具体的な使い方(モデルを使ったDB操作)

  • 公開:
  • カテゴリ: PHP Laravel
  • タグ: PHP,Laravel,5.5,5.4,5.3,Events,Listeners,EloquentORM,Model,QueryBuilder,5.6,Observer

Laravelでデータベースと連携しデータを取得したり登録・更新・削除など一連の操作を行うにはどうしたらよいでしょうか?

LaravelにはEloquent ORMという、データ操作を簡単に行う為の機能が提供されています。

今回はEloquentORMの基本から具体的な使い方までを見ていきます。

アジェンダ
  1. 開発環境
  2. Eloquent ORM
  3. モデル
  4. Modelクラスの設置個所
  5. モデル・マイグレーション・シーダー生成
  6. テーブルと初期データ作成
  7. モデル実装
    1. テーブルの指定
    2. 主キーの指定
    3. タイムスタンプの指定
    4. データベースの指定
  8. Eloquentでのデータ取得
    1. 全てのデータを取得する
    2. 条件を指定する
  9. レコード挿入
    1. save()
    2. create()
  10. データ更新
  11. OR生成メソッド
    1. firstOrCreate
    2. firstOrNew
    3. updateOrCreate
  12. レコードの削除
    1. delete()
    2. destroy()
  13. クエリスコープ
    1. グローバルスコープ
    2. ローカルスコープ
  14. イベント
    1. オブザーバクラス

開発環境

今回の開発環境は以下の通りです。

  • Linux CentOS 7
  • Apache 2.4
  • MySQL 5.7
  • PHP 7.2/7.1
  • Laravel

Laravelのバージョンについては5.6/5.5/5.4/5.3にて動作確認済みです。

Laravelソースのルートディレクトリを「laravel/」としています。

Eloquent ORM

Eloquent ORM(Eloquent Object Relational Mapping)とは、Laravelで提供されているデータ操作の為の機能です。データベースとモデルを関連付け、柔軟なデータ操作を行う為のLaravel独自の機能になります。

とはいえORM(Object Relational Mapping)という言葉は昔から存在していて、オブジェクト指向のプログラムとデータベースを繋ぐ「オブジェクト関係マッピング」を意味します。つまり、Eloquent ORMとは、Laravel独自のO/Rマッパーという事になります。

モデル

Eloquentでは、モデルクラスを定義した上で処理を実装していきます。ここで言うモデルとは、MVCのM「Model」の事です。モデルクラスに挙動や制約を定義し、コントローラなどでデータベース操作を行う際にそのモデルを使っていく、という流れになります。

Modelクラスの設置個所

通常は laravel/app 配下ですが、増えてくると散らかるので、laravel/app/Models 配下に設置するようにします。

autoloadでファイルを読み込むように、composer.jsonに以下を追記します。

laravel/composer.json
"autoload": {
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"App\\": "app/",
"Model\\": "app/Models/" // 追記
}
}
,

autoload内のpsr-4プロパティにModelを定義しています。

モデル・マイグレーション・シーダー生成

モデルを生成します。ついでにマイグレーションとシーダも作成し、データも用意していきます。

Laravelルートディレクトリへ移動し、以下のrtisanコマンドを叩きます。

# Laravelルートディレクトリへ移動
cd /path/to/laravel

# モデルとマイグレーションファイルを生成する
php artisan make:model Models/Frameworks -m

# 実行結果
[demo@localhost laravel]$ php artisan make:model Models/Frameworks -m
Model created successfully.
Created Migration: 2018_06_04_233748_create_frameworks_table

laravel/app/Models 配下にモデルファイル Frameworks.php が生成され、laravel/database/migrations 配下にマイグレーションファイル xxxx_create_frameworks_table.php が生成されます。

laravel
├─ app
│ ├─ Models
│ │ ├─
Frameworks.php

laravel
├─ database
│ ├─ migrations
│ │ ├─
xxxx_create_frameworks_table.php

次に初期データ投入用のシーダです。Laravelルートディレクトリへ移動し、以下のrtisanコマンドを叩きます。

# Laravelルートディレクトリへ移動
cd /path/to/laravel

# シーダファイルを生成する
php artisan make:seeder FrameworksTableSeeder

# 実行結果
[demo@localhost laravel]$ php artisan make:seeder FrameworksTableSeeder
Seeder created successfully.

laravel/database/seeds 配下に FrameworksTableSeeder.php が生成されます。

laravel
├─ database
│ ├─ seeds
│ │ ├─
FrameworksTableSeeder.php

テーブルと初期データ作成

モデルを実装する前にデータベースのテーブルとデータを作成しておきます。

マイグレーションを以下のように定義します。

laravel/database/migrations/xxxx_create_frameworks_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFrameworksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('frameworks', function (Blueprint $table) {
$table->increments('id');
$table->string('category', 20);
$table->string('name', 255);
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('frameworks');
}
}

シーダーを以下のように定義します。

laravel/database/seeds/FrameworksTableSeeder.php
<?php

use Illuminate\Database\Seeder;

class FrameworksTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('frameworks')->insert([
[ 'category' => 'PHP', 'name' => 'Laravel' ],
[ 'category' => 'PHP', 'name' => 'Symfony' ],
[ 'category' => 'PHP', 'name' => 'CakePHP' ],
[ 'category' => 'PHP', 'name' => 'zend framework' ],
[ 'category' => 'PHP', 'name' => 'codeigniter' ],
[ 'category' => 'PHP', 'name' => 'Phalcon' ],
[ 'category' => 'PHP', 'name' => 'FuelPHP' ],
[ 'category' => 'PHP', 'name' => 'Slim' ],
[ 'category' => 'PHP', 'name' => 'Yii' ],
[ 'category' => 'PHP', 'name' => 'Silex' ],
[ 'category' => 'PHP', 'name' => 'Flight' ],
[ 'category' => 'PHP', 'name' => 'BEAR.Sunday' ],
[ 'category' => 'PHP', 'name' => 'Ethna' ],
[ 'category' => 'Javascript', 'name' => 'React' ],
[ 'category' => 'Javascript', 'name' => 'AngularJS' ],
[ 'category' => 'Javascript', 'name' => 'Vue.js' ],
[ 'category' => 'Javascript', 'name' => 'Backbone.js' ],
[ 'category' => 'Javascript', 'name' => 'jQuery' ],
[ 'category' => 'Javascript', 'name' => 'Knockout.js' ],
[ 'category' => 'Python', 'name' => 'Django' ],
[ 'category' => 'Python', 'name' => 'Bottle' ],
[ 'category' => 'Python', 'name' => 'Flask' ],
[ 'category' => 'Python', 'name' => 'Tornado' ],
]);
}
}

シーディング対象を追加します。

laravel/database/seeds/DatabaseSeeder.php
<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$this->call([
FrameworksTableSeeder::class,
]);
}
}

マイグレーションとシーディングを実行し、データベースを構築します。

# Laravelルートディレクトリへ移動
cd /path/to/laravel

# マイグレーション実行
php artisan migrate

# 実行結果
[demo@localhost laravel]$ php artisan migrate
Migrating: xxxx_create_frameworks_table
Migrated: xxxx_create_frameworks_table

# シーディング実行
php artisan db:seed

# 実行結果
[demo@localhost laravel]$ php artisan db:seed
Seeding: BooksTableSeeder
Seeding: FrameworksTableSeeder

これでデータべースと初期データの準備は完了です。

mysql> select * from frameworks;
+----+------------+----------------+
| id | category | name |
+----+------------+----------------+
| 1 | PHP | Laravel |
| 2 | PHP | Symfony |
| 3 | PHP | CakePHP |
| 4 | PHP | zend framework |
| 5 | PHP | codeigniter |
| 6 | PHP | Phalcon |
| 7 | PHP | FuelPHP |
| 8 | PHP | Slim |
| 9 | PHP | Yii |
| 10 | PHP | Silex |
| 11 | PHP | Flight |
| 12 | PHP | BEAR.Sunday |
| 13 | PHP | Ethna |
| 14 | Javascript | React |
| 15 | Javascript | AngularJS |
| 16 | Javascript | Vue.js |
| 17 | Javascript | Backbone.js |
| 18 | Javascript | jQuery |
| 19 | Javascript | Knockout.js |
| 20 | Python | Django |
| 21 | Python | Bottle |
| 22 | Python | Flask |
| 23 | Python | Tornado |
+----+------------+----------------+

モデル実装

準備が整ったので、モデルの実装を行っていきます。まずは初期のソースを確認します。

laravel/app/Models/Frameworks.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
//
}

クラス宣言のみのミニマムの状態です。ここからEloquent ORMを使う為のモデルを定義していきます。

テーブルの指定

このモデルに紐づけるテーブル名を設定します。

基本的には、Laravelがクラス名に紐づくテーブル名を自動的に引いてきてくれるので、クラス名とテーブル名が同じ名前の場合は指定しなくてもOKです。例えば、今回使用しているFrameworksクラスに対して、紐づけるテーブルはframeworksテーブルなので、この場合は設定なくても動作します。

ちなみに、テーブル名がスネークケースの場合は、キャメルケースでのクラス名で一致します。

  • テーブル名「php_frameworks」=クラス「PhpFrameworks」
  • テーブル名「sample_name_list」=クラス「SampleNameList」

もし、作成したテーブルとクラスの名前が全く違う場合は、以下のようにしてメンバ変数 $table にテーブル名をセットします。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
// テーブル名の指定
protected $table = 'web_framework';
}

主キーの指定

Eloquentでのモデル実装時は、暗黙で「主キー=カラム名id」として処理を行います。

もし主キーのカラム名が「id」以外の場合は、以下のようにメンバ変数 $primaryKey に主キーのカラム名をセットします。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
// テーブル名の指定
protected $table = 'web_framework';

// 主キーの指定
protected $primaryKey = 'framework_id';
}

ちなみにEloquentでは、暗黙で主キー=自動増分(AUTO_INCREMENT)であるとして処理されます。併せて、デフォルトでは主キーは数値型(int型)であるという想定で処理が行われます。

「そんな事聞いてないよ!」という状況の場合は以下のようにして、そんな先人が決めつけた「まあ普通はそうでしょ」というルールをぶち破れます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
// テーブル名の指定
protected $table = 'web_framework';

// 主キーの指定
protected $primaryKey = 'framework_id';

// 自動増分ではない場合
public $incrementing = false;

// 主キーが数値(int)型ではない場合
protected $keyType = 'string';
}

タイムスタンプの指定

Eloquentでは、暗黙で created_at(作成日時)カラムと updated_at(更新日時)カラムがある前提でそれらを自動的に更新しようとします。

自動更新をOFFにしたい場合は、メンバ変数 $timestampsfalse を指定します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
// created_atupdated_atの自動更新をOFFにする
public $timestamps = false;
}

タイムスタンプはデフォルトでtimestamp型として日時を挿入しますが、フォーマットを変更する事も可能です。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
// created_atupdated_atの自動更新をOFFにする
public $timestamps = false;

// 日付カラムのフォーマットを指定する
protected $dateFormat = 'U';
}

上記の場合は、UNIX時間で挿入されます。

ちなみに、そもそも作成日時と更新日時のカラムがcreated_atとupdated_atではない場合もあります。そんな場合は以下のように定数を設定する事でカラムを指定できます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
// created_atupdated_atの自動更新をOFFにする
public $timestamps = false;

// 日付カラムのフォーマットを指定する
protected $dateFormat = 'U';

// 作成日時カラムの指定
const CREATED_AT = 'create_date';

// 更新日時カラムの指定
const UPDATED_AT = 'update_date';
}

データベースの指定

Eloquentはデフォルトでデータベースの設定ファイルから、defaultプロパティの値を見てそれを用いて接続を行いに行きます。

laravel/config/database.php
'default' => env('DB_CONNECTION', 'mysql'),

接続先を変更したい場合は、メンバ変数 $connection にコネクション名を設定します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
// データベースコネクション先の指定
protected $connection = 'redis';
}

Eloquentでのデータ取得

モデルの設定が完了したら、実際にデータを取得してみます。今回の実装はコントローラで行います。

データ取得の基本形は以下のようになります。

<?php

namespace App\Http\Controllers;

use App\Models\Frameworks;

class SampleController extends Controller
{
protected $frameworks;

public function __construct()
{
$this->frameworks = new Frameworks();
}

public function index()
{
//
}
}

書き方はたくさんありますが、今回はコンストラクタでモデルインスタンスを取得し、データ取得を行っていきます。

そしてこれから記述するクエリは、全てindex()メソッドの中に記述していきます。

全てのデータを取得する

まずは、全てのデータを取得します。条件を一切つけずに全てのデータを取得するには、all()メソッドを使います。

// 全てのデータを取得する
$data = $this->frameworks->all();

結果データコレクションはループで回して取り出す事が出来ます。

$data = $this->frameworks->all();

$item = [];
foreach ($data as $d) {
$item = [
'id' => $d->id,
'name' => $d->name,
'category' => $d->category,
];

print_r($item);
=> Array
(
[id] => 1
[name] => Laravel
[category] => PHP
)

}

これは条件を指定した場合も同様です。

条件を指定する

条件を指定する場合は、クエリビルダと同様の記述で指定が可能です。

例えば、PHPのフレームワークのみを取得する場合は、以下のように記述します。

$data = $this->frameworks
->where('category', 'PHP')
->get();

Laravelのクエリビルダについては
Laravelのクエリビルダ記法まとめ
を参照してください。

分割処理や集計に関してもクエリビルダと同様に実装できます。

尚、Eloquent独自のメソッドとして、主キーを指定してデータを取得する場合は、find()メソッドが使えます。

// id:1 のレコードを取得する
$this->frameworks->find(1);

レコード挿入

データを新規に登録する、新しいレコードを挿入する場合は、2つの方法があります。

save()

Eloquentでテーブルに新しいレコードを挿入する場合は、save()メソッドが使えます。

public function index()
{
$this->frameworks->name = "D3";
$this->frameworks->category = "Javascript";
$this->frameworks->save();
}

実行後、MySQLを確認するとデータが挿入されている事が確認できます。

mysql> select * from frameworks;
+----+------------+----------------+---------------------+---------------------+
| id | category | name | created_at | updated_at |
+----+------------+----------------+---------------------+---------------------+
| 24 | Javascript | D3 | 2018-06-06 00:13:49 | 2018-06-06 00:13:49 |
+----+------------+----------------+---------------------+---------------------+

作成日時と更新日時も自動で更新されています。

create()

登録できるカラムに制約を設けた形でレコード挿入(1件のみ)も行えます。その場合にはcreate()メソッドを使います。

クエリを書く前に、モデルへレコード挿入を許可するカラムを指定します。

class Frameworks extends Model
{
// 登録を許可するカラム
protected $fillable = ['name', 'category'];
}

ここで許可しないカラムへはデータを挿入できません。

逆に、許可しない設定もできます。

class Frameworks extends Model
{
// 登録を許可しないカラム
protected $guarded = ['admin'];
}

ここで禁止しないカラムはデータ挿入を許可する事になります。

全てを許可する場合は、以下のようにも記述できます。

protected $guarded = [];

許可する$fillableと許可しない$guardedは、どちらか一方のみを設定できます。両方は設定できません。

これらの設定を行ったら、create()メソッドでレコード挿入を行えます。

$data = $this->frameworks
->create([
'category' => 'Java',
'name' => 'JSF'
]);

データ更新

データを更新する場合ですが、これもEloquentなら同じくsave()メソッドが使えます。

// id:24 のレコードを取得する
$target = $this->frameworks->find(24);
// nameカラムを変更
$target->name = "D3.js";
// 更新
$target->save();

MySQLを確認します。

mysql> select * from frameworks;
+----+------------+----------------+---------------------+---------------------+
| id | category | name | created_at | updated_at |
+----+------------+----------------+---------------------+---------------------+
| 24 | Javascript | D3.js | 2018-06-06 00:13:49 | 2018-06-06 00:24:07 |
+----+------------+----------------+---------------------+---------------------+

データが更新されました。

また、複数のモデルを同時に更新する事も出来ます。例えばcategoryカラム値がJavascriptのレコードに関して、JSと変更します。

$this->frameworks
->where('category', 'Javascript')
->update(['category' => 'JS']);

実行後データを確認すると、更新が成功している事が確認できます。

mysql> select * from frameworks;
+----+----------+----------------+---------------------+---------------------+
| id | category | name | created_at | updated_at |
+----+----------+----------------+---------------------+---------------------+
| 14 | JS | React | NULL | 2018-06-06 00:31:17 |
| 15 | JS | AngularJS | NULL | 2018-06-06 00:31:17 |
| 16 | JS | Vue.js | NULL | 2018-06-06 00:31:17 |
| 17 | JS | Backbone.js | NULL | 2018-06-06 00:31:17 |
| 18 | JS | jQuery | NULL | 2018-06-06 00:31:17 |
| 19 | JS | Knockout.js | NULL | 2018-06-06 00:31:17 |
| 24 | JS | D3.js | 2018-06-06 00:13:49 | 2018-06-06 00:31:17 |
+----+----------+----------------+---------------------+---------------------+

OR生成メソッド

Eloquentではただの登録や更新だけではなく、対象の値を持ってデータベースを見に行き、その時の状態によって登録や更新、インスタンスを行う事が出来ます。

firstOrCreate

firstOrCreate()メソッドは、送信したカラム/値を持つレコードが存在するかをまず探します。そして該当するレコードが無かった場合は、レコード1件が挿入されます。

$data = $this->frameworks
->firstOrCreate(
['name' => 'Laravel'],
['category' => 'PHP']
);

このメソッドの注意点として、上記のように複数のカラム/値ペアをセットする場合ですが、レコードに値が存在するかどうかを評価するのは先頭のカラム/値ペアのみになります。従って、先頭のカラム/値ペアがレコードに存在していなければ、他のカラム/値ペアを合わせてインサートが行われます。

ちなみに該当するレコードが存在する場合はインサートは行われず、そのレコードが返されます。

firstOrNew

firstOrNew()メソッドは、送信したカラム/値を持つレコードが存在するかをまず探し、存在すればそのモデルインスタンスを返しますが、存在しなかった場合は、送信した値で新しいモデルインスタンスが生成され、返却されます。つまり、レコードは挿入されません。

$data = $this->frameworks
->firstOrNew(
['name' => 'Symfony'],
['category' => 'PHP']
);

戻り値として受け取った新しいモデルインスタンスを保存する、つまりレコードを挿入する場合は、そのモデルインスタンスに対してsave()メソッドを発行します。

$data = $this->frameworks
->firstOrNew(
['name' => 'Ember.js'],
['category' => 'JS']
);

$data->save();

updateOrCreate

updateOrCreate()メソッドは、渡したカラム/値ペアに合致するレコードがあるかを探し、あれば更新を行い、なければ新しいレコードが挿入されます。

$this->frameworks
->updateOrCreate(
['name' => 'React', 'category' => 'JS'],
['category' => 'Javascript']
);

このメソッドのポイントは、第一引数に渡した配列内のカラム/値ペアでレコードの存在を確認し、存在していれば第二引数の配列内のカラム/値ペアで更新を行います。

レコードが存在していない場合は、これらの値を以てレコード挿入が行われます。

レコードの削除

レコード(モデル)を削除する場合は、いくつかの方法が存在します。

delete()

通常のモデル削除であれば、delete()メソッドがあります。

$model = $this->frameworks->find(28);
$model->delete();

Eloquentではモデルベースで操作が行われるので、delete()メソッドを用いる場合は、削除するレコードを指定し削除を行うのではなく、削除するレコードのモデルインスタンスを一旦取得した後にdelete()メソッドを叩くという流れになっている点がポイントです。

と言っておいて難ですが、メソッドチェーンで一撃削除も出来ます。

// 条件式で複数取得+deleteメソッドをチェインして削除
$this->frameworks->where('category', 'JS')->delete();

destroy()

主キーが判明している場合は、destroy()メソッドでダイレクトにレコードを削除する事が出来ます。

// 1件の削除
$this->frameworks->destroy(24);

// 複数件まとめて削除
$this->frameworks->destroy([25, 26, 27]);

// 複数件まとめて削除
$this->frameworks->destroy(17, 18, 19);

上記例では3パターンで記述していますが、一番上は一件のみの削除、あとの2つは複数件のモデルをまとめて削除するパターンの書式です。

あくまでも、主キーを渡す事で削除が行えるというのがこのメソッドのポイントです。

クエリスコープ

Eloquentではクエリスコープという観念があり、全てのモデルに対して、予め何らかの条件を付与しておく事が出来ます。

例えばLaravelで論理削除(ソフトデリート)の仕組みをテーブルに組みこんだ場合、自動的に論理削除されたレコードはselectされなくなりますが、これと同じ事です。

例えばあるコミュニティサイトがあったとして、友達を検索する際に年齢を選べたとして、クエリスコープを仕込んでおくと、例えば20才未満は自動的に検索結果に表示されない(DBへの問い合わせから除外される)などといった仕組みを構築できます。

尚、クエリスコープには「グローバルスコープ」と「ローカルスコープ」があります。

グローバルスコープ

グローバルスコープは、前述した通り全体にがっつりクエリスコープを仕込みます。ではスコープを作成します。

laravel/app 配下に Scopes ディレクトリを作成し、CategoryScope.php を作成します。

laravel
├─ app
│   ├─
Scopes
│   │   └─
CategoryScope.php

グローバルスコープを定義します。

laravel/app/Scopes/CategoryScope.php
<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class CategoryScope implements Scope
{
/**
* Eloquentクエリビルダへ適用するスコープ
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('category', 'PHP');
}
}

categoryカラムがPHPであるもののみ取得するように定義しました。

次に、対象のモデルクラスへスコープを登録します。

laravel/app/Models/Frameworks.php
<?php

namespace App\Models;

use App\Scopes\CategoryScope;
use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
protected static function boot()
{
parent::boot();

static::addGlobalScope(new CategoryScope);
}
}

これで設定は完了しました。コントローラから取得してみると、条件指定を行わなくても、categoryがPHPのものしか取得されなくなります。

$this->frameworks->all();
=> select * from `frameworks` where `category` = 'PHP';

ある状況で一時的にあるグローバルスコープを外したい場合があるかもしれません。その場合はwithoutGlobalScope()メソッドを使います。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Frameworks;
use App\Scopes\CategoryScope;

class SampleController extends Controller
{
protected $frameworks;

public function __construct()
{
$this->frameworks = new Frameworks();
}

public function index()
{
// 指定グローバルスコープをスキップ
$data = $this->frameworks->withoutGlobalScope(CategoryScope::class)->get();
}
}

複数、もしくは全てのグルーバルスコープを解除したい場合は以下のように定義します。

// 複数のグローバルスコープをスキップ
$data = $this->frameworks->withoutGlobalScopes([
OneScope::class, TwoScope::class
])->get();

// 全てのグローバルスコープをスキップ
$data = $this->frameworks->withoutGlobalScopes()->get();

ローカルスコープ

ローカルスコープは、予め定義しておいたある条件を任意のタイミングでスポット的に使用できるものです。つまり、前もって条件を定義しておき、必要な時にそれらを当てて適用する事ができます。

ローカルスコープも、モデルに定義します。

laravel/app/Models/Frameworks.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Frameworks extends Model
{
// 登録を許可するカラム
protected $fillable = ['name', 'category'];

/**
* カテゴリーがPHPのものだけに限定するクエリスコープ
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeCategoryPhp($query)
{
return $query->where('category', 'PHP');
}

/**
* カテゴリーがPythonのものだけに限定するクエリスコープ
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeCategoryPython($query)
{
return $query->where('category', 'Python');
}
}

カテゴリがPHPのものに限定するスコープと、カテゴリがPythonのものに限定するスコープを記述しました。

これで定義は完了です。コントローラから使用してみます。

public function index()
{
// CategoryPhpスコープを適用する
$data = $this->frameworks->CategoryPhp()->get();

// CategoryPhpスコープとCategoryPythonスコープの両方を適用する
$data = $this->frameworks->CategoryPhp()->CategoryPython()->get();
}

これでスコープに定義した条件が適用されます。ちなみにスコープをチェインして複数適用させる事もできます。

スコープに引数を渡したい場合は、以下のように定義します。

/**
* 1つのカテゴリーに限定するクエリスコープ
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeCategory($query, $type)
{
return $query->where('category', $type);
}

あとはスコープメソッドに引数を渡せばOKです。

// カテゴリがPHPのものに限定する
$data = $this->frameworks->Category('PHP')->get();

イベント

Eloquentでは、モデルから行った操作について、細かくイベントが発行できるようになっています。

creating
新しいレコードをインサートした時
created
新しいレコードのインサートが完了した時
updating
既存のレコードを更新した時
updated
既存のレコード更新が完了した時
saving
登録・更新を行った時
saved
登録・更新が完了した時
deleting
レコードを削除した時
deleted
レコード削除が完了した時
restoring
論理削除(ソフトデリート)を行ったレコードを復帰させる時
restored
論理削除からの復帰が完了した時
retrieved
データを取得した時

saving/savedに関しては、creating/createdとupdating/updatedの共通版です。

イベントを設定するにはイベント&リスナーの仕組みが必要ですが、以下を参考にしてください。
Laravel イベント&リスナ

ではイベントを設定していきます。まずはモデルにイベントをマップします。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Events\AccessDetection; // イベントクラスをuse
//use App\Events\Slack;

class Frameworks extends Model
{
protected $dispatchesEvents = [
// データ取得時に発火
'retrieved' => AccessDetection::class,
//'saved' => Slack::class,
];
}

データを参照した時に発行される retrievedイベント に対して設定しました。また、savedイベントもコメントアウトされていますが、複数設定する場合の例として記述しています。

設定はこれだけです。必要な挙動はもちろんイベントリスナーで実装されていますから、あとはモデルからデータが参照された時点でイベントが発行され処理が行われます。

オブザーバクラス

イベントを登録する時に、例えばデータを保存した時のsavedイベントにはSlack通知・メール配信・ログファイル書き込み・別のテーブルへのログ書き込み…など、登録するイベントがたくさんある場合があります。しかもそれが別のイベントの時も…となると結構な数です。

そんな場合は、オブザーバクラスを作成する事で、イベントの登録をまとめる事が出来ます。

laravel/app 配下に Observersディレクトリを作成し、そこへFrameworksObserver.phpを作成します。

laravel
├─ app
│   ├─
Observers
│   │   └─
FrameworksObserver.php

オブザーバクラスを定義します。

laravel/app/Observers/FrameworksObserver.php
<?php

namespace App\Observers;

use App\Models\Frameworks;
use Log;
use App\Events\AccessDetection;

/**
* Frameworksモデルのオブザーバクラス
* Class FrameworksObserver
* @package App\Observers
*/
class FrameworksObserver
{
/**
* モデル取得イベントのリッスン
*
* @param \App\Models\Frameworks $frameworks
* @return void
*/
public function retrieved(Frameworks $frameworks)
{
// ログの書き込み
Log::debug($frameworks->name);
// イベント発火
event(new AccessDetection(str_random(100)));
}

/**
* 作成イベントのリッスン
*
* @param \App\Models\Frameworks $frameworks
* @return void
*/
public function created(Frameworks $frameworks)
{
//
}

/**
* 作成イベントのリッスン
*
* @param \App\Models\Frameworks $frameworks
* @return void
*/
public function updated(Frameworks $frameworks)
{
//
}

/**
* 削除イベントのリッスン
*
* @param \App\Models\Frameworks $frameworks
* @return void
*/
public function deleting(Frameworks $frameworks)
{
//
}
}

上記のようにして、各イベントメソッドに対して処理を定義していく事でそのイベントに対する処理を行う事が出来ます。ログの書き込みなど、必ずイベント&リスナでなくてはいけないという事はなく、自由に実装できます。

まとめ

Eloquent ORMはモデルという観念を用いた有用なデータ操作方法の1つですが、Laravelではデータ操作の方法はこれだけではありません。アプリケーションの要件や自身の使いやすさなどから最適な方法を使っていくと良いと思います。そういった意味でも、LaravelではデフォルトでModelディレクトリというのを用意していません。

Eloquent ORMは、Laravelで簡単にデータ操作を行うには便利なので是非試してみてください。