container = $_SESSION['franksContainer']; } else { $this->container = new \stdClass(); } if(isset($_POST['email'])) { if(filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) !== FALSE) { $this->container->userEmail = $_POST['email']; } } if(isset($this->container->userEmail)) { if(isset($_POST['register'])) { if($_POST['register'] == 'register') { $this->container->registered = REGISTERED; } elseif($_POST['register'] == 'decline') { $this->container->registered = DECLINED; } } } $_SESSION['franksContainer'] = $this->container; } /** * The getState() method will be implemented by each class that extends this * class. getState() returns the next decision state, form or final message. */ abstract public function getState(); /** * The __destruct() uses the TrackProgress class to provide the class name and * parent class of the state object the user is currently in and store it; * this data will be displayed in the HTML view below. */ function __destruct() { TrackProgress::trackObjects(new \ReflectionClass($this)); } } /** * Description of AbstractDecisionState * * AbstractDecisionState objects process conditionals to determine the next * state the user should move to. The returned $newState property will always * be a state object. * */ abstract class AbstractDecisionState extends AbstractRegistrationState { protected $newState; public function getState() { $this->getDecision(); return $this->newState; } /** * The getDecision() method sets the value for the $newState property, which * will be returned by getState(). */ abstract protected function getDecision(); } /** * Description of AbstractFormState * * AbstractFormState objects produce forms. These forms are terminal states for * the state machine process, indicating that the state machine needs user input * to continue. When the user provides input through these forms, the state * machine moves the user through the states again, and the process continues * until no more user input is needed and a final message is displayed. * */ abstract class AbstractFormState extends AbstractRegistrationState { protected $newForm; public function getState() { $this->getForm(); $form = '
'; $form .= $this->newForm; $form .= '
'; return $form; } /** * The getForm() method sets the value for the $newForm property, which * will be returned by getState() as part of the generated form. */ abstract protected function getForm(); } /** * Description of AbstractFinalState * * AbstractFinalState objects display messages regarding the final state of the * state machine. At this point, the user has provided all the information * needed, and the process ends. * */ abstract class AbstractFinalState extends AbstractRegistrationState { protected $finalMsg; public function getState() { $this->getMsg(); $this->finalMsg .= 'Reset'; return $this->finalMsg; } /** * The getMsg() method sets the value for the $finalMsg property, which * will be returned by getState(). */ abstract protected function getMsg(); /** * The __destruct() method destroys the session data that we have been using * to persist data through form submissions. Since this is the final state * of the process, we no longer need this data. */ function __destruct() { parent::__destruct(); $_SESSION['franksContainer'] = null; } } /** * Description of HasResponded * * HasResponded is the initial decision state that will be called by * RegistrationContext. This class lets us know whether the user has decided to * register for the event or decline to attend. * */ class HasResponded extends AbstractDecisionState { public function getDecision() { if(isset($this->container->registered)) { $this->newState = new IsRegisteredOrDeclined(); } else { $this->newState = new IsKnownUser(); } } } /** * Description of IsRegisteredOrDeclined * * IsRegisteredOrDeclined sets the message that will display the final state to * the user. * */ class IsRegisteredOrDeclined extends AbstractDecisionState { public function getDecision() { if($this->container->registered == REGISTERED) { $this->newState = new RegistrationMsg(); } elseif($this->container->registered == DECLINED) { $this->newState = new DeclinedMsg(); } } } /** * Description of IsKnownUser * * IsKnownUser determines the next form displayed to the user. * */ class IsKnownUser extends AbstractDecisionState { public function getDecision() { if(isset($this->container->userEmail)) { $this->newState = new RegisterForm(); } else { $this->newState = new EmailForm(); } } } /** * Description of RegisterForm * * RegisterForm creates the form for the user to register or decline. * */ class RegisterForm extends AbstractFormState { public function getForm() { $this->newForm = 'Please register or decline: ' . ''; } } /** * Description of EmailForm * * EmailForm creates the form to get the user's email address. * */ class EmailForm extends AbstractFormState { public function getForm() { $this->newForm = 'Email address: ' . ''; } } /** * Description of RegistrationMsg * * RegistrationMsg creates the message that the user has registered for the * event. * */ class RegistrationMsg extends AbstractFinalState { public function getMsg() { $this->finalMsg = '

Thank you.

' . $this->container->userEmail . ' has registered for this event.

'; } } /** * Description of DeclinedMsg * * DeclinedMsg creates the message that the user has declined to attend the * event. * */ class DeclinedMsg extends AbstractFinalState { public function getMsg() { $this->finalMsg = '

Thank you.

' . $this->container->userEmail . ' has declined to attend this event.

'; } } /** * Description of TrackProgress * * TrackProgress is a Singleton class that provides a static method to * facilitate view access to the path the user follows through each object in * the state machine. * */ class TrackProgress { private static $instance; private function __construct() {} private function __clone() {} public static function trackObjects(\ReflectionClass $reflection = null) { if(!is_null($reflection)) { static::$instance[] = $reflection; } return static::$instance; } } /** * Description of RegistrationContext * * RegistrationContext begins the state machine process by calling HasResponded. * Some state machine implementations would have the context class inherit the * base abstract class of the state machine and implement each of its abstract * methods, which gives the programmer a means to write scripts that carefully * control the movement through the state machine. In this example, however, * we don't know what the next state should be; this is determined by user * response. Therefore, we set up a loop here that moves the user through the * state machine until a terminal point is reached, either a form for more user * input or a final message. * */ class RegistrationContext { public function stateMachine() { $state = new HasResponded(); while($state instanceof AbstractRegistrationState) { $state = $state->getState(); } return $state; } } //Sets session if not already started. if(session_status() == PHP_SESSION_NONE || session_id() == '') { session_start(); } //Defines constants. define('REGISTERED', 1); define('DECLINED', 2); //Instantiates RegistrationContext to start state machine. $context = new RegistrationContext(); $final_state = $context->stateMachine(); echo ' Frank\'s State Machine
' . $final_state . '

State Machine Results: We have flowed through the state objects below to reach a form or final message. Note the parent classes of each state object: each AbstractDecisionState always ultimately leads to either an AbstractFormState or an AbstractFinalState.


'; foreach(TrackProgress::trackObjects() as $key => $val) { echo 'Current State: ' . $val->getName() . '
Parent Abstract Class: ' . $val->getParentClass()->getName() . '

'; } echo '
';