Dans mon précédent article, je vous ai présenté le design pattern Etat. Il est temps de vous présenter une méthode pour l’implémenter dans vos projets PHP.
J’ai écrit la bibliothèque States qui permet d’implémenter facilement ce comportement à vos objets, sans à avoir recours à une extension PHP tierce.
Nous pouvons l’installer facilement avec composer avec la commande suivante :
composer require teknoo/states
Présentation
Dans la bibliothèque States, les classes Context sont appelés Proxy et nécessite l’implémentation de l’interface ProxyInterface. Un trait est fourni pour vous éviter de l’écrire vous même.
Depuis la version 3, la bibliothèque nécessite PHP7.1 ou plus, les classes Proxy sont autonomes. Dans les versions précédentes, elles nécessitent une factory pour être instanciées.
Les états sont représentés par des classes implémentant l’interface StateInterface. Un trait est également disponible. Ces dernières doivent être listées dans le proxy, dans la méthode statique statesListDeclaration.
Les méthodes de ces états seront les méthodes appelées par notre objet Proxy. Les mots clés $this->, self:: et static:: représenteront l’objet (l’instance) Proxy ou la classe et non la classe State.
La visibilité des attributs de la classe Proxy, ainsi que des méthodes de cette même classe et de ses états sont conservés. Ainsi un élément protected ne sera accessible qu’aux méthodes de la classe et à ses enfants, les éléments private uniquement aux méthodes de ladite classe.
Les classes enfants à la classe Proxy hérite des états de leurs classes parentes. Il est possible de les étendre ou de les redéfinir.
Attention,Suite à une restriction interne à PHP, les méthodes des états doivent retourner une closure implémentant le corps de la méthode. C’est cette dernière qui sera appelée.
De plus, aucun attribut ne peut être déclaré dans ces états, ils seront ignorés.
Implementation
En suivant notre pseudo code de l’article précédent, son implémentation nous donne :
<?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
Automatisation
La bibliothèque propose un mécanisme permettant de sélectionner automatiquement le ou les états appropriés en fonction des attributs de l’objet. La sélection s’effectue à l’aide d’une liste d’assertions à définir via la méthode listAssertions().
La sélection n’est cependant pas exécuté automatiquement, mais uniquement lors de l’appel à la méthode updateStates(). Cette dernière peut être appelé à tout moment, dans le constructeur, avant ou après chaque appel, dans une méthode de la classe Proxy ou de ses états, voir depuis une méthode ou fonction extérieure.
Nous pouvons adapter notre classe Person pour parler la langue de son pays natal lorsque son interlocuteur ne lui spécifie pas le langage demandé.
<?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
Avec cette ultime évolution, notre objet va automatiquement parler la langue de son pays. Il est possible d’aller plus loin, la bibliothèque implémente d’autres assertions, basées sur les opérations numériques, ou sur des closures.
Cette évolution implémente également une variante. L’interlocuteur lui indique via la méthode spoke() la langue désirée, elle n’est plus passée en paramètre. Les méthodes hello() et birthday() sont directement implémentées dans les états et donc accessibles (si l’état est actif).
Commentaires

eee
aaaa

ss
dd

www
eee

aaaa
aa

sdfsdf@sdf.fr
sdf