DEV Community

Cover image for Refactor Controller into Action Class: Cleaner Laravel Code
Sontus Chandra Anik
Sontus Chandra Anik

Posted on

Refactor Controller into Action Class: Cleaner Laravel Code

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 
Enter fullscreen mode Exit fullscreen mode

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); } } 
Enter fullscreen mode Exit fullscreen mode

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); } } 
Enter fullscreen mode Exit fullscreen mode

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); } } 
Enter fullscreen mode Exit fullscreen mode

Optional: Add Form Request for Validation

Create a form request to move validation logic out too.

php artisan make:request StoreProductRequest 
Enter fullscreen mode Exit fullscreen mode

Update it:

// app/Http/Requests/StoreProductRequest.php public function rules(): array { return [ 'name' => 'required|string', 'price' => 'required|numeric', ]; } 
Enter fullscreen mode Exit fullscreen mode

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); } 
Enter fullscreen mode Exit fullscreen mode

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)