Gestion des relations dans Laravel, respect du modèle de dépôt

en créant une application dans Laravel 4 après avoir lu le livre de T. Otwell sur les modèles de bonne conception dans Laravel, je me suis trouvé à créer des dépôts pour chaque table sur l'application.

j'ai fini avec la structure de tableau suivante:

  • étudiants: carte d'identité, nom
  • cours: id, name, teacher_id
  • enseignants: id, nom
  • Missions: id, nom, course_id
  • Scores (agit comme un pivot entre les étudiants et les affectations): student_id, assignment_id, les scores de

j'ai des classes de dépôt avec des méthodes find, create, update et delete pour toutes ces tables. Chaque dépôt a un modèle éloquent qui interagit avec la base de données. Les relations sont définies dans la documentation de Laravel: http://laravel.com/docs/eloquent#relationships .

lors de la création un nouveau cours, tout ce que je fais est d'appeler la méthode create sur le référentiel de cours. Ce cours a des devoirs, donc lors de la création d'un, je veux aussi créer une entrée dans le tableau de score pour chaque étudiant dans le cours. Je le fais à travers le référentiel D'affectation. Cela implique que le référentiel d'affectation communique avec deux modèles éloquents, celui de L'affectation et celui de l'élève.

ma question Est: comme cette application va probablement croître en taille et plus de relations seront introduit, est-ce une bonne pratique de communiquer avec différents modèles éloquents dans les dépôts ou devrait-on utiliser d'autres dépôts à la place (je veux dire appeler d'autres dépôts à partir du dépôt D'affectation) ou devrait-on le faire dans les modèles éloquents tous ensemble?

de plus, est-ce une bonne pratique d'utiliser le tableau des scores comme un pivot entre les devoirs et les étudiants ou devrait-on le faire ailleurs?

109
demandé sur Antonio Carlos Ribeiro 2013-09-16 01:20:33

4 réponses

Gardez à l'esprit que vous êtes, demandez l'avis :D

voici le mien:

TL; DR: Oui, c'est très bien.

tu t'en sors bien!

je fais exactement ce que vous faites souvent et trouver il fonctionne très bien.

j'organise souvent, cependant, des référentiels autour de la logique d'affaires au lieu d'avoir un référentiel par table. C'est utile car c'est un point de vue centré sur la façon dont votre demande devrait résoudre votre "problème d'entreprise".

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

votre répertoire de "cours" devrait être en mesure de retourner un cours et les attributs/tâches des cours (y compris L'affectation).

vous pouvez accomplir cela avec éloquence, heureusement.

(je finis souvent avec un dépôt par tableau, mais certains référentiels sont utilisés beaucoup plus que d'autres, et ont donc beaucoup plus de méthodes. Votre référentiel de "cours" peut être beaucoup plus complet que votre référentiel de tâches, par exemple, si votre application se concentre davantage sur les cours et moins sur la collection de tâches D'un cours).

la partie délicate

j'utilise souvent des dépôts à l'intérieur de mes dépôts pour effectuer certaines actions de base de données.

Any un dépôt qui met en œuvre Eloquent afin de traiter des données retournera probablement des modèles éloquents. Dans cette optique, c'est très bien si votre modèle de cours utilise des relations intégrées afin de récupérer ou de sauvegarder des affectations (ou tout autre cas d'utilisation). Notre "mise en œuvre" est bâtie autour de L'éloquence.

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

ORMS

la partie la plus délicate de cette installation, pour moi du moins, est de déterminer si Eloquent nous aide ou nous fait du mal. Les ORMs sont un sujet délicat, car bien qu'ils nous aident grandement d'un point de vue pratique, ils couplent également votre code "business logic entities" avec le code qui effectue la recherche de données.

ce genre de confusion permet de savoir si la responsabilité de votre dépôt est réellement de traiter des données ou de gérer la récupération / mise à jour des entités (entités du domaine d'activité).

en outre, ils agissent comme les objets mêmes que vous passez à vos vues. Si vous devez ensuite vous éloigner de l'utilisation de modèles éloquents dans un dépôt, vous aurez besoin de vous assurer que les variables transmises à vos vues se comportent de la même manière ou ont les mêmes méthodes disponibles, sinon changer vos sources de données se répercutera dans changer vos vues, et vous avez (partiellement) perdu le but d'abstraction de votre logique sur les dépôts en premier lieu - le maintenabilité de votre projet descend comme.

de toute façon, ce sont des pensées quelque peu incomplètes. Ils sont, comme indiqué, simplement mon opinion, qui se trouve être le résultat de la lecture domaine Driven Design et regarder des vidéos comme "oncle bob's" keynote à Ruby Midwest au cours de l'année dernière.

67
répondu fideloper 2013-09-16 13:30:49

je termine un grand projet en utilisant Laravel 4 et j'ai dû répondre à toutes les questions que vous me posez en ce moment. Après avoir lu tous les livres Laravel disponibles à Leanpub, et des tonnes de Googling, je suis venu avec la structure suivante.

  1. Un Éloquent de la classe du Modèle par datable de la table
  2. une classe de dépôt par modèle éloquent
  3. une classe de Service qui peut communiquer entre plusieurs référentiels classe.

alors disons que je construis une base de données de films. J'aurais au moins les classes Modèles suivantes:

  • Film
  • Studio
  • Directeur
  • acteur
  • Examen

la base de données. Les classes du dépôt peuvent ressembler à ceci:

  • MovieRepository
  • StudioRepository
  • DirectorRepository
  • ActorRepository
  • rapport de réexamen

chaque classe de dépôt étendrait une classe BaseRepository qui implémente 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 dépôts ensemble et contient la véritable "logique commerciale" de l'application. Contrôleurs seulement communiquer avec les classes de Service pour créer, mettre à jour et supprimer des actions.

donc quand je veux créer un nouveau film dans la base de données, ma classe MovieController pourrait 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 publier des données aux contrôleurs, mais disons que les données renvoyées par Input:: all() dans la méthode postCreate() ressemble à quelque chose comme 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'
)

