Précédent   Forum des professionnels en informatique > PHP > Langage
Langage Forum sur le langage PHP, la POO, les conventions, la sécurité, etc. Avant de poster : FAQ Langage, toutes les FAQ PHP, cours langage et sources PHP
Partagez cette discussion sur d'autres réseaux sociaux : Viadeo Twitter Google Facebook Digg Delicious MySpace Yahoo
Réponse Proposer ce sujet en actualité
 
Outils de la discussion
Publicité
'
Vieux 09/02/2011, 20h37   #1
Futur Membre du Club
 
Homme Franck Dupont
Développeur multimédia
Inscription : avril 2008
Messages : 33
Détails du profil
Informations personnelles :
Nom : Homme Franck Dupont
Âge : 37
Localisation : France

Informations professionnelles :
Activité : Développeur multimédia
Secteur : High Tech - Multimédia et Internet

Informations forums :
Inscription : avril 2008
Messages : 33
Points : 19
Points : 19
Envoyer un message via MSN à kyfr59 Envoyer un message via Skype™ à kyfr59
Par défaut Modélisation d'une relation 1 à plusieurs

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 :
1
2
$billet = new Billet(15);
$billet->addComment('commentaire');
Solution 2 :

Code :
1
2
3
$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.
kyfr59 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 09/02/2011, 20h47   #2
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 015
Points : 5 015
Hello

