web-dev-qa-db-fra.com

Gestion des relations dans Laravel, adhérant au modèle de référentiel

Tout en créant une application dans Laravel 4 après avoir lu le livre de T. Otwell sur les bons modèles de conception dans Laravel, je me suis retrouvé à créer des référentiels pour chaque table de l'application.

J'ai fini avec la structure de table suivante:

  • Etudiants: identifiant, nom
  • Cours: id, name, teacher_id
  • Enseignants: identifiant, nom
  • Assignments: id, name, course_id
  • Scores (agit comme un pivot entre les étudiants et les devoirs): id_étudiant, id_affectation, scores

J'ai des classes de référentiel avec trouver, créer, mettre à jour et supprimer des méthodes pour toutes ces tables. Chaque référentiel a un modèle Eloquent qui interagit avec la base de données. Les relations sont définies dans le modèle de la documentation de Laravel: http://laravel.com/docs/eloquent#relationships .

Lors de la création d'un nouveau cours, tout ce que je fais est d'appeler la méthode create dans le référentiel de cours. Ce cours comporte des affectations. Par conséquent, lors de la création de celui-ci, je souhaite également créer une entrée dans le tableau des scores pour chaque élève du cours. Je le fais par l'intermédiaire du référentiel d'attribution. Cela implique que le référentiel d’affectation communique avec deux modèles Eloquent, avec le modèle d’affectation et d’étudiant.

Ma question est la suivante: étant donné que cette application grandira probablement et que de nouvelles relations seront introduites, est-ce une bonne pratique de communiquer avec différents modèles Eloquent dans des référentiels ou faut-il plutôt utiliser d'autres référentiels (par exemple, appeler d'autres référentiels à partir du référentiel Assignment ) ou devrait-il être fait dans les modèles Eloquent tous ensemble?

En outre, est-ce une bonne pratique d’utiliser le tableau des scores comme pivot entre les devoirs et les étudiants ou faut-il le faire ailleurs?

112
ehp

N'oubliez pas que vous demandez des avis: D

Voilà le mien:

TL; DR: Oui, c'est bien.

Tu t'en sors bien!

Je fais exactement ce que vous faites souvent et trouve que cela fonctionne très bien.

Toutefois, j’organise souvent des référentiels autour de la logique métier au lieu d’avoir un référentiel par table. Ceci est utile car il s'agit d'un point de vue centré sur la manière dont votre application doit résoudre votre "problème métier".

Un cours est une "entité", avec des attributs (titre, identifiant, etc.) et même d'autres entités (assignations, qui ont leurs propres attributs et éventuellement des entités).

Votre référentiel "Cours" devrait pouvoir renvoyer un cours et les attributs/attributions des cours (y compris l'attribution).

Heureusement, vous pouvez accomplir cela avec Eloquent.

(Je me retrouve souvent avec un référentiel par table, mais certains référentiels sont utilisés beaucoup plus que d’autres, et donc beaucoup plus de méthodes. Votre référentiel "cours" peut être beaucoup plus complet que votre référentiel Assignments, par exemple, si votre les centres d’application se concentrent davantage sur les cours et moins sur la collection d’affectations d’un cours).

La partie difficile

J'utilise souvent des référentiels à l'intérieur de mes référentiels pour effectuer certaines actions de base de données.

Tout référentiel mettant en œuvre Eloquent afin de gérer les données renverra probablement des modèles Eloquent. Dans cette optique, il n'y a pas de problème si votre modèle de cours utilise des relations intégrées pour récupérer ou enregistrer des affectations (ou tout autre cas d'utilisation). Notre "implémentation" est construite autour d'Eloquent.

D'un point de vue pratique, cela a du sens. Il est peu probable que nous changions les sources de données en une chose qu'Eloquent ne peut pas gérer (en une source de données non-SQL).

ORMS

La partie la plus délicate de cette configuration, du moins pour moi, consiste à déterminer si Eloquent nous aide ou nous nuit. Les ORM sont un sujet délicat, car bien qu’ils nous aident grandement d’un point de vue pratique, ils couplent également votre code "entités de logique métier" avec le code qui récupère les données.

Ce type de confusion nous empêche de savoir si la responsabilité de votre référentiel est de gérer les données ou de récupérer/mettre à jour des entités (entités de domaine métier).

