はじめに
皆さん、hash makeしてますか?ユーザーのパスワードのハッシュ化を行うのに欠かせません。 ただ、Eloquent Modelのfillメソッドを使用してモデルインスタンスに値を渡そうとすると以下のようになることはありませんか?
<?php
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use App\Http\Requests\UserRequest;
class UserController {
public function index(){
...
public function save(UserRequest $request, User $user) {
$user->fill(array_merge($request->all(), [ 'password' => Hash::make($request->password) ]))->save();
}
}
上記のコードで行っていることとしては、
1. Hash::makeを用いてパスワードのハッシュ化
2. array_mergeで$requestインスタンスのallメソッドで返される配列とパスワードの配列をマージ
3. モデルインスタンスのfillメソッドにマージした配列を渡す
なのですがハッシュ化するために可読性が低くなってしまっていますね。 FormRequestでハッシュ化することもできますが責務過多な気もします。 そんな時にカスタムキャストを使うとfillメソッドに$request->all()を単純に渡してあげるだけでhash makeすることができます!
カスタムキャストとは
アクセサ、ミューテタ、および属性キャストを使用すると、Eloquentモデルインスタンスで属性値を取得または設定するときに、それらの属性値を変換できます。たとえば、Laravel暗号化を使用して、データベースに保存されている値を暗号化し、Eloquentモデル上でそれにアクセスしたときに属性を自動的に復号できます。他に、Eloquentモデルを介してアクセスするときに、データベースに格納されているJSON文字列を配列に変換することもできます。
https://readouble.com/laravel/8.x/ja/eloquent-mutators.html
Eloquentモデルのインスタンスの属性値をgetで取得、もしくはsetする際に値を変更することができるようになる機能です。 この機能を利用することでパスワードのhash化をカスタムキャストに切り分けることができます!
カスタムキャストの定義
カスタムキャストを以下のように定義します。
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Facades\Hash;
class HashMake implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return $value;
}
public function set($model, string $key, $value, array $attributes)
{
if ($value !== null && Hash::needsRehash($value)){
$value = Hash::make($value);
}
return $value;
}
}
上記のコードの解説を致します。 モデルに設定した属性値に(今回はpasswordですね)セットされたときにセッターが動作します。渡された値は第三引数の$valueとして受け取ることができます。HashファサードのneedsRehashメソッドを用いて$valueにハッシュ化が必要かの真偽値を返します。
https://readouble.com/laravel/8.x/ja/hashing.html
Hash
ファサードが提供するneedsRehash
メソッドを使用すると、パスワードがハッシュされてから、ハッシャーによって使用される作業要素が変更されたかどうかを判別できます。一部のアプリケーションは、アプリケーションの認証プロセス中にこのチェックを実行することを選択しています。
引数に渡された値が config/hashing.php で設定されたハッシュドライバーのアルゴリズムでハッシュ化されていた場合はtrueを。設定されていなければfalseを返してくれるので一度ハッシュ化するとアルゴリズムを変更しない限りHashMakeしないように動作させることができます。
カスタムキャストをモデルに指定する
定義したキャストをUserモデルの属性値に指定します。
<?php
namespace App\Models;
use App\Casts\HashMake;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable {
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'password' => HashMake::class,
];
}
このように定義することでUserモデルインスタンスのpasswordにsetする際にHashMakeキャストが動いてハッシュ化を行ってくれます。
ビフォーアフター
モデルインスタンスのfillメソッドにrequestクラスのallメソッドを渡すだけのシンプルなコードにすることができました!
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use App\Http\Requests\UserRequest;
class UserController {
public function index(){
...
public function save(UserRequest $request, User $user) {
$user->fill($request->all())->save();
}
}
カスタムキャストでHash Makeするメリット・デメリット
最後にカスタムキャストのメリット・デメリットを考えてみましょう。
メリット
- fillメソッドをシンプルに記述できる
- hash化のロジックをカスタムキャストに切り分けることができる。
デメリット
- fillメソッドを使わないとあまり恩恵を受けることができない
- どこでハッシュ化を行っているか分かりづらくなってしまう。
メリットに関しては前述しているとおりなので省略させていただきます。 デメリットとしてはメリットの裏返しになってしまいますがfillメソッドを使わなければそこまで必要性は感じないかなという印象です。また、小規模な開発の場合そこまで気にはならないかもしれないですが、大規模な開発になるとどこでハッシュ化を行っているか分かりづらくなってしまう可能性があります。 というようにメリットデメリットを考えてカスタムキャストを活用していくとコードの可読性を高められる可能性があるかもしれません!参考になれば嬉しいです!