[PHP/MySQL] Articles et leurs Catégories - Comment les coder dans la base MySQL ?

Bonjour,

J’ai besoin d’aide pour modéliser en PHP et en MySQL (enfin surtout pour la partie MySQL) un système de news.
Il y a bien sûr une table “articles” (id_art, titre, texte, date, …) et une table “categories” (id_cat, nom).
Il faut donc également une table “lien_art_cat” faisant le lien entre un article et sa/ses catégorie(s) associée(s) (id_art, id_cat).

Un article peut avoir plusieurs catégories.
Une catégorie peut être fille d’une autre catégorie.
La modélisation doit pouvoir permettre de vérifier simplement si deux catégories, choisie par l’utilisateur lors de l’ajout d’un article, ne sont pas imbriquées l’une dans l’autre. Exemple : “Composants > Processeurs” et “Composants > Processeurs > AMD > Athlon”, dans ce cas là, la catégorie “Composants > Processeurs” doit être ignorée (car déjà comprise dans “Composants > Processeurs > AMD > Athlon”).
Il faut aussi pouvoir sélectionner ("SELECT … " en MySQL) simplement toutes les catégories enfants d’une catégorie donnée (utile pour trouver tous les articles traitant des “Composants” par exemple c’est à dire : “Composants > Processeurs”, “Composants > Processeurs > Intel”, “Composants > Processeurs > AMD”, “Composants > RAM”, “Composants > Cartes Graphiques”, … (récursif en profondeur donc))

Population de la table catégorie :

  • Composants (ID : 5)
  • Périphériques (ID : 2)
  • Processeurs (ID : 7)
  • Jeux Vidéos (ID : 9)
  • Intel (ID : 12)
  • Athlon (ID : 43)
  • AMD (ID : 1)Concepts/nomenclature :
    Catégorie parente : Catégorie se trouvant immédiatement au dessus d’une catégorie (qui est donc sa fille) dans la hiérarchie.
    Catégorie racine : Catégorie n’ayant pas de catégorie parente (Composants, Périphériques, Jeux Vidéos, etc.). Ce type de catégorie créer une branche à laquelle vont appartenir toutes les catégorie filles (et petites-filles et petites-petites-filles, etc.) de cette catégorie racine.
    Génération : distance entre une catégorie et la catégorie racine de sa branche (une sorte d’age). Une catégorie racine à donc une génération de 0.

Pour cela, j’avais songé à deux façons de faire :

Première façon :
Je me rends compte que pour la vérification, une comparaison du “chemin” de la catégorie serait le plus simple : si on trouve le chemin d’une catégorie au début du chemin d’une autre, alors la première catégorie peut être ignorée.
Exemple :
Le chemin de la catégorie “Composants > Processeurs” ($cat1) serait “5.7” ($chemin1) et celui de “Composants > Processeurs > AMD > Athlon” ($cat2) serait “5.7.1.43” ($chemin2).
Donc pour faire la vérification :[quote=""]
Trier les 2 chemins par génération croissante : compter le nombre de point dans le chemin.
si la chaîne $chemin1 se trouve dans la chaîne $chemin2 à la position 0 alors ignorer $cat1 finsi
[/quote]
Il suffirait d’ajouter un simple champ dans la table catégorie qui contiendrait le chemin de la catégorie.
Pour la sélection des articles d’une catégorie donnée (par exemple"Composants > Processeurs" de chemin “5.7”), la requête suivante devrait suffire :

SELECT id_art, titre, texte, date
FROM articles A, categories C, lien_art_cat L
WHERE
	C.chemin LIKE "5.7%" AND // On ne récupère que les catégories dont le chemin débute par "5.7", soit toutes les catégories filles de la catégorie 5.7
	L.id_cat = C.id_cat AND 
	A.id_art = L.id_art

A l’usage (affichage de toutes les catégories, avec la hiérarchie, dans un menu déroulant par exemple), je me demande si le fait que les chemins soient codés un peu bizarrement (pas de taille fixe pour les identifiant de catégorie, séparateur de niveau intégré, etc.) ne soit pas un problème ?

Deuxième façon :
Dans la table catégories, rajouter les champs “id_parente” (donne l’id_cat de la catégorie parente, NULL si pas de catégorie parente), id_cat_racine (donne l’id_cat de la catégorie racine de la branche) et generation (utile pour savoir rapidement si deux catégories sont soeurs).
Ainsi la vérification des deux catégories imbriquées se ferait ainsi :[quote=""]
Trier les 2 catégories par génération croissante.
si $cat1 est un ancêtre de $cat2 alors ignorer $cat1 finsi
[/quote]
Mais je ne vois pas comment trouver simplement si une catégorie est ancêtre d’une autre.
Pour la sélection : je ne vois pas de requête simple pour sortir tous les id_cat des catégories descendant d’une catégorie donnée.
Par contre, je pense que la génération d’un arbre hiérarchique ne devrait pas être un soucis avec cette solution (je pense).

Il y a sûrement une autre solution que les deux que j’ai envisagé (en plus de la solution “Obiwan Kenobi” :D)

Edit : Correction des fautes :wink:

Personnellement je prendrais plutôt la première méthode. La seconde est certes plus “jolie” car on ne référence que la catégorie mère et racine, mais je pense pas que les bases SQL soient pratiques pour les algorithmes récursifs. Parce que comme tu le dis, pour trouver si une catégorie est un ancêtre d’une autre il faut (d’après moi) soit faire un requete jusqu’à tomber sur la catégorie en question ou jusqu’à la racine, soit récupérer tous les parents de toutes les catégories et dans ce cas là autant générer un vrai arbres des catégories et lire toute la table catégorie à chaque fois.

