1. Anti pattern Flag parameter


Le flag parameter peut être considéré comme un anti-pattern.
Le principe est de passer un argument supplémentaire (le plus souvent de type boolean) à une méthode afin que celle-ci se comporte différemment selon ce paramètre.

Par exemple, supposons cette fonction :

function render(array $data, bool $flag)
{
    if ($flag) {
        // Premier comportement.
    } else {
        // Second comportement.
    }
}

// Code appelant
$data = getData();
$flag = getFlag();
render($data, $flag);


1.1. Solution 1 : découper la fonction en deux.

La fonction render doit être découpée en deux et c'est à l'appelant de ces fonctions de faire le if/else qui convient.

function firstRender(array $data)
{
    // Premier comportement.
}
function secondRender(array $data)
{
    // Second comportement.
}

// Code appelant
$data = getData();
$flag = getFlag();

if ($flag) {
    firstRender($data);
} else {
    secondRender($data);
}


1.2. Solution 2 : fusionner les deux paramètres

Si la fonction n'est pas simple à découper (plusieurs if/else interne). Même s'il conviendrait de la refactoriser, il peut être pertinent dans certaines conditions de faire porter la valeur du flag soit dans un objet que l'on transfère à la fonction, soit dans la classe qui contient la méthode.

1.2.1 Cas 1 : via un objet

class Renderer
{
    public function render(object $object)
    {
        $flag = $object->getFlag();
        // Suite des différentes conditions.
    }
}


1.2.2 Cas 2 : via un attribut de la classe

class Renderer
{
    protected bool $flag;

    function setFlag(bool $flag)
    {
        $this->flag = $flag;
    }

    function getFlag()
    {
        return $this->flag;
    }

    function render(object $object)
    {
        $flag = $this->getFlag();
        // Suite des différentes conditions.
    }
}


1.3. Solution 3 : créer plusieurs classes et une factory ou équivalent

Chaque classe possède un comportement propre. Ce comportement contient la logique selon le cas flag=true|false.
Le rôle de la Factory sera alors de fournir la bonne instance en fonction du paramètre flag.

// Code appelant
$rendererFactory = new RendererFactory();
$renderer = $rendererFactory->create($flag);
$renderer->render($data);


🧙‍♂️️Si la fonction existe déjà, il peut être long de tout refaire, il faut à ce moment là prendre un peu de temps de réflexion quant à l'intérêt de tout refaire pour un gain immédiat souvent faible.
Privilégier donc la réfacto des morceaux de code importants (à vous de définir cette importance en fonction du contexte).


1.4. Solution type Troll

🧙‍♂️️Cette solution n'est évidemment pas conseillée.


L'idée est de créer une instance Flag et de la passer à la fonction ; ainsi nous ne passons plus un boolean et donc nous sortons de l'anti-pattern.

<?php

class Flag
{
    protected bool $value;

    public function __construct(bool $value)
    {
        $this->value = $value;
    }

    public function isFalse(): bool
    {
        return !$this->isTrue();
    }

    public function isTrue(): bool
    {
        return true === $value;
    }
}

// Code appelant
$data = getData();
$flag = new Flag(true);
render($data, $flag);

2. Liste des anti patterns

Voici la liste des anti patterns les plus populaires.

2.1. 20 antipatterns généraux

* Accidental complexity
* Action at a distance
* Boat anchor
* Busy waiting
* Caching failure
* Cargo cult programming
* Coding by exception
* Design pattern
* Error hiding
* Flag parameter
* Hard code
* Lasagna code
* Lava flow
* Loop-switch sequence
* Magic numbers
* Magic strings
* Repeating yourself
* Shooting the messenger
* Shotgun surgery
* Soft code
* Spaghetti code

2.2. 12 antipatterns POO

* Anemic domain model
* Call super
* Circle–ellipse problem
* Circular dependency
* Constant interface
* God object
* Object cesspool
* Object orgy
* Poltergeists
* Sequential coupling
* Singleton Pattern
* Yo-yo problem

2.2.1 9 antipatterns méthodologiques

* Copy and paste programming
* Golden hammer
* Invented here
* Not invented here (NIH) syndrome
* Premature optimization
* Programming by permutation
* Reinventing the square wheel
* Silver bullet
* Tester-driven development

3. Anti Pattern : LoopSwitch


3.1. Exemple

Voici un exemple d'implémentation de cet anti pattern.

<?php

namespace App\Core;

/**
 * Class LoopSwitch
 */
class LoopSwitch
{
    protected const FIRST_STEP = 'first_step';
    protected const SECOND_STEP = 'second_step';

    /**
     * @var string[]
     */
    protected array $steps = [
        self::FIRST_STEP,
        self::SECOND_STEP,
    ];

    /**
     * Executes All the steps.
     */
    public function executeAllSteps()
    {
        foreach($this->steps as $step) {
            $this->executeStep($step) ;
        }
    }

    /**
     * @param string $step
     */
    public function executeStep(string $step)
    {
        switch ($step) {
            case self::FIRST_STEP:
                echo self::FIRST_STEP;
                break;
            case self::SECOND_STEP:
                echo self::SECOND_STEP;
                break;
        }
    }
}

4. Prototype

4.1. Introduction

Le Prototype est un design pattern très simplement accessible en PHP puisque l'usage de la méthode __clone est une application de celui-ci.

Le Prototype est la capacité d'un Object de se cloner en un nouvel Object qui hérite de l'état de l'Object cloné.

A noter que le __clone crée une nouvelle référence pour l'Object copié.

Dans le cas où un Object fait référence à d'autres Objects (par exemple un Attribute "Color $color" ), cette référence est copiée, ce qui peut être problématique si l'on modifie la valeur de l'Object référencé.

4.2. Le problème

Supposons qu'on a une Class Pencil qui aurait un Attribute Color :

Pencil.php
<?php
class Pencil implements PencilInterface
{
    protected ColorInterface $color;

    public function __construct(ColorInterface $color) 
    {
        $this->color = $color;
    }

    public function getColor(): ColorInterface 
    {
        return $this->color;
    }
}

PencilInterface.php
<?php
interface PencilInterface 
{
    public function getColor(): ColorInterface;
}

ColorInterface.php
<?php
interface ColorInterface 
{
    public function getValue(): string;
}

Color.php
<?php
class Color implements ColorInterface 
{
    public function __construct($value)
    {
        $this->value = $value;
    }

    public function setValue(string $value)
    {
        $this->value = $value;
    }

    public function getValue(): string
    {
        return $this->value;
    }
}


Voici un comportement problématique :

    $color = new Color('red');
    $firstPencil = new Pencil($color);
    $clonedPencil = clone $firstPencil;
    $color->setValue('green');
    echo $clonedPencil->getColor()->getValue(); // -> 'green'
    echo $firstPencil->getColor()->getValue(); // -> 'green'


Ici, les deux instances de Pencil se retrouvent avec une couleur ayant la valeur verte. Ce que l'on ne souhaitait pas forcément : on souhaite avoir un Pencil ayant une Color 'green' et l'autre ayant une Color 'red'.
La seule façon de résoudre ce problème est de surcharger la méthode __clone afin de cloner également l'instance de Color (et donc obtenir une nouvelle référence différente de la première).

4.3. Solution

Il faut réécrire la méthode __clone de cette façon :

Pencil.php
<?php
class Pencil implements PencilInterface
{
    protected ColorInterface $color;

    public function __construct(ColorInterface $color) 
    {
        $this->color = $color;
    }

    public function getColor(): ColorInterface 
    {
        return $this->color;
    }

    public function __clone() 
    {
        $this->color = clone $this->color;
    }
}


Se faisant, si l'on réexecute le bout de code précédent, on obtient ceci

    $color = new Color('red');
    $firstPencil = new Pencil($color);
    $clonedPencil = clone $firstPencil;
    $color->setValue('green');
    echo $clonedPencil->getColor()->getValue(); // -> 'red'
    echo $firstPencil->getColor()->getValue(); // -> 'green'

Le premier crayon a bien hérité d'une couleur verte, tandis que le second a bien une couleur qui a été modifiée en rouge.

4.4. Les ValueObjects

Le ValueObject est un Object immutable, c'est à dire que ces propriétés sont initialisés qu'à un seul moment, la grande majorité du temps dans le constructeur ; il est aussi possible de faire des setters qui ne settent qu'une seule fois la valeur, ce qui peut potentiellement produire un effet de bord selon le test effectué.

4.5. Setter d'un immutable

Voici à quoi pourrait ressembler un tel setter. Reprenons la Class Pencil :
<?php
class Pencil implements PencilInterface
{
protected ?ColorInterface $color = null;

public function __construct(ColorInterface $color)
{
$this->color = $color;
}


public function setColor(?ColorInterface $color = null): PencilInterface
{
if (\is_null($this->color)) {
$this->color = $color;
}

return $this;
}

public function getColor(): ColorInterface
{
return $this->color;
}
}


Ici, on sette la couleur une seule et unique fois, mais étant donné que l'Attribute color peut également être null, il est tout à fait possible de passer une première fois dans la méthode, setter $color à null, puis repasser une autre fois et setter $color avec une instance implémentant ColorInterface.
Le principe d'un Object immutable est de ne plus être modifié après sa création, ce qui potentiellement peut toujours être le cas ici.
Eventuellement, on pourrait rajouter un Attribute indiquant si l'état est fixe ou non, mais cela deviendrait compliqué à gérer s'il fallait identifier pour chaque Attribute si son setter a été ou non appelé. En outre, il faudrait également garantir que cet Attribute n'est setté qu'une seule fois.

Donc la solution de passer par des setters plutôt que par le constructeur n'est pas une bonne idée.

4.6. Comment cloner un Object possédant un Attribute ValueObject ?

Il est difficile de lier les concepts de Prototype et de ValueObject car il faudrait créer une méthode clone ayant des paramètres similaire à ceux du __construct.
Dans le cas de l'Attribute Color, si l'on considère que c'est un ValueObject, il ne serait plus possible de modifier la Color du Pencil cloné, ce qui serait déroutant.

Donc si l'on souhaite cloner un Object possédant un Attribute ValueObject, il faut accepter que cet Attribute sera immutable d'un clone à l'autre.

4.7. Factory+Pool

Pour créer un Pencil avec une Color immutable, on peut passer par une Factory, voire mélanger le pattern design Pool et Factory afin de conserver les différents Pencil créés.

On peut envisager de n'avoir qu'une seule Factory nommé PencilFactory (on pourrait éventuellement avoir une deuxième Factory nommée ColorFactory qui stockerait les différents instances de Color).

Ici, la PencilPoolFactory garde en mémoire les différentes instance de Color créées puis fabrique et retourne des instances de Pencil. Etant donné qu'il change d'état, il n'est donc pas possible de passer par une méthode statique.

<?php
class PencilPoolFactory
{
    /**
    * ColorInterface[]
    */
    protected array $colors = array();

    public function has(string $key)
    {
        return isset($this->colors[$key]);
    }

    public function get(string $colorValue): ColorInterface
    {
        return $this->colors[$colorValue];
    }
  
     public function add(ColorInterface $color, string $colorValue)
     {
         $this->colors[$colorValue] = $color;
     }

    public function create(string $colorValue)
    {
        if (!$this->has($colorValue)) {  
            $this->add(new Color($colorValue), $colorValue);
        }

        return new Pencil($this->get($colorValue));
    }
}


Voici comment l'utiliser :

    $pencilPoolFactory = new PencilPoolFactory();
    $redPencil = $pencilPoolFactory->create('red');
    $greenPencil = $pencilPoolFactory->create('green');

    echo $redPencil->getColor()->getValue(); // 'red'
    echo $greenPencil->getColor()->getValue(); // 'red'


