A free and open-source book on ZF3 for beginners

Translation into this language is not yet finished. You can help this project by translating the chapters and contributing your changes.

4.20. Les différents types de modèles

Avec Zend Framework 3, il n'existe pas de dossier Model unique pour stocker toutes les classes modèles. Au lieu de cela, par convention, les modèles sont subdivisés en fonction des principaux types suivants et chaque type est stocké dans son propre sous-dossier (voir le tableau 4.9) :

Table 4.9. Types de modèles et emplacements
Types de classe modèle Répertoire
Entités APP_DIR/module/Application/src/Entity
Entrepôts APP_DIR/module/Application/src/Repository
Objets valeurs APP_DIR/module/Application/src/ValueObject
Services APP_DIR/module/Application/src/Service
Fabriques Dans un sous-dossier Factory sous chaque dossier de classes. Par exemple, les fabriques d'un controleur sont placées dans APP_DIR/module/Application/src/Controller/Factory

La séparation des modèles en différents types facilite la conception de votre logique métier. On appelle cela le "Domain Driven Design" (ou DDD). Définit par Eric Evans dans son célèbre livre intitulé Domain-Driven Design — Tackling Complexity in the Heart of Software.

Nous décrirons ci-dessous les principaux types de modèles.

4.20.1. Les Entités

Les entités sont destinées à stocker certaines données et ont toujours une propriété identifiante, ce qui vous permet d'identifier les données de manière unique. Par exemple, une entité Utilisateur possèdera toujours une propriété login unique qui vous permetra d'identifier l'utilisateur par cet attribut. Vous pouvez modifier les autres attributs de cette entité, comme le prénom ou l'adresse, mais son identifiant ne change jamais. Les entités sont généralement stockées dans une base de données ou dans un système de fichiers.

Ci-dessous, vous pouvez trouver un exemple d'entité User, qui représente un visiteur du site:

// L'entité User représente un visiteur du site
class User 
{
    // Propriétés
    private $login;     // e.g. "admin"
    private $title;     // e.g. "Mr."
    private $firstName; // e.g. "John"
    private $lastName;  // e.g. "Doe"
    private $country;   // e.g. "USA"
    private $city;      // e.g. "Paris"
    private $postCode;  // e.g. "10543"
    private $address;   // e.g. "Jackson rd."
    
    // Méthodes    
    public function getLogin() 
    {
        return $this->login;
    }
        
    public function setLogin($login) 
    {
        $this->login = $login;
    }
    
    //...
}

Dans les lignes 5-12, nous définissons les propriétés du modèle User. La meilleure pratique consiste à définir les propriétés en utilisant le type d'accès privé et à les mettre à la disposition de l'appelant via des méthodes publiques getter et setter (comme getLogin() et setLogin()).

Les méthodes du modèle ne sont pas limitées par les getters et les setters. Vous pouvez créer d'autres méthodes qui manipulent les données du modèle. Par exemple, vous pouvez définir la méthode pratique getFullName(), qui retournera le nom complet de l'utilisateur ("Mr. John Doe").

4.20.2. Les Entrepôts

Les classes Entrepôts (Repositories en anglais) sont des modèles spécifiques au stockage et et à la récupération des entités. Par exemple, un UserRepository peut représenter une table de base de données et fournir des méthodes pour extraire des entités User. Vous utilisez généralement des repository lorsque vous stockez des entités dans une base de données. Avec les repositories, vous pouvez encapsuler la logique de requête SQL au même endroit, la maintenir et la tester facilement.

Nous en apprendrons davantage sur les repositories dans Gestion des bases de données avec Doctrine, lorsque nous parlerons de la bibliothèque Doctrine.

4.20.3. Les Objets Valeurs

Les classes Value objects sont en quelques sortes des modèles pour lesquels l'identité n'est pas aussi importante que pour les entités. Un objet de valeur est généralement une petite classe identifiée par tous ses attributs. Il n'a pas d'attribut d'identifiant. Les Value objects ont généralement des méthodes getter, mais n'ont pas de setters (les value objects sont immuables).

Par exemple, un modèle qui contient un montant en argent peut être traité comme un value object :

class MoneyAmount 
{
    // Properties
    private $currency;
    private $amount;
  
