Laravel を学習していて、時間を管理するフィールドを特定の時刻フォーマットで取得したいことがありました。
Eloquent モデルのアクセサを活用したら便利だったので備忘録として残しておきます。
- PHPのバージョン : 8.1.14
- Laravelのバージョン : 9.42.2
まずはデータベースのテーブル構造から。
今回は例として、ユーザ情報を管理する以下のようなテーブルがあるとします。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('first_name', 100);
$table->string('last_name', 100);
$table->timestamp('logined_at');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
};
また、このテーブルには以下のレコードが登録されているとします。
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('users')->insert([
[
'first_name' => '佐藤',
'last_name' => '一郎',
'logined_at' => Carbon::now()
]
]);
}
}
それでは artisan コマンドでユーザ情報のモデルを作成します。
作成直後は以下のようなコードになっているかと思います。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
use HasFactory;
}
この状態で logined_at
の値を取得した場合 Y-m-d H:i:s
形式の文字列となります。
User::findOrFail(1)->logined_at
// 2023-01-21 15:56:17
これを上記の形式ではなく Y/m/d H:i:s
形式の文字列で取り扱いたい場合、一案として以下のような方法もありますが、変換ロジックが方々に散らばることになるので保守性の悪化に繋がります。
Carbon::createFromFormat(
'Y-m-d H:i:s',
User::findOrFail(1)->logined_at
)->format('Y/m/d H:i:s');
// 2023/01/21 15:56:17
それならば変換ロジックを関数化して DRY にすれば...ということも考えられますが、ここでアクセサの出番です。
アクセサは、Eloquentの属性値にアクセスが合った時に、その値を変換するものです。アクセサを定義するには、アクセス可能な属性を表すprotectedなメソッドをモデル上に作成します。このメソッド名は、裏に存在するモデル属性やデータベースカラムの「キャメルケース」表現に対応させる必要があります。
引用文の通り、アクセサはモデルのフィールドにアクセスした時に、その値を変換するための機能です。
本記事の例で言うと「logined_at
フィールドの値を取得した側が変換を頑張るんじゃなくて、時刻フォーマットの変換はアクセサに任せて、取得した時には要件通りの時刻フォーマットにしておきましょう。」といった考え方になります。
ここまでを基に logined_at
フィールドを Y/m/d H:i:s
形式の文字列で取得するためのアクセサを実装した場合、以下のようになります。
なお、本記事のアクセサの書き方は Laravel 9 で追加されたものになり、Laravel 8 以前では異なる書き方をする必要がある点に注意してください。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
class User extends Model
{
use HasFactory;
/**
* `Y/m/d H:i:s`形式のログイン日時を取得
*
* @return Attribute
*/
protected function loginedAt(): Attribute
{
return Attribute::make(
get: fn ($value) => Carbon::parse($value)->format('Y/m/d H:i:s')
);
}
}
User::findOrFail(1)->logined_at
// 2023/01/21 15:56:17
ちなみにアクセサはモデルクラスのアクセス可能なプロパティであれば、データベースに実在するフィールドに限らないので、以下のような使い方も可能です。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
class User extends Model
{
use HasFactory;
/**
* `Y/m/d H:i:s`形式のログイン日時を取得
*
* @return Attribute
*/
protected function loginedAt(): Attribute
{
return Attribute::make(
get: fn ($value) => Carbon::parse($value)->format('Y/m/d H:i:s')
);
}
/**
* ユーザのフルネームを取得
*
* フルネームは『ユーザ姓』『半角スペース』『ユーザ名』の順に結合した文字列とする。
*
* @return Attribute
*/
protected function userName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}"
);
}
}
User::findOrFail(1)->userName;
// 佐藤 一郎
記事のタイトルからは少しそれてしまいましたが、アクセサを活用することで変換ロジックを DRY に保てて保守性も高まると思いますので上手に利用していきたいところです。