Design Pattern: State Pattern in PHP

State pattern implements a system where an object is able to change its behavior based on state(one or more properties) of that object. The behavior can change completely or partially.

This article demonstrates State pattern implementations in PHP. Check the following examples.

Implementation

Follow the steps below for State pattern implementation-

  • Create abstract class (or interface if that suites the requirement) for the states.
  • Create state classes for each of the steps/states and extend the abstract state class. 
  • Create class for context handling. Implement processing for handling different states.

Here is a simple example of state pattern implementation-

<?php
// State pattern implementation in PHP

// State abstract class
abstract class State {
    protected Context $context;

    public function __construct(Context $context) {
        $this->context = $context;
        $this->context->setState($this);
    }

    abstract function actionOne(): void;
    abstract function actionTwo(): void;
}

// Context class
class Context {
    private ?State $state;

    public function setState(State $state): void {
        $this->state = $state;
    }

    public function getState(): ?State {
        return $this->state;
    }

    public function performActionOne(): void {
        $this->state?->actionOne();
    }

    public function performActionTwo(): void {
        $this->state?->actionTwo();
    }
}

// First state class
class ConcreteStateOne extends State {
    public function __construct(Context $context) {
        parent::__construct($context);
    }

    public function actionOne(): void {
        echo "Calling 'actionOne' of - 'ConcreteStateOne'\n";
    }

    public function actionTwo(): void {
        echo "Calling 'actionTwo' of - 'ConcreteStateOne'\n";
    }
}

// Second state class
class ConcreteStateTwo extends State {
    public function __construct(Context $context) {
        parent::__construct($context);
    }

    public function actionOne(): void {
        echo "Calling 'actionOne' of - 'ConcreteStateTwo'\n";
    }

    public function actionTwo(): void {
        echo "Calling 'actionTwo' of - 'ConcreteStateTwo'\n";
    }
}


// Demo
$context = new Context();

new ConcreteStateOne($context);
$context->performActionOne();

// Change state class for the context
new ConcreteStateTwo($context);
$context->performActionOne();
$context->performActionTwo();

Output of this will be as below-

Calling 'actionOne' of - 'ConcreteStateOne'


Calling 'actionOne' of - 'ConcreteStateTwo'
Calling 'actionTwo' of - 'ConcreteStateTwo'

Examples

Here are a few examples of state pattern implementation in PHP-

Example #1: Order State Change

Here we are implementing an order processing system. This will move the order to next state after each processing.

As the state is changed after each step of processing, so the behavior of the object also changes and calling the same processing method will perform the processing of the next step.

Order State

  • Create file “OrderState.php”.
  • Create abstract class “OrderState”.
  • Define a protected property “$context” of type “OrderContext” (this class is defined later). Accept and set “$context” in the constructor.
  • In the constructor call the “setState” of the context and pass the current object($this) to it.
  • Declare an abstract function named “process”.
<?php
// OrderState.php

namespace BigBoxCode\DesignPattern\State\Order;

abstract class OrderState {

    protected OrderContext $context;

    public function __construct(OrderContext $context) {
        $this->context = $context;

        $this->context->setState($this);
    }

    abstract function process(): void;
}

Order Check State Class

  • Create file “OrderCheckState.php”.
  • Define class “OrderCheckState”.
  • Extend “OrderState” for the class.
  • In the “process” method implement execute the process/steps for the initial checking of order info, then set the state of the context to the next state- In Progress state.
<?php
// OrderCheckState.php

namespace BigBoxCode\DesignPattern\State\Order;


class OrderCheckState extends OrderState {
    public function __construct(OrderContext $context) {
        parent::__construct($context);
    }

    public function process(): void {
        // Write code to process the order
        echo "Checking the order validity and other information\n";

        $this->context->setState($this->context->getOrderInProgressState());
    }
}

Order In-Progress State Class

  • Create file “OrderInProgressState.php”.
  • Define class “OrderInProgressState” and extend abstract class “OrderState”.
  • In the “process” method implement execute the process/steps for the initial checking of order info, then set the state of the context to the next state- In Deliver state.