    // Constructor
    public function __construct($amount, $currency='USD') 
    {
        $this->amount = $amount;
        $this->currency = $currency;
    }
  
    // Gets the currency code  
    public function getCurrency() 
    {
        return $this->currency;  
    }
  
    // Gets the money amount
    public function getAmount() 
    {
        return $this->amount;
    }  
}

Aux lignes 4-5, nous définissons deux propriétés: devise et montant. Le modèle n'a pas de propriété d'identifiant, il est plutôt identifié par toutes les propriétés dans leur ensemble: si vous changez la devise ou le montant, vous aurez un objet montant différent.

Dans les lignes 8-12, nous définissons la méthode constructeur, qui initialise les propriétés.

Dans les lignes 15-24, nous définissons des méthodes getter pour les propriétés du modèle. Notez que nous n'avons pas de méthodes setter (le modèle est immuable).

4.20.4. Les Services

Les modèles Service usually encapsulent généralement certaines fonctionnalités de la logique métier. Les services ont généralement des noms facilement reconnaissables se terminant par un suffixe "er", comme FileUploader ou UserManager.

Ci-dessous, un exemple de service Mailer est présenté avec sa classe Value Object. Il a la méthode sendMail() qui prend un objet de valeur EmailMessage et qui envoi e-mail en utilisant la fonction standard PHP mail() :

<?php

// The Email message value object
class EmailMessage 
{  
    private $recipient;
    private $subject;
    private $text;  
  
    // Constructor
    public function __construct($recipient, $subject, $text) 
    {
        $this->recipient = $recipient;
        $this->subject = $subject;
        $this->text = $text;
    }
  
    // Getters
    public function getRecipient() 
    {
        return $this->recipient;
    }
  
    public function getSubject() 
    {
        return $this->subject;
    }
  
    public function getText() 
    {
        return $this->text;
    }
}
<?php

// The Mailer service, which can send messages by E-mail
class Mailer 
{
  
    public function sendMail($message) 
    {
        // Use PHP mail() function to send an E-mail
        if(!mail($message->getRecipient(), $message->getSubject(), 
             $message()->getText())) 
        {
            // Error sending message
            return false;
        }
    
        return true;
    }
}

Dans Zend Framework, vous déclarez généralement vos modèles Service dans Service Manager.

4.20.5. Les Fabriques

Les classes Fabriques sont généralement conçues pour instancier d'autres modèles (en particulier les modèles Service). Dans les cas les plus simples, vous pouvez créer une instance d'un service sans fabrique, simplement en utilisant l'opérateur new, mais parfois la logique de création de classe peut être assez complexe. Par exemple, les services dépendent souvent les uns des autres, vous devrez donc peut-être injecter des dépendances à un service. En outre, il peut parfois être nécessaire d'initialiser le service immédiatement après l'instanciation en appelant une (ou plusieurs) de ses méthodes.

Les classes fabriques ont généralement des noms se terminant par le suffixe Factory, comme CurrencyConverterFactory, MailerFactory, etc.

Pour un exemple concret, imaginons que nous ayons un service PurchaseManager, qui peut traiter les achats de certaines marchandises, et que le service PurchaseManager utilise un autre service appelé CurrencyConverter, qui peut se connecter à un système externe fournissant des taux de change. Écrivons une classe de fabrique pour PurchaseManager, qui instancierait le service et lui passerait la dépendance :

<?php
namespace Application\Service\Factory;

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Service\CurrencyConverter;
use Application\Service\PurchaseManager;

/**
 * This is the factory for PurchaseManager service. Its purpose is to instantiate the 
 * service and inject its dependencies.
 */
class PurchaseManagerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, 
                $requestedName, array $options = null)
    {
        // Get CurrencyConverter service from the service manager.
        $currencyConverter = $container->get(CurrencyConverter::class);
        
        // Instantiate the service and inject dependencies.
        return new PurchaseManager($currencyConverter);
    }
}

Dans le code ci-dessus, nous avons la classe PurchaseManagerFactory qui implémente l'interface Zend\ServiceManager\Factory\FactoryInterface. La classe factory a la méthode __invoke() dont le but est d'instancier l'objet. Cette méthode a l'argument $container qui est le gestionnaire de service. Vous pouvez utiliser $container pour extraire les services du gestionnaire de service et les transmettre à la méthode constructeur du service en cours d'instanciation.


Top