Bonjour,
Je poursuis mon premier projet avec Symfony. Aujourd'hui j'ai un problème en rapport avec la base de donnée et les entités.
J'ai un ensemble de contenus matérialisés par une entité Content. Chaque contenu a une catégorie et peut être disponible en une ou plusieurs langues. Pas de problème avec les catégories, j'ai une entité Category, mais pour les langues je ne sais pas trop comment faire et c'est pour cela que je viens vous demander conseil.
Il faut que je sois en mesure d'afficher facilement une liste de contenus dans une catégorie donnée et seulement les contenus dans les langues sélectionnées par l'utilisateur. Par exemple l'utilisateur pourrait demander tous les contenus de la catégorie sciences qui sont disponibles soit en françAis, soit en anglais (et alors tous les contenus qui sont disponibles en l'un ou l'autre, pas nécessairement les deux, seraient retournés).
Si on met Symfony, Doctrine et le DQL de côté et qu'on revient au PHP standard et au b.a.ba des bases de donnée brutes sans surcouche, je vois deux principales solutions possibles :
Solution 1: on ajoute à la table Content une colonne numérique languages, et les langues disponibles pour un contenu sont matérialisés par un champ de bits (bit field). Par exemple si on suppose que 1=françAis, 2=allemand, 4=italien et 8=anglais, on pourrait facilement récupérer tous les contenus qui sont soit en françAis, soit en anglais, dans une catégorie donnée, avec une requête de ce genre :
1 2 3 4 5 | select ... from Content c where c.category = ... and (languages&9) # 9 = 1|8 |
Avantages:
- C'est simple et rapide.
Inconvénients:
- Pas pratique de mettre à jour la liste des langues, et je risque d'être embêté quand l'application sera traduite en 64 langues, mais on s'en fout: ça ne changera pas tous les 4 matins et dépasser 64 est quand même très peu probable.
- Ca peut paraître un peu hacky comme idée; en tout cas c'est pas mainstream comme façon de penser.
Solution 2: plus classique, on crée une table de liaison ContentLanguage:
1 2 3 4 5 6 | create table ContentLanguage ( content int, language char(2), primary key(content,language), foreign key (content) references Content(id) ); |
ON peut alors récupérer des contenus avec une requête de ce type :
1 2 3 4 5 6 | select ... from Content c, ContentLanguage cl where c.id = cl.content # jointure and c.category = ... and cl.languages in ('fr', 'en') |
Si je ne crée pas de table Language, c'est volontaire. Je m'en fous, la liste de toutes les langues disponibles ne risque pas de changer tous les 4 matins. Donc à mon sens ça ferait une jointure inutile pour pas grand chose. Ca serait plus propre du point de vue de la normalisation et tuti quanti, mais franchement, osef.
Toute cette digression pour en revenir maintenant à Symfony, Doctrine et DQL: comment je fais pour implémenter soit la solution 1, soit la solution 2 ?
Pour la solution 1, à priori ce n'est pas compliqué, il suffit d'ajouter un champ dans ma classe entité :
1 2 3 4 5 6 7 8 | class Content { //... /** *@ORM\Column(type="integer") */ protected $languages; //... } |
Mais juste après, je m'aperçois que je suis piégé (pour ne pas dire b…). Les opérateurs bit à bit n'existent visiblement pas en DQL d'après la doc. C'est con, ça aurait presque été trop facile.
En ce qui concerne la solution 2, les relations many to many, ça se fait très bien avec Doctrine. Mais je me retrouve à être obligé de créer une entité ContentLanguage bidon qui ne me servira strictement à rien du tout. Ca me paraît lourd, extrêmement lourd, inutilement lourd.
Il n'y a pas moyen de ne pas devoir créer cette classe ? Genre pouvoir stocker les langues bêtement dans un array dans Content ? Parce que franchement je n'en vois pas l'utilité.
Au final, ça me donne l'impression que Doctrine est un énorme machin qui apporte plus de contrariétés que ce qu'il n'en résoud, et qu'il a été conçu pour des noob qui ne maîtrisent pas bien SQL.
Que faire ? IL y a une astuce que j'ai raté qui me permettrait d'implémenter ma solution n°1, ou bien je dois aller absolument au bout de la solution n°2 avec deux classes et deux tables ContentLanguage et Language, et en fait la pratique montre que ce n'est pas si lourd que ça ?
Merci pour vos réponses et bon week-end !