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) :
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.
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").
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.
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).
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.
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.