RitoLabo

Laravelのアクセサとミューテタを用いてEloquentの属性フォーマットを定義する

  • 公開:
  • カテゴリ: PHP Laravel
  • タグ: PHP,Laravel,5.5,5.4,5.3,EloquentORM,Model,5.6,Mutators,Accessors

LaravelのEloquentには「アクセサ」と「ミューテタ」というものがあります。これらを使うと、EloquentORM、つまりはモデルでのデータ操作を行う際に、予めデータの形式を定義しておく事が出来ます。

今回はLaravelのアクセサとミューテタを用いてEloquentの属性フォーマットを定義していきます。

アジェンダ
  1. 開発環境
  2. アクセサ
  3. ミューテタ
  4. 日付ミューテタ
    1. デフォルトの日付フォーマット
  5. 属性のキャスト

開発環境

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

  • 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/」としています。

アクセサ

アクセサの定義を行うと、予め取得するカラムのフォーマットを定義できます。

例えば苗字(first_name)と名前(last_name)を収録したカラムがあるとして、これらについてアクセサを定義します。

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

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Members extends Model
{
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}

public function getLastNameAttribute($value)
{
return ucfirst($value);
}
}

これで何を定義しているのかというと、first_nameカラムとlast_nameカラムへアクセスした際に取得できる値のフォーマットを定義しています。

尚、メソッド名には規則があり、
get[カラム名(アッパーキャメルケース)]Attribute
という命名規則になっています。

つまりは、priceカラムへの定義の場合はgetPriceAttribute()のようになるわけです。

両メソッドで ucfirst() メソッドを定義していますが、これは英字に対して先頭の文字を大文字にするものです。

データベースには英字の名前が全て小文字で収録されており、この定義の場合は、ファーストネーム・ラストネーム共に、頭文字が大文字になった状態で取得できるというわけです。

コントローラから取得します。

laravel/app/Http/Controllers/SampleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Members;

class SampleController extends Controller
{
protected $members;

public function __construct()
{
$this->members = new Members();
}

public function index()
{
$members = $this->members->all();
$data = [];
foreach ($members as $m) {
$data[]['name'] = sprintf('%s %s', $m->first_name, $m->last_name);
}
print_r($data);
}
}

上記の通り、ただ普通に取得しているだけになりますが、結果は以下のようになります。

Array
(
[0] => Array
(
[name] => Ross Rios
)

[1] => Array
(
[name] => Marianne Munoz
)

[2] => Array
(
[name] => Lois Gonzalez
)

[3] => Array
(
[name] => Everett Scott
)

[4] => Array
(
[name] => Jodi Manning
)

[5] => Array
(
[name] => Jan Reyes
)

[6] => Array
(
[name] => Rrdolph Hawkins
)

[7] => Array
(
[name] => Chad Lawson
)

[8] => Array
(
[name] => Freddie Brady
)

[9] => Array
(
[name] => Ruby Farmer
)

)

このように、モデルにアクセサを定義しておく事で、定義した通りのフォーマットで取得できるようになります。

また、既存のカラムを用いて、オリジナルの値を定義する事もできます。

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

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Members extends Model
{
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}

なんとなく想像がつくと思いますが、first_nameカラムとlast_nameカラムを合わせて、full_nameカラム(厳密にはカラムではないですが)を作成しています。テーブルにはfull_nameというカラムは存在していませんが、これでフルネームへアクセスできるようになります。

コントローラから取得します。

laravel/app/Http/Controllers/SampleController.php
public function index()
{
$members = $this->members->all();
$data = [];
foreach ($members as $m) {
$data[]['name'] = $m->full_name;
}
}

あたかもfull_nameカラムが存在しているかのように取得が行えます。結果は以下のようになります。

Array
(
[0] => Array
(
[name] => ross rios
)

[1] => Array
(
[name] => marianne munoz
)

[2] => Array
(
[name] => lois gonzalez
)

[3] => Array
(
[name] => everett scott
)

[4] => Array
(
[name] => jodi manning
)

[5] => Array
(
[name] => jan reyes
)

[6] => Array
(
[name] => Rrdolph hawkins
)

[7] => Array
(
[name] => chad lawson
)

[8] => Array
(
[name] => freddie brady
)

[9] => Array
(
[name] => ruby farmer
)

)