🧙‍♂️️Le PencilPoolFactory mélange deux design patterns mais cela produit un mauvais pattern (le mot antipattern est un peu trop populaire et désigne aujourd'hui des choses eux-mêmes populaires).
D'une première raison : ce PencilPoolFactory fabrique des instances de Color ET des instances de Pencil, il a donc deux responsabilités, ce n'est pas la faute de la combinaison des deux design patterns.
D'une deuxième raison : le PencilPoolFactory garde en mémoire les instances de Color, ce qui lui donne une nouvelle responsabilité car l'utilisateur sera tenté de faire également un $pencilPoolFactory->get('color').


4.7.1 Résolution du problème

Il faut découper le PencilPoolFactory en trois : PencilFactory, ColorFactory et PencilPool. Chacune de ces Classs aura une seule responsabilité.

4.7.1.1 PencilPool avec un Attribute $colorFactory

* Le PencilFactory aura un Attribute Singleton : $pencilPool.
* Le PencilPool aura un Attribute Singleton : $colorFactory.

PencilFactory.php
<?php
class PencilFactory
{
    PencilPoolInterface $pencilPool;

    public function __construct(
        PencilPoolInterface $pencilPool
    ) 
    {
        $this->pencilPool = $pencilPool
    }

    public function create(string $colorValue): PencilInterface
    {
        $color = $this->pencilPool->get($colorValue);

        return new Pencil($color);
    }

    protected function getColor(string $colorValue): ColorInterface
    {
        return $this->pencilPool->get($colorValue);
    }
}



Cette solution est dérangeante. Le PencilPool dispose d'un Attribute autre que celui pour lequel il est dédié, ce qui est déroutant car il stocke des références de ColorInterface ET de PencilFactoryInterface.

La solution inverse semble plus pertinente : le PencilFactory possède un Attribute $pencilPool qui ferait office de stockage des nouvelles instances.

4.7.1.2 ColorFactory avec un Attribute $pencilPool

Le PencilPool se réduit ici à son strict nécessaire : il stocke des instances implémentant ColorInterface

PencilPool.php
<?php
class PencilPool implements PencilPoolInterface
{
    /**
    * ColorInterface[]
    */
    protected array $colors = array();

    public function has(string $key)
    {
        return isset($this->colors[$key]);
    }

    public function get(string $colorValue): ColorInterface
    {
        return $this->colors[$colorValue];
    }
  
    public function add(ColorInterface $color, string $colorValue)
    {
        $this->colors[$colorValue] = $color;
    }
}

Et le PencilFactory possède un Attribute $pencilPool.

PencilFactory.php
<?php
class PencilFactory implements PencilFactoryInterface
{
    protected PencilPool $pencilPool;
    
    public function create(string $colorValue)
    {
        if (!($pencilPool->has($colorValue)) {
            $color = new Color($colorValue);
            $pencilPool->set($color, $colorValue);
        } else {
            $color = $pencilPool->get($colorValue);
        }

        return $color;
    }
}

La PencilFactory demande à sa $pencilPool si la Color existe, auquel cas elle le retourne. Si ce n'est pas le cas, elle la crée et le transmet au pencilPool pour que ce dernier le stocke.

Eventuellement, on pourrait imaginer une nouvelle Class plus complexe, avec un nom un peu ambigue comme PencilManager.

Cette Class ferait le lien entre le PencilFactory, le ColorFactory et le PencilPool.
Cela garantirait que les trois Class seraient simples et immutables (on ne les modifierait plus une fois conçues).

Sauf qu'on imagine bien que le PencilManager finirait par avoir plusieurs responsabilités, même si elles sont déléguées à d'autres Class, ce qui n'est en soi pas un problème si on fait en sorte que ce Manager ne représente que la fusion des trois autres.
Ce ne serait qu'une Facade, qui ne devrait faire qu'une chose : retourner un Pencil en fonction d'une valeur de couleur.




5. Le design pattern Singleton et l'implémentation des services

Avec les frameworks modernes, on passe notre temps à manipuler des Singletons car tout ce qu'on nomme service en est en fait un.

Un singleton n'est rien d'autre qu'une classe que l'on ne peut instancier qu'une et une seule fois par exécution. Si l'on désire créer une nouvelle instance et qu'il en existe déjà une, c'est celle-ci qui sera utilisée, magique donc !

5.1. La classe Singleton

5.1.1 Schema DML


5.1.2 Exemple de la classe Singleton

Voici une façon d'écrire une classe qui implémente le design pattern Singleton :

<?php
class Singleton
{
    private static $mySelf = null;

    private function __construct(){} // Empêche toute création d'instance.
    private function __clone(){} // Empêche toute copie d'instance.

    public static function getInstance()
    {
        if (self::$mySelf === null) {
            self::$mySelf = new Singleton();
        }
        
        return self::$mySelf;
    }
}

5.1.3 Exemple d'utilisation (attention, ça se complique !)

A l'usage, lorsque l'on cherche à créer deux instances, voici ce qu'il se passe:

<?php
$firstSingleton = Singleton::getInstance();
$secondSingleton = Singleton::getInstance();

5.1.4 Pourquoi l'usage de self et du static ?

Puisque que la classe n'est pas instanciée au moment de l'appel à getInstance, $this n'existe pas encore. Et comme $this n'existe pas, le seul moyen de faire appel à l'attribut mySelf est de passer par du static ::.

5.2. La classe SingletonMaker

5.2.1 Introduction

C'est tout de même très étrange comme façon de créer une instance, c'est un peu trop fantastique ce qu'il se passe, non ? On dirait que l'on hack la façon dont on doit utiliser les classes, notamment, il me semble vraiment étrange pour une classe de s'autocréer.
On pourrait éventuellement passer par un créateur de Singleton pour gérer tout ceci de façon plus claire, je vous propose donc de créer le SingletonMaker.

5.2.2 Exemple de la classe SingletonMaker

A noter que la classe aurait pu s'appeler SingletonFactory, mais elle s'appelle SingletonMaker pour éviter l'ambiguité avec le design pattern Factory, dont le code ci dessous n'est pas représentatif.

class Singleton
{

}

class SingletonMaker
{
    private static $singleton = null;

    public static function getSingleton()
    {
        if (self::$singleton === null) {
            self::$singleton = new Singleton();
        }

        return self::$singleton;
    }
}

C'est déjà un peu plus normal ce qu'il se passe ici, on passe par une classe qui nous retourne le Singleton en le créant si besoin.

5.2.3 Exemple d'utilisation

Rien de très différent de la fois d'avant.

<?php
$firstSingleton = SingletonMaker::getSingleton();
$secondSingleton = SingletonMaker::getSingleton();

5.3. Notre gestionnaire de services - la classe ServiceManager

Quelque part, nous venons de créer un gestionnaire de services, si on renomme un peu les choses, voici ce qu'on pourrait obtenir :

5.3.1 Proposition d'implémentation du ServiceManager

En renommant Singleton en MyFirstService, en créant une seconde classe MySecondService et en renommant le SingletonMaker par ServiceManager, on pourrait écrire ceci :

class MyFirstService
{

}

class MySecondService
{

}

class ServiceManager
{
    private static $services = [];

    public static function get(string $serviceClassName)
    {
        if (!isset(self::$services[$serviceClassName])) {
            self::$services[$serviceClassName] = new $serviceClassName();
        }

        return self::$services[serviceClassName];
    }
}


A l'usage, voici comment on récupérerait nos services :

$myFirstService = ServiceManager::get('MyFirstService');
$mySecondService = ServiceManager::get('MySecondService');

5.3.2 Appelation de services plus abstraites

Et eventuellement faire une appelation de services différente que l'on recalculerait à la volée.

Ex : 'myFirstService' comme nom du service et new ucfirst("myFirstService")() pour créer l'instance (pour rappel le ucfirst met en majuscule la première lettre d'une chaine de caractères).

class MyFirstService
{

}

class ServiceManager
{
    private static $services = [];

    public static function get(string $serviceName)
    {
        $serviceClassName = ucfirst($serviceName);

        if (!isset(self::$services[$serviceName])) {
            self::$services[$serviceName] = new $serviceClassName();
        }

        return self::$services[$serviceName];
    }
}


A l'usage, on pourrait l'appeler comme ceci :

$myService = ServiceManager::get('myFirstService');

5.3.2.1 Apparté

Derrière, on peut appeler ça un containeur de services, puis déclarer dans un fichier de configuration yml, xml ou ce que l'on souhaite le nom des services publics (haha).

6. Static Factory

6.1. Introduction

FactoryMethod/AbstractFactory/StaticFactory/SimpleFactory : peu importe le nom qu'on donne à ces designs patterns, il faut surtout comprendre que le but d'une classe suffixée Factory est celui-ci : construire et retourner des objets.
Que cette classe Factory étende une AbstractFactory, qu'elle contienne une ou plusieurs méthodes de fabrication, qu'elle soit elle même générée par une autre Factory, l'essentiel est qu'elle crée des instances.
En outre, si le Factory a ce rôle de constructeur, il paraitrait logique que tous les 'new' ne soient présents que dans de telles classes dans tout le code source du projet.

Les méthodes de création des classes suffixées Factory sont justement nommées FactoryMethod. "(create(...)/factory(...)/make(...)")


