0

For teaching purpose, I would like to create a simple implementation of State Pattern using PHP 7.4. So, I've tried to create a simple "document state machine" starting with Draft, sending to review and, after three "votes", can be published:

<?php

namespace StatePatternPHP;

interface DocumentManagement {
    public function review();
    public function approve();
    public function reject();
    public function publish();
}

abstract class State implements DocumentManagement {
    protected Document $document;
    public function __construct(Document $document) {
        $this->document = $document;
    }
    public function review() {
        throw new Exception("Document cannot be reviewed in this current state (". get_class($this).")");
    }
    public function approve() {
        throw new Exception("Document cannot be approved in this current state (". get_class($this).")");
    }

    public function publish() {
        throw new Exception("Document cannot be published in this current state (". get_class($this).")");
    }

    public function reject() {
        throw new Exception("Document cannot be rejected in this current state (". get_class($this).")");
    }
}

class Document implements DocumentManagement {

    private string $content;
    private State  $currentState;
    private int $approvals = 0;

    public function __construct(string $content) {
        $this->content = $content;
        $this->currentState = new Draft($this);
    }
    public function getContent(): string {
        return $this->content;
    }
    public function setContent(string $content){
        $this->content = $content;
        $this->currentState = new Draft($this);
        $this->approvals = 0;
    }
    public function setState(State $state){
        $this->currentState = $state;
    }
    public function addApproval(){
        $this->approvals++;
    }
    public function disapprove(){
        $this->approvals--;
    }
    public function getApprovals(){
        return $this->approvals;
    }
    public function review() {
        $this->currentState->review();
    }
    public function approve() {
        $this->currentState->approve();
    }
    public function publish() {
        $this->currentState->publish();
    }
    public function reject() {
        $this->currentState->reject();
    }

}

class Draft extends State {

    public function review() {
        $this->document->setState(new InReview($this->document));
    }
}

class InReview extends State {

    public function approve() {
        $this->document->addApproval();
    }
    public function publish() {
        if($this->document->getApprovals() > 2){ //needs 3 votes at least
            $this->document->setState(new Published($this->document));
        }else{
            parent::publish();
        }
    }
    public function reject() {
        $this->document->disapprove();
    }

}

class Published extends State {
    public function __construct(Document $document) {
        parent::__construct($document);
        print('document published !');
    }
}

$document = new Document("hello world !");
$document->review();
$document->approve();
$document->approve();
$document->approve();
$document->publish();
  • So, Could this be considered a valid State GoF Pattern implementation ?
  • Is this a valid S.O.L.I.D too? My main fear is about how open is to new state...
  • Is there any kind of improvement to do on it?

The sand-boxed version is here.

0

So, Could this be considered a valid State GoF Pattern implementation ?

Yes, you have a valid implementation of the GoF State pattern.

Is this a valid S.O.L.I.D too? My main fear is about how open is to new state...

In my view, yes, your code conforms with the SOLID principles. When adding a new state, there is no way of using that new state without modifying some existing code to implement the transition(s) to that new state. This is inherent to how statemachines work.

Is there any kind of improvement to do on it?

  • It would be nice if a client class of Document can check if a particular action is allowed before trying to perform it and getting an exception. Something along the lines of canPublish() that returns a boolean result if calling publish() will likely succeed.

  • The line if($this->document->getApprovals() > 2){ //needs 3 votes at least can also be written as if($this->document->getApprovals() >= 3){ and then doesn't need the comment.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.