Relaties beheren in Laravel, vasthouden aan het repositorypatroon

Tijdens het maken van een app in Laravel 4 na het lezen van het boek van T. Otwell over goede ontwerppatronen in Laravel, merkte ik dat ik opslagplaatsen maakte voor elke tabel in de applicatie.

Ik kreeg de volgende tabelstructuur:

  • Leerlingen: id, naam
  • Cursussen: id, naam, teacher_id
  • Leraren: id, naam
  • Opdrachten: id, naam, course_id
  • Scores (fungeert als spil tussen studenten en opdrachten): student_id, opdracht_id, scores

Ik heb repository-klassen met methoden voor zoeken, maken, bijwerken en verwijderen voor al deze tabellen. Elke repository heeft een welsprekend model dat samenwerkt met de database. Relaties worden gedefinieerd in het model volgens de documentatie van Laravel: http://laravel.com/docs/eloquent#relationships.

Als ik een nieuwe cursus maak, roep ik alleen de methode create aan in de cursusrepository. Die cursus heeft opdrachten, dus als ik er een maak, wil ik ook een vermelding in de scoretabel maken voor elke student in de cursus. Dit doe ik via de Assignment Repository. Dit houdt in dat de opdrachtrepository communiceert met twee Eloquent-modellen, met het Assignment- en Student-model.

Mijn vraag is: aangezien deze app waarschijnlijk in omvang zal groeien en er meer relaties zullen worden geïntroduceerd, is het dan een goede gewoonte om te communiceren met verschillende Eloquent-modellen in repositories of moet dit in plaats daarvan worden gedaan met behulp van andere repositories (ik bedoel het bellen van andere repositories van de Opdrachtrepository) of moet het allemaal samen in de Eloquent-modellen worden gedaan?

Is het ook een goede gewoonte om de scoretabel te gebruiken als een scharnier tussen opdrachten en studenten of moet het ergens anders worden gedaan?


Antwoord 1, autoriteit 100%

Ik ben een groot project aan het afronden met Laravel 4 en moest alle vragen beantwoorden die je nu stelt. Na het lezen van alle beschikbare Laravel-boeken bij Leanpub en heel veel Googlen, kwam ik tot de volgende structuur.

  1. Eén welsprekende modelklasse per dateerbare tabel
  2. Eén repository-klasse per welsprekend model
  3. Een Serviceklasse die kan communiceren tussen meerdere Repository-klassen.

Dus laten we zeggen dat ik een filmdatabase aan het bouwen ben. Ik zou op zijn minst de volgende lessen Eloquent Model hebben:

  • Film
  • Studio
  • Directeur
  • Acteur
  • Recensie

Een repositoryklasse zou elke Eloquent Model-klasse inkapselen en verantwoordelijk zijn voor CRUD-bewerkingen op de database. De repositoryklassen kunnen er als volgt uitzien:

  • MovieRepository
  • StudioRepository
  • DirectorRepository
  • ActorRepository
  • ReviewRepository

Elke repositoryklasse zou een BaseRepository-klasse uitbreiden die de volgende interface implementeert:

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);
}

Een Service-klasse wordt gebruikt om meerdere opslagplaatsen aan elkaar te lijmen en bevat de echte “bedrijfslogica” van de applicatie. Controllers communiceren alleenmet serviceklassen voor maken, bijwerken en verwijderen.

Dus als ik een nieuw Movie-record in de database wil maken, kan mijn MovieController-klasse de volgende methoden hebben:

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.
}

Het is aan jou om te bepalen hoe je gegevens naar je controllers POST, maar laten we zeggen dat de gegevens die worden geretourneerd door Input::all() in de postCreate()-methode er ongeveer zo uitzien:

$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'
)

Omdat de MovieRepository niet zou moeten weten hoe ze Actor-, Regisseur- of Studio-records in de database moeten maken, gebruiken we onze MovieService-klasse, die er ongeveer zo uit kan zien:

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;
}

Dus wat overblijft is een mooie, verstandige scheiding van zorgen. Repositories zijn alleen op de hoogte van het Eloquent-model dat ze invoegen en ophalen uit de database. Beheerders geven niet om repositories, ze geven gewoon de gegevens die ze verzamelen van de gebruiker af en geven deze door aan de juiste service. Het maakt de service niet uit hoede ontvangen gegevens worden opgeslagen in de database, het geeft alleen de relevante gegevens die door de verwerkingsverantwoordelijke zijn gegeven aan de juiste opslagplaatsen.


Antwoord 2, autoriteit 28%

Houd er rekening mee dat je om meningen vraagt ​​:D

Dit is de mijne:

TL;DR: Ja, dat is prima.

Het gaat goed met je!

