Discover the most practical design patterns used in Laravel with real-world examples. From Repository to Strategy patterns - level up your PHP development
Ever wondered why some Laravel code feels elegant and maintainable while others become a tangled mess? The secret lies in design patterns - battle-tested solutions that Laravel itself uses internally.
Let me show you the 12 patterns that'll transform your Laravel development game. π
π― Why Design Patterns Matter
Design patterns give you:
- Cleaner code that's easier to read
- Better testing capabilities
- Shared vocabulary with other developers
- Scalable architecture for growing apps
Laravel already uses many of these patterns internally. Understanding them means working with the framework, not against it.
1οΈβ£ Repository Pattern
Problem: Your controllers are fat, and database logic is everywhere.
Solution: Centralize data access in repositories.
interface UserRepositoryInterface {
public function find($id);
public function create(array $data);
}
class EloquentUserRepository implements UserRepositoryInterface {
public function find($id) {
return User::find($id);
}
}
// Bind in ServiceProvider
$this->app->bind(UserRepositoryInterface::class, EloquentUserRepository::class);
Real use: Swap database for cache or API without touching controllers.
2οΈβ£ Service Container (DI)
Laravel's superpower: Automatic dependency injection.
class OrderController extends Controller {
public function store(OrderService $orderService) {
return $orderService->processOrder($request->all());
}
}
No manual instantiation needed! Laravel resolves it automatically. π©β¨
3οΈβ£ Factory Pattern
Create objects without specifying exact classes.
class NotificationFactory {
public static function create($type) {
return match($type) {
'email' => new EmailNotification(),
'sms' => new SmsNotification(),
'push' => new PushNotification(),
};
}
}
Real use: Multi-channel notifications, payment gateways.
4οΈβ£ Strategy Pattern
Multiple algorithms, one interface.
interface PaymentStrategy {
public function pay($amount);
}
class CreditCardPayment implements PaymentStrategy {
public function pay($amount) { /* ... */ }
}
class PayPalPayment implements PaymentStrategy {
public function pay($amount) { /* ... */ }
}
Real use: Different shipping calculators, pricing strategies.
5οΈβ£ Observer Pattern
Laravel Events = Observer Pattern π―
// Event
class UserRegistered {
public function __construct(public $user) {}
}
// Listener
class SendWelcomeEmail {
public function handle(UserRegistered $event) {
Mail::to($event->user)->send(new WelcomeEmail());
}
}
// Trigger
event(new UserRegistered($user));
Real use: Send emails, update analytics, trigger webhooks - all decoupled.
6οΈβ£ Decorator Pattern
Laravel Middleware = Decorator Pattern π
class LogRequest {
public function handle($request, Closure $next) {
Log::info('Request: ' . $request->path());
return $next($request);
}
}
Add layers of functionality without modifying core logic.
7οΈβ£ Singleton Pattern
One instance to rule them all.
// In ServiceProvider
$this->app->singleton(ConfigManager::class);
Real use: Database connections, cache managers.
8οΈβ£ Adapter Pattern
Make incompatible interfaces work together.
interface PaymentGateway {
public function charge($amount);
}
class StripeAdapter implements PaymentGateway {
public function charge($amount) {
return $this->stripe->charges->create(['amount' => $amount]);
}
}
class PayPalAdapter implements PaymentGateway {
public function charge($amount) {
return $this->paypal->createPayment($amount);
}
}
Real use: Unified interface for multiple payment processors.
9οΈβ£ Builder Pattern
Laravel Query Builder is the perfect example!
$reports = (new ReportBuilder())
->forUser(1)
->between('2024-01-01', '2024-12-31')
->withDetails()
->get();
Real use: Complex queries, API request builders.
π Facade Pattern
Simple interface to complex subsystems.
// Instead of complex instantiation
Mail::send(new WelcomeEmail());
Cache::remember('key', 3600, fn() => expensive());
Laravel Facades simplify everything! πͺ
1οΈβ£1οΈβ£ Chain of Responsibility
Pass requests through a chain of handlers.
class ValidateData {
public function handle($data, Closure $next) {
if (!$this->isValid($data)) {
throw new ValidationException();
}
return $next($data);
}
}
Real use: Middleware pipelines, data processing chains.
1οΈβ£2οΈβ£ Command Pattern
Laravel Jobs = Command Pattern π¦
class ProcessPayment implements ShouldQueue {
public function __construct(protected $orderId) {}
public function handle() {
$order = Order::find($this->orderId);
// Process payment
}
}
ProcessPayment::dispatch($orderId);
Real use: Background jobs, queued tasks.
π¨ Combining Patterns in Real Apps
The magic happens when you combine patterns:
// Repository + Service + Strategy
class OrderService {
public function __construct(
protected OrderRepository $repository,
protected ShippingCalculator $calculator
) {}
public function createOrder($data) {
$shipping = $this->calculator->calculate($data['weight']);
$data['shipping_cost'] = $shipping;
return $this->repository->create($data);
}
}
π‘ Best Practices
- Don't over-engineer - Use patterns when they solve real problems
- Start simple - Refactor to patterns as complexity grows
- Use Laravel's built-ins - Don't reinvent the wheel
- Test everything - Patterns should make testing easier
π― Key Takeaways
- Laravel already uses these patterns internally
- Start with Repository, Service Container, and Observer
- Combine patterns for powerful architectures
- Use patterns to solve problems, not to show off
- Your code will be cleaner, testable, and scalable
π Read the Full Version
Want deeper dives into each pattern with more complex examples?
Read the complete guide on Medium
Follow me for more Laravel tips
Medium
π€ Which pattern do you use most?
Drop a comment below! Let's discuss which patterns have helped you the most in your Laravel projects.
Happy coding! π
Top comments (0)