View (Blade) 内に複雑な条件分岐を書いていませんか?それ、ViewModelを使うと、View (Blade) 内が簡潔になります。
ViewModelの実装例
まずは ViewModel の実装例を示します。
namespace App\ViewModels\UserRegistration;
use App\Enums\ProductTypeEnum;
/**
* ユーザー登録フォーム用 ViewModel
*/
final readonly class UserRegistrationFormModel
{
/**
* @param ProductTypeEnum $productType 製品種類
* @param ?string $serialNumber 製品シリアル番号
* @param string $productName 製品名
*/
public function __construct(
public ProductTypeEnum $productType,
public ?string $serialNumber,
public string $productName,
) {
}
/**
* 写真表示部分の CSS クラス名を得る
*
* @return string [] 製品種類に応じた CSS クラス名
*/
public function getProductPhotoCssClasses(): array
{
$classes = [
'photo-frame',
'product',
];
$classes[] = match ($this->productType) {
ProductTypeEnum::STATIONERY => 'stationery',
ProductTypeEnum::KITCHEN => 'kitchen',
ProductTypeEnum::EXTERIOR => 'exterior',
};
return $classes;
}
}
ControllerからViewに値を渡す
そしてControllerからViewに対して多くの変数を渡すよりも、ViewModelインスタンスひとつにまとめるのです。その実装例がこちらです。
class UserRegistrationFormController extends Controller
{
public function __invoke(
UserRegistraionRequest $requestData,
GetProductByProductTypeService $getProductByProductType,
) {
$product = $getProductByProductType->execute(
productType: $requestData->productType,
);
return view('components.user-registration.form', [
'viewModel' => new UserRegistrationFormModel( // 👈 ここです
productType: $requestData->productType,
serialNumber: $requestData->serialNumber,
productName: $product->name,
),
]);
}
}
この ViewModel は Blade テンプレートから Data Transfer Object のように扱えます。
@php
/** @var App\ViewModels\UserRegistration\UserRegistrationFormModel $viewModel */ // 👈 このphpdocをお忘れなく
@endphp
<form method="post">
@csrf
<div class="form-inner">
<div class="row">
// 👇 CSSクラス名はViewModelで計算していますので、Bladeテンプレート内での条件分岐がなくなり簡潔になります 👇
<div class="{!! implode(' ', $viewModel->getProductPhotoCssClasses()) !!}">
<img src="...">
</div>
</div>
@if(null !== $viewModel->serialNumber)
<div class="row">
{{ $viewModel->serialNumber }} // 👈 フィールドが定義されているので typo が起きにくい
</div>
@endif
<div class="row">
{{ $viewModel->productName }} // 👈 フィールドが定義されているので typo が起きにくい
</div>
<div class="row">
<input type="text" name="name">
</div>
<div class="row">
<input type="email" name="email" >
</div>
...
</div>
</form>
- CSSクラス名はViewModelで計算していますので、Bladeテンプレート内での条件分岐がなくなり簡潔になります。
- 各フィールドを参照するとき、定義済みのフィールドなので圧倒的に typo が起きにくいです。