確認環境
- Laravel 5.6
- PHP 7.1
先に結論
hasOne 等の Relationship を利用しているとき、
-
$user->contact()->update([/*...*/])のような形だとミューテータは動かない -
$user->contact->update(/*...*/)のような形だとミューテータは動く
事象
例として、ユーザ(user)と連絡先(contact) の1対1関係があり、電話番号は永続化の際にミューテータを介して暗号化する、というケースを考える。
app/User.php
<php
namespace App;
use Illuminate\Database\Eloquent\Model as Eloquent;
class User extends Eloquent
{
public function contact()
{
$this->hasOne('App\Contact');
}
}
app/Contact.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model as Eloquent;
class Contact extends Eloquent
{
protected $fillable = [
'user_id',
'telephone_number',
];
// 電話番号は暗号化して保存したい
public function setTelephoneNumberAttribute($value)
{
$this->attributes['telephone_number'] = encrypt($value);
}
public function getTelephoneNumberAttribute($value)
{
return decrypt($value);
}
}
このとき $user->contact()->update() による更新ではミューテータによる暗号化が行われない。
tinker
>>> $user = App\User::first();
=> App\User {
id: 1
}
>>> $user->contact()->create(['telephone_number' => '00-0000-0000'])
=> App\Contact {
id: 1,
user_id: 1,
// 暗号化されている
telephone_number: "eyJpdiI6ImpKdGZLMXhvZTRGdzRwekNGM3lXN0E9PSIsInZhbHVlIjoiOTJwMTQxY1JHRk5ueWYrMGRBVGNVNVhtZzJFMTk2a0l3ajlkTGRQT29wZz0iLCJtYWMiOiJlNWM4ZTJiZjFlMTg4MWRjOTMzMWVkZDIwNTU2NmY4OTIwMzU0YzM4NGIxMzYyNjAxYTMxM2MzYTlhYzJmZmNhIn0="
}
>>> $user->contact()->update(['telephone_number' => '11-1111-1111'])
=> 1
>>> $user->contact
=> App\Contact {
id: 1,
user_id: 1,
// 暗号化されていない!
telephone_number: "11-1111-1111"
}
ちなみに、attribute を再代入して save() する場合はミューテータによる暗号化が行われる。
tinker(続き)
>>> $user->contact->telephone_number = '11-1111-1111'
=> '11-1111-1111'
>>> $user->contact->save()
=> App\Contact {
id: 1,
user_id: 1,
// 暗号化されている
telephone_number: "eyJpdiI6IkhuQVo0MlpKajZoV0MxWmUxUDNlSEE9PSIsInZhbHVlIjoiMEo2TjNxN3AyREpQK2xhVVFqNWtFeGRyU01Feng2YVROdTBYWTFRSWFLdz0iLCJtYWMiOiI2YjY2NWFjNTc5YzBmZDdkZjYwZGM4YjdiODMyYmFhYmFhMGZmZjI1NTQxNjE5N2VlOGI2YWVmOWQ4MmZkMWMwIn0="
}
さらに調べた結果、$user->contact->update() (※)だとミューテータによる暗号化が行われることがわかった。
※ contact の () の有無の違い
tinker(続き)
>>> $user->contact->update(['telephone_number' => '22-2222-2222'])
=> 1
>>> $user->contact
=> App\Contact {
id: 1,
user_id: 1,
// 暗号化されている
telephone_number: "eyJpdiI6InlxNzFVMk9lWXJsODU4VzJpUG1QUXc9PSIsInZhbHVlIjoiTHNmU3BSQjdJR2s1SVc3NCt0MXRKVURPeGtQMmJVMXpLTFpZbUdxWjg0Zz0iLCJtYWMiOiI0MDk0OTFhODlkMWMxYjVkNTVjMDA2ZTI3MzljMGMyNDRmMjUxMDVjZGM3ODViNmIxMTdiNWI2ZTQxNWY0MmJkIn0="
}
なぜそうなるか?
$user->contact() と $user->contact で返ってくるオブジェクトが異なる。
tinker
>>> $user->contact()
=> Illuminate\Database\Eloquent\Relations\HasOne {}
>>> $user->contact
=> App\Contact {
id: 1,
user_id: 1,
telephone_number: "eyJpdiI6InlxNzFVMk9lWXJsODU4VzJpUG1QUXc9PSIsInZhbHVlIjoiTHNmU3BSQjdJR2s1SVc3NCt0MXRKVURPeGtQMmJVMXpLTFpZbUdxWjg0Zz0iLCJtYWMiOiI0MDk0OTFhODlkMWMxYjVkNTVjMDA2ZTI3MzljMGMyNDRmMjUxMDVjZGM3ODViNmIxMTdiNWI2ZTQxNWY0MmJkIn0="
}
前者は Query Builder に対する操作、後者は Eloquent ORM (App\Contact) に対する操作となるため、このような差が起こる。
知らないと嫌なタイミングで引っかかる罠だ。