Modélisation d'une relation 1 à plusieurs

Petit nouveau ! | 8 Messages

09 févr. 2011, 20:24

Bonjour,

Je m'interroge sur la meilleure façon de modéliser une relation "1 à plusieurs" classique.

Prenons par exemple le blog dans lequel un billet peut avoir plusieurs commentaires (avec une classe Billet et une classe Commentaire).

Solution 1 :

Code : Tout sélectionner

$billet = new Billet(15); $billet->addComment('commentaire');
Solution 2 :

Code : Tout sélectionner

$billet = new Billet(15); $commentaire = new Commentaire(); $commentaire->add($billet, 'commentaire');
La première solution semble plus séduisante, mais ça m'agace un peu que la classe Billet aille trifouiller dans la table des commentaires.

De plus, ça me semble plus dans le concept objet de passer l'objet Billet à l'objet Commentaire : de cette façon la classe Billet ne s'occupe que la table Billet et la classe Commentaire de la table commentaires.

Qu'en dites-vous ?

Merci d'avance.

Franck.

Avatar du membre
Administrateur PHPfrance
Administrateur PHPfrance | 13231 Messages

11 févr. 2011, 22:38

Pour moi, la bonne solution est la solution numéro 1, à savoir $billet->addCommentaire($commentaire).

Toutefois, je ne pense pas qu'il faille que le billet trifouille la table commentaire, mais plutôt qu'il déclenche les méthodes de la classe commentaire pour la sauvegarde.
Exemple :
class Billet 
{	
	/**
	 * Liste des commentaires associés au billet
	 * @var array
	 */
	protected $a_commentaire = array();
	
	/**
	 * Ajoute un commentaire au billet
	 * 
	 * @param Commentaire $commentaire
	 */
	public function addCommentaire( Commentaire $commentaire )
	{
		// Associe le billet courant au commentaire
		$commentaire->setBillet( $this );
		// Stocke le commentaire comme un des commentaires du billet courant
		$this->a_commentaire[ $commentaire->getIdCommentaire() ] = $commentaire;
	}
	
	/**
	 * Sauvegarde le billet et ses entitées associées
	 */
	public function save()
	{
		// Insert ou update de l'object courant
		// TODO
		
		// Lance la sauvegarde de chaque commentaire
		foreach( $this->a_commentaire as $commentaire)
		{
			$commentaire->save();
		}
	}
}

class Commentaire
{
	/**
	 * Le billet auquel est associé le commentaire courant
	 * @var Billet
	 */
	protected $billet = null;
	
	/**
	 * Affecte un billlet au commentaire courant
	 * 
	 * @param Billet $billet
	 */
	public function setBillet( Billet $billet )
	{
		$this->billet = $billet;
	}
	
	/**
	 * Sauvegarde le commentaire courant
	 */
	public function save()
	{
		// Insert ou update de l'objet courant
	}
}
Ensuite, tu peux pousser le concept beaucoup plus loin, à la Doctrine ou Propel, bref, à l'ORM, c'est à dire que sauver un commentaire entraine la sauvegarde du billet du dessus. Alors que dans le cas présent, il faut passer par le billet obligatoirement.
Attention, dans mon exemple, il manque le chargement des commentaires du billet, et la gestion de la collision (écraser un commentaire déjà associé à un billet, et mon code ne marche pas pour les billets qui n'existe pas encore en base de données (j'utilise l'id_commentaire pour le tableau, mais pour un commentaire non existant, il n'y a pas d'id)
Connaître son ignorance est la meilleure part de la connaissance
Pour un code lisible : n'hésitez pas à sauter des lignes et indenter

twitter - site perso - Github - Zend Certified Engineer

ViPHP
ViPHP | 2577 Messages

15 févr. 2011, 16:40

Bonjour,

En général on travail sur un billet ou sur un commentaire. Je ne pense pas que l'on manipule en même temps le plusieurs commentaires sauf suppression de tous les commentaires (surement lié à la suppression du billet) ou la lecture de tout les billets (lié à l'affichage).

Je verrai bien une fonction de lecture des commentaires d'un billet et une autre de suppression des commentaires d'un billet dans l'objet billet mais rien d'autre. Par contre toutes les autres fonctions sont liées directement à l'objet commentaire.

devlop78
Invité n'ayant pas de compte PHPfrance

