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.