Pour ce qui est du problème évoqué dans la première solution, je pense que tu peux t’en sortir en utilisant une lettre/chiffre pour identifier une sous-catégorie dans une catégorie donnée. Ainsi tu appelles 0,1,2,3 … les catégories racines, p.ex Composants reçoit 0, et ensuite les filles de Composants tu les numérotes aussi avec 0,1,2 …, pareil les petites filles …

Si il n’y a pas trop de filles pour une catégorie tu peux rester avec des chiffres, au pire tu étend aux lettres.
ex:
“Composants > Processeurs > AMD > Athlon” devient un truc comme “0.1.0.3”

Comme ça la taille est fixe pour chaque niveau de catégorie et tu peux même te passer des "."

myself ou benj te dirais que la solution 2 se fait avec un vrai SGBD, soit postgresql :slight_smile: donc oui, tu peux faire de la récursivité.

Ta solution 1 a un gros défaut : et si tu veux un âge infini (à la base, dans le cas d’arbre, c’est plutôt hauteur de l’arbre) ? Tu vas être limité par les identifiants, taille du champ, etc.

Pire : que se passe-t’il quand tu déplace une catégorie? paf : t’es bon pour tout refaire, alors que la solution des références t’évite ça.

Maintenant, vu que tes catégories ne sont pas nombreuses (100 grand max, non?), tu dois pouvoir tout charger dans un tableau php, et parcourir à l’envers (de la plus basse catégorie vers la plus haute, ou comme tu veux si le tableau est indexé par id_cat).

Et ainsi mettre en cache tes id une fois pour toute, sans passer par la bdd (ce que je trouve moche personnellement).

C’est pas faux… :sweet:

J’ai pas compris comment tu veux organiser le tableau PHP ?
Ce que tu trouves moche c’est de passer par une BDD ou de ne passer pas passer par une BDD ? Perso, c’est vrai que dernièrement je me suis rendu compte que, parfois, on peut très bien se passer de BDD pour stoquer des données, surtout lorsque qu’on a toujours besoin de l’ensemble des données d’une table.

Sinon, est-ce qu’un mix des deux solution pourrait marcher ? (les facilités d’usage de la première méthode et l’assurance de la pérénité avec la deuxième méthode qui conserve les références : dont on se servirait pour réparer le champ chemin suite à un déplacement d’une catégorie)

Sans-Nom> oui c’est ce que j’aurais dit. Mais j’aimerais aussi donner une solution qui pourrait parraitre sale.

Il est possible avec pgsql d’utiliser des colonnes de type tableau, à savoir tableau d’int ou tableau de char etc. Cela n’est pas possible pour tous les types avec mysql, mais uniquement avec le type enum. Pourquoi ne pas ajouter une colonne du type enum dans la table article énumérant toutes les catégories auxquelles un article fait partie ?

http://dev.mysql.com/doc/refman/5.0/fr/enum.html

je ne sais pas comment mysql réagit (je ne l’utilise pas) mais j’utilise des tableaux d’int comme FK pour d’autres tables et lorsque j’ai un changement/delete sur une clef, le CASCADE fonctionne parfaitement.

ptet je dis une connerie aussi [:shy]

Raynor> ce que je trouve sale, c’est le stockage du chemin. Perso quand je fais des catégories, je le fais toujours avec une notion de catégorie parente. Maintenant, ça fait longtemps que j’ai pas fait ça…

Pour la solution 2 (celle qui semble la plus pérenne), j’ai trouvé des trucs sympa :
Je suis tombé sur un Traducteur de requêtes XPATH vers SQL[/url] ([url=http://www.tgl0be.org/data/projets/bda/]traducteur) qui donne quelques idée pour les requètes SQL.
Et 2 “cours” sur les arbres en SQL :
Gestion d’arbres par représentation intervallaire
SQL et structure arborescente
Mais MySQL semble un peu trop léger pour cela :frowning:

Quelques pistes en MySQL et PHP :
Webmaster Hub > Requête SQL - hièrarchie - WHILE
Webmaster Hub > Représentation d’arborescence en PHP depuis MySQL
blogmarks.net : sql,arbre
Nested Set Trees… a PHP / mysql implementation
Storing Hierarchical Data in a Database

Je vais voir tout ça…

Merci de vos conseils.

Oui, la solution 2 me semble mieux aussi, après, soit tu gère l’arbre en PHP, soit en SQL si ta base le permet, apparemment PG-SQL oui, et Oracle aussi (requêtes “connect by … prior”).

En tout cas, ce que je sais et c’est l’idée que benj veut peut-être te dire, c’est que si tu as pour chaque parent ses enfants (ce qui est simple en php : foreach ( $cat as $id => $c) $cat[$c[‘parent’]][‘children’][] = $id;) tu calcules les premiers fils. Calculer les petits fils n’est pas compliqué : suffit de prendre tous les fils des fils :))

Donc, comme tes catégories sont peu nombreuses par rapport à tes news, c’est peut-être plus avantageux de faire comme cela, quite à raffraîchir la cache PHP (qui stockerai tout ton modèle, pour ne pas avoir à rechercher tout dans la BDD) dés que tu modifie tes catégories (rare aussi)

en me relisant je remarque que je m’explique vachement mal :paf:

Les forums en carton� propose une option de prévisualisation permettant de se relire ! :smiley: