弊ロジカルスタジオでは、エンジニアの業務ごとにチームに分かれて活動しています。 バックエンドチームでは、活動の一環としてMonthlyを発信しています!
日々の業務でぶち当たったことや疑問に思ったこと、調べたことをベースに、 自己学習やたまたま見かけた面白い情報なども交えながら、情報共有できればと思っています。
前回の記事はこちらになります。よろしければ併せてご覧ください!
目次 [非表示]
1.【Laravel】モデルのcasts()を使うとデータアクセス時に自動型変換してくれて便利
前提:Laravel Framework 11.19.0
例えばこんな感じのテーブルがあったとして
![]()
DBがMySQLだとすると、users.flagをLaravelで取得すると0 or 1で返ってきます。
取得した値をもとに条件分岐させたい場合、いちいちbooleanに変換させる処理が必要になって面倒です。
そこでusersのモデルファイル内に以下を記述することで、取得時にbooleanに自動変換してくれます。
protected function casts(): array
{
return [
'flag' => 'boolean',
];
}
protected function casts(): array
{
return [
'flag' => 'boolean',
];
}
$users = Users::first();
dd($users->flag);
dd($users->flag);
今回のケースではbooleanにしましたが、他にも様々な型に対応しています。
詳しくはこちらをご覧ください。
Laravel 11.x Eloquent:ミューテタ/キャスト
ちなみにEnumもいけます。
上記の例でuserTypeがEnumだとしたら、こんな感じに書きます。
protected function casts(): array
{
return [
'flag' => 'boolean',
'userType' => UserType::class,
];
}
protected function casts(): array
{
return [
'flag' => 'boolean',
'userType' => UserType::class,
];
}
2.【Laravel】可読性を向上!ルーティングの小技
画面やAPIを作成時、まずルーティングを定義すると思いますが、
一度定義したら再確認する機会も少ないため、意外と忘れがちな手法二つをまとめました。
参考: Laravel 11.x ルーティング
①ルートパラメータに制約を付ける
ルートパラメータの妥当性を検証します。
Requestでルートパラメータをバリデーションすることも多いですが、
Request内のコード量が増えるケースがあります。
簡単なチェックはルートの定義時に正規表現やヘルパーで可能です。
Route::get('/case/show/{bidStatus}/{no}', ['uses' => 'CaseController@show'])->name('show');
Route::get('/case/show/{bidStatus}/{no}', ['uses' => 'CaseController@show'])
->where('bidStatus', '(biddable|winning)')
->where('no', '[0-9]+')
->name('show');
Route::get('/case/show/{bidStatus}/{no}', ['uses' => 'CaseController@show'])->name('show');
Route::get('/case/show/{bidStatus}/{no}', ['uses' => 'CaseController@show'])
->where('bidStatus', '(biddable|winning)')
->where('no', '[0-9]+')
->name('show');
②ルート名を共通化する
例えば、登録画面を作成時は表示用と登録処理用のルートを定義することが多いと思います。
Route::get('/create', ['uses' => 'ExampleController@create'])->name('create');
Route::post('/create', ['uses' => 'ExampleController@postCreate'])->name('post-create');
<form action="{{ route('post-create') }}" method="post">
<!-- 省略 -->
</form>
Route::get('/create', ['uses' => 'ExampleController@create'])->name('create');
Route::post('/create', ['uses' => 'ExampleController@postCreate'])->name('post-create');
<form action="{{ route('post-create') }}" method="post">
<!-- 省略 -->
</form>
上記の場合、ルート名にgetやpostを入れたり工夫して名前が長くなったり、
スペルミスが起きることもありますが、下記の方法で片方のルート名を不要に出来ます。
Route::get('/create', ['uses' => 'ExampleController@create'])->name('create');
Route::post('/create', ['uses' => 'ExampleController@postCreate']);
<form action="{{ route('create') }}" method="post">
<!-- 省略 -->
</form>
Route::get('/create', ['uses' => 'ExampleController@create'])->name('create');
Route::post('/create', ['uses' => 'ExampleController@postCreate']);
<form action="{{ route('create') }}" method="post">
<!-- 省略 -->
</form>
/createがGETとPOSTに対応しているため、GET側の名前を使用します。
案件の規約等でGETとPOSTで別名にする必要が無ければ、
こういった手も検討ください。
もしくは、上記のように差異がGETとPOSTだけでならmatch()でまとめた方がシンプルに書けるケースもあります。
(この場合、メソッド側でGETとPOSTの処理を分ける工夫が必要になるので使うケースは少ないかもしれません)
Route::match(['get', 'post'], '/create', ['uses' => 'ExampleController@create'])->name('create');
Route::match(['get', 'post'], '/create', ['uses' => 'ExampleController@create'])->name('create');
3.【Laravel】uniqueバリデーション+自分自身を対象外にする
参考:Laravel 10.x バリデーション
例えば、ユーザー情報編集画面にてメールアドレスを変更する際、
他のユーザーが登録しているメールアドレスは登録できてほしくないですよね?
そんな時はuniqueバリデーションを設定します。
しかし普通に設定してしまうと、メールアドレスの変更はせずにメールアドレス以外のユーザー情報(名前等)のみを変更した場合、
そのメールアドレスは登録されているよ!とエラーになります。
自分自身のメールアドレスに引っ掛かってしまっているということですね。
uniqueバリデーションを設定しつつも、自分自身をバリデーションの対象外にすることで万事解決です👍
環境(前提条件): PHP7, Laravel6
使用例:
public function rules(): array
{
return [
'email' => 'unique:users,email_address',
];
}
public function rules(): array
{
return [
'email' => 'unique:users,email_address',
];
}
public function rules(): array
{
return [
'email' => Rule::unique('users','email_address')->ignore($user->id),
];
}
public function rules(): array
{
return [
'email' => Rule::unique('users','email_address')->ignore($user->id),
];
}
4.【Laravel】知ってた?PHPUnitのDataProviderでFacadeやfactory等のヘルパー関数は使えない
参考:https://wand-ta.hatenablog.com/entry/2019/09/04/204622
CSVインポートAPIのテストを実装する中で、DataProviderでstorageからテスト用のCSVをもってきて、
バリデーションエラーのテストをやりたいと思い以下のように実装しました。
前提:Laravel Framework 11.19.0
public static function dataProviderInvalidCsvPayload(): Generator
{
$path = storage_path('/csv/test.csv');
$encodedFile = base64_encode(file_get_contents($path));
yield 'CSVの文字コードが違う' => [
'file' => $encodedFile,
'name' => 'test.csv',
'errors' => […],
];
}public static function dataProviderInvalidCsvPayload(): Generator
{
$path = storage_path('/csv/test.csv');
$encodedFile = base64_encode(file_get_contents($path));
yield 'CSVの文字コードが違う' => [
'file' => $encodedFile,
'name' => 'test.csv',
'errors' => […],
];
}
しかし、エラーになりました。
Message: Call to undefined method Illuminate\\Container\\Container::storagePath()
Location: /var/www/src/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php:902Message: Call to undefined method Illuminate\\Container\\Container::storagePath()
Location: /var/www/src/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php:902
DataProviderはsetUp()等が実行される前に評価されるので、
まだLaravelのアプリケーションの初期化が終わっておらず、アクセスできないためです。
以下のようにClosureを使って遅延評価することで解決しました。
public static function dataProviderInvalidCsvPayload(): Generator
{
yield 'CSVの文字コードが違う' => [
'file' => fn() => {
$path = storage_path('/csv/test.csv');
return base64_encode(file_get_contents($path));
}
'name' => 'test.csv',
'errors' => […],
];
}public static function dataProviderInvalidCsvPayload(): Generator
{
yield 'CSVの文字コードが違う' => [
'file' => fn() => {
$path = storage_path('/csv/test.csv');
return base64_encode(file_get_contents($path));
}
'name' => 'test.csv',
'errors' => […],
];
}
最後までお読みいただきありがとうございました。
Backend Team Monthly は今後も更新予定ですので、お楽しみに!
また、ロジカルスタジオでは、エンジニアを募集中です。
ぜひご確認ください!