Design Patterns in PHP
A design pattern is a named, well-understood solution to a problem that comes up repeatedly in object-oriented code. Knowing the name and shape of a few common patterns means recognising the problem they solve — and being able to communicate a design decision to another developer in a single word, rather than re-explaining the whole structure every time.
Singleton — Exactly One Instance
The constructor is private, so new Config() is impossible from outside the class — the only way to get an instance is getInstance(), which creates it once and returns that same object on every subsequent call.
Config::getInstance(). Dependency injection (passing the needed object in explicitly, rather than reaching for a global singleton) is usually the better-regarded modern approach — Singleton is shown here because it's a genuinely common pattern to recognise, not necessarily one to reach for by default.
Factory — Delegating Object Creation
Calling code asks the factory for "an email notification" without needing to know — or directly reference — the specific EmailNotification class. This decouples the calling code from exactly which class gets instantiated, which becomes genuinely useful when that decision needs to vary (different config, different environment, a new notification type added later).
match expression (used above) is similar to switch from Fundamentals Chapter 3, but uses strict comparison by default, requires no break, and is itself an expression — it directly returns a value, rather than needing variables assigned inside each case.
Strategy — Swappable Behaviour Behind a Common Interface
ShoppingCart doesn't know or care which discount logic it's using — only that whatever it's given implements DiscountStrategy (Intermediate Chapter 2's interfaces, applied here). Swapping discount behaviour means passing a different object into the constructor; ShoppingCart itself never needs to change.
Repository — Hiding Storage Details Behind a Simple API
greetUser() works against the UserRepository interface — it has no idea whether data actually comes from MySQL, a JSON file, or an in-memory test array (a FakeUserRepository could be swapped in for testing). This is genuinely the same idea as Strategy, applied specifically to data access — and it's what makes automated testing of database-dependent code realistically feasible.
Coding Challenges
Implement a Singleton class called Logger with a private array property for storing log lines, a static getInstance() method, and a public method log(string $message) that appends to the array. Demonstrate that calling Logger::getInstance() from two different points in the script still shares the same log entries.
Create a Shape interface with a getArea() method, two implementations (Circle, Square), and a ShapeFactory with a static create(string $type, ...$args) method using match() to construct the right one. Create one of each via the factory and echo their areas.
Create a SortStrategy interface with a sort(array $items): array method, and two implementations: AscendingSort and DescendingSort. Write a class Report that accepts a SortStrategy via its constructor and a generate(array $items) method using it. Demonstrate swapping strategies on two different Report instances with the same data.
Chapter 2 Quick Reference
- Singleton — private constructor + static getInstance(); exactly one shared instance; use sparingly, prefer dependency injection generally
- Factory — a dedicated method/class for creating objects, decoupling calling code from specific classes
- Strategy — swappable behaviour behind a common interface, injected via the constructor
- Repository — hides data-storage details behind a simple, interface-based API; enables swapping real DB for a fake in tests
- match expression — strict comparison, no break needed, returns a value directly
- Next chapter: PHP and MVC architecture — building a simple framework from scratch