State Pattern
Part of the Object Oriented Programming section of Coddy's PHP journey — lesson 79 of 91.
The State Pattern is a behavioral design pattern that allows an object to change its behavior when its internal state changes. Instead of using complex conditionals to handle different states, you encapsulate each state in its own class and delegate behavior to the current state object.
Think of a vending machine. It behaves differently depending on whether it's waiting for money, has received payment, or is dispensing a product.
Each state has its own rules for what actions are allowed. The State Pattern models this by creating a class for each state.
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}
class PendingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ProcessingState());
return "Order moved to processing";
}
public function getStatus(): string {
return "Pending";
}
}
class ProcessingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ShippedState());
return "Order has been shipped";
}
public function getStatus(): string {
return "Processing";
}
}
class ShippedState implements OrderState {
public function proceed(Order $order): string {
return "Order already shipped";
}
public function getStatus(): string {
return "Shipped";
}
}
The context class holds a reference to the current state and delegates calls to it:
<?php
class Order {
private OrderState $state;
public function __construct() {
$this->state = new PendingState();
}
public function setState(OrderState $state): void {
$this->state = $state;
}
public function proceed(): string {
return $this->state->proceed($this);
}
public function getStatus(): string {
return $this->state->getStatus();
}
}
$order = new Order();
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus();
Output:
Pending
Order moved to processing
Processing
Order has been shipped
ShippedThe State Pattern eliminates sprawling switch statements and makes adding new states straightforward. Each state class is responsible for knowing which state comes next, keeping the logic organized and maintainable.
Challenge
EasyLet's build a document workflow system using the State Pattern. Documents in a content management system go through different stages — draft, review, and published — with each stage having its own rules about what actions are allowed and what happens when you try to advance the document.
You'll organize your code across four files:
DocumentState.php— Define aDocumentStateinterface that establishes the contract for all document states. It should have two methods:publish(Document $document): stringfor attempting to advance the document, andgetStatus(): stringfor returning the current state name.States.php— Include the DocumentState interface and create three state classes that implement it:DraftState— Whenpublish()is called, it transitions the document toReviewStateand returns"Document sent for review". Its status is"Draft".ReviewState— Whenpublish()is called, it transitions the document toPublishedStateand returns"Document published". Its status is"Under Review".PublishedState— Whenpublish()is called, it returns"Document is already published"without changing state. Its status is"Published".
Document.php— Include the States file and create aDocumentclass that acts as the context. Your document should:- Accept a title in its constructor and start in the
DraftState - Have a
setState(DocumentState $state): voidmethod to change the current state - Have a
publish(): stringmethod that delegates to the current state'spublish()method - Have a
getStatus(): stringmethod that returns the current state's status
- Accept a title in its constructor and start in the
main.php— Include the Document file. You'll receive one input: the number of times to callpublish()on the document.Create a
Documentwith the title"My Article". First, print the initial status. Then, callpublish()the specified number of times, printing the result of each call on a new line. Finally, print the final status.Output format:
Status: [initial status] [publish result 1] [publish result 2] ... Status: [final status]
Each state class knows which state comes next in the workflow, keeping the transition logic organized and easy to modify. Adding a new state like "Archived" would simply mean creating a new class and updating the relevant transitions.
Cheat sheet
The State Pattern is a behavioral design pattern that allows an object to change its behavior when its internal state changes by encapsulating each state in its own class.
Define a state interface that all state classes will implement:
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}Create concrete state classes that implement the interface:
<?php
class PendingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ProcessingState());
return "Order moved to processing";
}
public function getStatus(): string {
return "Pending";
}
}
class ProcessingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ShippedState());
return "Order has been shipped";
}
public function getStatus(): string {
return "Processing";
}
}
class ShippedState implements OrderState {
public function proceed(Order $order): string {
return "Order already shipped";
}
public function getStatus(): string {
return "Shipped";
}
}Create a context class that holds a reference to the current state and delegates calls to it:
<?php
class Order {
private OrderState $state;
public function __construct() {
$this->state = new PendingState();
}
public function setState(OrderState $state): void {
$this->state = $state;
}
public function proceed(): string {
return $this->state->proceed($this);
}
public function getStatus(): string {
return $this->state->getStatus();
}
}Usage example:
$order = new Order();
echo $order->getStatus() . "\n"; // Pending
echo $order->proceed() . "\n"; // Order moved to processing
echo $order->getStatus() . "\n"; // Processing
echo $order->proceed() . "\n"; // Order has been shipped
echo $order->getStatus(); // ShippedThe State Pattern eliminates complex switch statements and makes adding new states straightforward. Each state class is responsible for knowing which state comes next.
Try it yourself
<?php
require_once 'Document.php';
// Read input - number of times to call publish()
$n = intval(trim(fgets(STDIN)));
// TODO: Create a Document with title "My Article"
// TODO: Print the initial status in format: "Status: [status]"
// TODO: Call publish() n times, printing each result on a new line
// TODO: Print the final status in format: "Status: [status]"
?>This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe $this KeywordMethodsPropertiesConstructor (__construct)Destructor (__destruct)Recap - Simple Calculator4Inheritance
Basic InheritanceThe parent:: KeywordMethod OverridingThe final KeywordAbstract ClassesRecap - Employee Hierarchy7Encapsulation
Public, Protected, PrivateAccess Modifiers in DepthGetters and SettersInformation HidingConstructor Promotion (8.0)Recap - Student Records System10Advanced OOP Concepts
Composition vs InheritanceDependency InjectionAnonymous ClassesEnums (PHP 8.1)Fibers (PHP 8.1)Object Cloning Deep DiveGenerators & Iterators13Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRepository Pattern2Namespaces & Autoloading
Introduction to NamespacesThe use KeywordPSR-4 Autoloading StandardComposer AutoloaderRecap - Organized Project5Interfaces & Contracts
Introduction to InterfacesImplementing InterfacesMultiple Interface ImplementInterface vs Abstract ClassType Hinting with InterfacesRecap - Shape Calculator8Magic Methods
Magic Methods Introduction__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Object Cloning__serialize & __unserializeRecap - Custom Collection11Type System & Error Handling
Type DeclarationsNullable TypesUnion & Intersection TypesException ClassesCustom Exception HierarchyTry, Catch, FinallyRecap - Form Validator14Project: Library Management
Project OverviewBook and User Classes3Class Properties
Instance vs Static PropertiesConstants in ClassesStatic Methods & PropertiesPrivate & Protected PropertiesReadonly Properties (PHP 8.1)Recap - Bank Account Manager6Polymorphism
Method Overriding RevisitedPolymorphism via InterfacesType Hinting & Union TypesLate Static BindingRecap - Payment Processor9Traits
Introduction to TraitsUsing Multiple TraitsTrait Conflict ResolutionAbstract Methods in TraitsTraits vs Inheritance12Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern