PHP Design Patterns
Introduction to Design Patterns
Design patterns are reusable solutions to commonly occurring problems in software design. They represent best practices and proven architectures that help developers create more maintainable, flexible, and scalable applications. In PHP, design patterns provide structured approaches to object-oriented programming challenges.
Understanding design patterns is essential for writing professional PHP code that follows established conventions, promotes code reuse, and facilitates team collaboration.
Why Design Patterns Matter
Proven Solutions: Design patterns represent time-tested solutions to recurring design problems that have been refined through extensive use.
Communication: They provide a common vocabulary for developers, making code discussions and documentation more effective.
Code Quality: Patterns promote best practices like loose coupling, high cohesion, and separation of concerns.
Maintainability: Well-implemented patterns make code easier to understand, modify, and extend.
Framework Understanding: Many PHP frameworks use design patterns extensively, making pattern knowledge essential for framework mastery.
Pattern Categories
Creational Patterns: Deal with object creation mechanisms (Singleton, Factory, Builder).
Structural Patterns: Deal with object composition and relationships (Adapter, Decorator, Facade).
Behavioral Patterns: Deal with communication between objects and responsibility assignment (Observer, Strategy, Command).
Creational Patterns
Singleton Pattern
<?php
/**
* Singleton Pattern Implementation
*
* Ensures a class has only one instance and provides
* global access to that instance.
*/
/**
* Basic Singleton implementation
*/
class DatabaseConnection
{
private static ?DatabaseConnection $instance = null;
private PDO $connection;
private string $host;
private string $database;
/**
* Private constructor prevents direct instantiation
*/
private function __construct()
{
$this->host = $_ENV['DB_HOST'] ?? 'localhost';
$this->database = $_ENV['DB_NAME'] ?? 'test';
$username = $_ENV['DB_USER'] ?? 'root';
$password = $_ENV['DB_PASS'] ?? '';
try {
$dsn = "mysql:host={$this->host};dbname={$this->database};charset=utf8mb4";
$this->connection = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]);
} catch (PDOException $e) {
throw new RuntimeException('Database connection failed: ' . $e->getMessage());
}
}
/**
* Get the singleton instance
*/
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Get the PDO connection
*/
public function getConnection(): PDO
{
return $this->connection;
}
/**
* Prevent cloning
*/
private function __clone() {}
/**
* Prevent unserialization
*/
public function __wakeup()
{
throw new Exception("Cannot unserialize singleton");
}
}
/**
* Configuration Manager Singleton
*/
class ConfigManager
{
private static ?ConfigManager $instance = null;
private array $config = [];
private function __construct()
{
$this->loadConfiguration();
}
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function get(string $key, $default = null)
{
return $this->config[$key] ?? $default;
}
public function set(string $key, $value): void
{
$this->config[$key] = $value;
}
public function has(string $key): bool
{
return array_key_exists($key, $this->config);
}
private function loadConfiguration(): void
{
// Load from environment, files, etc.
$this->config = [
'app_name' => $_ENV['APP_NAME'] ?? 'My App',
'debug' => (bool)($_ENV['DEBUG'] ?? false),
'timezone' => $_ENV['TIMEZONE'] ?? 'UTC',
'cache_enabled' => true
];
}
private function __clone() {}
public function __wakeup() { throw new Exception("Cannot unserialize singleton"); }
}
// Usage examples
echo "=== Singleton Pattern Examples ===\n";
// Database connection singleton
try {
$db1 = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance();
echo "Same database instance: " . ($db1 === $db2 ? 'Yes' : 'No') . "\n";
} catch (RuntimeException $e) {
echo "Database connection error: " . $e->getMessage() . "\n";
}
// Configuration manager singleton
$config1 = ConfigManager::getInstance();
$config2 = ConfigManager::getInstance();
echo "Same config instance: " . ($config1 === $config2 ? 'Yes' : 'No') . "\n";
echo "App name: " . $config1->get('app_name') . "\n";
$config1->set('custom_setting', 'value');
echo "Custom setting from second instance: " . $config2->get('custom_setting') . "\n";
?>
Factory Pattern
<?php
/**
* Factory Pattern Implementation
*
* Creates objects without specifying their exact classes,
* providing flexibility in object creation.
*/
/**
* Product interface
*/
interface PaymentProcessorInterface
{
public function processPayment(float $amount, array $details): array;
public function refund(string $transactionId, float $amount): array;
public function getFeesTotalBalance(): float;
}
/**
* Concrete payment processors
*/
class StripePaymentProcessor implements PaymentProcessorInterface
{
private string $apiKey;
public function __construct(string $apiKey)
{
$this->apiKey = $apiKey;
}
public function processPayment(float $amount, array $details): array
{
// Simulate Stripe API call
return [
'success' => true,
'transaction_id' => 'stripe_' . uniqid(),
'amount' => $amount,
'processor' => 'Stripe',
'details' => $details
];
}
public function refund(string $transactionId, float $amount): array
{
return [
'success' => true,
'refund_id' => 'stripe_refund_' . uniqid(),
'original_transaction' => $transactionId,
'amount' => $amount
];
}
public function getFeesTotalBalance(): float
{
return $amount * 0.029; // 2.9% + $0.30
}
}
class PayPalPaymentProcessor implements PaymentProcessorInterface
{
private string $clientId;
private string $clientSecret;
public function __construct(string $clientId, string $clientSecret)
{
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
}
public function processPayment(float $amount, array $details): array
{
// Simulate PayPal API call
return [
'success' => true,
'transaction_id' => 'paypal_' . uniqid(),
'amount' => $amount,
'processor' => 'PayPal',
'details' => $details
];
}
public function refund(string $transactionId, float $amount): array
{
return [
'success' => true,
'refund_id' => 'paypal_refund_' . uniqid(),
'original_transaction' => $transactionId,
'amount' => $amount
];
}
public function getFeesTotalBalance(): float
{
return $amount * 0.034; // 3.4% + $0.30
}
}
class SquarePaymentProcessor implements PaymentProcessorInterface
{
private string $accessToken;
public function __construct(string $accessToken)
{
$this->accessToken = $accessToken;
}
public function processPayment(float $amount, array $details): array
{
return [
'success' => true,
'transaction_id' => 'square_' . uniqid(),
'amount' => $amount,
'processor' => 'Square',
'details' => $details
];
}
public function refund(string $transactionId, float $amount): array
{
return [
'success' => true,
'refund_id' => 'square_refund_' . uniqid(),
'original_transaction' => $transactionId,
'amount' => $amount
];
}
public function getFeesTotalBalance(): float
{
return $amount * 0.026; // 2.6% + $0.10
}
}
/**
* Simple Factory
*/
class PaymentProcessorFactory
{
public static function create(string $type, array $config): PaymentProcessorInterface
{
return match(strtolower($type)) {
'stripe' => new StripePaymentProcessor($config['api_key']),
'paypal' => new PayPalPaymentProcessor($config['client_id'], $config['client_secret']),
'square' => new SquarePaymentProcessor($config['access_token']),
default => throw new InvalidArgumentException("Unknown payment processor: $type")
};
}
}
/**
* Abstract Factory Pattern
*/
abstract class NotificationFactory
{
abstract public function createEmailNotification(): EmailNotificationInterface;
abstract public function createSmsNotification(): SmsNotificationInterface;
abstract public function createPushNotification(): PushNotificationInterface;
}
interface EmailNotificationInterface
{
public function send(string $to, string $subject, string $body): bool;
}
interface SmsNotificationInterface
{
public function send(string $phone, string $message): bool;
}
interface PushNotificationInterface
{
public function send(string $deviceToken, string $title, string $body): bool;
}
/**
* Production notification factory
*/
class ProductionNotificationFactory extends NotificationFactory
{
public function createEmailNotification(): EmailNotificationInterface
{
return new SmtpEmailNotification();
}
public function createSmsNotification(): SmsNotificationInterface
{
return new TwilioSmsNotification();
}
public function createPushNotification(): PushNotificationInterface
{
return new FirebasePushNotification();
}
}
/**
* Testing notification factory
*/
class TestingNotificationFactory extends NotificationFactory
{
public function createEmailNotification(): EmailNotificationInterface
{
return new MockEmailNotification();
}
public function createSmsNotification(): SmsNotificationInterface
{
return new MockSmsNotification();
}
public function createPushNotification(): PushNotificationInterface
{
return new MockPushNotification();
}
}
// Mock implementations for testing
class SmtpEmailNotification implements EmailNotificationInterface
{
public function send(string $to, string $subject, string $body): bool
{
echo "Sending email via SMTP to $to: $subject\n";
return true;
}
}
class TwilioSmsNotification implements SmsNotificationInterface
{
public function send(string $phone, string $message): bool
{
echo "Sending SMS via Twilio to $phone: $message\n";
return true;
}
}
class FirebasePushNotification implements PushNotificationInterface
{
public function send(string $deviceToken, string $title, string $body): bool
{
echo "Sending push notification via Firebase: $title\n";
return true;
}
}
class MockEmailNotification implements EmailNotificationInterface
{
public function send(string $to, string $subject, string $body): bool
{
echo "MOCK: Email to $to: $subject\n";
return true;
}
}
class MockSmsNotification implements SmsNotificationInterface
{
public function send(string $phone, string $message): bool
{
echo "MOCK: SMS to $phone: $message\n";
return true;
}
}
class MockPushNotification implements PushNotificationInterface
{
public function send(string $deviceToken, string $title, string $body): bool
{
echo "MOCK: Push notification: $title\n";
return true;
}
}
// Usage examples
echo "\n=== Factory Pattern Examples ===\n";
// Simple factory usage
$stripeProcessor = PaymentProcessorFactory::create('stripe', ['api_key' => 'sk_test_123']);
$paypalProcessor = PaymentProcessorFactory::create('paypal', [
'client_id' => 'paypal_client_123',
'client_secret' => 'paypal_secret_456'
]);
$stripeResult = $stripeProcessor->processPayment(100.00, ['card_token' => 'tok_123']);
echo "Stripe payment: " . json_encode($stripeResult) . "\n";
$paypalResult = $paypalProcessor->processPayment(150.00, ['email' => '[email protected]']);
echo "PayPal payment: " . json_encode($paypalResult) . "\n";
// Abstract factory usage
$environment = 'testing'; // or 'production'
$notificationFactory = $environment === 'production'
? new ProductionNotificationFactory()
: new TestingNotificationFactory();
$emailNotification = $notificationFactory->createEmailNotification();
$smsNotification = $notificationFactory->createSmsNotification();
$pushNotification = $notificationFactory->createPushNotification();
$emailNotification->send('[email protected]', 'Welcome', 'Welcome to our service!');
$smsNotification->send('+1234567890', 'Your verification code is 123456');
$pushNotification->send('device_token_123', 'New Message', 'You have a new message');
?>
Behavioral Patterns
Observer Pattern
<?php
/**
* Observer Pattern Implementation
*
* Defines a one-to-many dependency between objects
* so that when one object changes state, all its
* dependents are notified automatically.
*/
/**
* Subject interface
*/
interface SubjectInterface
{
public function attach(ObserverInterface $observer): void;
public function detach(ObserverInterface $observer): void;
public function notify(): void;
}
/**
* Observer interface
*/
interface ObserverInterface
{
public function update(SubjectInterface $subject): void;
}
/**
* Concrete subject - User model
*/
class User implements SubjectInterface
{
private array $observers = [];
private string $name;
private string $email;
private string $status;
private array $data = [];
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
$this->status = 'active';
}
public function attach(ObserverInterface $observer): void
{
$observerClass = get_class($observer);
$this->observers[$observerClass] = $observer;
}
public function detach(ObserverInterface $observer): void
{
$observerClass = get_class($observer);
unset($this->observers[$observerClass]);
}
public function notify(): void
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function setEmail(string $email): void
{
$oldEmail = $this->email;
$this->email = $email;
$this->data['old_email'] = $oldEmail;
$this->data['new_email'] = $email;
$this->notify();
}
public function setStatus(string $status): void
{
$oldStatus = $this->status;
$this->status = $status;
$this->data['old_status'] = $oldStatus;
$this->data['new_status'] = $status;
$this->notify();
}
// Getters
public function getName(): string { return $this->name; }
public function getEmail(): string { return $this->email; }
public function getStatus(): string { return $this->status; }
public function getData(): array { return $this->data; }
}
/**
* Email notification observer
*/
class EmailNotificationObserver implements ObserverInterface
{
public function update(SubjectInterface $subject): void
{
if ($subject instanceof User) {
$data = $subject->getData();
if (isset($data['new_email'])) {
$this->sendEmailChangeNotification($subject);
}
if (isset($data['new_status']) && $data['new_status'] === 'suspended') {
$this->sendAccountSuspendedNotification($subject);
}
}
}
private function sendEmailChangeNotification(User $user): void
{
echo "Email Notification: User {$user->getName()}'s email changed to {$user->getEmail()}\n";
}
private function sendAccountSuspendedNotification(User $user): void
{
echo "Email Notification: User {$user->getName()}'s account has been suspended\n";
}
}
/**
* Audit log observer
*/
class AuditLogObserver implements ObserverInterface
{
private array $logs = [];
public function update(SubjectInterface $subject): void
{
if ($subject instanceof User) {
$data = $subject->getData();
$timestamp = date('Y-m-d H:i:s');
if (isset($data['old_email'], $data['new_email'])) {
$this->logs[] = [
'timestamp' => $timestamp,
'user' => $user->getName(),
'action' => 'email_changed',
'old_value' => $data['old_email'],
'new_value' => $data['new_email']
];
}
if (isset($data['old_status'], $data['new_status'])) {
$this->logs[] = [
'timestamp' => $timestamp,
'user' => $subject->getName(),
'action' => 'status_changed',
'old_value' => $data['old_status'],
'new_value' => $data['new_status']
];
}
echo "Audit Log: Recorded change for user {$subject->getName()}\n";
}
}
public function getLogs(): array
{
return $this->logs;
}
}
/**
* Cache invalidation observer
*/
class CacheInvalidationObserver implements ObserverInterface
{
public function update(SubjectInterface $subject): void
{
if ($subject instanceof User) {
$data = $subject->getData();
if (isset($data['new_email']) || isset($data['new_status'])) {
$this->invalidateUserCache($subject);
}
}
}
private function invalidateUserCache(User $user): void
{
echo "Cache: Invalidated cache for user {$user->getName()}\n";
}
}
/**
* Event system using observer pattern
*/
class EventDispatcher
{
private array $listeners = [];
public function addListener(string $eventName, callable $listener): void
{
$this->listeners[$eventName][] = $listener;
}
public function dispatch(string $eventName, array $data = []): void
{
if (isset($this->listeners[$eventName])) {
foreach ($this->listeners[$eventName] as $listener) {
call_user_func($listener, $eventName, $data);
}
}
}
}
class OrderService
{
private EventDispatcher $eventDispatcher;
public function __construct(EventDispatcher $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
public function processOrder(array $orderData): void
{
// Process the order
echo "Processing order #{$orderData['id']}\n";
// Dispatch events
$this->eventDispatcher->dispatch('order.created', $orderData);
$this->eventDispatcher->dispatch('order.processed', $orderData);
}
}
// Usage examples
echo "\n=== Observer Pattern Examples ===\n";
// User observer example
$user = new User('John Doe', '[email protected]');
// Attach observers
$user->attach(new EmailNotificationObserver());
$user->attach(new AuditLogObserver());
$user->attach(new CacheInvalidationObserver());
// Make changes - observers will be notified
echo "Changing user email:\n";
$user->setEmail('[email protected]');
echo "\nChanging user status:\n";
$user->setStatus('suspended');
// Event dispatcher example
echo "\n=== Event Dispatcher Example ===\n";
$eventDispatcher = new EventDispatcher();
// Add event listeners
$eventDispatcher->addListener('order.created', function($eventName, $data) {
echo "Email: Order #{$data['id']} confirmation sent to {$data['customer_email']}\n";
});
$eventDispatcher->addListener('order.created', function($eventName, $data) {
echo "Inventory: Stock reduced for order #{$data['id']}\n";
});
$eventDispatcher->addListener('order.processed', function($eventName, $data) {
echo "Shipping: Order #{$data['id']} queued for shipping\n";
});
$orderService = new OrderService($eventDispatcher);
$orderService->processOrder([
'id' => 12345,
'customer_email' => '[email protected]',
'total' => 99.99
]);
?>
Strategy Pattern
<?php
/**
* Strategy Pattern Implementation
*
* Defines a family of algorithms, encapsulates each one,
* and makes them interchangeable at runtime.
*/
/**
* Strategy interface for shipping calculation
*/
interface ShippingStrategyInterface
{
public function calculateCost(float $weight, float $distance, array $options = []): float;
public function getEstimatedDelivery(): int; // days
public function getName(): string;
}
/**
* Concrete shipping strategies
*/
class StandardShippingStrategy implements ShippingStrategyInterface
{
public function calculateCost(float $weight, float $distance, array $options = []): float
{
$baseCost = 5.00;
$weightCost = $weight * 0.50;
$distanceCost = $distance * 0.10;
return $baseCost + $weightCost + $distanceCost;
}
public function getEstimatedDelivery(): int
{
return 5; // 5 business days
}
public function getName(): string
{
return 'Standard Shipping';
}
}
class ExpressShippingStrategy implements ShippingStrategyInterface
{
public function calculateCost(float $weight, float $distance, array $options = []): float
{
$baseCost = 15.00;
$weightCost = $weight * 1.00;
$distanceCost = $distance * 0.25;
return $baseCost + $weightCost + $distanceCost;
}
public function getEstimatedDelivery(): int
{
return 2; // 2 business days
}
public function getName(): string
{
return 'Express Shipping';
}
}
class OvernightShippingStrategy implements ShippingStrategyInterface
{
public function calculateCost(float $weight, float $distance, array $options = []): float
{
$baseCost = 30.00;
$weightCost = $weight * 2.00;
$distanceCost = $distance * 0.50;
// Additional cost for special handling
$extraCost = $options['fragile'] ?? false ? 10.00 : 0.00;
return $baseCost + $weightCost + $distanceCost + $extraCost;
}
public function getEstimatedDelivery(): int
{
return 1; // Next business day
}
public function getName(): string
{
return 'Overnight Shipping';
}
}
/**
* Context class that uses shipping strategies
*/
class ShippingCalculator
{
private ShippingStrategyInterface $strategy;
public function __construct(ShippingStrategyInterface $strategy)
{
$this->strategy = $strategy;
}
public function setStrategy(ShippingStrategyInterface $strategy): void
{
$this->strategy = $strategy;
}
public function calculateShipping(float $weight, float $distance, array $options = []): array
{
$cost = $this->strategy->calculateCost($weight, $distance, $options);
$delivery = $this->strategy->getEstimatedDelivery();
$name = $this->strategy->getName();
return [
'method' => $name,
'cost' => $cost,
'delivery_days' => $delivery,
'estimated_delivery' => date('Y-m-d', strtotime("+{$delivery} business days"))
];
}
}
/**
* Discount strategy interface
*/
interface DiscountStrategyInterface
{
public function calculateDiscount(float $amount, array $context = []): float;
public function getDescription(): string;
}
/**
* Concrete discount strategies
*/
class PercentageDiscountStrategy implements DiscountStrategyInterface
{
private float $percentage;
public function __construct(float $percentage)
{
$this->percentage = $percentage;
}
public function calculateDiscount(float $amount, array $context = []): float
{
return $amount * ($this->percentage / 100);
}
public function getDescription(): string
{
return "{$this->percentage}% discount";
}
}
class FixedAmountDiscountStrategy implements DiscountStrategyInterface
{
private float $discountAmount;
public function __construct(float $discountAmount)
{
$this->discountAmount = $discountAmount;
}
public function calculateDiscount(float $amount, array $context = []): float
{
return min($this->discountAmount, $amount);
}
public function getDescription(): string
{
return "\${$this->discountAmount} off";
}
}
class BuyOneGetOneDiscountStrategy implements DiscountStrategyInterface
{
public function calculateDiscount(float $amount, array $context = []): float
{
$quantity = $context['quantity'] ?? 1;
$itemPrice = $context['item_price'] ?? ($amount / $quantity);
$freeItems = intval($quantity / 2);
return $freeItems * $itemPrice;
}
public function getDescription(): string
{
return 'Buy One Get One Free';
}
}
/**
* Order processing with strategy pattern
*/
class Order
{
private array $items = [];
private DiscountStrategyInterface $discountStrategy;
private ShippingStrategyInterface $shippingStrategy;
public function addItem(string $name, float $price, int $quantity = 1): void
{
$this->items[] = [
'name' => $name,
'price' => $price,
'quantity' => $quantity,
'total' => $price * $quantity
];
}
public function setDiscountStrategy(DiscountStrategyInterface $strategy): void
{
$this->discountStrategy = $strategy;
}
public function setShippingStrategy(ShippingStrategyInterface $strategy): void
{
$this->shippingStrategy = $strategy;
}
public function calculateTotal(): array
{
$subtotal = array_sum(array_column($this->items, 'total'));
$discount = 0;
if (isset($this->discountStrategy)) {
$discount = $this->discountStrategy->calculateDiscount($subtotal, [
'quantity' => array_sum(array_column($this->items, 'quantity')),
'item_price' => $this->items[0]['price'] ?? 0
]);
}
$afterDiscount = $subtotal - $discount;
$shipping = 0;
$shippingInfo = [];
if (isset($this->shippingStrategy)) {
$calculator = new ShippingCalculator($this->shippingStrategy);
$shippingInfo = $calculator->calculateShipping(2.5, 100); // 2.5kg, 100 miles
$shipping = $shippingInfo['cost'];
}
return [
'items' => $this->items,
'subtotal' => $subtotal,
'discount' => [
'amount' => $discount,
'description' => $this->discountStrategy->getDescription() ?? 'No discount'
],
'shipping' => $shippingInfo,
'total' => $afterDiscount + $shipping
];
}
}
// Usage examples
echo "\n=== Strategy Pattern Examples ===\n";
// Shipping strategy examples
$shippingCalculator = new ShippingCalculator(new StandardShippingStrategy());
$standardShipping = $shippingCalculator->calculateShipping(2.5, 100);
echo "Standard shipping: " . json_encode($standardShipping) . "\n";
$shippingCalculator->setStrategy(new ExpressShippingStrategy());
$expressShipping = $shippingCalculator->calculateShipping(2.5, 100);
echo "Express shipping: " . json_encode($expressShipping) . "\n";
$shippingCalculator->setStrategy(new OvernightShippingStrategy());
$overnightShipping = $shippingCalculator->calculateShipping(2.5, 100, ['fragile' => true]);
echo "Overnight shipping: " . json_encode($overnightShipping) . "\n";
// Order with different strategies
echo "\n=== Order Processing with Strategies ===\n";
$order = new Order();
$order->addItem('Laptop', 999.99, 1);
$order->addItem('Mouse', 29.99, 2);
// Apply different discount strategies
echo "No discount:\n";
$total = $order->calculateTotal();
echo "Total: \$" . number_format($total['total'], 2) . "\n";
echo "\n10% discount:\n";
$order->setDiscountStrategy(new PercentageDiscountStrategy(10));
$order->setShippingStrategy(new StandardShippingStrategy());
$total = $order->calculateTotal();
print_r($total);
echo "\n\$50 fixed discount:\n";
$order->setDiscountStrategy(new FixedAmountDiscountStrategy(50));
$total = $order->calculateTotal();
echo "Total: \$" . number_format($total['total'], 2) . "\n";
echo "\nBuy one get one free (on mouse):\n";
$order->setDiscountStrategy(new BuyOneGetOneDiscountStrategy());
$total = $order->calculateTotal();
echo "Total: \$" . number_format($total['total'], 2) . "\n";
?>
Structural Patterns
Decorator Pattern
<?php
/**
* Decorator Pattern Implementation
*
* Allows behavior to be added to objects dynamically
* without altering their structure.
*/
/**
* Base component interface
*/
interface NotificationInterface
{
public function send(string $message): string;
public function getCost(): float;
}
/**
* Concrete base notification component
*/
class BasicNotification implements NotificationInterface
{
public function send(string $message): string
{
return "Basic notification: $message";
}
public function getCost(): float
{
return 0.00;
}
}
/**
* Base decorator
*/
abstract class NotificationDecorator implements NotificationInterface
{
protected NotificationInterface $notification;
public function __construct(NotificationInterface $notification)
{
$this->notification = $notification;
}
public function send(string $message): string
{
return $this->notification->send($message);
}
public function getCost(): float
{
return $this->notification->getCost();
}
}
/**
* Concrete decorators
*/
class EmailDecorator extends NotificationDecorator
{
public function send(string $message): string
{
$base = $this->notification->send($message);
return $base . " + Email notification sent";
}
public function getCost(): float
{
return $this->notification->getCost() + 0.05;
}
}
class SmsDecorator extends NotificationDecorator
{
public function send(string $message): string
{
$base = $this->notification->send($message);
return $base . " + SMS notification sent";
}
public function getCost(): float
{
return $this->notification->getCost() + 0.25;
}
}
class PushNotificationDecorator extends NotificationDecorator
{
public function send(string $message): string
{
$base = $this->notification->send($message);
return $base . " + Push notification sent";
}
public function getCost(): float
{
return $this->notification->getCost() + 0.02;
}
}
class SlackDecorator extends NotificationDecorator
{
private string $channel;
public function __construct(NotificationInterface $notification, string $channel = '#general')
{
parent::__construct($notification);
$this->channel = $channel;
}
public function send(string $message): string
{
$base = $this->notification->send($message);
return $base . " + Slack notification sent to {$this->channel}";
}
public function getCost(): float
{
return $this->notification->getCost() + 0.01;
}
}
/**
* Coffee shop example with decorators
*/
interface CoffeeInterface
{
public function getDescription(): string;
public function getCost(): float;
}
class BasicCoffee implements CoffeeInterface
{
public function getDescription(): string
{
return "Basic coffee";
}
public function getCost(): float
{
return 2.00;
}
}
abstract class CoffeeDecorator implements CoffeeInterface
{
protected CoffeeInterface $coffee;
public function __construct(CoffeeInterface $coffee)
{
$this->coffee = $coffee;
}
public function getDescription(): string
{
return $this->coffee->getDescription();
}
public function getCost(): float
{
return $this->coffee->getCost();
}
}
class MilkDecorator extends CoffeeDecorator
{
public function getDescription(): string
{
return $this->coffee->getDescription() . ", milk";
}
public function getCost(): float
{
return $this->coffee->getCost() + 0.50;
}
}
class SugarDecorator extends CoffeeDecorator
{
public function getDescription(): string
{
return $this->coffee->getDescription() . ", sugar";
}
public function getCost(): float
{
return $this->coffee->getCost() + 0.25;
}
}
class WhipCreamDecorator extends CoffeeDecorator
{
public function getDescription(): string
{
return $this->coffee->getDescription() . ", whip cream";
}
public function getCost(): float
{
return $this->coffee->getCost() + 0.75;
}
}
class VanillaSyrupDecorator extends CoffeeDecorator
{
public function getDescription(): string
{
return $this->coffee->getDescription() . ", vanilla syrup";
}
public function getCost(): float
{
return $this->coffee->getCost() + 0.60;
}
}
// Usage examples
echo "\n=== Decorator Pattern Examples ===\n";
// Notification decorators
echo "=== Notification Examples ===\n";
$notification = new BasicNotification();
echo "Basic: " . $notification->send("Test message") . " (Cost: $" . $notification->getCost() . ")\n";
$emailNotification = new EmailDecorator($notification);
echo "With Email: " . $emailNotification->send("Test message") . " (Cost: $" . $emailNotification->getCost() . ")\n";
$smsNotification = new SmsDecorator($emailNotification);
echo "With SMS: " . $smsNotification->send("Test message") . " (Cost: $" . $smsNotification->getCost() . ")\n";
$fullNotification = new SlackDecorator(
new PushNotificationDecorator($smsNotification),
'#alerts'
);
echo "Full stack: " . $fullNotification->send("Test message") . " (Cost: $" . $fullNotification->getCost() . ")\n";
// Coffee shop examples
echo "\n=== Coffee Shop Examples ===\n";
$coffee = new BasicCoffee();
echo $coffee->getDescription() . " - $" . $coffee->getCost() . "\n";
$milkCoffee = new MilkDecorator($coffee);
echo $milkCoffee->getDescription() . " - $" . $milkCoffee->getCost() . "\n";
$sweetMilkCoffee = new SugarDecorator($milkCoffee);
echo $sweetMilkCoffee->getDescription() . " - $" . $sweetMilkCoffee->getCost() . "\n";
$deluxeCoffee = new VanillaSyrupDecorator(
new WhipCreamDecorator(
new SugarDecorator(
new MilkDecorator($coffee)
)
)
);
echo $deluxeCoffee->getDescription() . " - $" . $deluxeCoffee->getCost() . "\n";
?>
Related Topics
For more PHP object-oriented programming topics:
- PHP Classes and Objects - OOP fundamentals
- PHP Inheritance - Class inheritance patterns
- PHP Interfaces and Traits - Contracts and mixins
- PHP Abstract Classes - Abstract base classes
- PHP Magic Methods - Special behaviors
Summary
Design patterns provide proven solutions to common programming problems:
Creational Patterns: Singleton ensures single instances, Factory creates objects flexibly, providing clean object creation mechanisms.
Behavioral Patterns: Observer enables loose coupling between objects, Strategy makes algorithms interchangeable, promoting flexible designs.
Structural Patterns: Decorator adds behavior dynamically, Adapter bridges incompatible interfaces, improving code organization and flexibility.
Benefits: Design patterns improve code maintainability, promote best practices, provide common vocabulary, and enable flexible architectures.
Implementation Guidelines: Choose patterns based on actual needs, avoid over-engineering, understand the problem before applying patterns, and prioritize simplicity.
Best Practices: Study pattern intent and structure, practice with real examples, understand when not to use patterns, and adapt patterns to PHP's strengths.
Mastering design patterns enables you to write more professional, maintainable, and scalable PHP applications that follow established architectural principles.