Laravelアクションの異なる使用方法
Laravelのアクションに関する詳しい情報はこちらとこちらで見ることができます。この記事では、アクションを使う二つの異なるアプローチと、それぞれの良い点と悪い点を示したいと思います。
例として、Order
モデルと関連する OrderLine
モデルがあり、オーダーを作成するAPIエンドポイントを追加する必要がある状況を考えてみましょう。
最初の古典的なアプローチは以下のようになるかもしれません。
- セキュリティチェックを行う
OrderPolicy
クラスの作成メソッド。
final class OrderPolicy
{
use HandlesAuthorization;
public function store(User $user, OrderStoreRequest $request): bool
{
//...
}
}
- ポリシーを呼び出し、入力データのバリデーションルールを含む
OrderStoreRequest
クラス。
final class OrderStoreRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('store', [
Order::class,
$this,
]);
}
public function rules(): array
{
return [
//...
];
}
}
Order
とOrderLine
モデルを単一トランザクションで保存するCreateOrderAction
クラス。
final class CreateOrderAction
{
public function __invoke(CreateOrderData $createOrderData): int
{
return DB::transaction(function () use ($createOrderData) {
//...
});
}
}
- 上記のクラスすべてを組み合わせた
OrderController
のstoreメソッド。
final class OrderController extends Controller
{
public function store(OrderStoreRequest $request, CreateOrderAction $createOrder): Response
{
$orderId = $createOrder(CreateOrderData::fromRequest($request));
return $this->response()->created(content: $orderId);
}
}
このアプローチは従来のLaravelの方法論に従っており、いくつかの利点を提供します。
- Laravelのドキュメントを詳しく参照できます。
- ほとんどのLaravel開発者はこのアプローチに慣れています。
- フレームワークのツールは簡潔で読みやすいコードを促進します。
- アクションはアプリケーションの他の部分で再利用することができます。
しかし、データの変更やトランザクション内での保存だけでなく、セキュリティやバリデーションチェックまでも再利用したい場合、いくつかの課題に直面するかもしれません。
次に、別のアプローチを見てみましょう。それは以下のような形になるかもしれません。
- 必要なセキュリティチェックを含む
CreateOrderSecurityAction
クラス。
final class CreateOrderSecurityAction
{
public function __invoke(int $userId, CreateOrderData $createOrderData): bool
{
//...
}
}
- データバリデーションを担当する
CreateOrderValidationAction
クラス。
final class CreateOrderValidationAction
{
public function __invoke(CreateOrderData $createOrderData): array
{
$errors = [];
//...
return $errors;
}
}
- 上記二つのクラスを結合し、
Order
とOrderLine
モデルを単一トランザクションで保存するCreateOrderAction
クラス。
final readonly class CreateOrderAction
{
public function __construct(
private CreateOrderSecurityAction $createOrderSecurity,
private CreateOrderValidationAction $createOrderValidation,
) {
}
/**
* @throws OrderSecurityException
* @throws OrderValidationException
*/
public function __invoke(int $userId, CreateOrderData $createOrderData): int
{
if (! ($this->createOrderSecurity)($userId, $createOrderData)) {
throw new OrderSecurityException();
}
$validationErrors = ($this->createOrderValidation)($createOrderData);
if (! empty($validationErrors)) {
throw new OrderValidationException($validationErrors);
}
return DB::transaction(function () use ($createOrderData) {
//...
});
}
}
- 簡単に
CreateOrderAction
を呼び出すOrderController
。
final class OrderController extends ApiController
{
public function store(Request $request, CreateOrderAction $createOrder): ?Response
{
try {
$orderId = $createOrder(auth()->id(), $CreateOrderData::fromRequest($request));
} catch (OrderSecurityException) {
$this->response()->errorForbidden();
return null;
} catch (OrderValidationException $exception) {
return response()->json(['errors' => $exception->getErrors()], 400);
}
return $this->response()->created(content: $orderId);
}
}
このアプローチの主な利点は、アクションをビジネス要件(セキュリティ、バリデーション、エンティティの不変条件を強制する)と共に再利用する能力です。しかし、この場合、Laravelフレームワークの全能力を駆使してコードを効率的に書くことはそれほど簡単ではないかもしれません。
どちらのアプローチも、プロジェクトの具体的なニーズに応じて妥当な選択です。もしプロジェクト全体で完全なビジネスアクションを再利用する必要がなければ、古典的なLaravelアプローチがストレートな選択です。しかし、同じ完全なビジネスアクションを呼び出す必要があるより大きく複雑なプロジェクトにおいては、二番目のアプローチの方が適しているかもしれません。
選択にかかわらず、どちらの方法も知っていることは有益で、より情報に基づいた決定を下すことができます。
こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/ayacaste/different-ways-to-use-laravel-actions-138k