Backend Team Monthly vol.2【 Laravel 】

弊ロジカルスタジオでは、エンジニアの業務ごとにチームに分かれて活動しています。 バックエンドチームでは、活動の一環としてMonthlyを発信しています!

日々の業務でぶち当たったことや疑問に思ったこと、調べたことをベースに、 自己学習やたまたま見かけた面白い情報なども交えながら、情報共有できればと思っています。

前回の記事はこちらになります。よろしければ併せてご覧ください!

Backend Team Monthly vol.1【 React / Docker / PowerCMS X / kintone / VSCode 】

 


1.【Laravel】モデルのcasts()を使うとデータアクセス時に自動型変換してくれて便利

前提:Laravel Framework 11.19.0

例えばこんな感じのテーブルがあったとして

users
bigint id PK ID
string name ユーザー名
string userType ユーザー種別
boolean flag なにかのフラグ

DBがMySQLだとすると、users.flagをLaravelで取得すると0 or 1で返ってきます。

取得した値をもとに条件分岐させたい場合、いちいちbooleanに変換させる処理が必要になって面倒です。

そこでusersのモデルファイル内に以下を記述することで、取得時にbooleanに自動変換してくれます。

/**
 * @return array<string, mixed>
 */
protected function casts(): array
{
    return [
        'flag' => 'boolean',
    ];
}
$users = Users::first();
dd($users->flag); // => true or false

今回のケースではbooleanにしましたが、他にも様々な型に対応しています。詳しくはこちらをご覧ください。

Laravel 11.x Eloquent:ミューテタ/キャスト

ちなみにEnumもいけます。上記の例でuserTypeがEnumだとしたら、こんな感じに書きます。

/**
 * @return array<string, mixed>
 */
protected function casts(): array
{
    return [
        'flag' => 'boolean',
		    'userType' => UserType::class,
    ];
}

 

2.【Laravel】可読性を向上!ルーティングの小技

画面やAPIを作成時、まずルーティングを定義すると思いますが、一度定義したら再確認する機会も少ないため、意外と忘れがちな手法二つをまとめました。

参考: Laravel 11.x ルーティング

①ルートパラメータに制約を付ける

ルートパラメータの妥当性を検証します。

Requestでルートパラメータをバリデーションすることも多いですが、Request内のコード量が増えるケースがあります。

簡単なチェックはルートの定義時に正規表現やヘルパーで可能です。

// routes/web.php

// 【修正前】bidStatusやnoにどんな値も指定可能。
Route::get('/case/show/{bidStatus}/{no}', ['uses' => 'CaseController@show'])->name('show');

// 【修正後】正規表現で値を限定する。
Route::get('/case/show/{bidStatus}/{no}', ['uses' => 'CaseController@show'])
    ->where('bidStatus', '(biddable|winning)') // biddableかwinningのみ
    ->where('no', '[0-9]+') // 数値のみ
    ->name('show');

②ルート名を共通化する

例えば、登録画面を作成時は表示用と登録処理用のルートを定義することが多いと思います。

// routes/web.php
Route::get('/create', ['uses' => 'ExampleController@create'])->name('create'); // 登録画面を表示する。
Route::post('/create', ['uses' => 'ExampleController@postCreate'])->name('post-create'); // 登録処理を実行する。

// *.blade.php
<form action="{{ route('post-create') }}" method="post">
    <!-- 省略 -->
</form>

上記の場合、ルート名にgetやpostを入れたり工夫して名前が長くなったり、スペルミスが起きることもありますが、下記の方法で片方のルート名を不要に出来ます。

// routes/web.php
Route::get('/create', ['uses' => 'ExampleController@create'])->name('create');
Route::post('/create', ['uses' => 'ExampleController@postCreate']);

// *.blade.php
<form action="{{ route('create') }}" method="post">
    <!-- 省略 -->
</form>

/createがGETとPOSTに対応しているため、GET側の名前を使用します。

案件の規約等でGETとPOSTで別名にする必要が無ければ、こういった手も検討ください。

もしくは、上記のように差異がGETとPOSTだけでならmatch()でまとめた方がシンプルに書けるケースもあります。

(この場合、メソッド側でGETとPOSTの処理を分ける工夫が必要になるので使うケースは少ないかもしれません)

// routes/web.php
Route::match(['get', 'post'], '/create', ['uses' => 'ExampleController@create'])->name('create');

 

3.【Laravel】uniqueバリデーション+自分自身を対象外にする

参考:Laravel 10.x バリデーション

例えば、ユーザー情報編集画面にてメールアドレスを変更する際、他のユーザーが登録しているメールアドレスは登録できてほしくないですよね?

そんな時はuniqueバリデーションを設定します。

しかし普通に設定してしまうと、メールアドレスの変更はせずにメールアドレス以外のユーザー情報(名前等)のみを変更した場合、そのメールアドレスは登録されているよ!とエラーになります。自分自身のメールアドレスに引っ掛かってしまっているということですね。

uniqueバリデーションを設定しつつも、自分自身をバリデーションの対象外にすることで万事解決です👍

環境(前提条件): PHP7, Laravel6

使用例:

// uniqueバリデーション
public function rules(): array
{
    return [
	      // unique:テーブル名,カラム名
	      'email' => 'unique:users,email_address',
    ]; 
}    
// 自分自身を対象外としたuniqueバリデーション
public function rules(): array
{
    return [
        // 第2引数として渡すことで、そのカラムの同一性をチェックできる
	      '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' => […],
	];
}

しかし、エラーになりました。

Message:  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' => […],
	];
}

最後までお読みいただきありがとうございました。

 

Backend Team Monthly は今後も更新予定ですので、お楽しみに!

また、ロジカルスタジオでは、エンジニアを募集中です。

ぜひ下のバナーから確認してみて下さい!