Menu
Coddy logo textTech

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
Shipped

The 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 icon

Challenge

Easy

Let'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 a DocumentState interface that establishes the contract for all document states. It should have two methods: publish(Document $document): string for attempting to advance the document, and getStatus(): string for returning the current state name.
  • States.php — Include the DocumentState interface and create three state classes that implement it:
    • DraftState — When publish() is called, it transitions the document to ReviewState and returns "Document sent for review". Its status is "Draft".
    • ReviewState — When publish() is called, it transitions the document to PublishedState and returns "Document published". Its status is "Under Review".
    • PublishedState — When publish() is called, it returns "Document is already published" without changing state. Its status is "Published".
  • Document.php — Include the States file and create a Document class that acts as the context. Your document should:
    • Accept a title in its constructor and start in the DraftState
    • Have a setState(DocumentState $state): void method to change the current state
    • Have a publish(): string method that delegates to the current state's publish() method
    • Have a getStatus(): string method that returns the current state's status
  • main.php — Include the Document file. You'll receive one input: the number of times to call publish() on the document.

    Create a Document with the title "My Article". First, print the initial status. Then, call publish() 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();          // Shipped

The 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]"

?>
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming