Skip to Content
Course content

135: The Facade Pattern in PHP

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a project recently where we had to implement a checkout process. It seemed simple enough at first: take the money, check the stock, and send an email. But as the project grew, that "simple" process turned into a nightmare of dependencies. I wanted to show you this because it's the perfect way to understand why we use the Facade pattern.

Dealing with the sprawl

Here is how my code looked initially. I was instantiating everything right in my controller. It was a mess.

<?php
// These are our complex subsystem classes
class InventoryManager {
    public function checkStock($productId) { return "Stock checked for $productId"; }
    public function reserveItem($productId) { return "Item $productId reserved"; }
}

class PaymentGateway {
    public function authorize($amount) { return "Payment of $$amount authorized"; }
    public function capture($amount) { return "Payment of $$amount captured"; }
}

class ShippingCarrier {
    public function createLabel($address) { return "Label created for $address"; }
    public function schedulePickup() { return "Pickup scheduled"; }
}

class NotificationService {
    public function sendEmail($email, $msg) { return "Email sent to $email: $msg"; }
}

// The "messy" way of doing things
$inventory = new InventoryManager();
$payment = new PaymentGateway();
$shipping = new ShippingCarrier();
$notifications = new NotificationService();

echo $inventory->checkStock('PHP-BOOK-123') . "\n";
echo $inventory->reserveItem('PHP-BOOK-123') . "\n";
echo $payment->authorize(29.99) . "\n";
echo $payment->capture(29.99) . "\n";
echo $shipping->createLabel('123 Dev Lane') . "\n";
echo $shipping->schedulePickup() . "\n";
echo $notifications->sendEmail('user@example.com', 'Your order is on the way!') . "\n";
?>>

If you look at that, it works. But think about what happens if I want to change the payment gateway to a different provider, or if the ShippingCarrier class adds a new required parameter to createLabel(). I'd have to hunt down every single place in my application where I've manually orchestrated this sequence and fix it. It's brittle. I'm basically forcing my controller to know exactly how the internal plumbing of the warehouse works.

Simplifying the interface

I realized I didn't actually care about the individual steps; I just wanted to "Place an Order." So, I decided to wrap all that complexity inside a single class. This is the essence of the Facade pattern: providing a simplified interface to a complex set of classes.

<?php
class OrderFacade {
    private $inventory;
    private $payment;
    private $shipping;
    private $notifications;

    public function __construct() {
        // The Facade manages the subsystem instances
        $this->inventory = new InventoryManager();
        $this->payment = new PaymentGateway();
        $this->shipping = new ShippingCarrier();
        $this->notifications = new NotificationService();
    }

    public function placeOrder($productId, $amount, $address, $email) {
        echo $this->inventory->checkStock($productId) . "\n";
        echo $this->inventory->reserveItem($productId) . "\n";
        echo $this->payment->authorize($amount) . "\n";
        echo $this->payment->capture($amount) . "\n";
        echo $this->shipping->createLabel($address) . "\n";
        echo $this->shipping->schedulePickup() . "\n";
        echo $this->notifications->sendEmail($email, 'Your order is on the way!') . "\n";
        
        return "Order completed successfully!";
    }
}

// Now the client code is blissfully ignorant of the complexity
$orderSystem = new OrderFacade();
echo $orderSystem->placeOrder('PHP-BOOK-123', 29.99, '123 Dev Lane', 'user@example.com');
?>>

Notice how much cleaner the calling code is. I've gone from seven lines of orchestration down to one. I've hidden the "how" and only exposed the "what".

The trade-off and the 'Ah-ha!' moment

Now, you might be thinking, "Wait, didn't I just move the mess from the controller into a different class?" In a way, yes. But here is the crucial difference: we've decoupled the client from the subsystems.

If we decide to swap out the PaymentGateway for Stripe or PayPal, I only have to change one line inside the OrderFacade. The rest of my application—the controllers, the API endpoints, the CLI tools—doesn't even know a change happened. They just keep calling placeOrder().

A Facade isn't meant to replace your business logic; it's meant to act as a "front door." It prevents your higher-level code from becoming tightly coupled to the specific implementation details of your low-level services. It's about creating a boundary that keeps the complexity from leaking into the rest of your app.




📋 Practical Task

Build a Cloud Backup Facade

You are building a system that backs up files to the cloud. To do this, your system uses three complex subsystems: FileCompressor, CloudStorageApi, and BackupLogger.

Requirements:

  • Create the three subsystem classes:
    • FileCompressor with a method compress($file).
    • CloudStorageApi with methods connect() and upload($file).
    • BackupLogger with a method log($message).
  • Create a CloudBackupFacade class that simplifies this process into a single method: backup($file).
  • Inside the backup()` method, the Facade should orchestrate the following:
    1. Compress the file.
    2. Connect to the API.
    3. Upload the compressed file.
    4. Log the success message.
  • Instantiate the CloudBackupFacade and call the backup('my_database.sql') method to verify it works.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.