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)