<?php
// OrderInProgressState.php

namespace BigBoxCode\DesignPattern\State\Order;


class OrderInProgressState extends OrderState {
    public function __construct(OrderContext $context) {
        parent::__construct($context);
    }

    public function process(): void {
        // Write code to process the order
        echo "Processing the order\n";

        $this->context->setState($this->context->getOrderDeliverState());
    }
}

Order Deliver State Class

  • Create file “OrderDeliverState.php”.
  • Define class “OrderDeliverState” and extend abstract class “OrderState”.
  • In the “process” method after performing deliver state processing, Change the order to next state.
<?php
// OrderDeliverState.php

namespace BigBoxCode\DesignPattern\State\Order;


class OrderDeliverState extends OrderState {
    public function __construct(OrderContext $context) {
        parent::__construct($context);
    }

    public function process(): void {
        // Write code to process the order
        echo "Delivering the order\n";

        $this->context->setState($this->context->getOrderReceiveState());
    }
}

Order Receive State Class

  • Create file “OrderReceiveState.php”.
  • Define class “OrderReceiveState” and extend abstract class “OrderState”.
  • In the “process” method after performing deliver state processing, Change the order to next state.
<?php
// OrderReceiveState.php

namespace BigBoxCode\DesignPattern\State\Order;


class OrderReceiveState extends OrderState {
    public function __construct(OrderContext $context) {
        parent::__construct($context);
    }

    public function process(): void {
        // Write code to process the order
        echo "Order received\n";

        $this->context->setState(null);
    }
}

Order Context Class

  • Create file “OrderContext.php”.
  • Define class “OrderContext”.
  • Define properties “$state” (for storing the current state) and other properties for each state. All these properties will be of “OrderState”.
  • In the constructor initialize all the properties, by creating and assigning new objects of those state classes.
  • In the constructor set the state(initial state) to check the state.
  • Define method for getting and setting “$state”.
  • Define methods for getting the objects for different states.
  • Define method “runNextProcess”. If the current state is set and available then call the “process” from the state, else return a message that the order processing is complete.
<?php
// OrderContext.php

namespace BigBoxCode\DesignPattern\State\Order;


class OrderContext {
    private ?OrderState $state;
    private OrderState $orderCheckState;
    private OrderState $orderInProgressState;
    private OrderState $orderDeliverState;
    private OrderState $orderReceiveState;

    public function __construct() {
        $this->orderCheckState = new OrderCheckState($this);
        $this->orderInProgressState = new OrderInProgressState($this);
        $this->orderDeliverState = new OrderDeliverState($this);
        $this->orderReceiveState = new OrderReceiveState($this);

        // Set the placed state as default
        $this->state = $this->orderCheckState;
    }

    public function setState(?OrderState $state): void {
        $this->state = $state;
    }

    public function getState(): OrderState|null {
        return $this->state;
    }

    public function getOrderCheckState(): OrderState {
        return $this->orderCheckState;
    }

    public function getOrderInProgressState(): OrderState {
        return $this->orderInProgressState;
    }

    public function getOrderDeliverState(): OrderState {
        return $this->orderDeliverState;
    }

    public function getOrderReceiveState(): OrderState {
        return $this->orderReceiveState;
    }

    public function runNextProcess(): void {
        if ($this->state != null) {
            $this->state->process();
        } else {
            echo "Order processing complete\n";
        }
    }
}

Demo

In the client, create an object of “OrderContext”. Then call “runNextProcess” method, which will execute the current processing steps and move the order to the next state.

Once all the steps are complete, then calling the “runNextProcess” method will not do anything.

<?php
// demo.php

require __DIR__ . '/../../vendor/autoload.php';

use BigBoxCode\DesignPattern\State\Order\OrderContext;


$order = new OrderContext();

$order->runNextProcess();
$order->runNextProcess();
$order->runNextProcess();
$order->runNextProcess();

// Trying to process after all steps are complete
$order->runNextProcess();

Output

Following output will be generated-

Checking the order validity and other information
Processing the order
Delivering the order
Order received

Order processing complete

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check State pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.