Skip to main content
Back to Blog Best Practices

Writing Clean, Maintainable Laravel Code

Abdelrahman Shrief
Abdelrahman Shrief February 04, 2026
5 min read

Single Responsibility Principle

Each class should have one reason to change. Keep your controllers thin:

<?php

namespace App\Http\Controllers;

use App\Actions\CreateOrderAction;
use App\Http\Requests\StoreOrderRequest;
use App\Http\Resources\OrderResource;

class OrderController extends Controller
{
    public function __construct(
        private CreateOrderAction $createOrder,
    ) {}

    public function store(StoreOrderRequest $request): OrderResource
    {
        $order = $this->createOrder->execute(
            user: $request->user(),
            items: $request->validated('items'),
            shippingAddress: $request->validated('shipping_address'),
        );

        return new OrderResource($order);
    }
}

Action Classes

Extract complex logic into dedicated action classes:

<?php

namespace App\Actions;

use App\Models\Order;
use App\Models\User;
use App\Services\PaymentService;
use App\Services\InventoryService;
use App\Notifications\OrderConfirmation;
use Illuminate\Support\Facades\DB;

class CreateOrderAction
{
    public function __construct(
        private PaymentService $paymentService,
        private InventoryService $inventoryService,
    ) {}

    public function execute(User $user, array $items, array $shippingAddress): Order
    {
        return DB::transaction(function () use ($user, $items, $shippingAddress) {
            // Reserve inventory
            $this->inventoryService->reserve($items);

            // Create order
            $order = Order::create([
                'user_id' => $user->id,
                'status' => 'pending',
                'shipping_address' => $shippingAddress,
            ]);

            // Attach items
            foreach ($items as $item) {
                $order->items()->create($item);
            }

            // Calculate totals
            $order->calculateTotals();

            // Send notification
            $user->notify(new OrderConfirmation($order));

            return $order;
        });
    }
}

Repository Pattern

Abstract your data access layer for flexibility:

<?php

namespace App\Repositories;

use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;

interface UserRepositoryInterface
{
    public function find(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function getActive(): LengthAwarePaginator;
    public function create(array $data): User;
    public function update(User $user, array $data): User;
}

class UserRepository implements UserRepositoryInterface
{
    public function find(int $id): ?User
    {
        return User::find($id);
    }

    public function findByEmail(string $email): ?User
    {
        return User::where('email', $email)->first();
    }

    public function getActive(): LengthAwarePaginator
    {
        return User::query()
            ->where('active', true)
            ->orderBy('name')
            ->paginate(15);
    }

    public function create(array $data): User
    {
        return User::create($data);
    }

    public function update(User $user, array $data): User
    {
        $user->update($data);
        return $user->fresh();
    }
}

Service Classes

Handle business logic in dedicated service classes:

<?php

namespace App\Services;

use App\Models\User;
use App\Repositories\UserRepositoryInterface;
use App\Notifications\WelcomeNotification;
use Illuminate\Support\Facades\Hash;

class UserService
{
    public function __construct(
        private UserRepositoryInterface $users,
    ) {}

    public function register(array $data): User
    {
        $user = $this->users->create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        $user->notify(new WelcomeNotification());

        return $user;
    }
}

Conclusion

Clean code is maintainable code. Follow SOLID principles, extract logic into dedicated classes, and always think about the next developer who will read your code.

Abdelrahman Shrief
Abdelrahman Shrief Senior Backend Developer

Share this post

Need Help With Your Project?

Let's discuss how I can help bring your ideas to life with expert backend development.

Get in Touch