Slide 1

Slide 1 text

Doctrine, objet typé, et colonne JSON AFUP Day Lille - 2023 Grégoire Pineau @lyrixx Dév @JoliCode Core Team @Symfony 1

Slide 2

Slide 2 text

Problématique 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

Un CMS flexible ● Plusieurs types de blocs (Image, Text, TopTravel, …). Plus d’une vingtaine de blocs disponibles ● Chaque bloc a sa configuration : ○ Des propriétés (alt, src, responsive, content, destination, …) ○ Des valeurs par défaut (responsive=true, …) ○ Son propre système de rendu ● On peut composer les blocs ○ Text + Image ○ Text + Vidéo 4

Slide 5

Slide 5 text

La configuration d’un bloc 5

Slide 6

Slide 6 text

L’objectif ● Eviter la duplication de code ● Pouvoir stocker les blocs en base de données ● Avoir au maximum des objets PHP ● Et si possible un objet par type de bloc ● Avoir un système performant : éviter les requêtes SQL inutiles (autant lors d’un SELECT que lors d’un UPDATE) 6

Slide 7

Slide 7 text

L’entité Block 7

Slide 8

Slide 8 text

Comment faire ? 8

Slide 9

Slide 9 text

Quelles sont les solutions ? 1. Utiliser l'héritage doctrine ○ MappedSuperClass ○ Single table ○ Class table 2. Stocker du PHP sérialisé 3. Stocker du PHP sérialisé en JSON 4. Existe-t-il d’autres solutions ? le mystère reste entier… 9

Slide 10

Slide 10 text

MappedSuperClass 10

Slide 11

Slide 11 text

MappedSuperClass Permet de définir des propriétés communes à différentes classes Ou pour définir les propriétés par défaut d’une classe située dans un bundle tiers L'application pourra ensuite ajouter d'autres propriétés Cependant, on ne peut pas mettre de relation vers la MappedSuperClass Ce n’est pas une solution adaptée à notre besoin 11

Slide 12

Slide 12 text

Single Table 12

Slide 13

Slide 13 text

Single Table ● Une seule table, mais remplie de vide ● Impossible d'avoir deux classes avec une propriété ayant le même nom mais un type différent ● Impossible d'avoir des champs non null dans une classe fille ● Problème de performance avec une relation car doctrine ne peut pas créer de proxy Avec beaucoup de blocs différents, on perd beaucoup d’espace disque. Et le typage est faible ! 13 object 1 object 2 id col_1 col_2 col_3 col_4 1 foo bar 2 hello world

Slide 14

Slide 14 text

Class Table 14

Slide 15

Slide 15 text

Class Table ● Une table par type de bloc ● Beaucoup de join ➡ performances réduites ● Problème de performance avec une relation car doctrine ne peut pas créer de proxy Avec une table par type de bloc, la base de données va comporter beaucoup de tables ce qui peut être gênant. De plus, les performances vont être trop impactées 15 object 2 id col_3 col_4 2 hello world object 1 id col_1 col_2 1 foo bar

Slide 16

Slide 16 text

Du PHP sérialisé 16

Slide 17

Slide 17 text

● Doctrine va sérialiser la configuration via serialize() / unserialize() ● Avoir du PHP sérialisé dans la DB est un NO GO ○ Non interopérable ○ Peut comporter un \0 ○ Si on déplace ou renomme ou modifie une classe, la donnée ne sera plus déserialisable Il ne faut jamais stocker du PHP sérialisé dans une base de données Du PHP sérialisé 17

Slide 18

Slide 18 text

Du PHP sérialisé en JSON 18

Slide 19

Slide 19 text

Du PHP sérialisé en JSON ● Doctrine va sérialiser la configuration via json_encode() / json_decode() ● La configuration est sous forme de tableau PHP. So 2013 󰘮 On perd l’objectif initial d’avoir une classe par type de block 19

Slide 20

Slide 20 text

Existe-il une autre solution ? ⚠ expérimentale ⚠ 20

Slide 21

Slide 21 text

Type doctrine 21

Slide 22

Slide 22 text

Les types customs Doctrine 22

Slide 23

Slide 23 text

Les types customs Doctrine 23

Slide 24

Slide 24 text

Les types customs Doctrine 24

Slide 25

Slide 25 text

Configuration Interface 25

Slide 26

Slide 26 text

Implémentation du Type 26

Slide 27

Slide 27 text

Implémentation du Type 27

Slide 28

Slide 28 text

Attention Si vous êtes sensible ou psychorigide, la slide suivante va vous choquer ! Vous pouvez checker twitter pendant les 60 prochaines secondes A tout de suite 28

Slide 29

Slide 29 text

Implémentation du Type 3 (😨) Fixé dans doctrine 4.0 https://github.com/doctrine/dbal/pull/5036 29

Slide 30