18 févr. 2011, 01:05

J'ai vu un article sur les résultats objet par PDO. C'est en vérité très prometteur pour moi. Ainsi, une ligne = un objet x . Un groupe de lignes = un objet y "traversable" indexant des objets x. Et où l'on peut modifier les valeurs de la ligne (par exemple setName()) et où le mutateur vérifie qu'il s'agit d'une donnée valide avant de l'accepter. Ainsi, une fois que objetX->update(), enfin, on pourrait imaginer, ça coule de source.

Concernant ton cas, je n'ai pas l'expérience pour ça, et je m'arrache souvent les cheveux à essayer de trouver au fond qui est censé faire quoi. En réalité, je suis capable de faire une application rapidement et salement. Mais le dernier mot ne me plaît pas :p

Il me semble toutefois plus logique d'utiliser le deuxieme. Prenons exemple sur les noeuds HTML. Tu ne dis pas à table d'insérer un "tr" (string). Tu crées un tr (objet) et tu demandes à table de l'insérer. Toutefois, il faut prendre garde, car logiquement, avec une contrainte référencielle, le commentaire doit posséder une référence au billet valide. Ce qui implique qu'il en ai connaissance à un moment donné. Et dans ton exemple, tu ne le donnes pas, ce qui implique que l'objet table va dire à l'objet tr quel id_table il doit avoir, et lui dire de se créer. Vu comme ça, j'aurais donc tendance à dire que table et tr ne sont pas liés, que tu as table d'un coté, et que tu crées un tr d'un autre, quitte à les lier une fois seulement que tr est bel et bien créé.

C'est une très bonne question :D

devlop78
Invité n'ayant pas de compte PHPfrance

18 févr. 2011, 20:25

Sinon, j'ai pensé à autre chose, plus global :

- Une classe DataBase pour une base de données.
- Un type de classe Table
- Une classe LineList
- une classe Line

Tu peux donc faire

$unBillet = DataBase::$billets->getLineById(5);
$unBillet->titre = "Je change de titre";
$unBillet->update();

Et

$unNouveauBillet = new Comment();
$unNouveauComment->titre = "Un nouveau billet";
DataBase::$billets->getLineById(5)->add($unNouveauComment);

On peut imaginer que lors de new Comment(), l'identifiant référenciel au billet est à NULL. Une fois mis dans billet, c'est billet qui va dire à l'objet Comment quel ID il a, et qu'il doit se créer. On peut aussi imaginer tout stocker pour éviter que

$monbillet = DataBase::$billets->getLineById(5);
$monbilletCopy = DataBase::$billets->getLineById(5);

Si je fais $monbillet->titre =""; On risque d'avoir problème sauf si getLineById() nous a fourni la même instance. Ainsi on peut imaginer toute une arborescence et une cohérence malgré les mises à jour à droite et à gauche.

Mais là, je crois que le framework est la bienvenue ...

devlop78
Invité n'ayant pas de compte PHPfrance

18 févr. 2011, 22:07

Tiens, je suis tombé dessus en regardant un peu Zend et Symfony :

http://www.propelorm.org
$book = new Book();
$book->setTitle("War & Peace");
// associate the $author object with the current $book
$book->save();

$author = new Author();
$author->setFirstName("Leo");
$author->setLastName("Tolstoy");
$author->addBook($book);
$author->save();
Ca m'a l'air bien sympa. Ici le père ajoute son fils.
<?php
$author = new Author();
$author->setFirstName("Leo");
$author->setLastName("Tolstoy");
$author->save();

$book = new Book();
$book->setTitle("War & Peace");
// associate the $author object with the current $book
$book->setAuthor($author);
$book->save();
Ici le fils dit au père de l'ajouter. Ce qui va dans la logique SQL. Bien sûr, là, $author peut être un 'row' cherché par la méthode "findPk()".

Ce qui me faisait un peu peur, je crois en avoir parlé, c'est de la double instanciation des objets. C'est un risque de mettre à jour l'un et d'utiliser l'autre. Ou alors, dans une collection, que celle-ci ne se mette pas à jour si à coté on rajoute un élément. On a déjà un minimum garanti grâce à "Propel Instance Pool" qui renvoie la même instance pour une recherche sur une PK. Après, il faut voir le reste.

Bref, très intéressant, je pense m'y plonger bientôt :)