puisque le MovieRepository ne devrait pas savoir comment créer des enregistrements D'acteur, de réalisateur ou de Studio dans la base de données, nous allons utiliser notre classe MovieService, qui pourrait ressembler à quelque chose comme 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;
}

donc ce qu'il nous reste c'est une bonne séparation des préoccupations. Les dépositaires ne connaissent que le modèle éloquent qu'ils insèrent et extraient de la base de données. Les contrôleurs ne se soucient pas des dépôts, ils remettent simplement les données qu'ils recueillent auprès de l'utilisateur et les transmettent au service approprié. Le service ne se soucie pas comment les données qu'il reçoit sont enregistrées dans la base de données, il ne fait que transférer les données pertinentes qu'il a été donné par le contrôleur aux dépôts appropriés.

200
répondu Kyle Noland 2014-10-10 20:35:36

j'aime penser à cela en termes de ce que mon code fait et de ce dont il est responsable, plutôt que"bien ou mal". C'est ainsi que je divise mes responsabilités:

  • les contrôleurs sont la couche HTTP et les requêtes de route à travers les API sous-jacentes (alias, 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, quelles relations elles peuvent avoir, ainsi que toute attributs qui peuvent être nécessaires (comme une méthode de nom pour retourner un prénom et un nom concaténés)
  • Les dépôts
  • représentent les requêtes et interactions les plus complexes avec les Modèles (Je ne fais pas de requêtes sur les méthodes des modèles).
  • moteurs de recherche - classes qui m'aident à construire des requêtes de recherche complexes.

avec ceci en tête, il est logique d'utiliser chaque fois un dépôt (que l'on crée des interfaces).etc. est un tout autre sujet). J'aime cette approche, parce que cela signifie que je sais exactement où aller quand je suis besoin de faire certains travaux.

j'ai aussi tendance à construire un dépôt de base, généralement une classe abstraite qui définit les principales opérations par défaut - essentiellement des opérations CRUD, et puis chaque enfant peut simplement étendre et ajouter des méthodes si nécessaire, ou surcharger les valeurs par défaut. L'injection de votre modèle aide également Ce modèle à être assez robuste.

5
répondu Oddman 2013-09-16 14:00:18

pensez aux dépôts comme un classeur cohérent de vos données (pas seulement vos ORMs). L'idée est que vous voulez saisir des données dans une API cohérente et simple à utiliser.

si vous vous trouvez à ne faire que Model::all(), Model::find(), Model::create (), vous ne tirerez probablement pas grand-chose de l'Abstraction d'un dépôt. D'autre part, si vous voulez faire un peu plus d'une logique d'entreprise à vos requêtes ou des actions, vous pouvez créer un dépôt pour le rendre plus facile à utilisez L'API pour traiter les données.

je pense que vous vous demandiez si un dépôt serait le meilleur moyen de gérer certaines des syntaxes plus verbeuses nécessaires pour connecter des modèles connexes. Selon la situation, il y a quelques choses que je peux faire:

  1. accrocher un nouveau modèle enfant à partir d'un modèle parent (un-un ou un-plusieurs), Je voudrais ajouter une méthode au dépôt enfant quelque chose comme createWithParent($attributes, $parentModelInstance) et ce serait juste ajouter le $parentModelInstance->id dans le champ parent_id des attributs et de l'appel créer.

  2. en attachant une relation beaucoup-beaucoup, je crée en fait des fonctions sur les modèles pour que je puisse exécuter $instance->attachChild($childInstance). Notez que cela nécessite des éléments existants de part et d'autre.

  3. créer des modèles connexes en un seul passage, je crée quelque chose que j'appelle une passerelle (il peut être un peu à distance de Fowler définition.) De cette façon, 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 controller ou une commande.

5
répondu Ryan Tablada 2013-09-16 14:03:59