Refactoring a Laravel Controller into an Action Class is a great way to make your code cleaner, more maintainable, and testable. This approach follows the Single Responsibility Principle — where each action does one thing.
Why Use Action Classes?
- Keeps controllers clean and focused.
- Separates business logic from HTTP layer.
- Easier to test and reuse logic.
- Improves code readability and organization.
Folder Structure Example
app/ ├── Actions/ │ └── Product/ │ └── CreateProduct.php ├── Http/ │ └── Controllers/ │ └── ProductController.php
Step-by-Step Example
Scenario:
We want to move the logic of creating a product from the controller into an action class.
Before (Fat Controller)
// app/Http/Controllers/ProductController.php use App\Models\Product; use Illuminate\Http\Request; class ProductController extends Controller { public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string', 'price' => 'required|numeric', ]); $product = Product::create($validated); return response()->json([ 'message' => 'Product created successfully!', 'product' => $product ], 201); } }
After (Using Action Class)
Create the Action Class
// app/Actions/Product/CreateProduct.php namespace App\Actions\Product; use App\Models\Product; class CreateProduct { public function handle(array $data): Product { return Product::create($data); } }
Refactor the Controller
// app/Http/Controllers/ProductController.php use App\Actions\Product\CreateProduct; use Illuminate\Http\Request; class ProductController extends Controller { public function store(Request $request, CreateProduct $createProduct) { $validated = $request->validate([ 'name' => 'required|string', 'price' => 'required|numeric', ]); $product = $createProduct->handle($validated); return response()->json([ 'message' => 'Product created successfully!', 'product' => $product ], 201); } }
Optional: Add Form Request for Validation
Create a form request to move validation logic out too.
php artisan make:request StoreProductRequest
Update it:
// app/Http/Requests/StoreProductRequest.php public function rules(): array { return [ 'name' => 'required|string', 'price' => 'required|numeric', ]; }
Then your controller becomes super clean:
public function store(StoreProductRequest $request, CreateProduct $createProduct) { $product = $createProduct->handle($request->validated()); return response()->json([ 'message' => 'Product created!', 'product' => $product ], 201); }
Benefits Recap
✅ Controller: only handles HTTP layer
✅ Action: handles business logic
✅ Request: handles validation
This separation of concerns leads to cleaner, testable, and maintainable Laravel apps.
Top comments (0)