En outre, ils agissent comme les objets mêmes que vous transmettez à vos points de vue. Si vous devez par la suite cesser d’utiliser des modèles Eloquent dans un référentiel, vous devez vous assurer que les variables transmises à vos vues se comportent de la même manière ou disposent des mêmes méthodes, sinon la modification de vos sources de données se répercutera. vues, et vous avez (partiellement) perdu le but de soustraire votre logique aux référentiels - la maintenabilité de votre projet est réduite en tant que.

Quoi qu'il en soit, ce sont des pensées quelque peu incomplètes. Comme indiqué, ils ne sont que mon opinion, ce qui est le résultat de la lecture de Domain Driven Design et du visionnage de vidéos du même genre keynote de "oncle bob" à Ruby Midwest au cours de la dernière année.

68
fideloper

Je termine un grand projet en utilisant Laravel 4 et je devais répondre à toutes les questions que vous posez maintenant. Après avoir lu tous les livres disponibles Laravel à Leanpub, et des tonnes de googling, je suis venu avec la structure suivante.

  1. Une classe de modèles Eloquent par table datable
  2. Une classe de référentiel par modèle Eloquent
  3. Une classe de service pouvant communiquer entre plusieurs classes de référentiel.

Alors, disons que je construis une base de données de films. J'aurais au moins les classes suivantes du modèle Eloquent suivantes:

  • Film
  • Studio
  • Réalisateur
  • Acteur
  • La revue

Une classe de référentiel encapsulerait chaque classe de modèle Eloquent et serait responsable des opérations CRUD sur la base de données. Les classes du référentiel peuvent ressembler à ceci:

  • MovieRepository
  • StudioRepository
  • DirectorRepository
  • ActeurRépôt
  • ReviewRepository

Chaque classe de référentiel étendrait une classe BaseRepository qui implémentait l'interface suivante:

interface BaseRepositoryInterface
{
    public function errors();

    public function all(array $related = null);

    public function get($id, array $related = null);

    public function getWhere($column, $value, array $related = null);

    public function getRecent($limit, array $related = null);

    public function create(array $data);

    public function update(array $data);

    public function delete($id);

    public function deleteWhere($column, $value);
}

Une classe de service est utilisée pour coller plusieurs référentiels ensemble et contient la véritable "logique métier" de l'application. Les contrôleurs niquement communiquent avec les classes de service pour les actions Créer, Mettre à jour et Supprimer.

Ainsi, lorsque je veux créer un nouvel enregistrement Movie dans la base de données, ma classe MovieController peut avoir les méthodes suivantes:

public function __construct(MovieRepositoryInterface $movieRepository, MovieServiceInterface $movieService)
{
    $this->movieRepository = $movieRepository;
    $this->movieService = $movieService;
}

public function postCreate()
{
    if( ! $this->movieService->create(Input::all()))
    {
        return Redirect::back()->withErrors($this->movieService->errors())->withInput();
    }

    // New movie was saved successfully. Do whatever you need to do here.
}

C'est à vous de déterminer comment vous POST données à vos contrôleurs, mais supposons que les données renvoyées par Input :: all () dans la méthode postCreate () ressemblent à ceci:

$data = array(
    'movie' => array(
        'title'    => 'Iron Eagle',
        'year'     => '1986',
        'synopsis' => 'When Doug\'s father, an Air Force Pilot, is shot down by MiGs belonging to a radical Middle Eastern state, no one seems able to get him out. Doug finds Chappy, an Air Force Colonel who is intrigued by the idea of sending in two fighters piloted by himself and Doug to rescue Doug\'s father after bombing the MiG base.'
    ),
    'actors' => array(
        0 => 'Louis Gossett Jr.',
        1 => 'Jason Gedrick',
        2 => 'Larry B. Scott'
    ),
    'director' => 'Sidney J. Furie',
    'studio' => 'TriStar Pictures'
)

Étant donné que MovieRepository ne devrait pas savoir comment créer des enregistrements Actor, Director ou Studio dans la base de données, nous utiliserons notre classe MovieService, qui pourrait ressembler à ceci:

public function __construct(MovieRepositoryInterface $movieRepository, ActorRepositoryInterface $actorRepository, DirectorRepositoryInterface $directorRepository, StudioRepositoryInterface $studioRepository)
{
    $this->movieRepository = $movieRepository;
    $this->actorRepository = $actorRepository;
    $this->directorRepository = $directorRepository;
    $this->studioRepository = $studioRepository;
}

