Patterns Solve Common Problems
Don't over-engineer. Use patterns when they fit.
Repository Pattern
Abstract data access:
interface UserRepositoryInterface
{
public function find(int $id): ?User;
public function findByEmail(string $email): ?User;
public function save(User $user): void;
}
class EloquentUserRepository implements UserRepositoryInterface
{
public function find(int $id): ?User
{
return User::find($id);
}
}
// In controller
public function __construct(
private UserRepositoryInterface $users
) {}
Use when: You might change data sources, want testability.
Strategy Pattern
Swappable algorithms:
interface PaymentGateway
{
public function charge(int $amount): bool;
}
class StripeGateway implements PaymentGateway { }
class PayPalGateway implements PaymentGateway { }
class PaymentProcessor
{
public function __construct(
private PaymentGateway $gateway
) {}
public function process(Order $order): void
{
$this->gateway->charge($order->total);
}
}
Use when: Multiple ways to do something, runtime switching.
Factory Pattern
Object creation logic:
class NotificationFactory
{
public static function create(string $type): Notification
{
return match($type) {
'email' => new EmailNotification(),
'sms' => new SmsNotification(),
'push' => new PushNotification(),
default => throw new InvalidArgumentException(),
};
}
}
Use when: Complex object creation, many similar objects.
Observer Pattern
React to events:
// Laravel events are observers
class OrderPlaced { }
class SendConfirmation
{
public function handle(OrderPlaced $event) { }
}
class UpdateInventory
{
public function handle(OrderPlaced $event) { }
}
Use when: Multiple reactions to one action, decoupling.
Don't Overuse
Patterns add complexity. Use them when:
- The problem is clear
- Simpler solutions don't work
- The team understands the pattern