Slide 30 text

Utilisation 30

Slide 31

Slide 31 text

31

Slide 32

Slide 32 text

32 Contenu de la base de données

Slide 33

Slide 33 text

🎉🎉 🎉🎉 Ça marche … enfin presque 33

Slide 34

Slide 34 text

“Ça marche pas” © 34

Slide 35

Slide 35 text

Requête SQL effectuée 35

Slide 36

Slide 36 text

L’Unit Of Work (UOW) 36

Slide 37

Slide 37 text

L’unit of work 37 ● Pièce centrale de Doctrine ● Probablement la plus complexe ● 3685 lignes de code ● Vous ne voudriez sûrement pas avoir à la debugger 🤗 ● https://github.com/doctrine/orm/blob/2.11.x/lib/Doctrine/ORM/UnitOfWork.php

Slide 38

Slide 38 text

L’unit of work (2) 38 ● Encapsulée par l’entity manager ● Contient un cache de tous les objets récupérés de la DB 💡 ● Gère les écritures en base de données ● Gère les transactions ● S’occupe de savoir quel objet doit être INSERT / UPDATE / DELETE ● Protips: pas besoin de persist() un objet qui vient de la DB ● S’occupe de gérer les dépendances entre les objets ● Fait des snapshots des objets qu’il rencontre pour faire un diff au moment du flush()

Slide 39

Slide 39 text

Pourquoi ça ne fonctionne pas ● Comme Doctrine fait un snapshot du block avec sa configuration ● Et comme la configuration est un object ● Et comme les objets sont passés par référence ➡ Doctrine ne voit pas la différence sur notre object ↪ Doctrine ne sauvegarde pas notre objet 39

Slide 40

Slide 40 text

Listener 40

Slide 41

Slide 41 text

Les events Doctrine 41 ● Doctrine émet beaucoup d'événements durant son cycle de vie : ○ preRemove + postRemove ○ prePersist + postPersist ○ preUpdate + postUpdate ○ postLoad ○ loadClassMetadata + onClassMetadataNotFound ○ preFlush + onFlush + postFlush ○ onClear ● Comme avec Symfony, il est possible d’ajouter de nouvelles fonctionnalités en écoutant ces événements ● Les extensions Doctrine se basent sur ces événements

Slide 42

Slide 42 text

Les events Doctrine - Protips ● Ne pas mettre votre logique métier dans un listener. À n’utiliser que pour les traitements génériques. ● Ne pas flusher dans un listener. 42

Slide 43

Slide 43 text

Un listener Doctrine 43 ● postLoad & postPersist : pour sauvegarder les données initiales ● preFlush : on calcule un diff, et on dit à Doctrine de sauvegarder si besoin ● onFlush : on effectue les changements en base de données ● onClear : on supprime les données sauvegardées Environ 140 lignes de code Le code est disponible sur GitHub 🔍

Slide 44

Slide 44 text

Un listener Doctrine 44

Slide 45

Slide 45 text

Un listener Doctrine 45

Slide 46

Slide 46 text

Un listener Doctrine 46

Slide 47

Slide 47 text

Un listener Doctrine 47

Slide 48

Slide 48 text

Un listener Doctrine 48

Slide 49

Slide 49 text

🎉🎉 🎉🎉 Ça marche … vraiment 49

Slide 50

Slide 50 text

Limitations ● Que faire si on supprime une classe ? ● Migrations de données ● Performance de la sérialisation 50

Slide 51

Slide 51 text

Conclusion ✓ Avoir des objects PHP fortementements typé ○ Une classe par type de bloc ✓ Avoir une colonne JSON ○ Qu’on peut requêter ✓ Performant ✓ Scalable ✓ Maintenable ✓ Facile d’utilisation - Transparent 51

Slide 52

Slide 52 text

D’autres cas d’utilisation 52

Slide 53

Slide 53 text

D’autres cas d’utilisation 53 ● Stocker des données marketing dans la classe User ● Stocker les préférences mail d’un utilisateur ● Stocker la configuration des notifiers que des utilisateurs pourraient ajouter ● Stocker la configuration d’un produit ● Dénormaliser de la donnée, comme une souscription Stripe

Slide 54

Slide 54 text

Peut être que… https://github.com/dunglas/doctrine-json-odm Ou peut être que cette fonctionnalité sera un jour native à Doctrine Ou peut être le mettre dans le DoctrineBridge À nous de faire du lobbying 󰙣 54

Slide 55

Slide 55 text

Merci 55 https:/ /github.com/lyrixx/symfony-doctrine-inlined-properties

Slide 56

Slide 56 text

Des questions ? Symfony Live Paris - 2022 Grégoire Pineau @lyrixx Dév @JoliCode Core Team @Symfony https:/ /github.com/lyrixx/symfony-doctrine-inlined-properties 56