Ik doe precies wat jij vaak doet en vind het geweldig werken.

Ik organiseer opslagplaatsen echter vaak rond bedrijfslogica in plaats van een repo-per-tabel te hebben. Dit is handig omdat het een standpunt is dat is gericht op hoe uw toepassing uw “zakelijke probleem” moet oplossen.

Een cursus is een “entiteit”, met attributen (titel, id, enz.) en zelfs andere entiteiten (Opdrachten, die hun eigen attributen en mogelijk entiteiten hebben).

Uw repository voor “Cursus” moet een cursus en de attributen/opdrachten van de cursus kunnen retourneren (inclusief de opdracht).

Gelukkig kun je dat bereiken met Eloquent.

(Ik heb vaak een repository per tabel, maar sommige repository’s worden veel meer gebruikt dan andere, en hebben dus veel meer methoden. Uw repository voor “cursussen” kan veel completer zijn dan uw Assignments-repository, bijvoorbeeld , als uw toepassing meer draait om cursussen en minder om een ​​verzameling opdrachten van cursussen).

Het lastige deel

Ik gebruik vaak repositories in mijn repositories om bepaalde databaseacties uit te voeren.

Elke repository die Eloquent implementeert om gegevens te verwerken, zal waarschijnlijk Eloquent-modellen retourneren. In dat licht is het prima als je cursusmodel ingebouwde relaties gebruikt om Opdrachten op te halen of op te slaan (of een andere use-case). Onze “implementatie” is opgebouwd rond Eloquent.

Vanuit praktisch oogpunt is dit logisch. Het is onwaarschijnlijk dat we gegevensbronnen wijzigen in iets dat Eloquent niet aankan (in een niet-sql-gegevensbron).

ORMS

Het lastigste deel van deze opzet, voor mij althans, is om te bepalen of Eloquent ons echt helpt of schaadt. ORM’s zijn een lastig onderwerp, want hoewel ze ons vanuit praktisch oogpunt enorm helpen, koppelen ze ook uw “business logic-entiteiten”-code aan de code die het ophalen van gegevens uitvoert.

Dit soort warboel vertroebelt of de verantwoordelijkheid van uw repository eigenlijk is voor het verwerken van gegevens of voor het ophalen/bijwerken van entiteiten (bedrijfsdomeinentiteiten).

Bovendien fungeren ze als de objecten die u aan uw weergaven doorgeeft. Als u later Eloquent-modellen in een repository niet meer moet gebruiken, moet u ervoor zorgen dat de variabelen die aan uw views worden doorgegeven zich op dezelfde manier gedragen of dezelfde methoden beschikbaar hebben, anders zal het veranderen van uw gegevensbronnen leiden tot het veranderen van uw views, en je hebt (gedeeltelijk) het doel verloren om je logica in de eerste plaats naar repositories te abstraheren – de onderhoudbaarheid van je project gaat achteruit als.

Hoe dan ook, dit zijn enigszins onvolledige gedachten. Ze zijn, zoals gezegd, slechts mijn mening, die toevallig het resultaat is van het lezen van Domain Driven Design en het bekijken van video’s zoals “uncle bob’s” keynotein Ruby Midwest in de afgelopen jaar.


Antwoord 3, autoriteit 2%

Ik beschouw het graag in termen van wat mijn code doet en waarvoor het verantwoordelijk is, in plaats van “goed of fout”. Zo verdeel ik mijn verantwoordelijkheden:

  • Controllers zijn de HTTP-laag en leiden verzoeken door naar de onderliggende api’s (ook wel: het bestuurt de stroom)
  • Modellen vertegenwoordigen het databaseschema en vertellen de toepassing hoe de gegevens eruit zien, welke relaties ze kunnen hebben, evenals alle globale attributen die nodig kunnen zijn (zoals een naammethode voor het retourneren van een aaneengeschakelde voor- en achternaam)
  • Repositories vertegenwoordigen de meer complexe zoekopdrachten en interacties met de modellen (ik doe geen vragen over modelmethoden).
  • Zoekmachines – klassen waarmee ik complexe zoekopdrachten kan maken.

Met dit in gedachten is het elke keer logisch om een ​​repository te gebruiken (of je interfaces.etc. maakt, is een heel ander onderwerp). Ik hou van deze aanpak, omdat het betekent dat ik precies weet waar ik heen moet als ik bepaald werk moet doen.

Ik heb ook de neiging om een ​​basisrepository te bouwen, meestal een abstracte klasse die de belangrijkste standaardwaarden definieert – in feite CRUD-bewerkingen, en dan kan elk kind zo nodig methoden uitbreiden en toevoegen, of de standaardwaarden overbelasten. Door je model te injecteren, wordt dit patroon ook behoorlijk robuust.

Other episodes