Essayer les Value Object, c'est les adopter
Qui suis-je pour parler en ces lieux ?
Je suis le susnommé Kevin Nadin
Twitter : @kevinjhappy
Je travaille à Darkmira comme développeur PHP, c'est une boite avec pleins de dev cool comme toi, ou toi, ou...
non pas toi ;)
Value Object, quésaco ?
En un mot:
Mini classe qui représente une donnée
(bon, ça fait plus qu'un mot tout ça)
Exemple: je veux gérer un email, donc je fais une classe avec une donnée
<?php
class EmailAddress {
private $emailAddress;
}
J'ai mis ma propriété en privé volontairement, je rajoute un getter
<?php
class EmailAddress {
private $emailAddress;
public function toString()
{
return $this->emailAddress;
}
}
Et je passe par le constructeur pour....
ben construire mon objet :D
<?php
class EmailAddress {
private $emailAddress;
public function __construct(string $emailAddress)
{
$this->emailAddress = $emailAddress;
}
public function toString(): string
{
return $this->emailAddress;
}
}
Et là on a déjà un Value Object !!
Buuuut... Wait...
Ça sert à rien ton truc !
<?php
class EmailAddress {
private $emailAddress;
public function __construct(string $emailAddress)
{
$this->emailAddress = $emailAddress;
}
public function toString(): string
{
return $this->emailAddress;
}
}
Pourquoi on ne manipule pas une variable string directement ??
1- Immutabilité
On applique ici un principe de Defensive Programming, les variables que l'on crée ne sont plus modifiables !
Il n'y a pas de setter dans ma classe, et le paramètre est privée, donc une fois crée en passant par le constructeur il n'est plus modifiable
2- Domaine
On applique aussi un principe de DDD "Domain Driven Design", une variable string ne "signifie" rien alors qu'une classe "EmailAddress" est bien plus parlante dans le contexte dans lequel elle est utilisée !
On sait ici que l'on parle d'un e-mail, et pas d'un nom de famille par exemple.
3- Validateur possible
On peut compléter le constructeur pour permettre de rajouter des conditions
<?php
class EmailAddress {
private $emailAddress;
public function __construct(string $emailAddress)
{
if (filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
throw new Exception('Erreur sur email');
}
$this->emailAddress = $emailAddress;
}
public function toString(): string {/*...*/}
}
4- Exception spécifique
Et sur la condition que nous avons ajouté, on lève une exception dédiée pour cette classe
<?php
class EmailAddress {
private $emailAddress;
public function __construct(string $emailAddress)
{
if (filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
throw new InvalidEmailAddressException($emailAddress);
}
$this->emailAddress = $emailAddress;
}
public function toString(): string {/*...*/}
}
class InvalidEmailAddressException extends LogicException {/*...*/}
5- Defensive Programming, again !
Cette classe n'a pas à être étendue, on ferme la porte aux modifications par héritage
=> la classe passe en "final"
<?php
final class EmailAddress {
private $emailAddress;
//...
}
Ok pour la classe
<?php
final class EmailAddress {
private $emailAddress;
public function __construct(string $emailAddress)
{
if (filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
throw new InvalidEmailAddressException($emailAddress);
}
$this->emailAddress = $emailAddress;
}
public function toString(): string
{
return $this->emailAddress;
}
}
mais dans quel but ?
1- Rajoute une couche de contrat dans vos services
=> sépare les rôles
<?php
class LoginService {
public function login(string $email, string $password) {
// TODO: vérifier l'email
// TODO: vérifier la validité du mot de passe
//...
}
}
<?php
class LoginService {
public function login(EmailAddress $email, VerifiedPassword $password) {
//...
}
}
Devient :
2- Rend le code plus compréhensible
<?php
class LoginService {
public function login(EmailAddress $email, VerifiedPassword $password) {
//...
}
}
Une variable string pourrait laisser la place à tout et n'importe quoi, ici on sait que l'on passe un Email valide et un mot de passe vérifié
3- Augmente la maintenabilité
Si vous souhaitez rajouter une vérification supplémentaire sur les emails, c'est très facile car la vérification est centralisé.
4- Permet la réutilisabilité
(...oui c'est Français...)
Le Value Object peut être réutilisée facilement à tout moment où nous aurions besoin de manipuler un email
5- Encourage le typage fort
Parce que c'est important ;)
6- Découplage du Framework
Les Value Object n'ont aucune dépendance envers aucune autre classe, ils se suffisent à eux-même
Cependant il est possible de faire des Value Object utilisant d'autres Value Object
Exemple de VO dans un VO part 1
<?php
final class DateTimeInFuture {
private $dateTime;
public function __construct(DateTime $dateTime)
{
if ($dateTime < new \DateTime()) {
throw new DateTimeNotInFutureException($dateTime);
}
$this->dateTime = $dateTime;
}
public function toDateTime(): DateTime
{
return $this->dateTime;
}
}
Exemple de VO dans un VO part 2
<?php
final class DateTimeRangeInFuture {
private $startDate;
private $endDate;
public function __construct(
DateTimeInFuture $startDate,
DateTimeInFuture $endDate
) {
if ($startDate->toDateTime() > $endDate->toDateTime()) {
throw new InvalidDateTimeRangeException($startDate, $endDate);
}
$this->startDate = $startDate;
$this->endDate = $endDate;
}
public function start(): DateTime
{
return $this->startDate->toDateTime();
}
public function end(): DateTime
{
return $this->endDate->toDateTime();
}
}
Ah ouais... pourquoi pas :D
(j'aurai pu y penser tout seul d'abord ...)
Il ne vous reste plus qu'à vous laisser tenter !
Essayer les Value Object,
By Kevin JHappy
Essayer les Value Object,
Lightning talk for AFSY 2019-08-27
- 756