Laravel の Eloquent ORM で update() 実行時にミューテータ（setXxxAttribute()）が動かない

## 確認環境

* Laravel 5.6
* PHP 7.1

## 先に結論

`hasOne` 等の Relationship を利用しているとき、

* `$user->contact()->update([/*...*/])` のような形だとミューテータは動かない
* `$user->contact->update(/*...*/)` のような形だとミューテータは動く

## 事象

例として、ユーザ（user）と連絡先（contact） の1対1関係があり、電話番号は永続化の際にミューテータを介して暗号化する、というケースを考える。

```php:app/User.php
<php

namespace App;

use Illuminate\Database\Eloquent\Model as Eloquent;

class User extends Eloquent
{
    public function contact()
    {
        $this->hasOne('App\Contact');
    }
}
```


```php: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()` による更新ではミューテータによる暗号化が行われない。

```php: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()` する場合はミューテータによる暗号化が行われる。

```php: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` の `()` の有無の違い

```php: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` で返ってくるオブジェクトが異なる。

```php: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`) に対する操作となるため、このような差が起こる。

知らないと嫌なタイミングで引っかかる罠だ。