En effet, au sens objet, la relation 1-n se caractérise par une aggregation (un objet porte l'instance de plusieurs autres). Au sens d'une base de données relationnelles, ça se caractérise par une clé étrangère (avec eventuellement une contrainte sur cette clé).

Moi je verrai ça comme ça:
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
class Billet {
   // SplObjectStorage
   protected $_commentaires;
 
   public static function obtenirCommentaires () {
   }
 
   public function ajouterCommentaire (Commentaire $commentaire) {
       $this->_commentaires->attach($commentaire);
   }
 
   public function retirerCommentaire (Commentaire $commentaire) {
       $this->_commentaires->detach($commentaire);
   }
 
   public function sauvegarder () {
      foreach ($this->_commentaire as $commentaire) {
         $commentaire->save();
      }
   }
}
 
class Commentaire { 
//...
}
Je te recommande d'utiliser un factory au niveau de tes classes modèles d'une manière générale (utilise une classe abstraite Model pour ça). Ce factory pourra fonctionner de la façon suivante:
Code :
1
2
 
$commentaire  = Commentaire::find($id);
Citation:
ça m'agace un peu que la classe Billet aille trifouiller dans la table des commentaires.
Tout à fait, tu t'appercevra vite que définir les responsabilités d'une classe c'est de loin le plus difficile Eclate au maximum tes entitées, les relations apparaitront d'elles-mêmes.
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 09h19   #3
Futur Membre du Club
 
Homme Franck Dupont
Développeur multimédia
Inscription : avril 2008
Messages : 33
Détails du profil
Informations personnelles :
Nom : Homme Franck Dupont
Âge : 37
Localisation : France

Informations professionnelles :
Activité : Développeur multimédia
Secteur : High Tech - Multimédia et Internet

Informations forums :
Inscription : avril 2008
Messages : 33
Points : 19
Points : 19
Envoyer un message via MSN à kyfr59 Envoyer un message via Skype™ à kyfr59
Salut Benjamin,

Alors là, ya quelque chose qui m'échappe complètement : j'ai bien compris que SplObjectStorage allait me permettre de stocker les objets commentaires dans mon objet billet, mais je n'en vois pas du tout l'utilité !

Car je dois créer un objet commentaire qui doit être passé à $billet->ajouterCommentaire(), hors, pour créer un objet commentaire j'ai besoin de l'ID du billet concerné.

Ce qui donnerait ceci :

Code :
1
2
3
4
$billet = Billet(15);
$commentaire = new Commentaire($billet, 'commentaire'); // Pour récupérer l'ID du billet concerné
$billet->ajouterCommentaire($commentaire); // Ce qui ne sert plus à rien
$billet->save();
Je vois bien l'utilisation d'un tableau d'objets pour $billet->obtenirCommentaires() mais j'ai du mal à saisir l'utilité de ajouterCommentaire() et retirerCommentaire().

Merci d'avance.

Franck.
kyfr59 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 10h11   #4
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 015
Points : 5 015
Tu m'a dis dans ton premier post que tu modélisait une relation 1-n. Donc dans le cadre des classes Billet et Commentaire, ça s'exprime par "un billet à plusieurs commentaire" et "un commentaire porte sur un et un seul billet".

Donc la classe Billet doit se munir d'une méthode obtenirCommentaires (ou getComments) et la classe Commentaire peut se munir d'une méthode obtenirBillet (ou getArticle). Effectivement, ça marche dans les deux sens, mais fait attention au nombre d'instances:
Code :
1
2
3
4
 
$billet = new Billet(15);
$commentaire = $billet->getFirstcomment(); // par exemple
$billet2 = $commentaire->getBillet();
Dans cet exemple, les objets référencés par $billet et $billet2 sont identiques mais ils sont dupliqués ce qui peut poser des problèmes de collision: si tu fais des opérations sur l'un, l'autre ne sera pas à jour.
Le pattern IdentityMap peut t'aider à résoudre ce genre de problèmes: martinfowler.com/eaaCatalog/identityMap.html

Citation:
Alors là, ya quelque chose qui m'échappe complètement : j'ai bien compris que SplObjectStorage allait me permettre de stocker les objets commentaires dans mon objet billet, mais je n'en vois pas du tout l'utilité !
C'est pratique pour l'affichage et pour la gestion des commentaires. Personnellement, je trouve plus cohérent de faire $billet->ajouterCommentaire($commentaire);
que $commentaire = new Commentaire($billet, 'un commentaire');
Ainsi, la classe commentaire n'a pas besoin de savoir sur quel billet elle porte, c'est la classe Billet qui s'occupe de ses propres commentaires.

Pour résumer, je penche davantage pour l'approche "un billet porte n commentaire" plutôt que "un commentaire porte sur un billet" car c'est plus simple de gérer des billets que des commentaires.

Au niveau des vues ça fait sens:
Code :
1
2
3
4
5
6
 
<h1><?=$billet->title?></h1>
<p><?=$billet->body?></h1>
<?php foreach ($billet->obtenirCommentaires as $comment): ?>
<div><?=$comment->body?>
<?php endforeach; ?>
Sinon, comment ferais-tu pour récupérer tes commentaires ?
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 10h34   #5
Futur Membre du Club
 
Homme Franck Dupont
Développeur multimédia
Inscription : avril 2008
Messages : 33
Détails du profil
Informations personnelles :
Nom : Homme Franck Dupont
Âge : 37
Localisation : France

Informations professionnelles :
Activité : Développeur multimédia
Secteur : High Tech - Multimédia et Internet

Informations forums :
Inscription : avril 2008
Messages : 33
Points : 19
Points : 19
Envoyer un message via MSN à kyfr59 Envoyer un message via Skype™ à kyfr59
Le problème est que pour pouvoir faire $billet->ajouterCommentaire($commentaire), je dois instancier un objet $commentaire auparavant.

Et donc faire $commentaire = new Commentaire($billet, 'un commentaire')

Que penses-tu de passer les données au lieu de l'objet à la fonction, comme ceci :

Code :
$billet->ajouterCommentaire('commentaire', 'auteur').
kyfr59 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 10h37   #6
Expert Confirmé
 
Avatar de RunCodePhp
 
Inscription : janvier 2010
Messages : 2 707
Détails du profil
Informations personnelles :
Localisation : Réunion

Informations forums :
Inscription : janvier 2010
Messages : 2 707
Points : 3 277
Points : 3 277
Salut

Si l'ajout d'un commentaire doit avoir un ID de billet et un contenu, alors faut juste renseigner ces 2 données, non ?
Code :
1
2
3
$billet = Billet(15);
$commentaire = new Commentaire(array($billet->getID(), 'commentaire'));
$billet->ajouterCommentaire($commentaire);
Ou plus directement :
Code :
1
2
$billet = Billet(15);
$billet->ajouterCommentaire(new Commentaire(array($billet->getID(), 'commentaire')));
Ne faut il pas faire comme ceci par exemple, passer en argument un tableau, et la classe Commentaire se charge de créer coté Bdd le nouveau commentaire (ça à l'air d'être le cas).

En tout cas, si 1 billet doit avoir une collection d'Objets Commentaire, Billet::ajouterCommentaire() devient obligatoire, car dans ton exemple de code, $commentaire est totalement isolé, ne fait pas partie de $billet.


Disons que j'interviens aussi pour dire que je viens de découvrir SplObjectStorage grâce à ce topic, je pense que ça va m'être utile.
__________________
Win XP | WampServer 2.2d | Apache 2.2.21 | Php 5.3.10 | MySQL 5.5.20
Si debugger, c'est supprimer des bugs, alors programmer ne peut être que les ajouter [Edsger Dijkstra]
RunCodePhp est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 11h03   #7
Futur Membre du Club
 
Homme Franck Dupont
Développeur multimédia
Inscription : avril 2008
Messages : 33
Détails du profil
Informations personnelles :
Nom : Homme Franck Dupont
Âge : 37
Localisation : France

Informations professionnelles :
Activité : Développeur multimédia
Secteur : High Tech - Multimédia et Internet

Informations forums :
Inscription : avril 2008
Messages : 33
Points : 19
Points : 19
Envoyer un message via MSN à kyfr59 Envoyer un message via Skype™ à kyfr59
J'ai également découvert SplObjectStorage grâce à Benjamin. Je vais y jeter un oeil, enfin, quand j'en aurais finis avec ce topic, grrr.

Ta solution première solution me semble la plus efficace, et la plus simple RunCodePhp.

Mais une fois que tu as appelé new Commentaire(array($billet->getID(), 'commentaire')), ton commentaire est lié au billet (dans la base).

La fonction $billet->ajouterCommentaire($commentaire) me semble donc superflue.

Il suffirait juste d'une fonction $billet->getCommentaires() et le tour est joué.

Qu'en dites vous ?
kyfr59 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 11h04   #8
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 015
Points : 5 015
Je rebondis sur vos proposition.

Je pense que Billet::ajouterCommentaire peut avoir les deux signatures; ça se modélise comme ça:
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 
class Billet extends SplObjectStorage {
	/**
	 * @var SplObjectStorage
	 */
	protected $_comments;
 
	/**
	 * Ajouter un commentaire.
	 * 
	 * Cette méthode supporte deux signatures:
	 *    Billet::ajouterCommentaire(Commentaire $comment);
	 *    Billet::ajouterCommentaire('title','body');
	 * 
	 * @param Commentaire $comment
	 * @return Commentaire
         */
	public function ajouterCommentaire () {
		$argv = func_get_args();
		$argc = func_num_args();
		if ($argc === 1 && is_a($argv[0], 'Commentaire')) {
			return $this->_comments->attach($argv[0]);
		}
		elseif ($argc === 2) {
			return $this->_comments->attach(new Commentaire($argv[0],$argv[1]);
		}
		else {
			throw new BadMethodCallException(__METHOD__ . " attends 1 ou 2 paramètres, $argc fournis");
		}
	}
}
Comme ça tu peux faire indifférement:
Code :
1
2
3
4
5
6
 
$billet = new Billet(15);
$billet->ajouterCommentaire('titre', 'foobar');
// Equivaut à 
$commentaire = new Commentaire('titre','foobar');
$billet->ajouterCommentaire($commentaire);
Dans les deux cas, ton commentaire t'es retourné par Billet::ajouterCommentaire.
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 11h23   #9
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 015
Points : 5 015
Appendum: SplObjectStorage est une super classe pour gérer des objets mais ce n'est pas une hashmap !! Si votre but est de retrouver vos petits par un système clé valeur, l'idéal reste le bon vieux tableau.

Vous remarquerez par ailleurs que SplObjectStorage est un itérateur, ça va bien nous servir pour créer des fonctions de filtrage par exemple:
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
class GenericFilterIterator extends FilterIterator {
	/**
	 * @var array
	 */
	protected $_callbacks;
 
	public function accept () {
		$current = $this->getInnerIterator()->current();
		foreach ($this->_callbacks as $callback) {
			if (!$callback($current)) return false;
		}
		return true;
	}
 
	public function attach ($closure) {
		if (is_callable($closure)) {
			$this->_callbacks[] = $closure;
			return true;
		}
		return false;
	}
}
et on va l'utiliser comme ça (PHP 5.3 only mais adaptable à des version antérieures):
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
class FooBar {
	public $_name;
	public $_value;
	public function __construct ($name, $value) {
		$this->_name = $name;
		$this->_value = $value;
	}
	public function __toString () { return "{$this->_name} {$this->_value}"; }
}
 
$object_storage = new SplObjectStorage();
$object_storage->attach(new FooBar('hello','there'));
$object_storage->attach(new FooBar('hello','roger'));
$object_storage->attach(new FooBar('degage','roger'));
 
$filter = new GenericFilterIterator($object_storage);
$filter->attach(function ($object) {
	return $object->_name == 'hello';
});
 
foreach ($filter as $object) {
	echo $object . '<br />';
}
Renseignez vous sur http://www.php.net/~helly/php/ext/spl/
il existe tout un tas d'itérateur pour tous les usages, filtres, limites, XML et j'en passe...

Pour pouvoir utiliser cette merveille de technologie que nous envie la Nasa, il faut que votre objet Traversable implémente l'interface Iterator.
Dans le cas d'un tableau associatif, vous pouvez passer par un ArrayIterator ou un ArrayObject comme ceci:
Code :
1
2
3
4
5
 
$arr = array(1,2,3);
$iterator = new ArrayIterator($arr);
$iiterator = new LimitIterator($iterator, 0,1);
foreach ($iiterator as $value) { echo $value; }
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 11h25   #10
Expert Confirmé
 
Avatar de RunCodePhp
 
Inscription : janvier 2010
Messages : 2 707
Détails du profil
Informations personnelles :
Localisation : Réunion

Informations forums :
Inscription : janvier 2010
Messages : 2 707
Points : 3 277
Points : 3 277
Citation:
Mais une fois que tu as appelé new Commentaire(array($billet->getID(), 'commentaire')), ton commentaire est lié au billet (dans la base).

La fonction $billet->ajouterCommentaire($commentaire) me semble donc superflue.

Il suffirait juste d'une fonction $billet->getCommentaires() et le tour est joué.
Si j'ai bien compris, Billet::ajouterCommentaire() est loin d'être superflue, comme je l'avais dit, elle est obligatoire, car c'est toi qui a défini que tout Objet Billet DOIT contenir 1 ou plusieurs commentaire.

Quand on appel : new Commentaire(...), le commentaire et certes créé coté Bdd, mais ne fait absolument pas partie de l'Objet $billet en court.
Du coup, si tu appel Billet::getCommentaires(), ça va rien renvoyer du tout vu qu'aucun Objet Commentaire à été ajouté, je dis bien Objet.

C'est peut être ça qui te turlupine : Billet::getCommentaires() renvoie uniquement des Objets Commentaires, donc coté traitements n'effectue pas de requêtes SQL pour récupérer les commentaires.
Disons que ce serait dans ce cas présent, après l'ajout d'1 commentaire, des requêtes inutiles (superflux).


Disons qu'il faudrait peut être 2 méthodes coté Billet : Une qui récupère les Commentaires en Bdd qui les rajouterait au Billet, une autre renvoie les Objets Commentaires (getCommentaires()).
Faut voir ...
Benjamin a peut être un avis ... plus avisé.
__________________
Win XP | WampServer 2.2d | Apache 2.2.21 | Php 5.3.10 | MySQL 5.5.20
Si debugger, c'est supprimer des bugs, alors programmer ne peut être que les ajouter [Edsger Dijkstra]
RunCodePhp est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 11h29   #11
Futur Membre du Club
 
Homme Franck Dupont
Développeur multimédia
Inscription : avril 2008
Messages : 33
Détails du profil
Informations personnelles :
Nom : Homme Franck Dupont
Âge : 37
Localisation : France

Informations professionnelles :
Activité : Développeur multimédia
Secteur : High Tech - Multimédia et Internet

Informations forums :
Inscription : avril 2008
Messages : 33
Points : 19
Points : 19
Envoyer un message via MSN à kyfr59 Envoyer un message via Skype™ à kyfr59
Merci Benjamin,

Dernière chose, si j'utilise le premier appel :

Code :
1
2
$billet = new Billet(15);
$billet->ajouterCommentaire('titre', 'foobar');
De quelle manière ma fontion ajouterCommentaire() devrait elle faire l'ajout dans la base ?

Directement en SQL :
Code :
1
2
3
4
public function ajouterCommentaire($comment, $auteur)
{
  $query = "INSERT INTO commentaires VALUES...";
}
Avec l'instanciation d'un objet Commentaire :
Code :
1
2
3
4
5
public function ajouterCommentaire($comment, $auteur)
{
  $commentaire = new Commentaire();
  $commentaire->add($this->id, $comment, $auteur);
}
Ou via une fonction statique :
Code :
1
2
3
4
public function ajouterCommentaire($comment, $auteur)
{
  Commentaire::add($this->id, $comment, $auteur);
}
kyfr59 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 11h35   #12
Futur Membre du Club
 
Homme Franck Dupont
Développeur multimédia
Inscription : avril 2008
Messages : 33
Détails du profil
Informations personnelles :
Nom : Homme Franck Dupont
Âge : 37
Localisation : France

Informations professionnelles :
Activité : Développeur multimédia
Secteur : High Tech - Multimédia et Internet

Informations forums :
Inscription : avril 2008
Messages : 33
Points : 19
Points : 19
Envoyer un message via MSN à kyfr59 Envoyer un message via Skype™ à kyfr59
Citation:
Envoyé par RunCodePhp Voir le message
C'est peut être ça qui te turlupine : Billet::getCommentaires() renvoie uniquement des Objets Commentaires, donc coté traitements n'effectue pas de requêtes SQL pour récupérer les commentaires.
Oui, c'est ça qui me turlupinait, ça commence à s'éclaircir !

Merci.
kyfr59 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/02/2011, 13h22   #13
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 015
Points : 5 015
Citation:
De quelle manière ma fontion ajouterCommentaire() devrait elle faire l'ajout dans la base ?
Billet::ajouterCommentaire n'a pas à savoir comment insérer un commentaire dans la base de données. C'est le rôle de la classe Commentaire de fournir une méthode pour l'insertion (Commentaire::creer(xxx,yyy) par exemple).

La méthode Billet::ajouterCommentaire doit donc soit
- appeller Commentaire::creer et récupérer son retour (une instance de Commentaire) pour l'insérer dans sa liste de commentaire (Billet::$_commentaires)
- laisser le contrôleur de commentaire insérer en DB (s'il en est capable), là encore, tu peux faire un constructeur avec plusieurs signature comme je l'ai montré plus haut (une signature pour la création et une signature pour la réccupération de données).

Histoire de garder ça propre et clair, je te recommande de ne pas abuser des signatures multiples: PHP n'étant pas fait pour à la base, ça devient complexe pour manipuler classes et objets...

Voici ce que je te recommande pour la classe Commentaire (on part de l'hypothese que tu dispose d'un singleton Database pour PDO, si tu ne sais pas les faire, je t'en fournirai un, c'est très pratique à utiliser):
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
 
abstract class Model {
 
	protected $_data;
	protected static $_statements;
	protected static $_initialized;
 
	protected function __construct ($data) {
	    $this->_data = $data;
	}
 
	public function __get ($key) {
		if (isset($this->_data[$key])) {
			return $this->_data[$key];
		}
		else {
			throw new OutOfRangeException("Cannot find $key in model's data");
		}
	}
 
	public function __set ($key, $value) {
		if ($key == 'id') {
			throw new LogicException('Cannot change id in Model instances');
		}
 
		if (isset($this->_data[$key])) {
			$this->_data[$key] = (string)$value;
			$this->update();
		}
		else {
			throw new OutOfRangeException("Cannot find $key in model's data");
		}
	}
 
	protected static function _init () {
	    if (func_num_args() == 0) throw BadMethodCallException(__METHOD__ . ' expects at least 1 parameter');
	    $statements = func_get_arg(0);
 
		if (isset($statements['create'], $statements['retrieve'], $statements['update'], $statements['delete'])) {
			static::$_statements['create'] = Database::prepare($statements['create']);
			static::$_statements['retrieve'] = Database::prepare($statements['retrieve']);
			static::$_statements['update'] = Database::prepare($statements['update']);
			static::$_statements['delete'] = Database::prepare($statements['delete']);
			return static::$_initialized = true;
		}
		else
			throw new InvalidArgumentException("Statements are missing");
	}
 
	public static function find ($id) {
		if (!static::$_initialized && !static::_init()) return false;
 
		if (static::$_statements['retrieve']->execute(array(':id' => $id))) {
		    return new static (static::$_statements['retrieve']->fetch(PDO::FETCH_ASSOC));
		}
		else
		    return false;
	}
 
	public static function create ($values) {
		if (!static::$_initialized && !static::_init()) return false;
 
		if (static::$_statements['create']->execute($values)) {
			$id = Database::lastInsertId();
			return static::find($id);
		}
		else {
			return false;
		}
	}
 
	public function delete () {
		return static::$_statements['delete']->execute(array(':id' => $this->_data['id']));
	}
 
	protected function update () {
		return static::$_statements['update']->execute($this->_data);
	}
}
Avec le commentaires du développeur (moi tout seul )
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
 
<?php
 
/**
 *
 * Cette class est abstraite car elle ne permet
 * pas d'elle même la création d'une instance de modèle.
 * On verra plus loin de quoi elle a besoin pour fonctionner
 * correctement.
 *
 * Dans cette classe, on part du principe que les tables en
 * base de données qui porteront les valeur possède une clé
 * primaire nommée 'id'. C'est nécéssaire pour se simplifier
 * la vie des deux cotés et c'est une recommandation de ma
 * part.
 *
 */
abstract class Model {
 
    /**
     * On va stocker dans cette variable un tableau
     * qui caractérise les données de notre objet.
     * En fait, ce seront les données renvoyées par le
     * 'select'.
     */
	protected $_data;
 
	/**
	 * On va conserver les PDOStatement qui nous serviront
	 * à effectuer les opérations CRUD (Create, Retrieve,
	 * Update, Delete) qui nous permettront de manager
	 * conjointement les données de la l'objet et leur
	 * valeurs en base de données.
	 * Cette propriété doit rester statique car elle
	 * est dépendante d'une classe modèle donnée et non
	 * pas d'un objet, il est donc innutile de délcarer
	 * 4 PDOStatement par instance car chaque instance à
	 * le même job.
	 * Les données renvoyées par ces statements sont en
	 * revanche dépendantes de l'instance en cours donc
	 * elles sont dans la propriété d'instance _data.
	 */
	protected static $_statements;
 
	/**
	 * On conserve ici un flag qui nous indique si les
	 * propriété statique (de classe si tu préfères) ont
	 * été correctement initialisées.
	 */
	protected static $_initialized;
 
	/**
	 * On choisit de garder le constructeur protéger et
	 * de ne donner un accès aux instances qu'au travers
	 * des méthodes 'find' et 'create' (voir plus bas).
	 * ça signifie entre autre que faire un new sur une
	 * classe dérivée de modèle échoura systématiquement.
	 *
	 * Ce constructeur par défaut mets à jour
	 * les données de l'objet (_data) avec le tableau
	 * qu'il reçoit en paramètre.
	 */
	protected function __construct ($data) {
	    $this->_data = $data;
	}
 
	/**
	 * On définit ici une méthode magique pour accéder
	 * directement au contenu de _data.
	 * ça permet de faire
	 * $instance_modele->nom_de_champ
	 */
	public function __get ($key) {
		if (isset($this->_data[$key])) {
			return $this->_data[$key];
		}
		else {
			throw new OutOfRangeException("Cannot find $key in model's data");
		}
	}
 
	/**
	 * On définit ici une méthode magique
	 * pour éditer le contenu de _data.
	 * Ainsi, à chaque edition de l'objet
	 * de travail, les champs correspondant
	 * en database dont mis à jour instantanment.
	 * On appelle cela 'ActiveRecord' ou les objets
	 * et leurs données en DB sont mis à jour ensemble.
	 */
	public function __set ($key, $value) {
 
	    /**
	     * on interdit formellement de changer
	     * l'id utilisé dans la base de données !!!
	     * Sinon ça pourrait découpler l'objet et
	     * sa représentation mysql ce qui violerait
	     * le design pattern active record et créerait
	     * des doublons partout.
	     */
		if ($key == 'id') {
			throw new LogicException('Cannot change id in Model instances');
		}
 
		/**
		 * Si la clé existe
		 */
		if (isset($this->_data[$key])) {
		    /**
		     * on la modifie
		     */
			$this->_data[$key] = (string)$value;
 
			/**
			 * et on met à jour dans la base de données
			 */
			$this->update();
		}
		else {
			throw new OutOfRangeException("Cannot find $key in model's data");
		}
	}
 
	/**
	 * Dans cette méthode, on va initialiser les
	 * PDOStatement nécéssaire à toutes les operations
	 * CRUD. Elle reste protégé pour que les classes
	 * filles puissent surcharger son comportement
	 * (en fait elles DOIVENT le surcharger) et que
	 * de l'exterieur de la classe on ne puiss pas
	 * l'appeller.
	 * Note: cette méthode devrait porter un paramètre
	 * $statements mais elle n'en porte aucun.
	 * On procède de cette manière pour que les classes
	 * filles puisent porter une signature sans paramètres
	 * qui sera appellée par les méthodes statiques
	 * find et create qui ne doivent pas lui passer
	 * de paramètres.
	 * On utilisera à cet effet le late static binding
	 * dans les méthodes find et create qui permettront
	 * d'appeller _init dans la fille qui se chargera
	 * de définir les statement puis les passera à
	 * _ini de la mère (Model).
	 */
	protected static function _init () {
	    if (func_num_args() == 0) throw BadMethodCallException(__METHOD__ . ' expects at least 1 parameter');
 
	    /**
	     * les statements sont dans le premier paramètre
	     */
	    $statements = func_get_arg(0);
 
	    /**
	     * On verifie qu'on a tout ce qu'il nous faut
	     */
		if (isset($statements['create'], $statements['retrieve'], $statements['update'], $statements['delete'])) {
 
		    /**
		     * on créé tous les statements CRUD
		     */
			static::$_statements['create'] = Database::prepare($statements['create']);
			static::$_statements['retrieve'] = Database::prepare($statements['retrieve']);
			static::$_statements['update'] = Database::prepare($statements['update']);
			static::$_statements['delete'] = Database::prepare($statements['delete']);
			return static::$_initialized = true;
		}
		else
			throw new InvalidArgumentException("Statements are missing");
	}
 
	/**
	 * Cette méthode est, avec create, le seul moyen
	 * d'instancier des objets Model.
	 */
	public static function find ($id) {
	    /**
	     * Si la classe (attention, pas l'instance! )
	     * n'est pas initialisée, on appelle _init dans
	     * le contexte de la fille de Model.
	     * On utilise pour ça le mot clé static.
	     * CF late static binding.
	     */
		if (!static::$_initialized && !static::_init()) return false;
 
		/**
		 * on execute le statement de récupération des données
		 */
		if (static::$_statements['retrieve']->execute(array(':id' => $id))) {
 
		    /**
		     * on vérifie qu'on trouve bien quelque chose
		     */
		    if (static::$_statements['retrieve']->rowCount()) {
 
    		    /**
    		     * et on appelle le constructeur DE LA FILLE toujours avec
    		     * le mot clé static en lui passant le retour de la requête SQL.
    		     * voir le constructeur.
    		     * Et on renvoie l'instance ainsi crée.
    		     */
    		    return new static (static::$_statements['retrieve']->fetch(PDO::FETCH_ASSOC));
		    }
		}
 
	    /**
	     * on s'est gaufré, il n'y a pas d'objet
	     */
	    return false;
	}
 
	/**
	 * Create est avec find la seule méthode permettant de 
	 * réccupérer des instances, elle attends en paramètres
	 * un tableau associatif clé valeur dont les clés 
	 * correspondent aux éléments présents dans la requête 
	 * préparée.
	 */
	public static function create ($values) {
	    /**
	     * Toujours pareil, on tente d'initialiser si ce n'est pas
	     * défjà fait
	     */
		if (!static::$_initialized && !static::_init()) return false;
 
		/**
		 * On tente l'insertion
		 */
		if (static::$_statements['create']->execute($values)) {
 
		    /**
		     * Si c'est ok, on réccupère l'id inséré
		     */
			$id = Database::lastInsertId();
 
			/**
			 * et on renvoie l'objet en passant par find
			 * (pattern DRY: Don't Repeat Yourself)
			 */
			return static::find($id);
		}
		else {
			return false;
		}
	}
 
	/**
	 * Contrairement à find et create qui 
	 * sont des méthodes de classe, delete
	 * est une méthode d'instance car elle
	 * a besoin que l'objet existe pour 
	 * le supprimer (logique).
	 * On ne fait donc qu'appelle le 
	 * statement de suppression avec l'id 
	 * présent dans les données de l'objet
	 */
	public function delete () {
		return static::$_statements['delete']->execute(array(':id' => $this->_data['id']));
	}
 
	/**
	 * Contrairement à find et create,
	 * update est une méthode d'instance
	 * car elle a besoin des données de l'objet
	 * pour le mettre à jour en base (logique).
	 * Comme tu le vois dans __set, cette
	 * méthode est appellée à chaque modification
	 * de ton objet.
	 * On ne fais donc qu'appeller le statement
	 * d'update pour mettre les donénes de
	 * l'objet à jour dans la db.
	 */
	protected function update () {
		return static::$_statements['update']->execute($this->_data);
	}
}
Maintenant, on va pouvoir créer notre classe:
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
class TestModel extends Model {
 
    public static function _init () {
        $statements = array(
            'create' => 'INSERT INTO `test` (`name`,`value`) VALUES (:name, :value)',
            'retrieve' => 'SELECT `id`,`name`,`value` FROM `test` WHERE `id`=:id',
            'update' => 'UPDATE `test` SET `name`=:name, `value`=:value WHERE `id`=:id',
            'delete' => 'DELETE FROM `test` WHERE `id`=:id',
        );
        return parent::_init($statements);
    }
 
    public static function create ($name, $value) {
        return parent::create(array(':name' => $name, ':value' => $value));
    }
}
Et on peut l'utiliser comme ceci (dans un de mes contrôleurs, pense à ajouter de la sécurité):
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 
class SummaryController extends BaseController {
 
     public static function index () {
 
     }
 
     public static function create () {
         $test = TestModel::create('time', time());
         return compact('test');
     }
 
     public static function retrieve () {
         $test = TestModel::find(self::$_request->id);
         return compact('test');
     }
 
     public static function delete () {
         $founds = self::retrieve();
         $test = $founds['test'];
         $result = $test->delete();
         return compact('test', 'result');
     }
 
     public static function update () {
         $test = TestModel::find(self::$_request->id);
         $test->value = self::$_request->value;
         return compact('test', 'result');
     }
}
Ensuite dans une vue, on peut faire:
Code :
1
2
3
4
 
<h1>Object created: <?=$test->id?></h1>
<span>Name: <?=$test->name?></span><br />
<span>Value: <?=$test->value?></span>
C'est In Ze Pocket si j'ose dire
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 11/02/2011, 10h19   #14
Modérateur
 
Avatar de Michel Rotta
 
Homme Michel Rotta
Responsable d'exploitation informatique
Inscription : septembre 2005
Messages : 4 913
Détails du profil
Informations personnelles :
Nom : Homme Michel Rotta
Âge : 49
Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

Informations professionnelles :
Activité : Responsable d'exploitation informatique
Secteur : Distribution

Informations forums :
Inscription : septembre 2005
Messages : 4 913
Points : 7 505
Points : 7 505
Si je peux me permettre d'intervenir, un peu tard dans votre conversation.

Structurellement ce n'est pas à l'objet billet d'ajouter un commentaire, mais à l'objet commentaire de lier un billet. Donc c'est dans l'objet commentaire qu'il faut prévoir une méthode setBillet() avec deux paramètres possibles :
  • soit l'id du billet a lier qui sera alors stockée dans le champ correspondant du commentaire en mémoire, et, a terme, sauvegardé
  • soit l'objet billet en lui même duquel il faudra récupérer l'id et faire le traitement ci-dessus.
Il convient aussi de prévoir un objet permettant de gérer une collection de commentaire. On pourra alors prévoir une méthode pour ton objet billet qui va recevoir en paramètre la collection de commentaires à ajouter au billet. Il suffira, pour chaque commentaire, d'utiliser la méthode setBillet() avec l'objet billet concerné pour faire l'ajout.


Accessoirement, ce que vous travaillez ici et ce type d'accès est déjà utilisé dans de nombreux ORM, il serait peut-être intéressant d'y jeter un œil avant de réinventer la roue.

Je me retire sur la pointe des pieds.
__________________
Si tu donnes un poisson à un homme, il mangera un jour. Si tu lui apprends à pêcher, il mangera toujours (Lao Tseu).
  • Pensez à valoriser les réponses pertinantes, cliquez sur le bouton vert +1 pour indiquer votre accord avec la solution proposée.
  • Pensez à utiliser la balise [code] pour afficher du code, elle est cachée sous le bouton [#] dans l'éditeur.
  • Une discussion est terminée ? Alors le bouton est votre ami !
Michel Rotta est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 11/02/2011, 10h31   #15
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 015
Points : 5 015
Pas envie de prendre un ORM, j'aurais l'impression d'annihiler des fourmis à la bombe atomique.
Cela étant, je trouve que Michel Rotta à raison dans un sens: c'est effectivement la table "commentaire" qui porte l'information "id_billet". Dons si on s'en tiens à une stricte modélisation objet, c'est effectivement au commentaire de connaitre son billet...
Cependant, je maintiens qu'il est plus pratique d'avoir une classe Billet capable de retrouver ses commentaires (et de les stocker dans sa map au passage), quite à appeller une méthode statique de Commentaire pour ramasser des billet.

Est ce que ça serait convenable :
Code :
1
2
3
4
5
6
7
8
 
class Commentaire extends Model {
//....
const BILLET_ID = 1;
const COMMENTAIRE_ID = 2;
public static function find ($id, $type = self::COMMENTAIRE_ID) {
//...
}
ça devrait permettre de trouver les commentaires soit par leurs ID soit par le billet dont ils dépendent.

-- EDIT: Edwin on the rock!
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 11/02/2011, 10h39   #16
Modérateur
 
Avatar de Michel Rotta
 
Homme Michel Rotta
Responsable d'exploitation informatique
Inscription : septembre 2005
Messages : 4 913
Détails du profil
Informations personnelles :
Nom : Homme Michel Rotta
Âge : 49
Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

Informations professionnelles :
Activité : Responsable d'exploitation informatique
Secteur : Distribution

Informations forums :
Inscription : septembre 2005
Messages : 4 913
Points : 7 505
Points : 7 505
Ce n'est que ce que j'ai décris plus haut.

Ton billet peut récupérer par une méthode un objet collection d'objets commentaire (il n'y a pas d'erreur sur les s).

Par contre, si tu crées un nouveau commentaire, il convient d'instantier un nouvel objet commentaire, de le compléter y compris avec l'objet billet associé et de le sauvegarder. C'est la seul bonne méthode pour garder une indépendance entre des entités dans ton modèle. Ici tu n'en as que 2, tu peux peut-être jongler un peu, imagine avec une structure plus habituel et entre 50 et 100 tables...


Pour ce qui est de tuer des fourmis avec des bombes atomique, j'ai entendu dire qu'elles étaient résistantes aux radiations...

J'en reviens à l'ORM, jette un œil attentif du côté de Doctrine 2, il a une approche très similaire à la tienne et pourrait te faciliter grandement la vie. Même pour deux tables. Justement en ne réinventant pas la roue.

Reste a savoir si ton projet est pour apprendre à manipuler les données (et alors ton approche est la bonne) ou a faire un site en exploitation (et alors l'ORM est une option à prendre sérieusement en considération).

A partir d'ici la disgression sur les ORM, Framework et patterne continue ici.
__________________
Si tu donnes un poisson à un homme, il mangera un jour. Si tu lui apprends à pêcher, il mangera toujours (Lao Tseu).
  • Pensez à valoriser les réponses pertinantes, cliquez sur le bouton vert +1 pour indiquer votre accord avec la solution proposée.
  • Pensez à utiliser la balise [code] pour afficher du code, elle est cachée sous le bouton [#] dans l'éditeur.
  • Une discussion est terminée ? Alors le bouton est votre ami !
Michel Rotta est déconnecté   Envoyer un message privé Réponse avec citation 00
Réponse Proposer ce sujet en actualité
Outils de la discussion



Fuseau horaire GMT +2. Il est actuellement 11h32.


 
 
 
 
Partenaires

Hébergement Web