Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Doctrine, objet typé, et colonne JSON

Doctrine, objet typé, et colonne JSON

Le dépot git pour la démo : https://github.com/lyrixx/symfony-doctrine-inlined-properties

Les bases de données savent gérer des colonnes JSON depuis des années déjà, et
ces colonnes permettent d'accélérer le développement en simplifiant le code, les
migrations, et la maintenance.

Cependant, manipuler un array PHP n'est pas pratique : les analyseurs statiques
de code sont perdus (a moins de spécifier énormément de chose via de la PHPDoc),
PHP ne peut pas controller le type au runtime, mais surtout la lisibilité du
code est réduite. En effet, à moins de lire tout le code, il est difficile de
savoir quelles sont les clés obligatoires, lesquelles sont optionnelles, et
enfin comment est typée la donnée.

À travers cette présentation, nous allons voir comment étendre Doctrine pour
avoir le meilleur des deux mondes : des colonnes en JSON, et des objets PHP
fortement typés.

7602f2751868682b296171f58589c851?s=128

Grégoire Pineau

April 07, 2022
Tweet

More Decks by Grégoire Pineau

Other Decks in Technology

Transcript

  1. Doctrine, objet typé, et colonne JSON Symfony Live Paris -

    2022 Grégoire Pineau @lyrixx Dév @JoliCode Core Team @Symfony 1
  2. Problématique 2

  3. 3

  4. 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
  5. La configuration d’un bloc 5

  6. 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
  7. L’entité Block 7

  8. Comment faire ? 8

  9. 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
  10. MappedSuperClass 10

  11. 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
  12. Single Table 12

  13. 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
  14. Class Table 14

  15. 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
  16. Du PHP sérialisé 16

  17. • 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
  18. Du PHP sérialisé en JSON 18

  19. 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
  20. Existe-il une autre solution ? ⚠ expérimentale ⚠ 20

  21. Type doctrine 21

  22. Les types customs Doctrine 22

  23. Les types customs Doctrine 23

  24. Les types customs Doctrine 24

  25. Configuration Interface 25

  26. Implémentation du Type 26

  27. Implémentation du Type 27

  28. 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
  29. Implémentation du Type 3 (😨) Fixé dans doctrine 4.0 https://github.com/doctrine/dbal/pull/5036

    29
  30. Utilisation 30

  31. 31

  32. 32 Contenu de la base de données

  33. 🎉🎉 🎉🎉 Ça marche … enfin presque 33

  34. “Ça marche pas” © 34

  35. Requête SQL effectuée 35

  36. L’Unit Of Work (UOW) 36

  37. 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
  38. 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()
  39. 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
  40. Listener 40

  41. 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
  42. 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
  43. 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 🔍
  44. Un listener Doctrine 44

  45. Un listener Doctrine 45

  46. Un listener Doctrine 46

  47. Un listener Doctrine 47

  48. Un listener Doctrine 48

  49. 🎉🎉 🎉🎉 Ça marche … vraiment 49

  50. Limitations • Que faire si on supprime une classe ?

    • Migrations de données • Performance de la sérialisation 50
  51. 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
  52. D’autres cas d’utilisation 52

  53. 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
  54. 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
  55. Merci 55

  56. 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