ちなみに、前半で定義した getFirstNameAttribute()getLastNameAttribute() をそのまま定義しておくとそれも適用されるので、頭文字が大文字のフルネームを取得できます。

ミューテタ

ミューテタは、言うなればアクセサの逆のようなものです。アクセサでは取得の際の定義を行いましたが、ミューテタは値を代入する際のフォーマットを定義します。

先ほど、データベースには英字の名前が全て小文字で収録されていると言いましたが、新たなレコードを挿入する際も、やはりもれなく小文字で収録したいです。

そこで、ミューテタで以下のように定義します。

laravel/app/Models/Members.php
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}

public function setLastNameAttribute($value)
{
$this->attributes['last_name'] = strtolower($value);
}

アクセサはgetプレフィックスだったのに対して、ミューテタはsetプレフィックスです。命名規則はアクセサと同じです。

ここでは、strtolower()メソッドを用いて、全てを小文字にしています。

コントローラから値をセットし、保存してみます。

laravel/app/Http/Controllers/SampleController.php
$member = $this->members->find(1);
$member->first_name = 'Cristiano';
$member->last_name = 'Ronaldo';
$member->save();

保存結果を見てみます。

mysql> select first_name, last_name from members limit 1;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
|
cristiano | ronaldo |
+------------+-----------+

セット時には頭文字を大文字で代入しましたが、保存時にはミューテタの定義通り、小文字で保存されている事が確認できます。

日付ミューテタ

Laravelでは、作成日・更新日に対してcreated_atカラムとupdated_atカラムを定義すると、色々と自動でやってくれますが、Eloquentではこれらのカラムに対して、自動的にCarbonインスタンスで取り回しを行います。つまりはただの日時の文字列ではなく、色々と柔軟にできる便利な形で提供されるという事です。

ちなみに Carbon というのは、日時を操作するPHPのAPIで、PHPのDateTimeクラスから継承されています。つまり、Laravel固有のものではなく、広くPHPでの日時操作に使われているシンプルなAPI拡張です。(動作にはPHP 5.4以降が必要)

ちなみに Carbonという名前の由来はRadiocarbon dating(放射性炭素年代測定)からきているらしいです。…由来は由来として動機が知りたくなります。

そんなCarbonインスタンスを用いて入力時の日時フォーマットを定義できます。まずは、どのカラムについてCarbonインスタンスを当てるかを定義します。

laravel/app/Models/Members.php
class Members extends Model
{
protected $dates = [
'created_at',
'updated_at'
];

上記で設定したカラムについては、レコード挿入時に日時を上手く定義されているフォーマットにしてくれます。例えば、PHPでもおなじみのnow()メソッドですが、Laravelで普通に使うとこんな感じになります。

Illuminate\Support\Carbon Object
(
[date] => 2018-06-26 00:44:28.942253
[timezone_type] => 3
[timezone] => UTC
)

が、このまま突っ込んでもあとはモデルがカラムの形式合わせてレコードに入れてくれます。

$member = $this->members->find(1);
$member->created_at = now();
$member->save();

そしてデータの取得時も、Carbonインスタンスによって自在に形を変化させる事が出来ます。

$member = $this->members->find(1);
$member->created_at;
$member->created_at->getTimestamp(); // => 1529941179
$member->created_at->subYear(10); // 10年前
$member->created_at->addYear(10); // 10年後
$member->created_at->subMonth(10); // 10ヵ月前
$member->created_at->addMonth(10); // 10ヵ月後
$member->created_at->subWeek(10); // 10週間前
$member->created_at->addWeek(10); // 10週間後
$member->created_at->subDays(10); // 10日前
$member->created_at->addDays(10); // 10日後
$member->created_at->subHours(10); // 10時間前
$member->created_at->addHours(10); // 10時間後

デフォルトの日付フォーマット

作成日と更新日をcreated_atカラムとupdated_atカラムにした場合は自動で日時を挿入してくれますが、その時に挿入する日時のフォーマットを変更する事が出来ます。

デフォルトのタイムスタンプのフォーマットは Y-m-d H:i:s ですが、それを変更したい場合は以下のようにモデルに設定します。

laravel/app/Models/Members.php
class Members extends Model
{
protected $dateFormat = 'U';

メンバ変数 $dateFormat に任意のフォーマットを設定します。ちなみに上記はUnix Timestampの設定です。ただしこの場合はカラムを数値型にする必要があります。

mysql> show columns from members;
+------------+------------------+
| Field | Type |
+------------+------------------+
| id | int(10) unsigned |
| first_name | varchar(50) |
| last_name | varchar(50) |
|
updated_at | int(11) |
|
created_at | int(11) |
+------------+------------------+

マイグレーションなら以下になります。

$table->integer('updated_at');
$table->integer('created_at');

コントローラから値をセットし、保存します。

laravel/app/Http/Controllers/SampleController.php
public function index()
{
$this->members->first_name = "steve";
$this->members->last_name = "aurora";
$this->members->created_at = now();

$this->members->save();
}

結果は以下になります。

mysql> select first_name, last_name, updated_at, created_at from members;
+------------+-----------+------------+------------+
| first_name | last_name |
updated_at | created_at |
+------------+-----------+------------+------------+
| steve | aurora |
1530021575 | 1530021575 |
+------------+-----------+------------+------------+

Unix Timestampで登録できている事が確認できました。

少しイメージしずらいかもしれないので、違うパターンの例も出しておきます。例えば時間無しの年月日にフォーマットを指定する場合は以下のように設定します。

protected $dateFormat = 'Y-m-d';

そして、日時カラムはdate型で作ります。

mysql> show columns from members;
+------------+------------------+
| Field | Type |
+------------+------------------+
| id | int(10) unsigned |
| first_name | varchar(50) |
| last_name | varchar(50) |
|
updated_at | date |
|
created_at | date |
+------------+------------------+

マイグレーションなら以下のようになります。

$table->date('updated_at');
$table->date('created_at');

コントローラから値をセットし、保存します。記述は先ほどと同じです。そして、結果は以下になります。

mysql> select first_name, last_name, updated_at, created_at from members;
+------------+-----------+------------+------------+
| first_name | last_name |
updated_at | created_at |
+------------+-----------+------------+------------+
| steve | aurora |
2018-06-26 | 2018-06-26 |
+------------+-----------+------------+------------+

date型で登録できている事を確認できました。

属性のキャスト

モデルにメンバ変数 $casts を設定する事で、任意のカラムに対して属性のキャストを行う事が出来ます。ここで言う「キャスト」とは、データ取得の際にそのカラムのフォーマットを変更する事です。

例えば、datetime型のカラムをdate型としてここに設定すれば、データ取得時にdate型で受け取る事が出来ます。

class Members extends Model
{
protected $casts = [
'created_at' => 'date',
];

上記の場合は、created_atカラムに対してdate型で取得するようにキャストしています。例ではcreated_atカラムはdatetime型ですが、実際に取得してみると結果は以下のようになります。

$member = $this->members->find(11);
$member->created_at;
=> Illuminate\Support\Carbon Object
(
[date] => 2018-06-26 00:00:00.000000
[timezone_type] => 3
[timezone] => UTC
)

結果の時間の部分が丸まっているのが確認できます。

このようにして、$castsプロパティでは以下へのキャストを行う事が出来ます。(もちろん、変更するカラムに対してキャスト可能な型を指定します)

  • integer
  • real
  • float
  • double
  • string
  • boolean
  • object
  • array
  • collection
  • date
  • datetime
  • timestamp

上記のリストを見てなんとなく気が付いたかもしれませんが、キャストはただカラムからの取得フォーマットを変えるだけではなく、オブジェクトやコレクション、配列と言った形にもキャストできます。

まとめ

以上で作業は完了です。ミューテタやアクセサを上手く使いこなすと、データ取得を行った後でいちいち欲しい形へ値を変換しなくて済むのでソースコードも綺麗になりますし、手間も省けますので、是非試してみてください。