← Back to Dashboard

Facade Pattern

Laravel: All Laravel Facades (Cache::, DB::, Mail::), Artisan facade

Facade Pattern

The Real-World Analogy

Imagine you work at a fintech company and your team is tasked with generating a monthly compliance report. To produce this report, you need to pull transaction records from the ledger service, fetch merchant details from the partner API, aggregate totals by category, apply tax calculations, format everything into a PDF-ready structure, and finally run it through a validation check against regulatory rules. That is six distinct subsystems, each with its own interface, configuration, and error handling.

Now imagine every developer on your team has to know the exact method signatures, call order, and error recovery for all six subsystems every time they need a report. Someone will forget to validate. Someone will aggregate before fetching merchant details. Someone will skip the tax step on refunds. The complexity is not in any single subsystem -- it is in the orchestration.

A Facade is like the "Generate Report" button on an internal dashboard. You press it, and behind the scenes the system coordinates all six subsystems in the correct order. You do not need to know how the sausage is made. The facade provides a simple, unified interface to a complex subsystem.

The Anti-Pattern: Direct Subsystem Coupling

Here is what happens when every piece of calling code interacts with subsystem classes directly:

// Every caller must know about ALL of these classes and their exact usage
$fetcher = new TransactionFetcher();
$fetcher->setDateRange('2025-01-01', '2025-01-31');
$transactions = $fetcher->fetchAll();

$aggregator = new DataAggregator();
$aggregator->loadTransactions($transactions);
$totals = $aggregator->groupByCategory();

$taxCalc = new TaxCalculator();
$taxCalc->setRegion('PH');
$withTax = $taxCalc->applyToTotals($totals);

$formatter = new ReportFormatter();
$formatter->setTemplate('monthly-compliance');
$report = $formatter->render($withTax);

$validator = new ComplianceValidator();
$result = $validator->validate($report);
if (!$result->isValid()) {
    throw new \RuntimeException('Report failed compliance check');
}

return $report;

Why this is a problem:

  1. Tight coupling. The calling code is coupled to five concrete classes. If DataAggregator changes its method signature, every caller breaks.
  2. Duplication. This exact sequence gets copy-pasted into controllers, CLI commands, scheduled jobs, and API endpoints. Each copy drifts slightly over time.
  3. Error-prone orchestration. The order matters. Calling applyToTotals() before groupByCategory() produces garbage. Nothing enforces the correct sequence.
  4. Knowledge burden. New developers must understand the entire subsystem before they can generate a simple report.

The Fix: Introduce a Facade

Wrap the subsystem behind a single class with a clean, minimal interface:

class ReportFacade
{
    public function __construct(
        private TransactionFetcher $fetcher,
        private DataAggregator $aggregator,
        private TaxCalculator $taxCalc,
        private ReportFormatter $formatter,
        private ComplianceValidator $validator,
    ) {}

    public function generateMonthlyReport(string $startDate, string $endDate, string $region): string
    {
        $this->fetcher->setDateRange($startDate, $endDate);
        $transactions = $this->fetcher->fetchAll();

        $this->aggregator->loadTransactions($transactions);
        $totals = $this->aggregator->groupByCategory();

        $this->taxCalc->setRegion($region);
        $withTax = $this->taxCalc->applyToTotals($totals);

        $this->formatter->setTemplate('monthly-compliance');
        $report = $this->formatter->render($withTax);

        $result = $this->validator->validate($report);
        if (!$result->isValid()) {
            throw new \RuntimeException('Report failed compliance check');
        }

        return $report;
    }
}

// Now every caller does this:
$report = $reportFacade->generateMonthlyReport('2025-01-01', '2025-01-31', 'PH');

The orchestration logic lives in one place. Callers interact with a single method. If the subsystem changes internally, only the facade needs updating.

GoF Facade vs. Laravel "Facade"

This is a critical distinction that trips up many Laravel developers. The two concepts share a name but differ in purpose:

  • GoF Facade (this module): A structural pattern that provides a simplified interface to a complex subsystem. It is an actual class that composes multiple subsystem objects and exposes a cleaner API. The intent is to reduce coupling between client code and a set of related classes.

  • Laravel Facade: A static proxy that provides a convenient syntax (Cache::get('key')) to access a service resolved from the IoC container. Under the hood, Cache::get() calls app('cache')->get(). The intent is syntactic convenience, not subsystem simplification.

Both are useful. Both reduce complexity in their own way. But when someone says "Facade pattern" in a design patterns discussion, they mean the GoF version: a class that wraps and simplifies a group of related objects.

When to Use the Facade Pattern

  • You have a subsystem of three or more classes that are frequently used together in a specific sequence
  • You want to decouple client code from the internals of a library or module
  • You need to provide a simple entry point for a complex operation (report generation, payment orchestration, onboarding flows)
  • You are wrapping a third-party SDK whose API is verbose or unintuitive

When NOT to Use It

  • Do not create a facade for a single class -- that is just an unnecessary wrapper
  • Do not use a facade to hide bad design. If your subsystem is tangled, refactor the subsystem first
  • Do not let the facade become a "God class" that does everything. A facade delegates; it should contain orchestration logic, not business logic

Key takeaway: The Facade pattern does not add new functionality. It provides a simpler interface to existing functionality. When you find yourself writing the same sequence of subsystem calls in multiple places, it is time to extract a facade.

What You Learned

  • The Facade pattern provides a simple interface to a complex subsystem of multiple collaborating classes
  • The facade doesn't replace the subsystem — it wraps it, letting callers avoid knowing the internal wiring
  • Each subsystem class retains its own single responsibility; the facade just orchestrates them
  • Facades reduce coupling: callers depend on one class instead of three or four

Facade Pattern in Laravel

Laravel's Facade System: Static Proxies

Laravel's most visible use of the word "Facade" is its static proxy system. Every Cache::, DB::, Mail::, Log:: call is a Laravel Facade -- a class that extends Illuminate\Support\Facades\Facade and provides static access to an object resolved from the service container.

How It Works Under the Hood

// When you write:
Cache::get('user:123');

// Laravel actually does:
app('cache')->get('user:123');

Every Laravel Facade class has a getFacadeAccessor() method that returns the container binding key:

// Illuminate\Support\Facades\Cache
class Cache extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return 'cache';
    }
}

When you call a static method on the facade, PHP triggers __callStatic() in the base Facade class, which resolves the underlying instance from the container and forwards the method call to it. The facade itself holds no logic -- it is purely a proxy.

Why This Matters

This system gives you two things:

  1. Convenient syntax. Cache::get('key') is shorter and more readable than app('cache')->get('key') or injecting CacheManager everywhere.
  2. Testability. Because the facade resolves from the container, you can swap the underlying instance in tests: Cache::shouldReceive('get')->once()->andReturn('cached-value');

Real-Time Facades

Laravel 11 supports "real-time facades" -- any class can be used as a facade by prefixing its namespace with Facades\:

use Facades\App\Services\PaymentService;

// This resolves App\Services\PaymentService from the container
// and calls processPayment() on the resolved instance
PaymentService::processPayment($data);

You do not need to create a facade class. Laravel generates the proxy dynamically. This is useful for one-off cases where you want facade-style access without creating a dedicated facade class.

GoF Facade in Laravel: Real Examples

Beyond the static proxy system, Laravel uses the GoF Facade pattern (simplified interface to a subsystem) in several places:

The Illuminate\Auth\AuthManager

The AuthManager is a facade over the authentication subsystem. Behind it sit guards (SessionGuard, TokenGuard), user providers (EloquentUserProvider, DatabaseUserProvider), and hashers. Client code simply calls:

Auth::attempt(['email' => $email, 'password' => $password]);

The AuthManager coordinates guard selection, credential verification, user resolution, session management, and event dispatching. Without this facade, every authentication call would need to interact with multiple classes directly.

The Illuminate\Notifications\ChannelManager

When you send a notification, Laravel's ChannelManager orchestrates delivery across channels (mail, SMS, Slack, database). Your code does:

$user->notify(new PaymentReceived($transaction));

Behind the scenes, the ChannelManager determines which channels to use (from the notification's via() method), resolves each channel driver, formats the notification for that channel, and dispatches it. That is a GoF Facade in action.

The Illuminate\Filesystem\FilesystemManager

Storage::disk('s3')->put('file.txt', $content) is deceptively simple. The FilesystemManager:

  • Reads disk configuration from config/filesystems.php
  • Resolves the appropriate Flysystem adapter (Local, S3, SFTP)
  • Creates and configures the adapter with credentials and options
  • Returns a Filesystem instance that wraps the adapter

You interact with a single put() call. The manager handles driver resolution, adapter creation, and configuration.

Writing Your Own Facade in Laravel

If you have a complex subsystem (say, a reporting pipeline), you can create a GoF facade:

// app/Services/Reporting/ReportFacade.php
namespace App\Services\Reporting;

class ReportFacade
{
    public function __construct(
        private DataSource $source,
        private Aggregator $aggregator,
        private Formatter $formatter,
    ) {}

    public function generate(string $type, array $filters): string
    {
        $data = $this->source->fetch($filters);
        $aggregated = $this->aggregator->process($data);
        return $this->formatter->format($type, $aggregated);
    }
}

Register it in the container:

// AppServiceProvider
$this->app->singleton(ReportFacade::class);

And optionally create a Laravel static facade for it:

// app/Facades/Report.php
namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class Report extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return \App\Services\Reporting\ReportFacade::class;
    }
}

// Now you can call:
Report::generate('monthly', ['region' => 'PH']);

Here you have both patterns working together: a GoF Facade that simplifies a subsystem, and a Laravel Facade that provides convenient static access to it.


Key takeaway: Laravel uses the word "Facade" for its static proxy system, but the GoF Facade pattern is also present throughout the framework in managers and coordinators that simplify complex subsystems. Understanding both meanings prevents confusion and helps you apply each pattern where it fits.

This example is pre-filled and runnable. Click "Run" to see the output. Feel free to modify the code and experiment.

Output

                

            

Complete the code below to pass all test cases. Use the hints if you get stuck.