The state pattern is useful when you need to change the behavior of an object based upon changes to its internal state. You can also use the pattern to simplify methods with long conditionals that depend on the object state.
# to do: change to phpclass Document is field state:string// ... method publish()is switch (state)"draft": state ="moderation" break"moderation":if(currentUser.role =="admin") state ="published" break"published":// Do nothing. break// ...
Software systems sometimes face the compatibility issue, when the output of one system may not conform with the expected input of another system, in this cases we use a adapter.
Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
The adapter essentially encapsulates the adaptee and presents a new interface, or appearance, to the client class. It does this by wrapping the adaptee’s interface and exposing a new target interface that makes sense to the client.
<?php// --- Target Interface --- //// The interface the client expects.interface LoggerInterface {publicfunctionlog(string$level,string$message):void;}// --- Adaptee --- //// Existing class with an incompatible interface.classLegacyLogger{publicfunctionwriteLog(string$text):void{echo"Legacy log: $text\n";}}// --- Adapter --- //// The Adapter makes the Adaptee compatible with the Target interface.classLegacyLoggerAdapterimplementsLoggerInterface{privateLegacyLogger$legacyLogger;publicfunction__construct(LegacyLogger$legacyLogger){$this->legacyLogger=$legacyLogger;}publicfunctionlog(string$level,string$message):void{// Convert the expected format into what the legacy system understands.$formattedMessage=strtoupper($level).": ".$message;$this->legacyLogger->writeLog($formattedMessage);}}// --- Concrete Class (for comparison) --- //// A modern JSON logger that already matches the interface.classJsonLoggerimplementsLoggerInterface{publicfunctionlog(string$level,string$message):void{echojson_encode(['level'=>$level,'message'=>$message], JSON_PRETTY_PRINT)."\n";}}// --- Client Code --- //functionclientCode(LoggerInterface$logger):void{$logger->log('info','Adapter pattern in action!');}// Use the modern loggerecho"Using JsonLogger:\n";clientCode(newJsonLogger());// Use the legacy logger via adapterecho"\nUsing LegacyLogger through Adapter:\n";$legacyAdapter=newLegacyLoggerAdapter(newLegacyLogger());clientCode($legacyAdapter);//Output//Using JsonLogger{"level":"info","message":"Adapter pattern in action!"}//Using LegacyLogger through AdapterLegacy log: INFO: Adapter pattern in action!
Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.
This design pattern uses a number of different design principles, to provide ease of access to complex classes, what we call subsystem. This is done by encapsulating the subsystem classes into a Facade class, and then hiding them from the client classes so that the clients do not know about the details of the subsystem.
When use it: Use the Facade pattern when you need to have a limited but straightforward interface to a complex subsystem.
<?php// --- Subsystems --- //classInventoryService{publicfunctioncheckStock(string$productId):bool{echo"Checking stock for product: $productId\n";// Simulated logicreturntrue;}publicfunctionreserveItem(string$productId):void{echo"Reserving item: $productId\n";}}classPaymentService{publicfunctionprocessPayment(float$amount,string$method):bool{echo"Processing payment of $$amount via $method\n";// Simulated logicreturntrue;}}classShippingService{publicfunctionship(string$productId,string$address):void{echo"Shipping product '$productId' to $address\n";}}classNotificationService{publicfunctionsendConfirmation(string$email,string$message):void{echo"Sending confirmation email to $email: $message\n";}}// --- Facade --- //classOrderFacade{privateInventoryService$inventory;privatePaymentService$payment;privateShippingService$shipping;privateNotificationService$notification;publicfunction__construct(){$this->inventory=newInventoryService();$this->payment=newPaymentService();$this->shipping=newShippingService();$this->notification=newNotificationService();}/** * Simplified order process using the Facade */publicfunctionplaceOrder(string$productId,float$amount,string$paymentMethod,string$address,string$customerEmail):void{echo"Starting order process...\n";if(!$this->inventory->checkStock($productId)){echo"Product not available in stock.\n";return;}$this->inventory->reserveItem($productId);if(!$this->payment->processPayment($amount,$paymentMethod)){echo"Payment failed. Order canceled.\n";return;}$this->shipping->ship($productId,$address);$this->notification->sendConfirmation($customerEmail,"Your order for '$productId' has been successfully processed!");echo"Order completed successfully.\n";}}// --- Client Code --- //$orderFacade=newOrderFacade();$orderFacade->placeOrder( productId:'SKU12345', amount:199.99, paymentMethod:'Credit Card', address:'123 Business Street, Cityville', customerEmail:'customer@example.com');
As you can see, a facade class can be used to wrap all the interfaces and classes for a subsystem. It is your decision as to what you want to wrap.
Now let’s improve this code by using Dependency Injection (DI) in it.
Dependency Injection means giving an object its dependencies from something outside, instead of the object creating them by itself.
<?php// --- Interfaces --- //interface InventoryInterface {publicfunctioncheckStock(string$productId):bool;publicfunctionreserveItem(string$productId):void;}interface PaymentInterface {publicfunctionprocessPayment(float$amount,string$method):bool;}interface ShippingInterface {publicfunctionship(string$productId,string$address):void;}interface NotificationInterface {publicfunctionsendConfirmation(string$email,string$message):void;}// --- Concrete Implementations --- //classInventoryServiceimplementsInventoryInterface{publicfunctioncheckStock(string$productId):bool{echo"Checking stock for product: $productId\n";returntrue;}publicfunctionreserveItem(string$productId):void{echo"Reserving item: $productId\n";}}/** * Stripe payment service */classStripePaymentServiceimplementsPaymentInterface{publicfunctionprocessPayment(float$amount,string$method):bool{echo"Processing payment of $$amount via $method\n";returntrue;}}/** * PayPal payment service */classPaypalPaymentServiceimplementsPaymentInterface{publicfunctionprocessPayment(float$amount,string$method):bool{echo"Processing PayPal payment of $$amount using $method\n";returntrue;}}classShippingServiceimplementsShippingInterface{publicfunctionship(string$productId,string$address):void{echo"Shipping product '$productId' to $address\n";}}classNotificationServiceimplementsNotificationInterface{publicfunctionsendConfirmation(string$email,string$message):void{echo"Sending confirmation email to $email: $message\n";}}// --- Facade --- //classOrderFacade{publicfunction__construct(privateInventoryInterface$inventory,privatePaymentInterface$payment,privateShippingInterface$shipping,privateNotificationInterface$notification){}/** * High-level method hiding all subsystem complexity */publicfunctionplaceOrder(string$productId,float$amount,string$paymentMethod,string$address,string$customerEmail):void{echo"Starting order process...\n";if(!$this->inventory->checkStock($productId)){echo"Product not available in stock.\n";return;}$this->inventory->reserveItem($productId);if(!$this->payment->processPayment($amount,$paymentMethod)){echo"Payment failed. Order canceled.\n";return;}$this->shipping->ship($productId,$address);$this->notification->sendConfirmation($customerEmail,"Your order for '$productId' has been successfully processed!");echo"Order completed successfully.\n";}}// --- Client Code --- //$inventory=newInventoryService();$payment=newPaypalPaymentService();$shipping=newShippingService();$notification=newNotificationService();$orderFacade=newOrderFacade($inventory,$payment,$shipping,$notification);$orderFacade->placeOrder( productId:'SKU12345', amount:199.99, paymentMethod:'PayPal', address:'123 Business Street, Cityville', customerEmail:'customer@example.com');
What changed?
Interfaces: define contracts for each subsystem. You can swap implementations easily (e.g., replace PayPalPaymentService with StripePaymentService).
Dependency Injection: the Facade doesn’t create its dependencies — they’re injected externally in client code.
Loose coupling: classes depend on abstractions, not concrete classes.
Testable: in unit tests, you can inject mock objects.
Factory method provides an interface for creating objects in a superclass, allows subclasses to decide wich class to instantiate.
//Concrete or direct instantiation. Client code with no factory.$myEmailNotification=newemailNotification;$mySmsNotification=newsmsNotification;//Abstracted object creation. Client code with a factory-based instantiation.$myEmailNotification=NotificationFactory::create('email');$mySmsNotification=NotificationFactory::create('sms');
Factories allow client code to operate on generalizations, this is called coding to an interface, not an implementation and is a fundamental principle in object-oriented programming that promotes flexibility, maintainability, and testability in software design.
It means that client code should interact with a system through an abstract interface (or abstract class) rather than create the object directly, without worrying about the details of object creation.
<?php// interface defines a contract — all notification classes must have a send() method.interface Notification{publicfunctionsend(string$to,string$message):void;}// Concrete implementations of different notification typesclassEmailNotificationimplementsNotification{publicfunctionsend(string$to,string$message):void{echo"Sending EMAIL to {$to}: {$message}".PHP_EOL;}}classSmsNotificationimplementsNotification{publicfunctionsend(string$to,string$message):void{echo"Sending SMS to {$to}: {$message}".PHP_EOL;}}classPushNotificationimplementsNotification{publicfunctionsend(string$to,string$message):void{echo"Sending PUSH notification to {$to}: {$message}".PHP_EOL;}}// The Factory class — decides which notification to createclassNotificationFactory{publicstaticfunctioncreate(string$type):Notification{returnmatch(strtolower($type)){'email'=>newEmailNotification(),'sms'=>newSmsNotification(),'push'=>newPushNotification(),default=>thrownewInvalidArgumentException("Unknown notification type: {$type}")};}}// Usage exempletry{$notification=NotificationFactory::create('email');$notification->send('user@example.com','Your order has been shipped.');$notification=NotificationFactory::create('sms');$notification->send('+5547999999999','Your code is 123456.');$notification=NotificationFactory::create('push');$notification->send('User123','You have a new message.');}catch(Exception$e){echo"Error: ".$e->getMessage();}
Singleton – ensure that a class has only one instance, while providing a global access to this instance.
<?phpclassLogger{privatestatic?Logger$uniqueInstance=null;privatearray$logs=[];// prevents direct instantiationprivatefunction__construct(){}// no one outside the class can duplicate the instance.privatefunction__clone(){}/* __wakeup must be public (by PHP’s design), since it’s automatically called during unserialize().*/publicfunction__wakeup(){thrownew\Exception("Cannot unserialize a singleton.");}/* Lazy construction / initialization - returns the single instance of this class*/publicstaticfunctiongetInstance():Logger{if(self::$uniqueInstance===null){self::$uniqueInstance=newLogger();}returnself::$uniqueInstance;}// MethodspublicfunctionaddLog(string$message):void{$this->logs[]=$message;}publicfunctionshowLogs():void{foreach($this->logsas$log){echo$log.PHP_EOL;}}}// Usage exemple$logger1=Logger::getInstance();$logger2=Logger::getInstance();$logger1->addLog("First log entry.");$logger2->addLog("Second log entry.");$logger1->showLogs();//Checking the solid proof...if($logger1===$logger2){echo"Same instance".PHP_EOL;}else{echo"Different instances".PHP_EOL;}echo"Logger1 ID: ".spl_object_id($logger1).PHP_EOL;echo"Logger2 ID: ".spl_object_id($logger2).PHP_EOL;/*Output:First log entry.Second log entry.Same instanceLogger1 ID: 1Logger2 ID: 1*/
The code above works perfectly for our purpose, but it’s not inheritance-safe, meaning it always creates a Logger instance, even if called from a subclass. Therefore:
classFileLoggerextendsLogger{}FileLogger::getInstance();//still returns the instance of Logger, not a instance of FileLogger
If we want that each subclass has its own singleton, we must change this behavior by modifying the getInstance() method, like this:
Composition (“has-a”) or Inheritance (“is-a” ), how to choose the corret relationship?
First of all, you have to know that, in the scenario of modern object-oriented design principles, there is a strong preference for Compositionover Inheritance as better design principle.
This happens because, once you extend a class (inherit from it), you become bound to its structure and behavior, making it difficult to change it later. Let’s see a brief about each relantionship below.
“Is-a” → Inheritance Used when one class is a type of another class.
Example: Caris-aVehicle → class Car extends Vehicle
“Has-a” → Composition / Aggregation Used when one class contains or uses another class.
Example: Carhas-aEngine → class Car { private Engine $engine; }
How to choose?
Use is-a when you want to reuse behavior and the relationship is naturally hierarchical.
Use has-a when you want flexibility, replaceable components, and loose coupling.
Code exemples
IS-A (Inheritance) – Use when a class is a type of another class.
<?phpclassVehicle{publicfunctionmove(){echo"The vehicle is moving\n";}}classCarextendsVehicle// Car *is-a* Vehicle{}$car=newCar();$car->move();// inherited
HAS-A (Composition) – Use when a class contains another class.
<?phpclassEngine{publicfunctionstart(){echo"Engine started\n";}}classCar{privateEngine$engine;// Car *has-a* Enginepublicfunction__construct(){$this->engine=newEngine();}publicfunctionturnOn(){$this->engine->start();}}$car=newCar();$car->turnOn();
Now, let’s improve the HAS-A, with a more flexible and modern example using interfaces + dependency injection, allowing a car to use diesel, gasoline, ethanol (alcohol), electric, or any other engine.
Trygve Reenskaug developed the MVC pattern during his time as a visiting scientist at Xerox Palo Alto Research Center (PARC) while working on Smalltalk-79 in the late 1970s. He aimed to create a structure suitable for programs that allowed users to interact with complex and extensive data sets.
His first version of the design included four components: model, view, thing, and editor. After collaborating and discussing the idea with other Smalltalk developers, the team refined the concept into the now-familiar three parts: model, view, and controller (MVC).
The MVC pattern separates the responsibilities of a system into three distinct components. To understand this better, consider a simple diagram of the MVC structure.
Model
Starting with the model — it holds the core data and the business logic that users interact with and modify. For example, in a grocery store system, both cashiers and customers use the model to create and manage orders. An important aspect of MVC is that the model operates independently and does not depend on the other parts of the system, there fore, it contains all the state, methods, and data required for it to function independently.
View
As the name suggests, it provides a way for users to visualize the model or specific parts of it. In a grocery store system, for instance, the view might be the display showing the list of items and their prices.
Often, the view also includes interactive components such as buttons and input fields that let users engage with the system.
Essentially, the model represents the backend — the underlying logic and data that power the application, and the view the front end, the presentation layer. It is possible to have several views, all used within the same model.
Observer Design Pattern
When some value changes in the backend or the model, it should ideally tell the view to update itself accordingly. This is done by using the Observer Design Pattern. In the observer pattern, the observers act like subscribers. In this case, any view is also an observer. When the model changes, it notifies all the views that are subscribed to it.