In my previous article, I presented the design pattern State. It’s time to introduce a method to implement it in your PHP projects.
I wrote the library States which allows to easily implement this behavior to your objects, without having to use a third-party PHP extension.
We can install it easily with the following command:
composer require teknoo/states
Presentation
In the States library, Context classes are called Proxy and require the implementation of the interface ProxyInterface. A trait is provided to avoid writing it yourself.
Since version 3, the library requires PHP7.1 or higher, Proxy classes are autonomous. In previous versions, they require a factory to be instantiated.
The states are represented by classes implementing the interface StateInterface. A trait is also available. States classes must be listed in the Proxy class, in the static method statesListDeclaration.
Methods of these states will be methods called by our object Proxy. The keywords $this->, self:: and static:: will represent the Proxy object (or instance) or class and not the State class / object.
The visibility of Proxy’s attributes, of Proxy’s methods and all methods in states are preserved. A protected element will be accessible only to the methods of the class and to its children, private elements only to the methods of the class.
Children classes of the Proxy class inherit of states from their parents classes. It is possible to extend or redefine them.
Attention,Because of an internal PHP restriction, state methods must return a closure implementing the body of the method. It’s this closure will be called alter.
Moreover, no attribute can be declared in these states, they will be ignored.
Implementation
By following our pseudo code of the previous article, we can write this implementation:
<?php
namespace Acme;
require 'vendor/autoload.php';
use Closure;
use DateTimeImmutable;
use DateTimeInterface;
use Teknoo\States\Proxy\ProxyInterface;
use Teknoo\States\Proxy\ProxyTrait;
use Teknoo\States\State\StateInterface;
use Teknoo\States\State\StateTrait;
class French implements StateInterface
{
use StateTrait;
private function sayHello(): Closure
{
return function(string $you): string {
return 'Bonjour ' . $you . ', ' . $this->name . ', je viens de ' . $this->country;
};
}
private function displayDate(): Closure
{
return function(): string {
return $this->birthday->format('d m Y');
};
}
}
class English implements StateInterface
{
use StateTrait;
private function sayHello(): Closure
{
return function(string $you): string {
return 'Hello ' . $you . ', ' . $this->name . ', I come from ' . $this->country;
};
}
private function displayDate(): Closure
{
return function(): string {
return $this->birthday->format('m d, Y');
};
}
}
class Person implements ProxyInterface
{
use ProxyTrait;
public function __construct(
private string $name,
private DateTimeInterface $birthday,
private string $country
) {
$this->initializeStateProxy();
}
protected static function statesListDeclaration(): array
{
return [
French::class,
English::class,
];
}
private function selectLanguage(string $country): self
{
if ('France' === $country) {
return $this->switchState(French::class);
}
return $this->switchState(English::class);
}
public function hello(string $country, string $you)
{
return $this->selectLanguage($country)->sayHello($you);
}
public function birthday(string $country)
{
return $this->selectLanguage($country)->displayDate();
}
}
$frenchMan = new Person('Roger', new DateTimeImmutable('1970-04-10'), 'France');
echo $frenchMan->hello('France', 'Jean').PHP_EOL;
echo $frenchMan->birthday('England').PHP_EOL;
//Display
//Bonjour Jean, Roger, je viens de France
//04 10, 1970
Automating
This library provides also a mechanism to select automatically the appropriate state(s) according to the attributes of the object. The selection is made using a list of assertions to be defined via the static method listAssertions().
The selection is not executed automatically, however, only when calling the method updateStates(). It can be called at any time, in the constructor, before or after each call, in a method of the Proxy class or its states, or from an external method or function.
We can adapt our class Person to speak the language of the native country when its interlocutor does not specify the required language.
<?php
namespace Acme;
require 'vendor/autoload.php';
use Closure;
use DateTimeImmutable;
use DateTimeInterface;
use Teknoo\States\Automated\AutomatedInterface;
use Teknoo\States\Automated\AutomatedTrait;
use Teknoo\States\Automated\Assertion\Property;
use Teknoo\States\Automated\Assertion\Property\IsEqual;
use Teknoo\States\Proxy\ProxyInterface;
use Teknoo\States\Proxy\ProxyTrait;
use Teknoo\States\State\StateInterface;
use Teknoo\States\State\StateTrait;
class French implements StateInterface
{
use StateTrait;
public function hello(): Closure
{
return function($you): string {
return 'Bonjour ' . $you . ', ' . $this->name . ', je viens de ' . $this->country;
};
}
public function birthday(): Closure
{
return function(): string {
return $this->birthday->format('d m Y');
};
}
}
class English implements StateInterface
{
use StateTrait;
public function hello(): Closure
{
return function($you): string {
return 'Good morning ' . $you . ', ' . $this->name . ', I come from ' . $this->country;
};
}
public function birthday(): Closure
{
return function(): string {
return $this->birthday->format('m d, Y');
};
}
}
class Person implements ProxyInterface, AutomatedInterface
{
use ProxyTrait;
use AutomatedTrait;
public function __construct(
private string $name,
private DateTimeInterface $birthday,
private string $country
) {
$this->initializeStateProxy();
$this->updateStates();
}
protected static function statesListDeclaration(): array
{
return [
French::class,
English::class,
];
}
protected function listAssertions(): array
{
return [
(new Property([English::class]))
->with('country', new IsEqual('England')),
(new Property([French::class]))
->with('country', new IsEqual('France')),
];
}
public function spoke(string $country): self
{
$this->country = $country;
return $this->updateStates();
}
}
$frenchMan = new Person('Roger', new DateTimeImmutable('1970-04-10'), 'France');
echo $frenchMan->hello('Jean').PHP_EOL;
echo $frenchMan->spoke('England')->birthday().PHP_EOL;
//Display
//Bonjour Jean, Roger, je viens de France
//04 10, 1970
With this last evolution, our object will automatically speak the language of user’s country. It is possible to go further, the library implement other assertions, based on digital operations, or on closures.
This evolution also implements a variant. The interlocutor tells it, via the method spoke(), the required language, it is no longer passed as a parameter. The method hello() and birthday() methods are directly implemented in states and are accessible if the state is enabled.
Comments

eee
aaaa

ss
dd

www
eee

aaaa
aa

sdfsdf@sdf.fr
sdf