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)