Laravelのグローバルスコープを使って、MySQLのテーブルに保存されたデータのフォーマットを変換する方法
Laravelのマイグレーションを使ってテーブルを作成、データを登録、モデルを作成する。
Laravelのマイグレーションを使って、以下のテーブルを作成します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBusinessHoursTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('business_hours', function (Blueprint $table) {
$table->smallIncrements('id');
$table->morphs('parent_model');
$table->unsignedTinyInteger('day_of_week');
$table->time('open');
$table->time('lastcall')
->nullable();
$table->time('close');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('business_hours');
}
}
SQLを使って、作成したテーブルに以下のデータを登録します。
INSERT INTO `business_hours` VALUES (1,'App\\User',1,0,'10:30:00','18:00:00','18:30:00','2020-05-24 14:35:55','2020-05-24 14:35:55'),
(2,'App\\User',1,1,'10:30:00','18:00:00','18:30:00','2020-05-24 14:35:55','2020-05-24 14:35:55'),
(3,'App\\User',1,2,'10:30:00','18:00:00','18:30:00','2020-05-24 14:35:55','2020-05-24 14:35:55'),
(4,'App\\User',1,3,'10:30:00','18:00:00','18:30:00','2020-05-24 14:35:55','2020-05-24 14:35:55'),
(5,'App\\User',1,4,'10:30:00','18:00:00','18:30:00','2020-05-24 14:35:55','2020-05-24 14:35:55'),
(6,'App\\User',1,5,'10:30:00','18:00:00','18:30:00','2020-05-24 14:35:55','2020-05-24 14:35:55'),
(7,'App\\User',1,6,'10:30:00','18:00:00','18:30:00','2020-05-24 14:35:55','2020-05-24 14:35:55');
そして、このテーブルを操作するBusinessHourモデルを作成します。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class BusinessHour extends Model
{
const TABLE = 'business_hours';
const ID = 'id';
const PARENT_MODEL = 'parent_model';
const DAY_OF_WEEK = 'day_of_week';
const OPEN = 'open';
const LAST_ORDER = 'last_order';
const CLOSE = 'close';
const KEY = self::ID;
protected $primaryKey = self::KEY;
const DELETED_AT = 'deleted_at';
protected $table = self::TABLE;
protected $dates = [self::DELETED_AT];
protected $perPage = 100;
protected $fillable = [
BusinessHour::PARENT_MODEL,
BusinessHour::DAY_OF_WEEK,
BusinessHour::OPEN,
BusinessHour::LAST_ORDER,
BusinessHour::CLOSE,
];
}
このモデルは、飲食店やサービス業の営業時間を保存するためのテーブルです。
マイグレーションで使用した$table->morphs('parent_model');
は、ポリモーフィックリレーションを保存するためのカラムの宣言です、
ポリモーフィックリレーションとは、他のテーブルのカラムを親として持つことができるカラムです。
例えば、レストランの情報を保存するrestaurantsテーブルやスーパーマーケットの情報を保存するsupermarketsテーブルのカラムを親に持つことができます。
この宣言を使うと、parent_modelとparent_model_idというカラムを自動的に作成してくれます。
今回の例では、parent_modelにApp\Userを、parent_model_idに1を指定しているので、usersテーブルの1カラム目が親になっています。
それでは、マイグレーションで作成したテーブルにデータが登録されていることを確認しましょう。
データの存在を確認するには、tinker
を使うのがラクです。
php artisan tinker
Psy Shell v0.10.4 (PHP 7.2.29 — cli) by Justin Hileman
>>> App\BusinessHour::get();
=> Illuminate\Database\Eloquent\Collection {#4258
all: [
App\BusinessHour {#4238
id: 1,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 0,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4241
id: 2,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 1,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4224
id: 3,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 2,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4297
id: 4,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 3,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4298
id: 5,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 4,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4299
id: 6,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 5,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4300
id: 7,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 6,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
],
}
グローバルスコープを追加して、tinkerで確認する。
MySQLのtime型は時間・分・秒を保存する宣言ですが、実際のサービスの表示では、秒までを使うことはあまりないでしょう。
レストランの開店時間に秒を表示するのはやりすぎです。
常に指定したフォーマットでデータを取得したい場合、Laravelでは、グローバルスコープを使うのが便利です。
それでは、BusinessHourモデルに以下のコードを追加してみましょう。
/**
* モデルの「初期起動」メソッド
*
* @return void
*/
protected static function booted()
{
static::addGlobalScope('time_format', function (Builder $builder) {
$builder->addSelect(
'*',
DB::raw('TIME_FORMAT('.BusinessHour::OPEN. ', "%H:%i")'.' as '.BusinessHour::OPEN),
DB::raw('TIME_FORMAT('.BusinessHour::LAST_ORDER.', "%H:%i")'.' as '.BusinessHour::LAST_ORDER),
DB::raw('TIME_FORMAT('.BusinessHour::CLOSE. ', "%H:%i")'.' as '.BusinessHour::CLOSE),
);
});
}
この例では、グローバルスコープをtime_formatという名前で登録しました。
Builderのメソッドにselect()ではなく、addSelect()を使いました。
select()は1つのクエリで一回だけしか呼び出せません。複数回、呼び出すと、一番最後の呼び出しのみが有効となります。
なので、グローバルスコープでselect()を呼び出すと、他の箇所では必ずaddSelect()で呼び出す必要があります。
そのように設計していれば問題ないのですが、ここでaddSelect()を使えば、あとでselect()が呼び出されても問題ありません。もちろんaddSeelct()が呼び出されても問題ありません。
また、addSelect()の引数に'*'を指定して、ポリモーフィックリレーションが壊れないようにしています。
そして、DB::raw()を呼び出して、MySQLのメソッドを指定して、時間のフォーマットを書き換えます。
'as'を使って、元のカラムと同じ名前を指定することで、既存のカラムを上書きしていますが、複数のフォーマットを使いたいのであれば、そのフォーマットに適した名前をつけるのがいいと思います。
それでは、再びtinker
を使って、グローバルスコープが適用されていることを確認しましょう。
php artisan tinker
Psy Shell v0.10.4 (PHP 7.2.29 — cli) by Justin Hileman
>>> BusinessHour::get();
=> Illuminate\Database\Eloquent\Collection {#4279
all: [
App\BusinessHour {#4280
id: 1,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 0,
open: "10:30",
last_order: "18:00",
close: "18:30",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4281
id: 2,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 1,
open: "10:30",
last_order: "18:00",
close: "18:30",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4282
id: 3,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 2,
open: "10:30",
last_order: "18:00",
close: "18:30",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4283
id: 4,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 3,
open: "10:30",
last_order: "18:00",
close: "18:30",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4284
id: 5,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 4,
open: "10:30",
last_order: "18:00",
close: "18:30",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4285
id: 6,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 5,
open: "10:30",
last_order: "18:00",
close: "18:30",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4286
id: 7,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 6,
open: "10:30",
last_order: "18:00",
close: "18:30",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
],
}
グローバルスコープをキャンセルする方法をtinkerで確認する。
時間のフォーマットから秒が表示されなくなっていますね。
グローバルスコープの適用をキャンセルしてデータを取得したい場合は、グローバルスコープの名前を引数にwithoutGlobalScope()メソッドをチェーンメソッドでつなげて呼び出します。
php artisan tinker
Psy Shell v0.10.4 (PHP 7.2.29 — cli) by Justin Hileman
>>> App\BusinessHour::withoutGlobalScope('time_format')->get();
=> Illuminate\Database\Eloquent\Collection {#4258
all: [
App\BusinessHour {#4238
id: 1,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 0,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4241
id: 2,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 1,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4224
id: 3,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 2,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4297
id: 4,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 3,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4298
id: 5,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 4,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4299
id: 6,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 5,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
App\BusinessHour {#4300
id: 7,
parent_model_type: "App\User",
parent_model_id: 1,
day_of_week: 6,
open: "10:30:00",
last_order: "18:00:00",
close: "18:30:00",
created_at: "2020-05-24 22:35:55",
updated_at: "2020-05-24 22:35:55",
},
],
}
また秒が表示されていますね。
グローバルスコープは応用範囲が広く、さまざまな使い方ができます。例えば、生年月日から年齢を計算したり、貨幣の区切りを変更したり、数値で持っている性別を文字列に変更したり、本当にさまざまな使いみちがあります。
次の例は、時間のフォーマット変更に加えて、date_formatという名前で日付のフォーマットを変更するグローバルスコープを追加しています。
/**
* モデルの「初期起動」メソッド
*
* @return void
*/
protected static function booted()
{
static::addGlobalScope('time_format', function (Builder $builder) {
$builder->addSelect(
'*',
DB::raw('TIME_FORMAT('.BusinessHour::OPEN. ', "%H:%i")'.' as '.BusinessHour::OPEN),
DB::raw('TIME_FORMAT('.BusinessHour::LAST_ORDER.', "%H:%i")'.' as '.BusinessHour::LAST_ORDER),
DB::raw('TIME_FORMAT('.BusinessHour::CLOSE. ', "%H:%i")'.' as '.BusinessHour::CLOSE),
);
});
static::addGlobalScope('date_format', function (Builder $builder) {
$builder->addSelect(
DB::raw('DATE_FORMAT('.BusinessHour::CREATED_AT.', "%Y/%m/%d")'.' as '.BusinessHour::CREATED_AT),
DB::raw('DATE_FORMAT('.BusinessHour::UPDATED_AT.', "%Y/%m/%d")'.' as '.BusinessHour::UPDATED_AT)
);
});
}
アプリケーションサーバに比べて、データベースサーバは多重化しにくいので、このようなフォーマット変換をデータベース側でやらせることには異論があるかもしれません。
そこまでの負荷ではない場合、私はできるだけの処理をデータベースサーバ側でやらせるようにしています。
今回は、Laravelでグローバルスコープを使って、フォーマット変換を行う方法を説明しました。
合わせて、tinkerとポリモーフィックリレーションにも少しだけ触れました。
Laravelって知れば知るほど、かゆいところに手が届くすばらしいフレームワークですね。