Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

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.

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 AFUP Day Lille -

    2023 Grégoire Pineau @lyrixx Dév @JoliCode Core Team @Symfony 1
  2. 3

  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. • 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
  10. 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
  11. 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
  12. 31

  13. 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
  14. 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()
  15. 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
  16. 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
  17. 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
  18. 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 🔍
  19. Limitations • Que faire si on supprime une classe ?

    • Migrations de données • Performance de la sérialisation 50
  20. 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
  21. 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
  22. 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
  23. 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