StructuralPHPverifiedVerified
Facade Pattern in PHP
Provides a simplified, unified interface to a complex subsystem, hiding its internal complexity from clients.
How to Implement the Facade Pattern in PHP
1Step 1: Define complex subsystem classes
class VideoDecoder
{
public function decode(string $filename): string { return "decoded:{$filename}"; }
}
class AudioMixer
{
public function mix(string $audio): string { return "mixed:{$audio}"; }
}
class SubtitleRenderer
{
public function render(string $subs): string { return "rendered:{$subs}"; }
}
class VideoEncoder
{
public function encode(string $video, string $audio, string $subs): string
{
return "output.mp4";
}
}2Step 2: Create a simple facade that hides subsystem complexity
class VideoConverterFacade
{
private VideoDecoder $decoder;
private AudioMixer $mixer;
private SubtitleRenderer $subtitles;
private VideoEncoder $encoder;
public function __construct()
{
$this->decoder = new VideoDecoder();
$this->mixer = new AudioMixer();
$this->subtitles = new SubtitleRenderer();
$this->encoder = new VideoEncoder();
}
public function convert(string $filename): string
{
$video = $this->decoder->decode($filename);
$audio = $this->mixer->mix($filename);
$subs = $this->subtitles->render($filename);
return $this->encoder->encode($video, $audio, $subs);
}
}
// Usage — one simple call instead of managing four subsystems
$converter = new VideoConverterFacade();
echo $converter->convert('movie.avi'); // "output.mp4"<?php
declare(strict_types=1);
// [step] Define subsystem interfaces
interface InventoryServiceInterface
{
public function checkStock(string $productId): int;
public function reserve(string $productId, int $quantity): bool;
public function release(string $productId, int $quantity): void;
}
interface PaymentGatewayInterface
{
public function charge(string $customerId, float $amount, string $currency): PaymentResult;
public function refund(string $transactionId): PaymentResult;
}
interface ShippingServiceInterface
{
public function calculateCost(string $address, float $weight): float;
public function createShipment(string $orderId, string $address): string;
}
interface NotificationServiceInterface
{
public function sendEmail(string $to, string $subject, string $body): void;
}
final readonly class PaymentResult
{
public function __construct(
public bool $success,
public string $transactionId,
public ?string $error = null,
) {}
}
final readonly class OrderResult
{
public function __construct(
public bool $success,
public string $orderId,
public ?string $trackingNumber = null,
public ?string $error = null,
public float $totalCharged = 0,
) {}
}
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
// [step] Implement the e-commerce facade
final class OrderFacade
{
public function __construct(
private readonly InventoryServiceInterface $inventory,
private readonly PaymentGatewayInterface $payments,
private readonly ShippingServiceInterface $shipping,
private readonly NotificationServiceInterface $notifications,
private readonly LoggerInterface $logger,
) {}
/**
* @param array<array{productId: string, quantity: int, price: float}> $items
*/
public function placeOrder(
string $customerId,
string $email,
array $items,
string $shippingAddress,
): OrderResult {
$orderId = uniqid('order_', true);
$this->logger->info("Processing order", ['orderId' => $orderId]);
// Step 1: Check and reserve inventory
$reservedProducts = [];
try {
foreach ($items as $item) {
$stock = $this->inventory->checkStock($item['productId']);
if ($stock < $item['quantity']) {
$this->releaseReserved($reservedProducts);
return new OrderResult(false, $orderId, error: "Insufficient stock for {$item['productId']}");
}
$this->inventory->reserve($item['productId'], $item['quantity']);
$reservedProducts[] = $item;
}
// Step 2: Calculate total with shipping
$subtotal = array_sum(array_map(fn($i) => $i['price'] * $i['quantity'], $items));
$shippingCost = $this->shipping->calculateCost($shippingAddress, 1.0);
$total = $subtotal + $shippingCost;
// Step 3: Process payment
$payment = $this->payments->charge($customerId, $total, 'USD');
if (!$payment->success) {
$this->releaseReserved($reservedProducts);
return new OrderResult(false, $orderId, error: "Payment failed: {$payment->error}");
}
// Step 4: Create shipment
$trackingNumber = $this->shipping->createShipment($orderId, $shippingAddress);
// Step 5: Send confirmation
$this->notifications->sendEmail($email, "Order {$orderId} confirmed", "Tracking: {$trackingNumber}");
$this->logger->info("Order completed", ['orderId' => $orderId, 'total' => $total]);
return new OrderResult(
success: true,
orderId: $orderId,
trackingNumber: $trackingNumber,
totalCharged: $total,
);
} catch (\Throwable $e) {
$this->releaseReserved($reservedProducts);
$this->logger->error("Order failed", ['orderId' => $orderId, 'error' => $e->getMessage()]);
return new OrderResult(false, $orderId, error: $e->getMessage());
}
}
/** @param array<array{productId: string, quantity: int, price: float}> $items */
private function releaseReserved(array $items): void
{
foreach ($items as $item) {
try {
$this->inventory->release($item['productId'], $item['quantity']);
} catch (\Throwable $e) {
$this->logger->error("Failed to release inventory", ['product' => $item['productId']]);
}
}
}
}Facade Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Facade Pattern in the Real World
“A hotel concierge is a facade for the city’s complex infrastructure. Instead of you directly calling a taxi company, booking a restaurant, and arranging a museum ticket separately, the concierge handles all of it through a single conversation. The underlying services still exist in their full complexity—you just don’t deal with them directly.”