Structural Patterns: Adapter, Decorator, Facade
Compose objects and simplify interfaces with structural patterns.
Structural Patterns: Adapter, Decorator, Facade is a free PHP Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the PHP Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Composing objects
Structural patterns describe how to compose classes and objects into larger structures while keeping them flexible. We cover three workhorses:
- Adapter — make an incompatible interface usable.
- Decorator — add behavior without subclassing.
- Facade — present a simple front to a complex subsystem.
Adapter: the problem
You depend on a PaymentGateway interface, but a third-party SDK exposes chargeCard($amountInCents) with a different shape. Calling the SDK directly leaks its API everywhere. Below, the two interfaces are clearly incompatible — that mismatch is exactly what an adapter resolves.
<?php
// What our app wants to depend on
interface PaymentGateway { public function pay(float $dollars): string; }
// What the vendor actually gives us (cents, array result)
final class StripeSdk {
public function chargeCard(int $cents): array {
return ['id' => 'ch_1', 'amount' => $cents];
}
}
// dollars vs cents, string vs array -> shapes don't line up
var_dump((new StripeSdk())->chargeCard(2000));
Adapter in code
The adapter holds the adaptee and implements your target interface, converting arguments and return values. Your application code stays clean and vendor-agnostic.
<?php
interface PaymentGateway { public function pay(float $dollars): string; }
// Third-party SDK with an incompatible API
final class StripeSdk {
public function chargeCard(int $cents): array {
return ['id' => 'ch_1', 'amount' => $cents];
}
}
final class StripeAdapter implements PaymentGateway {
public function __construct(private StripeSdk $sdk) {}
public function pay(float $dollars): string {
$res = $this->sdk->chargeCard((int) round($dollars * 100));
return "charged {$res['amount']}c -> {$res['id']}";
}
}
echo (new StripeAdapter(new StripeSdk()))->pay(20.0), PHP_EOL;
Decorator: the idea
A Decorator wraps an object that shares its interface and adds behavior before/after delegating. Unlike inheritance, decorators stack at runtime, letting you combine features in any order without a class explosion. Here is the minimal skeleton: a wrapper that implements the same interface and holds the inner instance.
<?php
interface Coffee { public function cost(): float; }
final class Espresso implements Coffee {
public function cost(): float { return 2.0; }
}
final class WithMilk implements Coffee {
public function __construct(private Coffee $inner) {}
public function cost(): float { return $this->inner->cost() + 0.5; }
}
echo (new WithMilk(new Espresso()))->cost(), PHP_EOL; // 2.5
Decorator in code
Each decorator implements the same interface and holds an inner instance. Here we wrap a data source with caching and logging, composing them freely.
<?php
interface DataSource { public function read(): string; }
final class FileSource implements DataSource {
public function read(): string { return 'raw-data'; }
}
final class UpperCaseDecorator implements DataSource {
public function __construct(private DataSource $inner) {}
public function read(): string { return strtoupper($this->inner->read()); }
}
final class ExclaimDecorator implements DataSource {
public function __construct(private DataSource $inner) {}
public function read(): string { return $this->inner->read() . '!!!'; }
}
$src = new ExclaimDecorator(new UpperCaseDecorator(new FileSource()));
echo $src->read(), PHP_EOL; // RAW-DATA!!!
Decorators in the wild
PSR-7 / PSR-15 middleware is essentially the decorator pattern applied to HTTP handling: each middleware wraps the next, adding auth, logging or caching around the request/response. Stream wrappers and PSR-6/16 cache layers also commonly use decoration. The key trait: the wrapper is substitutable for the thing it wraps.
Decorator vs inheritance
Inheritance fixes behavior at compile time and only allows one base. Decoration is dynamic and composable. Choose decorators when:
- combinations of optional features multiply,
- behavior should be added/removed at runtime,
- you must extend a
finalor third-party class you can't subclass.
Facade: the idea
A Facade provides a single, simplified entry point to a complex subsystem of many classes. It does not hide the subsystem (you can still reach in), but it offers a convenient high-level API for the common case. Compare the verbose subsystem dance below with the one-line facade call on the next scene.
<?php
// Without a facade, the client wires every step itself
final class Cpu { public function boot(): string { return 'cpu '; } }
final class Disk { public function load(): string { return 'disk '; } }
final class Ram { public function check(): string { return 'ram'; } }
$out = (new Cpu())->boot() . (new Disk())->load() . (new Ram())->check();
echo $out, PHP_EOL; // cpu disk ram
Facade in code
The facade orchestrates several collaborators behind one method. Clients call place() instead of wiring inventory, payment and shipping themselves.
<?php
final class Inventory { public function reserve(string $sku): bool { return true; } }
final class Payments { public function charge(float $amt): bool { return true; } }
final class Shipping { public function dispatch(string $sku): string { return 'tracking-99'; } }
final class OrderFacade {
public function __construct(
private Inventory $inv,
private Payments $pay,
private Shipping $ship
) {}
public function place(string $sku, float $amount): string {
if (!$this->inv->reserve($sku)) { return 'out of stock'; }
if (!$this->pay->charge($amount)) { return 'payment failed'; }
return 'shipped: ' . $this->ship->dispatch($sku);
}
}
echo (new OrderFacade(new Inventory(), new Payments(), new Shipping()))
->place('ABC', 49.99), PHP_EOL;
Distinguishing the three
- Adapter changes an interface so two existing parts can talk; same behavior, different shape.
- Decorator keeps the interface but adds behavior, stackable at runtime.
- Facade introduces a new, simpler interface in front of many classes.
A handy test: Adapter is about compatibility, Decorator about enhancement, Facade about simplification.
Note on Laravel facades
Laravel's "facades" are a static-proxy mechanism backed by the service container, not the GoF Facade pattern. They share the goal of a convenient front but differ in implementation. Don't conflate framework terminology with the structural pattern when reasoning about design.
Quick Check
Identify the pattern from its intent.
Recap
You composed objects with three structural patterns: Adapter to bridge incompatible interfaces (e.g. a vendor SDK), Decorator to stack behavior at runtime (the basis of PSR-15 middleware), and Facade to put a simple front on a complex subsystem. Each lowers coupling so your code resists vendor lock-in and feature bloat.
Frequently asked questions
Is the “Structural Patterns: Adapter, Decorator, Facade” lesson free?
Yes — the full text of “Structural Patterns: Adapter, Decorator, Facade” is free to read here on the web, and the PHP Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the PHP Academy course, upgrade to CoddyKit PRO.
What will I learn in “Structural Patterns: Adapter, Decorator, Facade”?
Compose objects and simplify interfaces with structural patterns. You practise PHP Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start PHP Academy?
No prior experience is required. PHP Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Structural Patterns: Adapter, Decorator, Facade” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this PHP Academy lesson?
Yes. Every PHP Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- SOLID Principles in Practice
- Creational Patterns: Factory, Builder, Singleton
- Structural Patterns: Adapter, Decorator, Facade
- Behavioral Patterns: Strategy, Observer, Command