Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
N
ne-soyez-plus-l-esclave-de-doctrine
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Greg0ire
ne-soyez-plus-l-esclave-de-doctrine
Commits
3984bf37
Verified
Commit
3984bf37
authored
7 years ago
by
Greg0ire
Browse files
Options
Downloads
Patches
Plain Diff
Write talk
parent
793e32af
Branches
Branches containing commit
No related tags found
1 merge request
!9
Retours ngrekas
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
talk.md
+173
-0
173 additions, 0 deletions
talk.md
with
173 additions
and
0 deletions
talk.md
0 → 100644
+
173
−
0
View file @
3984bf37
Bonsoir, bonsoir,
je me présente, je suis Grégoire Paris, Software Engineer chez ManoMano.com
depuis 3 jours, car je viens de quitter Universciné, un fournisseur de vidéo à
la demande.
C'est mon premier talk alors il va falloir être très indulgents avec moi,
heureusement je ne suis pas seul, je suis venu avec Maxime Veber, qui va se
présenter à son tour.
…
Nous sommes venu vous parler d'un outil que vous connaissez bien, Doctrine,
conçu par Benjamin Eberlei, maintenu au jour le jour par Marco Pivetta et
Ghuilherme Blanco, et plus récemment par Michael Moravec et Luis Cobucci,
particulièrement en ce qui concerne la prochaine version, Doctrine 3.
Ce soir, nous allons essayer de vous montrer que Doctrine est un outil qui est
très peu intrusif pour peu qu'on sache comment faire pour s'en découpler.
Rentrons tout de suite dans le vif du sujet avec une bonne vieille entité telle
que vous la connaissez, telle que n'importe programmeur lisant la documentation
Doctrine ou Symfony la produira.
Alors, une entité, pour le programmeur lambda, c'est avant tout une suite de
propriétés typées. Chaque propriété vient avec un setter et un getter, à
l'exception notable de l'identifiant, qui est initialisé par Doctrine, qui
demande le prochain identifiant à la base de données. Ces setters et getters
sont dépourvus de toute logique, et si vous les testez, vous aurez
probablement l'impression de perdre votre temps et de faire un travail
répétitif, à tel point que certains vont jusqu'à créer des paquets Composer
pour automatiser ce genre de tests, et que jusqu'à très récemment, il y avait
une commande qui permettait de les générer.
On peut remarquer que Doctrine ne fait pas d'active record et ne vous oblige
pas à étendre quoi que ce soit, ce qui est une très bonne chose, car ce serait
une forme de couplage très forte.
Ce modèle porte un nom ou des noms, il est connu sous le nom de modèle
anémique, car il plein de vide.
Mais si vous avez écouté le talk de Romain Kuzniak tout à l'heure, vous savez
que ce n'est pas la seule architecture possible.
Maxime et moi, nous aimons et utilsons le Domain Driven Design dans nos application.
C'est une architecture dont voici certains points clés, le principal point-clé
étant dans le nom, DDD. Une des choses importantes en DDD, c'est qu'on commence
par concevoir son domaine, et qu'on repousse le choix du framework, de la
méthode de communication (http ou console), de la base de données et au final,
de la plupart des outils tiers au plus tard possible.
On commence par concevoir son modèle, c'est à dire principalement ses entités
et les règles métier qui vont avec. Comme il n'y a pas de dépendances tierces
impliquées à ce moment là, les tests sont faciles à écrire car on maitrise
totalement le code.
Concevoir une entité c'est s'assurer qu'elle est conforme à tout moment, dès sa
construction, aux règles demandées par le client.
Faire du DDD, c'est faire de la bonne POO, et faire de la bonne POO, c'est
respecter l'encapsulation. Et l'entité qu'on a vu précédemment ne le fait pas
du tout: elle vous laisse lire et écrire librement ce que vous voulez dans
chacune des propriétés, sans aucun contrôle.
Une fois qu'on a fait la partie domaine, alors et seulement alors, on
s'intéresse à l'infrastructure, et dans ce talk on va vous parler de la partie
persistence avec Doctrine, et comment l'utiliser sans corrompre notre entité.
Par exemple, saviez vous que Doctrine n'a pas du tout besoin de setters?
Lorsque je me suis mis au DDD, j'avais ce préjugé, et je me suis rendu compte
que Doctrine n'appelle aucune de vos méthodes. Pour être exact, quasiment
aucune de vos méthodes, et en Doctrine 3, ce sera vraiment aucune. Même pas le
constructeur. Ce que fait Doctrine lors de l'hydratation, c'est qu'il utilise
des techniques de magie noire comme de la réflection, ou de la déserialisation
de string pour initialiser les propriétés.
Sachant cela, ce que vous pouvez faire, c'est utiliser le constructeur pour
valider les règles métier, des sortes de règles de validation aussi appelées
invariants car ce sont des choses qui doivent être vraies tout le temps, qui
sont données par le client.
De cette façon, il devient impossible d'avoir un objet invalide. Si ce que vous
passez au constructeur est invalide, alors une exception métier est jetée, avec
un message explicite. Pour rendre le debug de votre application facile, il faut
crasher aussitôt et de la façon la plus visible possible. Plutôt que de cacher
la poussière sous le tapis, on jette une exception et on se débarasse de tout
une classe de bug venant du fait que vous pensez quelque chose être vrai alors
qu'il ne l'est pas. Par exemple ici, après l'instanciation, si on fait appel à
getId(). C'est un des problèmes avec les identifiants. L'autre problème, c'est
le couplage qu'on a avec la base de données qui est censée rester un outil
externe et ne devrait pas dicter les ids.
Comme solution, vous pouvez utiliser les UUIDs, si vous n'avez pas de clé
naturelle. Une clé naturelle, c'est par exemple l'ISBN d'un livre. Si vous avez
la chance d'en avoir une dans votre domaine, utilisez la, sinon utilisez les
UUIDs.
UUID, ça veut dire universally unique identifier, ce qui signifie que
l'identifiant est unique non seulement au sein de votre application, mais de
l'univers tout entier. Si vous faites la refonte d'une application en Symfony
2, ça peut s'avérer très utile si vous avez besoin de donner la possibilité à
votre client de créer ses propres entités, mais aussi d'en importer depuis
l'ancienne base, car vous ne risquez pas d'avoir les collisions que vous auriez
en cas d'auto-increment. Autre chose, un UUID, c'est dur à deviner, alors que
si vous avez un entier, il est fort probable qu'une entité avec un entier
voisin existe également, information toujours bonne à prendre quand on est
malveillant. Autre information que ça peut laisser transparaitre: le nombre
d'entité.
Maintenant qu'on a résolu le problème des ids, revenons à nos moutons. On a un
constructeur, avec n propriétés, et chaque propriété a x règles de validation…
ça peut vite devenir très lourd pour le constructeur, surtout que certaines
règles de validation devront aussi être vérifier si vous avez des mutateurs
(des méthodes qui changent vos propriétés). Pour résoudre ce problème, on peut
utiliser des value objects. Voilà à quoi ça peut ressembler.
On a une classe, sans identités, avec seulement une ou deux propriétés qui vont
ensemble, et des règles métier pour les valider. Le nom de value object vient
du fait que ce genre d'objet est caractérisé par sa valeur, et que deux value
object doivent être considérés comme égaux si et seulement si toutes leurs
propriétés sont égales. Ce qui va se passer si vous partez là dessus, c'est que
vous allez passer au constructeur des value objects.
Le problème avec cette approche, c'est que l'instanciation peut vite devenir
très pénible, car il faut ajouter un use statement par value object, plus celui
de la classe principale.
Une bonne solution, c'est d'utiliser des constructeurs nommés, qui sont des
factory statiques. Je dis des parce que vous pouvez en avoir plusieurs. Ces
constructeurs nommés, vous allez leur passer des types primitifs et vous allez
pouvoir les charger de mettre des valeurs par défaut ou non suivant le
constructeur. Vous pouvez même mettre le constructeur en privé si vous voulez
forcer les gens à créer de nouveaux constructeurs nommés si ceux que vous avez
faits ne sont pas assez complets.
Alors maintenant, on a un obtenu des entités qui ne sont plus des tableaux
améliorés. On n'est pas en train de modéliser une base de données, on a fait
les chose à l'endroit et on a modélisé le problème du client, code first, ou
même model first pour ceux qui commencent par faire de l'UML et génèrent
ensuite du code. Comment persister une structure arborescente de value objects
avec Doctrine?
Deux solutions: les custom types, et les value objects. Commençons par la plus
simple, les custom types.
Un custom type, c'est un type à vous, qui va devenir utilisable dans vos
mappings Doctrine au même type que string, json_array ou integer.
Pour en créer un, vous devez pour le moment étendre une classe de base, et
surcharger quelques méthodes. Dans le futur, il y aura probablement un gros
refactoring de cette partie pour que vous n'ayez plus qu'à implémenter une
interface. Il y a également gros à parier que
`getName`
disparaitra, de même
qu'il avait disparu dans le composant form de Symfony. Pour le moment, il vous
faut utiliser le même nom dans cette méthode et lors de l'enregistrement dans
la registre des types, qui se comme ceci.
Les 2 méthodes qui nous intéressent,
`convertToPHPValue`
et
`convertToDatabaseValue`
doivent permettre de convertir une valeur provenant
d'une colonne de la base de données en value object et vice versa. Avec cette
méthode, le constructeur de vos value object va être appelé lors de
l'hydratation, et vous pourrez voir si vous avez oublié de migrer vos données
dans le cas où une nouvelle règle métier n'est pas respectée.
Le problème, c'est que ça ne gère qu'une seule colonne. Si vous désirez gérer
plusieurs colonnes, tournez vous plutôt vers les embeddables.
Les embeddables sont des values objects marqué comme tels par du mapping
Doctrine, et embarqués dans votre classe de base. Dans la classe embarquée,
vous pouvez mapper chaque champ comme vous le feriez pour n'importe quelle
autre classe, et Doctrine va simplement stocker ces nouveaux champs dans la
même table que l'entité qui embarque l'embeddable.
L'annotation
`Embedded`
vous permet de spécifier un préfixe pour les colonnes
correspondant à la classe embarquée, ce qui vous permet d'éviter les
collisions. Notez que le constructeur de la classe embarquée n'est pas appelée
lors de l'hydratation.
Le seul problème avec cette implémentation, c'est la nullabilité. Si votre
embeddable est optionnel, plutôt que de mettre null, doctrine va l'hydrater
avec un value object dont les propriétés seront null.
Pour pallier ce problème un paquet est disponible, ce paquet utilise un
listener qui va agir en postLoad pour vérifier si l'embeddable est considéré
null. Si oui, il utilisera le composant property access pour mettre ce champ à
`null`
. La conséquence, c'est qu'il vous faut un setter.
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment