Entités historisée

a marqué ce sujet comme résolu.

Bonjour,

Je suis en train de réaliser une application avec Symfony et je me demandais si un Bundle ne répondrait pas à mon besoin histoire de ne pas ré-inventer la roue.

Concrètement c'est une application de prise de commandes, j'ai donc pour simplifier les entités Commande, Detail et Produit.

Une Commande contient un ou plusieurs Detail, Detail contenant et un un seul Produit.

Le problème est qu'un produit peut être modifié dans le temps, donc si j'enregistre une commande avec un soda à 1.50€ aujourd'hui, si je consulte cette même commande dans 2 ans je dois toujours retrouver cette même information, le problème c'est que le soda peut voir son prix modifier voir même être réutilisé pour devenir un café…

Ma première idée était donc de créer une entité intermédiaire (ProduitHisto par exemple) et plutôt que de créer un lien entre Detail et Produit je ferai un lien entre Detail et ProduitHisto, de cette manière même si l'entité Produit originale est modifiée dans le future si je consulte la commande je retrouve toujours toutes les mêmes informations.

Cependant je me dit que je ne doit pas être le premier à avoir ce genre de besoin et je me demandais s'il n'existait pas des Bundle qui répondrait à cette problèmatique sans que j'ai a écrire deux fois la même entité et gérer l'historisation moi-même.

J'ai fais une rapide recherche sur Google et j'ai trouvé EntityAudit mais je n'ai pas l'impression que cela correspond à mon besoin.

Ce que je souhaite faire c'est de dire qu'il y a un lien entre ManyToOne entre Detail et Produit et que ce lien me garantisse que l'entité Produit liée soie toujours celle qui a été utilisée à l'origine même si une modification dans Produit a eu lieu.

Merci d'avance pour votre aide.
Cordialement, La source.

+0 -0

Salut !

Parmi ce que propose le projet gedmo/doctine-extensions, il y a un comportement loggable, qui semble-t-il permet d'historiser les changements sur des entités.

Après, il faudra peut-être adapter un peu pour avoir les versions des entités correspondant à la date de la commande, mais ça pourrait être une bonne base, je pense.

Edit

Tu as le même comportement implémenté avec une philosophie différente chez KnpLabs

+0 -0

Un grand merci pour ta réponse.

Effectivement à première vue le comportement loggable me semble parfait, du coup j'ai essayé de l'utiliser.

J'ai tout d'abord tenter d'ajouter l'extension doctrine directement dans mon projet… alors je n'ai pas d'erreurs en utilisant les namespace, mais l'utilisation des annotations ne déclenche rien non plus.