public function create(array $input)
{
    $movieData    = $input['movie'];
    $actorsData   = $input['actors'];
    $directorData = $input['director'];
    $studioData   = $input['studio'];

    // In a more complete example you would probably want to implement database transactions and perform input validation using the Laravel Validator class here.

    // Create the new movie record
    $movie = $this->movieRepository->create($movieData);

    // Create the new actor records and associate them with the movie record
    foreach($actors as $actor)
    {
        $actorModel = $this->actorRepository->create($actor);
        $movie->actors()->save($actorModel);
    }

    // Create the director record and associate it with the movie record
    $director = $this->directorRepository->create($directorData);
    $director->movies()->associate($movie);

    // Create the studio record and associate it with the movie record
    $studio = $this->studioRepository->create($studioData);
    $studio->movies()->associate($movie);

    // Assume everything worked. In the real world you'll need to implement checks.
    return true;
}

Il ne reste donc qu’une séparation sereine et raisonnable des préoccupations. Les référentiels ne connaissent que le modèle Eloquent qu’ils insèrent et extraient de la base de données. Les contrôleurs ne se soucient pas des référentiels, ils transfèrent simplement les données qu'ils collectent à l'utilisateur et les transmettent au service approprié. Le service ne se soucie pas de la manière dont les données qu'il reçoit est sauvegardée dans la base de données, il transfère simplement les données pertinentes fournies par le contrôleur au référentiels appropriés.

209
Kyle Noland

J'aime penser ce que fait mon code et ce dont il est responsable, plutôt que "bien ou mal". Voici comment je sépare mes responsabilités:

  • Les contrôleurs sont la couche HTTP et acheminent les demandes vers l’APIS sous-jacent (c’est-à-dire qu’il contrôle le flux).
  • Les modèles représentent le schéma de base de données et indiquent à l'application à quoi ressemblent les données, leurs relations éventuelles, ainsi que tous les attributs globaux éventuellement nécessaires (par exemple, une méthode de dénomination permettant de renvoyer un nom et un nom concaténés).
  • Les référentiels représentent les requêtes plus complexes et les interactions avec les modèles (je ne fais aucune requête sur les méthodes de modèle).
  • Moteurs de recherche - classes qui m'aident à créer des requêtes de recherche complexes.

En gardant cela à l'esprit, il est toujours judicieux d'utiliser un référentiel (que vous créiez interfaces.etc. Constitue un tout autre sujet). J'aime cette approche, car cela signifie que je sais exactement où aller lorsque j'ai besoin de faire certains travaux.

J'ai également tendance à créer un référentiel de base, généralement une classe abstraite définissant les principaux paramètres par défaut - essentiellement des opérations CRUD, puis chaque enfant peut simplement étendre et ajouter des méthodes, si nécessaire, ou surcharger les paramètres par défaut. L'injection de votre modèle aide également ce modèle à être assez robuste.

5
Oddman

Considérez les référentiels comme un classeur cohérent de vos données (pas uniquement vos ORM). L'idée est que vous souhaitiez récupérer les données dans une API simple à utiliser cohérente.

Si vous vous trouvez juste en train de faire Model :: all (), Model :: find (), Model :: create (), vous ne tirerez probablement aucun avantage à extraire un référentiel. D'autre part, si vous souhaitez appliquer un peu plus de logique métier à vos requêtes ou actions, vous pouvez créer un référentiel afin de simplifier l'utilisation de l'API pour le traitement des données.

Je pense que vous demandiez si un référentiel serait le meilleur moyen de traiter une partie de la syntaxe plus détaillée qui est nécessaire pour connecter des modèles connexes. Selon la situation, je peux faire certaines choses:

  1. Suspendre un nouveau modèle enfant à un modèle parent (un-un ou plusieurs-plusieurs), j'ajouterais une méthode au référentiel enfant de type createWithParent($attributes, $parentModelInstance), ce qui ajouterait simplement le $parentModelInstance->id dans le parent_id champ des attributs et appelez create.

  2. En joignant une relation multiple, je crée en fait des fonctions sur les modèles pour pouvoir exécuter $ instance-> attachChild ($ childInstance). Notez que cela nécessite des éléments existants des deux côtés.

  3. En créant des modèles associés en une seule fois, je crée quelque chose que j'appelle une passerelle (cela peut être un peu différent des définitions de Fowler). Comment je peux appeler $ gateway-> createParentAndChild ($ parentAttributes, $ childAttributes) au lieu d'un tas de logique qui pourrait changer ou qui compliquerait la logique que j'ai dans un contrôleur ou une commande.

5
Ryan Tablada