6.2. Intention

Fournir une interface permettant de créer des familles d'objets liés ou dépendants sans spécifier leurs classes concrètes. A la différence de l'AbstractFactory qui définit des Factory ayant plusieurs méthodes de fabrication, la StaticFactory définit des Factory qui n'ont qu'une seule méthode statique de fabrication pour un ensemble d'objets.

🧙‍♂️️Ce que je comprends plutôt de ce qu'est le StaticFactory :
A la différence de l'AbstractFactory, on ne définit ici qu'une seule classe Factory ; étant donné qu'il n'y a qu'une seule classe Factory, il n'est donc pas nécessaire de mettre du code dans une classe abstraite (donc le fichier 'AbstractSomethingFactory.php' n'existerait pas). Le design pattern StaticFactory est un cas particulier de l'AbstractFactory.


6.3. Exemple concret

Supposons que l'on souhaite afficher à l'écran le nom du repas à prendre en fonction du moment de la journée (par exemple pour une personne qui n'aurait plus toute sa tête). Pour simplifier, on ne considère que trois types de repas : le petit déjeuner, le déjeuner et le diner.

6.3.1 Code procédural

En procédural, voici ce que l'on pourrait écrire :

index.php
<?php
$currentHour = (int) date('H');

if ($currentHour < 12) {
    echo 'breakfeast';
} elseif ($currentHour < 16) {
    echo 'lunch';
} else {
    echo 'dinner';
}

Ici, il y a un problème fondamental : la logique heure/type de repas n'est pas récupérable en l'état dans les autres fichiers de l'application, il faudra alors dupliquer ce code. Si un nouveau repas est introduit (comme le petit déjeuner), il faudra chercher dans l'ensemble du code cette logique pour la changer.

Une fonction aurait tout sa place ici.
Par exemple :

<?php
    function getMealName($currentHour): string
    {
        if ($currentHour < 12) {
            $mealName = 'breakfeast';
        } elseif ($currentHour < 16) {
            $mealName = 'lunch';
        } else {
            $mealName = 'dinner';
        }

        return $mealName;
    }
    echo getMealName((int) date('H'));

Cette fonction résout le problème principal : la logique heure/type de repas est à un seul endroit.
Là où cela se complique, c'est si l'on souhaite retourner autre chose que le nom du repas, par exemple les horaires de celui-ci, on pourrait éventuellement faire une deuxième fonction comme celle-ci :

    function getMealTime($currentHour): string
    {
        if ($currentHour < 12) {
            $mealTime = '0h - 12h';
        } elseif ($currentHour < 16) {
            $mealTime = '12h - 16h';
        } else {
            $mealTime = '> 16h';
        }

        return $mealTime;
    }
    echo getMealTime((int) date('H'));

Mais cette fonction génère à nouveau un problème de duplication.
Non seulement on duplique à nouveau la logique heure/type de repas mais s'il faut rajouter un type de repas, il faudra modifier les deux fonctions.

La solution ici est de renvoyer une donnée plus complexe qui contiendrait plusieurs informations. Cette donnée pourrait contenir à la fois le nom du repas mais également les horaires de celui-ci. Il existe des solutions pour cela : on peut retourner un tableau, une structure particulière (ex: json/xml/yaml) ou bien un Objet. Le tableau ou la structure pose une difficulté : le client de la fonction doit connaitre le nom des clefs d'accès aux différentes informations. Dans le cas d'un Objet, le client a accès aux méthodes publiques de celui-ci pour accéder à une représentation des différentes informations.

6.3.2 En POO

6.3.3 Création des modèles Meal

En POO, on pourrait représenter chaque type de repas de la façon suivante :

MealInterface.php
<?php 

namespace App\Model\Meal;

interface MealInterface
{
    public function getName();
}

AbstractMeal.php
<?php
namespace App\Model\Meal;

abstract class AbstractMeal implements MealInterface
{
    protected string $name;

    public function getName(): string
    {
        return $this->name;
    }
}

Breakfeast.php
<?php
namespace App\Model\Meal;

class Breakfast extends AbstractMeal implements MealInterface
{
    public function __construct()
    {
        $this->name = 'breakfast';
    }
}

Lunch.php
<?php
namespace App\Model\Meal;

class Lunch extends AbstractMeal implements MealInterface
{
    public function __construct()
    {
        $this->name = 'lunch';
    }
}

Dinner.php
<?php
namespace App\Model\Meal;

class Dinner extends AbstractMeal implements MealInterface
{
    public function __construct()
    {
        $this->name = 'dinner';
    }
}

6.3.4 Mise en application sans Factory

Maintenant que les différentes classes sont prêtes, on peut les utiliser :

MealController.php
<?php 
    namespace App\Meal\Controller;

    use App\Meal\Model\Breakfast;
    use App\Meal\Model\Lunch;
    use App\Meal\Model\Dinner;

    class MealController 
    {
        public function index()
        {
            $currentHour = (int) date('H');
            
            if ($currentHour < 12) {
                $meal = new Breakfast();
            } elseif ($currentHour < 16) {
                $meal = new Lunch();
            } else {
                $meal = new Dinner();
            }

            echo $meal->getName();
        }
    }


Ici, le Controller doit avoir connaissance des différentes classes pour faire son affichage. Ce qui pose un problème dans le cas d'une modification des types de repas : il faut modifier l'ensemble des fichiers qui manipulent les classes concrètes (là où il y a des 'use').

Le but de la Factory est d'éviter cela afin que le Controller n'ait plus à gérer les différents modèles. Tout ce qu'il doit savoir est qu'il manipule une instance qui implémente l'interface MealInterface. Cette interface a une méthode getName(), commune à toutes les classes qui l'implémentent.

6.3.5 Avec l'Abstract Factory

Voici une implémentation possible de la classe MealFactory, elle reprend la logique du MealController :

MealFactory.php
<?php
    namespace App\Meal\Factory;

    use App\Meal\Model\MealInterface;
    use App\Meal\Model\Breakfast;
    use App\Meal\Model\Lunch;
    use App\Meal\Model\Dinner;

    class MealFactory implements MealFactoryInterface
    {
        public static function create(int $hour): MealInterface
        {
            if ($hour < 12) {
                $meal = new Breakfast();
            } elseif ($hour < 16) {
                $meal = new Lunch();
            } else {
                $meal = new Dinner();
            }

            return $meal;
        }
    }


Elle implémente l'interface MealFactoryInterface. Cette interface permettra dans le cas où l'on souhaite créer une deuxième MealFactory (par exemple pour un pays où les repas seraient différents) de mettre en place le mécanisme d'AbstractFactory (ie. plusieurs classes de type MealFactory (FrenchMealFactory/AfricanMealFactory)).

MealFactory.php
<?php
    namespace App\Meal\Factory;

    use App\Meal\Model\MealInterface;

    interface MealFactoryInterface
    {
        public function create(int $hour): MealInterface;
    }


Voici désormais ce que fera le MealController :

MealController.php
<?php 
    namespace App\Meal\Controller;

    use App\Meal\Factory\MealFactory;

    class MealController
    {
        public function index()
        {
            $mealFactory = MealFactory::create((int) date('H'));
            echo $meal->getName();
        }
    }


🧙‍♂️️L'usage des méthodes static est assez déconseillée car le Controller a ici une dépendance avec la classe concrète de la Factory. Si on souhaite utiliser une autre MealFactory, il faudra modifier dans l'ensemble du code cet usage. Mieux vaut que MealFactory implémente l'interface MealFactoryInterface. On injectera ensuite la Factory dans le constructeur des classes qui en dépendent.



6.4. Sources

📖️️https://designpatternsphp.readthedocs.io/en/latest/Creational/StaticFactory/README.html

📖️️https://waytolearnx.com/2020/02/design-patterns-static-factory-en-php.html

7. Le design pattern Decorator

7.1. Schema DML


8. Le design pattern Observer

8.1. Schema DML



8.2. Implementation

Ce DP introduit deux notions : l'Observer et l'Observable.
L'Observable doit envoyer une notification aux Observers qui lui sont rattachés pour leur indiquer que l'une de ces données a été changée.

La classe Observable doit donc gérer à la fois son modèle et la liste des Observers (qui sont des attributs de la classe).
Le sujet observé doit donc être au courant que les observateurs existent.

Voici une implémentation classique :

class Subject
{
    protected Observer[] $observers;
    public function __construct(Observer[] $observers) {
        $this->observers = $observers;
    }

    public function doSomething()
    {
        $this->observer->notify($this);
    }
}
abstract class Observer
{
    public function notify(Subject $subject)
    {
         // Fais quelque chose
    }
}
class MyObserver extends Observer
{

}


Une façon différente de procéder serait d'utiliser des EventsListeners : on crée une classe qui met à jour le modèle et qui notifie une liste de Listeners en fonction d'une typologie d'évènement (par exemple des changements d'état).



9. Immutable MVC en PHP version 2019


Ceci est une proposition de traduction de la page Immutable MVC: MVC In PHP 2019 Edition (https://r.je/immutable-mvc-in-php) de Tom Butler, que j'estime être très doué.
A noter que je n'ai pas trouvé quelle était la licence sur le contenu de son article, il est bien entendu que cette page sera supprimée si la copie de l'article original est interdite.

-- Début de la traduction --

9.1. MVC Revisité

Les articles les plus populaires de ce site Web concernent le MVC, dont le plus ancien a maintenant près de dix ans. Beaucoup de choses ont changé au cours de cette période, et ma propre expérience s'est également enrichie. Ma précédente série d'articles sur les "accidents vasculaires cérébraux" s'est en quelque sorte éteinte parce que je voulais revenir en arrière et peaufiner les exemples des articles précédents.

Le PHP a beaucoup changé depuis que j'ai écrit ces articles. Je vais prendre un nouveau départ et donner des exemples de code plus propres, plus complets et plus modernes.

Neuf ans se sont écoulés depuis que j'ai écrit le premier article sur les MVC ici et j'ai quelques observations à faire à ce sujet en regardant en arrière :

L'écosystème du PHP n'a pas changé. Les gens confondent toujours l'approche "contrôleur au lieu de médiateur" et MVC. Si la situation a empiré parce que les cadres populaires sont maintenant si bien intégrés, essayer de faire quelque chose de différent semble tout simplement mal.
Les points que j'ai soulevés sont toujours valables. Donner à la vue l'accès au modèle présente des avantages significatifs que je ne répéterai pas ici. Jetez un coup d'œil à mon précédent article si vous êtes intéressé.
Les exemples de code que j'ai donnés ne sont pas assez clairs ou complets. J'ai passé plus de temps à me concentrer sur les concepts qu'à donner des exemples de code. Je vais rectifier cela ici.
En plus d'écrire des exemples plus complets, il est temps de faire une mise à jour avec les nouvelles fonctionnalités de PHP et les tendances de programmation comme l'immutabilité. Je vais vous montrer comment écrire une structure MVC complètement immuable. Ensuite, je vous montrerai des contrôleurs réutilisables et un système de routage.

Outre l'utilisation de nouvelles fonctionnalités de PHP telles que l'indication du type de retour (ndlr, PHP 7.0), les deux principaux changements que j'ai apportés à mon style de codage sont l'immutabilité et le fait de privilégier les arguments par rapport aux constructeurs ayant des propriétés connexes.

9.2. Hello World

Voici une démonstration du trinome MVC en immutable à travers le fichier mvc.php dont voici le contenu :

class Model
{
    public $text;
    
    public function __construct() 
    {
        $this->text = 'Hello world!';
    }
}

class View
{
    private $model;

    public function __construct(Model $model) 
    {
        $this->model = $model;
    }

    public function output()
    {
        return '<a href="mvc.php?action=textclicked">' . $this->model->text . '</a>';
    }
}

class Controller
{
    private $model;

    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    public function textClicked()
    {
        $this->model->text = 'Text Updated';
    }
}

$model = new Model();

// It is important that the controller and the view share the model.
$controller = new Controller($model);

$view = new View($model);

if (isset($_GET['action'])) $controller->{$_GET['action']}();
echo $view->output();


9.3. Le modèle

Dans cet exemple, le modèle est mutable mais les autres composants ne le sont pas. Tout d'abord, rendons le modèle immuable :

class Model
{
    private $text;

    public function __construct($text = 'Hello World')
    {
        $this->text = $text;
    }

    public function getText()
    {
        return $this->text;
    }
    
    public function setText($text)
    {
        return new Model($text);
    }
}

Il n'est plus possible de modifier l'état du modèle une fois qu'il a été instancié. L'appel de la méthode setText crée une nouvelle instance. Tout endroit où l'instance originale est référencée ne verra pas de changement dans l'état du modèle. Le contrôleur et la vue devront être mis à jour pour utiliser cette nouvelle classe de modèle.

Auparavant, le modèle et le contrôleur partageaient une instance de modèle. Le contrôleur modifiait l'état du modèle et la vue lisait l'état de l'instance de modèle. Maintenant que le modèle est immuable, nous ne pouvons plus compter sur le fait que la vue et le contrôleur partagent la même instance de modèle. Au lieu de cela, l'action du contrôleur retournera une instance de modèle qui sera ensuite transmise à la vue.

9.4. Le contrôleur

Au lieu de mettre à jour l'état d'une instance de modèle existante et mutable, le contrôleur renvoie une nouvelle instance de modèle après avoir effectué ses modifications :

class Controller
{
    public function textClicked(Model $model): Model
    {
        return $model->setText('Text Clicked');
    }
}


Plutôt que de m'appuyer sur les constructeurs et les propriétés, j'ai choisi d'utiliser des arguments pour faire passer le modèle qui va être mis à jour. L'action du contrôleur reçoit maintenant une instance de modèle immuable et renvoie une nouvelle instance avec les modifications nécessaires.

Il est à noter que cela ne fait pas partie de MVC, l'utilisation d'arguments de constructeur avec des propriétés liées ou d'arguments à l'action de contrôleur ont le même effet. Mais en évitant les constructeurs, il n'est plus nécessaire de conserver le modèle comme une propriété et le contrôleur est considérablement simplifié. Moins de code pour faire le même travail est toujours une bonne chose.

9.5. La vue

La vue nécessite peu de modification pour arriver au même résultat (plus besoin de l'attribut publique).

class View
{
    public function output(Model $model)
    {
        return '<a href="mvc.php?action=textClicked">' . $model->getText() . '</a>';
    }
}

L'avantage de faire passer le modèle en argument est que une seule instance de View permet d'afficher autant de modèles que l'on souhaite.

9.6. Mise en place du trinome

Il ne reste plus qu'à instancier tout ce petit monde, plus besoin de passer le modèle aux constructeurs de la View et du Controller.

<?php
$model = new Model();
$controller = new Controller();
$view = new View();

if (isset($_GET['action']))
{
    $model = $controller->{$_GET['action']}($model);
}

echo $view->output($model);


Dans cette version immuable, le modèle est passé au contrôleur qui construit une nouvelle instance du modèle qui est ensuite passé à la vue.

9.7. Conclusion

Cette implémentation immuable de MVC présente certains avantages par rapport à la version mutable :

L'état est mieux géré de sorte que l'application ne souffre pas d'une action à distance où le changement d'un objet à un endroit (dans le contrôleur) provoque ensuite des changements dans un composant apparemment sans rapport (la vue).
Il y a moins d'État dans l'ensemble. Il n'y a plus de références aux différents objets en plusieurs endroits. Le contrôleur et la vue n'ont plus de référence au modèle, on leur donne une instance avec laquelle travailler au moment où ils en ont besoin, pas avant.

-- Fin de la traduction --

Bien évidemment, ce code ne doit pas se retrouver de cette façon dans votre application car le paramètre HTTP action doit être vérifié (ie : on contrôle que l'utilisateur a le droit ou non de faire l'action).

9.8. Vue d'artiste

Attention : le schéma suivant ne représente pas vraiment la beauté conceptuelle derrière le MVC.

10. DML 0.1 : Dot Modeling Language


Le DML est une représentation simplifiée du diagramme de classe UML à base de petits ronds.

Chaque petit rond représente un type d'élément de la POO (interface, classe, trait, classe abstraite).

Cette représentation a pour objectif de détecter les problèmes de dépendances entre les classes de notre projet afin d'éviter des incohérences de conception et de produire du code spaghetti.

A la différence du diagramme de classe, les types de liens (extends, implements) ne sont pas représentés car ces liens sont induits par les éléments liés.
Une interface liée par une classe l'est forcément pas un lien d'implementation (implements). De même qu'une classe liée par une autre classe l'est forcément par un lien d'héritage (extends), ceci étant vrai pour les classes abstraites.
Le DML s'occupe uniquement de représenter les use entre les classes.

10.1. Légende du DML

L'idée du DML est d'offrir aux développeurs un outil simple pour prendre des décisions de conception. Cette représentation est plus rapide à mettre en oeuvre sur un tableau noir ou blanc, qu'il soit numérique ou non.



* les interfaces sont représentées par des ronds vides ;
* les classes abstraites sont représentées par des ronds marqués par un point central ou par des hachures ;
* les classes sont représentées par des ronds pleins ;
* les traits sont représentés par des ronds contenant un trait ou des rayures ;
* la dépendance entre les classes est marquée par un trait simple dont la longueur peut être nulle si le lien entre deux éléments est évident.

10.2. Implémentation en DML

En DML, une classe qui implémente une interface peut être représentée de la façon suivante.
Le lien est implicitement un implements.

Dans le cas où l'implémentation est classique (MyClass extends MyInterface), on peut coller les deux ronds ensemble.

10.3. Héritage en DML

En DML, une classe qui hérite d'une classe abstraite peut être représentée de la façon suivante.
Le lien est implicitement un extends.


10.4. Trait en DML

En DML, une classe qui utilise un trait peut être représentée de la façon suivante.
Le lien est un use.


10.5. Représentation générale.

Voici une réprésentation générale de tous les éléments. Il est tout à fait possible de mélanger les représentations courtes et longues des dépendances entre les classes.


Voici la même représentation avec cette fois-ci des liens de longueur nulle.

Le nom des éléments peut disparaitre, seuls les liens entre ceux-ci existent.
* Il est envisageable de changer la couleur de chaque élément pour savoir ce que représente chaque rond. (Par exemple, une couleur par composant/vendor/bundle)
* Il est possible aussi de changer la taille des ronds selon l'importance de la dépendance lors de la conception (par exemple, il n'est pas nécessaire d'afficher la dépendance avec la classe Request dans une classe Controller, au contraire de la dépendance avec un Service (Repository/WsClient/Manager/Provider/Transformer...), si on souhaite le faire, le rond peut être à peine visible).


10.6. DML centrée

Avec le DML, il est possible de représenter une seule classe afin de voir le nombre de dépendance qu'elle a. Plus le nombre de ronds qui gravitent autour du rond principal est grand, plus la classe est difficile à maintenir.


11. L'héritage


Une classe SubClass peut hériter d'une autre classe MainClass.
Elle hérite ainsi de toutes les méthodes et de tous les attributs.
Ceci évite de dupliquer du code et permet de faire de l'override en redéfinissant ci-besoin les attributs/méthodes.

11.1. Code PHP


interface MainInterface 
{
    public function method();
}

class MainClass implements MainInterface 
{
    public function method()
    {
    }
}

class FirstSubClass extends MainClass
{
    public function method()
    {
        // Another implementation of the method.
    }
}

class SecondSubClass extends MainClass
{
    public function method()
    {
        // Another implementation of the method.
    }
}


11.2. Schéma DML



12. Immutable MVC en PHP version 2019


Ceci est une proposition de traduction de la page Immutable MVC: MVC In PHP 2019 Edition (https://r.je/immutable-mvc-in-php) de Tom Butler, que j'estime être très doué.
A noter que je n'ai pas trouvé quelle était la licence sur le contenu de son article, il est bien entendu que cette page sera supprimée si la copie de l'article original est interdite.

-- Début de la traduction --

12.1. MVC Revisité

Les articles les plus populaires de ce site Web concernent le MVC, dont le plus ancien a maintenant près de dix ans. Beaucoup de choses ont changé au cours de cette période, et ma propre expérience s'est également enrichie. Ma précédente série d'articles sur les "accidents vasculaires cérébraux" s'est en quelque sorte éteinte parce que je voulais revenir en arrière et peaufiner les exemples des articles précédents.

Le PHP a beaucoup changé depuis que j'ai écrit ces articles. Je vais prendre un nouveau départ et donner des exemples de code plus propres, plus complets et plus modernes.

Neuf ans se sont écoulés depuis que j'ai écrit le premier article sur les MVC ici et j'ai quelques observations à faire à ce sujet en regardant en arrière :

L'écosystème du PHP n'a pas changé. Les gens confondent toujours l'approche "contrôleur au lieu de médiateur" et MVC. Si la situation a empiré parce que les cadres populaires sont maintenant si bien intégrés, essayer de faire quelque chose de différent semble tout simplement mal.
Les points que j'ai soulevés sont toujours valables. Donner à la vue l'accès au modèle présente des avantages significatifs que je ne répéterai pas ici. Jetez un coup d'œil à mon précédent article si vous êtes intéressé.
Les exemples de code que j'ai donnés ne sont pas assez clairs ou complets. J'ai passé plus de temps à me concentrer sur les concepts qu'à donner des exemples de code. Je vais rectifier cela ici.
En plus d'écrire des exemples plus complets, il est temps de faire une mise à jour avec les nouvelles fonctionnalités de PHP et les tendances de programmation comme l'immutabilité. Je vais vous montrer comment écrire une structure MVC complètement immuable. Ensuite, je vous montrerai des contrôleurs réutilisables et un système de routage.

Outre l'utilisation de nouvelles fonctionnalités de PHP telles que l'indication du type de retour (ndlr, PHP 7.0), les deux principaux changements que j'ai apportés à mon style de codage sont l'immutabilité et le fait de privilégier les arguments par rapport aux constructeurs ayant des propriétés connexes.

12.2. Hello World

Voici une démonstration du trinome MVC en immutable à travers le fichier mvc.php dont voici le contenu :

class Model
{
    public $text;
    
    public function __construct() 
    {
        $this->text = 'Hello world!';
    }
}

class View
{
    private $model;

    public function __construct(Model $model) 
    {
        $this->model = $model;
    }

    public function output()
    {
        return '<a href="mvc.php?action=textclicked">' . $this->model->text . '</a>';
    }
}

class Controller
{
    private $model;

    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    public function textClicked()
    {
        $this->model->text = 'Text Updated';
    }
}

$model = new Model();

// It is important that the controller and the view share the model.
$controller = new Controller($model);

$view = new View($model);

if (isset($_GET['action'])) $controller->{$_GET['action']}();
echo $view->output();


12.3. Le modèle

Dans cet exemple, le modèle est mutable mais les autres composants ne le sont pas. Tout d'abord, rendons le modèle immuable :

class Model
{
    private $text;

    public function __construct($text = 'Hello World')
    {
        $this->text = $text;
    }

    public function getText()
    {
        return $this->text;
    }
    
    public function setText($text)
    {
        return new Model($text);
    }
}

Il n'est plus possible de modifier l'état du modèle une fois qu'il a été instancié. L'appel de la méthode setText crée une nouvelle instance. Tout endroit où l'instance originale est référencée ne verra pas de changement dans l'état du modèle. Le contrôleur et la vue devront être mis à jour pour utiliser cette nouvelle classe de modèle.

Auparavant, le modèle et le contrôleur partageaient une instance de modèle. Le contrôleur modifiait l'état du modèle et la vue lisait l'état de l'instance de modèle. Maintenant que le modèle est immuable, nous ne pouvons plus compter sur le fait que la vue et le contrôleur partagent la même instance de modèle. Au lieu de cela, l'action du contrôleur retournera une instance de modèle qui sera ensuite transmise à la vue.

12.4. Le contrôleur

Au lieu de mettre à jour l'état d'une instance de modèle existante et mutable, le contrôleur renvoie une nouvelle instance de modèle après avoir effectué ses modifications :

class Controller
{
    public function textClicked(Model $model): Model
    {
        return $model->setText('Text Clicked');
    }
}


Plutôt que de m'appuyer sur les constructeurs et les propriétés, j'ai choisi d'utiliser des arguments pour faire passer le modèle qui va être mis à jour. L'action du contrôleur reçoit maintenant une instance de modèle immuable et renvoie une nouvelle instance avec les modifications nécessaires.

Il est à noter que cela ne fait pas partie de MVC, l'utilisation d'arguments de constructeur avec des propriétés liées ou d'arguments à l'action de contrôleur ont le même effet. Mais en évitant les constructeurs, il n'est plus nécessaire de conserver le modèle comme une propriété et le contrôleur est considérablement simplifié. Moins de code pour faire le même travail est toujours une bonne chose.

12.5. La vue

La vue nécessite peu de modification pour arriver au même résultat (plus besoin de l'attribut publique).

class View
{
    public function output(Model $model)
    {
        return '<a href="mvc.php?action=textClicked">' . $model->getText() . '</a>';
    }
}

L'avantage de faire passer le modèle en argument est que une seule instance de View permet d'afficher autant de modèles que l'on souhaite.

12.6. Mise en place du trinome

Il ne reste plus qu'à instancier tout ce petit monde, plus besoin de passer le modèle aux constructeurs de la View et du Controller.

<?php
$model = new Model();
$controller = new Controller();
$view = new View();

if (isset($_GET['action']))
{
    $model = $controller->{$_GET['action']}($model);
}

echo $view->output($model);


Dans cette version immuable, le modèle est passé au contrôleur qui construit une nouvelle instance du modèle qui est ensuite passé à la vue.

12.7. Conclusion

Cette implémentation immuable de MVC présente certains avantages par rapport à la version mutable :

L'état est mieux géré de sorte que l'application ne souffre pas d'une action à distance où le changement d'un objet à un endroit (dans le contrôleur) provoque ensuite des changements dans un composant apparemment sans rapport (la vue).
Il y a moins d'État dans l'ensemble. Il n'y a plus de références aux différents objets en plusieurs endroits. Le contrôleur et la vue n'ont plus de référence au modèle, on leur donne une instance avec laquelle travailler au moment où ils en ont besoin, pas avant.

-- Fin de la traduction --

Bien évidemment, ce code ne doit pas se retrouver de cette façon dans votre application car le paramètre HTTP action doit être vérifié (ie : on contrôle que l'utilisateur a le droit ou non de faire l'action).

12.8. Vue d'artiste

Attention : le schéma suivant ne représente pas vraiment la beauté conceptuelle derrière le MVC.