Du coup j'ai fait des recherches et j'ai trouvé le Bundle stof/doctrine-extensions-bundle qui intégrait l'extension Doctrine dans Symfony.
Du coup j'ai suivit "bêtement" les instructions pour l'installation… et sa marche pas :(
Je suis un peu refroidi à l'étape 3, quand je l'applique (juste avec le composant loggable) d'abord j'ai une erreur Unrecognized options "naming_strategy, auto_mapping" under "doctrine.orm"
Si je commente ces paramètres (qui y sont par défaut et je n'aime pas trop y toucher sans comprendre pourquoi) en faisant une mise à jour de la base de données je remarque qu'il veut me créer une table historique contenant l'ensemble des modifications qui ont lieu pour toutes les entités, et même si je valide et que j'essaye je reçois une erreur 500 Unknown Entity namespace alias 'AppBundle'.

Ce qui me gène c'est qu'une table historique soie crée, car en cas de modification voir de suppression la clé étrangère ne va plus être valide ou pointé sur l'élément modifier hors moi je souhaite que si je fais $detail->getProduit() le produit retourné soie celui qui a été utilisé à l'époque.

J'ai regardé rapidement KnpLabs… et je dois dire que je ne comprend pas la philosophie qui est derrière… J'ai juste l'impression que c'est un message de log qu'on ajoute à un événement, ou alors j'ai pas compris le code exposé.

+0 -0

StofDoctrineExtensionsBundle n'est étrangement pas autant maintenu que je m'y attendrais, et je n'ai pas non-plus réussi à lui faire charger les listeners nécessaires, c'est pourquoi j'embarque directement les extensions de Gedmo dans mes applications Symfony — c'est vraiment pas la mort et ça me permet aussi d'utiliser la version 3 de la librairie qui amène quelques intéressantes possibilités niveau syntaxe et réutilisation du code, mais c'était surtout pour la gestion des hiérarchies que j'avais regardé.

Mais effectivement, le comportement loggable de Knp ne semble pas enregistrer les versions, dommage… Ils essaient de détrôner le travail de l3pp4rd, mais sans cette fonctionnalité, ce n'est pas encore gagné.

Edit

A part ça, pour le coup de la clé étrangère, tu peux regarder si tu ne peux pas lier à l'entité chargée d'enregistrer l'historique d'une manière ou d'une autre. Ou alors tu enregistres avec la liaison la version intéressante de l'entité liée. La différence entre ces deux est que s'il y a une entité matérialisée pour les différences, l'enregistrement de la version pourrait être automatisée.
J'avais bien dit que ce n'était qu'une base.  ;)

+0 -0

Je suis capable d'arriver au comportement souhaité si je créée moi même une deuxième entité qui permet d'historiser les modifications. Le problème c'est que je n'ai pas qu'une seule entité à gérer de cette manière et c'est un peu répétitif.

Je me suis dit que je devrai créer moi même ma propre extension Doctrine, malheureusement une rapide recherche Google ne m'a pas permis de trouver d'explication sur la marche à suivre pour créer une extension…

Le principe est le suivant, pour une entité donnée on ajoute une annotation (exemple @History), cette annotation a pour effet de créer dans la base de données une nouvelle table ayant la même structure que l'entité suivit du suffixe _history. Par ailleurs une clé étrangère est ajoutée dans la table de l'entité pointant vers la table historisée.
A chaque ajout ou modification dans l'entité on ajoute une entrée dans la table history et on modifie la clé étrangère pour pointé vers cette nouvelle entrée.

Lorsqu'on créée une relation entre la table historisée et une autre, cette relation ne se fait pas sur la table de l'entité mais la table historisée.
Lors d'une affection (exemple: $detail->setProduit($produit)) le système traduit afin que la liaison dans la base de données soie entre la table historisée et la table de la clé étrangère.

De cette manière, on peu manipuler comme bon nous semble l'entité (CRUD) tout en garantissant que les autres entités ayant une relation avec elle conserve toujours l'élément tel qu'il était lors de l'affectation.

Sur le principe cela fonctionne, et si je ne trouve pas rapidement un moyen de le faire sous forme d'extension (je trouve sa nettement plus propre et cela a l'avantage d'être générique) je le ferai manuellement pour chacune de mes entités. Si quelqu'un aurai des liens sur la façon de créer une extension doctrine (j'adorerai un tuto complet :D ) je lui en serai reconnaissant.

+0 -0

A vue de nez, ton comportement peut se résumer à la création d'une nouvelle entité à chaque modification. Il pourrait suffire d'un listener qui, quand l'entité arrive dans Doctrine avec des modifications, regarde si elle a l'annontation/indication d'être "historisable" ( :D ). Si oui, remplir quelques métadonnées (date de création, date "d'utilisation" pour récupérer la plus récente, ID de l'entité "source) ainsi que demander la création plutôt que la mise à jour.

Bon, ça, c'est bien joli sur le clavier et dans la tête, j'en conviens…  ^^

+0 -0

A la base j'étais parti sur l'idée de tout faire dans la même table et de créer un attribut définissant si l'élément est supprimer (un soft delete quoi). Le problème de cette méthode c'est qu'on peu se retrouver avec une table avec des milliers et plus d'enregistrement avec finalement peu d'actif.

Même si l'on met une clé sur cet attribut j'ai peur qu'on dégrade les performances en lecture sans compter le poids des index.

C'est pour cette raison que j'ai plutôt imaginé une structure sur deux table, de cette manière on a que les enregistrements réellement utile dans la table de l'entité.

+0 -0

A la base j'étais parti sur l'idée de tout faire dans la même table et de créer un attribut définissant si l'élément est supprimer (un soft delete quoi). Le problème de cette méthode c'est qu'on peu se retrouver avec une table avec des milliers et plus d'enregistrement avec finalement peu d'actif.

A mon avis c'est quand même clairement le plus simple.

Quand tu changes le prix et/ou la désignation d'un produit, tu crées un nouveau produit avec un nouvel ID à chaque fois, et le précédent est marqué comme désactivé.

Si tu veux faire un truc un peu plus intelligent, tu peux faire une vérification au moment de la modification du produit: s'il n'a jamais été commandé, alors tu peux faire la modification directement, il n'y a pas besoin d'en créer un nouveau.

Sinon, encore plus simple, en supposant que tu ne changeras que le prix et jamais la nature du produit, tu copies le prix unitaire actuel dans tes détails de commande, comme ça tu es sûr qu'il restera enregistré tel quel peu importe les changements de prix qui ont eu lieu depuis le jour de la commande. De toute façon reprendre un ID de produit pour changer un soda en café, c'est une mauvaise idée, et je ne vois pas comment ça pourrait se justifier. Tu décides de vendre du café et tu arrêtes de vendre des sodas, c'est pas compliqué, c'est un nouveau produit et l'ancien est effacé si tu ne le vends plus, point barre.

Même si l'on met une clé sur cet attribut j'ai peur qu'on dégrade les performances en lecture sans compter le poids des index.

Franchement, tu as le temps avant que tes tables implosent. C'est pas plusieurs milliers de produits qu'il te faudra, mais plusieurs millions avant que ça arrive vraiment.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte