IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

Modélisation d'une relation 1 à plusieurs


Sujet :

Langage PHP

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Avril 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France

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

    Informations forums :
    Inscription : Avril 2008
    Messages : 39
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $billet = new Billet(15);
    $billet->addComment('commentaire');
    Solution 2 :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  2. #2
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    $commentaire  = Commentaire::find($id);
    ç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.

  3. #3
    Membre averti
    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Avril 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France

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

    Informations forums :
    Inscription : Avril 2008
    Messages : 39
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  4. #4
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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

    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 ?

  5. #5
    Membre averti
    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Avril 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France

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

    Informations forums :
    Inscription : Avril 2008
    Messages : 39
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    $billet->ajouterCommentaire('commentaire', 'auteur').

  6. #6
    Membre Expert Avatar de RunCodePhp
    Profil pro
    Inscrit en
    Janvier 2010
    Messages
    2 962
    Détails du profil
    Informations personnelles :
    Localisation : Réunion

    Informations forums :
    Inscription : Janvier 2010
    Messages : 2 962
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    $billet = Billet(15);
    $commentaire = new Commentaire(array($billet->getID(), 'commentaire'));
    $billet->ajouterCommentaire($commentaire);
    Ou plus directement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  7. #7
    Membre averti
    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Avril 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France

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

    Informations forums :
    Inscription : Avril 2008
    Messages : 39
    Par défaut
    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 ?

  8. #8
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Par défaut
    Je rebondis sur vos proposition.

    Je pense que Billet::ajouterCommentaire peut avoir les deux signatures; ça se modélise comme ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  9. #9
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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; }

  10. #10
    Membre Expert Avatar de RunCodePhp
    Profil pro
    Inscrit en
    Janvier 2010
    Messages
    2 962
    Détails du profil
    Informations personnelles :
    Localisation : Réunion

    Informations forums :
    Inscription : Janvier 2010
    Messages : 2 962
    Par défaut
    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é.

  11. #11
    Membre averti
    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Avril 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France

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

    Informations forums :
    Inscription : Avril 2008
    Messages : 39
    Par défaut
    Merci Benjamin,

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

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public function ajouterCommentaire($comment, $auteur)
    {
      $query = "INSERT INTO commentaires VALUES...";
    }
    Avec l'instanciation d'un objet Commentaire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public function ajouterCommentaire($comment, $auteur)
    {
      Commentaire::add($this->id, $comment, $auteur);
    }

Discussions similaires

  1. Réponses: 25
    Dernier message: 16/02/2011, 17h40
  2. Réponses: 5
    Dernier message: 08/04/2009, 17h39
  3. Réponses: 2
    Dernier message: 01/04/2009, 16h44
  4. Modélisation d'une relation
    Par loganblack dans le forum Schéma
    Réponses: 4
    Dernier message: 10/07/2007, 11h26
  5. [ADO.NET]Comment réaliser une relation sur plusieurs champs?
    Par kleomas dans le forum Accès aux données
    Réponses: 3
    Dernier message: 13/03/2006, 12h40

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo