Facade Design Pattern in PHP

Placing an order usually isn't one operation — it's checking inventory, charging a card, and scheduling a shipment, each handled by a different class with its own setup and its own methods. If every controller that needs to place an order has to know about, instantiate, and correctly sequence all three of those classes, that knowledge ends up duplicated everywhere an order can be placed, and any change to the sequence means hunting down every call site. The Facade pattern solves this by giving client code one simple method that hides the coordination behind it.

What Is the Facade Pattern?

Facade is a structural pattern that provides a simplified, unified interface to a larger body of code — a subsystem made up of several classes. The facade class doesn't reimplement any of that subsystem's logic; it just knows the right classes to call, in the right order, with the right data passed between them, and exposes that as one clean method.

The Problem It Solves

Without a facade, client code has to reach directly into every class involved in a task. Here's what placing an order looks like when a controller talks to each service itself:

<?php
// Before: client code must know about, and correctly coordinate, three services
$inventory = new InventoryService();
$payment   = new PaymentService();
$shipping  = new ShippingService();

if (!$inventory->reserve('SKU-123', 2)) {
    throw new Exception('Out of stock');
}
if (!$payment->charge('cus_42', 59.98)) {
    $inventory->release('SKU-123', 2);
    throw new Exception('Payment declined');
}
$shipping->scheduleShipment('SKU-123', 2, '123 Main St');
?>

Every controller that places an order now has to repeat this exact sequence, including the rollback logic when payment fails. Miss a step in one call site and you get an inconsistent order.

Worked Example: An OrderFacade

The three subsystem classes stay exactly as they are — the facade doesn't replace them, it sits in front of them:

<?php
class InventoryService
{
    public function reserve(string $sku, int $qty): bool { /* ... */ return true; }
    public function release(string $sku, int $qty): void { /* ... */ }
}

class PaymentService
{
    public function charge(string $customerId, float $amount): bool { /* ... */ return true; }
}

class ShippingService
{
    public function scheduleShipment(string $sku, int $qty, string $address): void { /* ... */ }
}
?>

The facade coordinates all three behind one method, including the rollback rule, written and tested in exactly one place:

<?php
class OrderFacade
{
    private InventoryService $inventory;
    private PaymentService $payment;
    private ShippingService $shipping;

    public function __construct(
        InventoryService $inventory,
        PaymentService $payment,
        ShippingService $shipping
    ) {
        $this->inventory = $inventory;
        $this->payment = $payment;
        $this->shipping = $shipping;
    }

    public function placeOrder(string $sku, int $qty, string $customerId, float $amount, string $address): bool
    {
        if (!$this->inventory->reserve($sku, $qty)) {
            return false;
        }

        if (!$this->payment->charge($customerId, $amount)) {
            $this->inventory->release($sku, $qty);
            return false;
        }

        $this->shipping->scheduleShipment($sku, $qty, $address);
        return true;
    }
}
?>

Now every controller that needs to place an order does exactly this — one call, one clear result:

<?php
// After: client code just calls the facade
$orderFacade = new OrderFacade(
    new InventoryService(),
    new PaymentService(),
    new ShippingService()
);

$success = $orderFacade->placeOrder('SKU-123', 2, 'cus_42', 59.98, '123 Main St');
echo $success ? 'Order placed!' : 'Order failed.';
?>

The Facade Doesn't Hide the Subsystem

A common misconception is that a facade replaces direct access to the subsystem entirely. It doesn't. InventoryService, PaymentService, and ShippingService are still public, ordinary classes — a report that only needs to check stock levels can instantiate InventoryService directly and skip the facade entirely, since it doesn't need payment or shipping at all. The facade is a convenient shortcut for the common case, not the only door in.

Real-World Tie-Ins

Frameworks lean on this pattern heavily. Laravel's Facades (like Cache::get() or Mail::send()) are a well-known example — a short, static-looking call that quietly resolves and coordinates a much larger underlying service out of the container. An Auth::attempt() call, for instance, hides hashing, session handling, and "remember me" cookie logic behind one line.

Next up: Composite looks at a different structural problem — treating a single object and a whole tree of objects through the exact same interface.