2014-11-04 16 views
6

Niedawno zacząłem studiować Laravel 4 i jego możliwości. Chcę zaimplementować wzór repozytorium, aby przenieść tam logikę modelu. I w tym momencie spotkałem się z wieloma niedogodnościami lub niezrozumieniem, jak je zorganizować. Ogólne pytanie Mam coś takiego: Czy można wdrożyć i zastosować ten wzór w Laravel bez bólu głowy i czy jest to warte?Implementacja wzorca repozytoryjnego z Laravelem

Pytanie zostanie podzielone na kilka części, które spowodowały moje zamieszanie.

1) Laravel zapewnia wygodny sposób wiązania modelu jako parametru kontrolera, np. Robię to w ten sposób:

// routes.php 
Route::bind('article', function($slug) 
{ 
    return Article::where('slug', $slug)->first(); 
}); 

Route::get('articles/{article}', '[email protected]'); 

// controllers/ArticlesController.php 
class ArticlesController extends BaseController { 

    public function getArticle(Article $article) 
    { 
     return View::make('article.show', compact('article')); 
    } 
} 

Jeśli chcę użyć deseniu Repository, to nie mogę użyć tego podejścia, ponieważ w tym przypadku regulator jasno zdawać sobie sprawę z istnienia modeli Article? czy będzie to poprawne ponownie napisać ten przykład używając repozytorium wzorzec ten sposób:

// routes.php 
Route::get('articles/{slug}', '[email protected]'); 

// controllers/ArticlesController.php 
class ArticlesController extends BaseController { 

    private $article; 

    public function __construct(ArticleRepository $article) { 
     $this->article = $article; 
    } 

    public function getArticle($slug) 
    { 
     $article = $this->article->findBySlug($slug); 

     return View::make('article.show', compact('article')); 
    } 
} 

2) Załóżmy, że mój kod powyżej, z wykorzystaniem Repository jest poprawna. Teraz chcę zwiększać licznik wyświetleń artykułu za każdym razem, gdy zostanie on wyświetlony, jednak chcę wykonać to przetwarzanie w Event. Oznacza to, że kod jest następujący:

// routes.php 
Route::get('articles/{slug}', '[email protected]'); 

// controllers/ArticlesController.php 
class ArticlesController extends BaseController { 

    private $article; 

    public function __construct(ArticleRepository $article) { 
     $this->article = $article; 
    } 

    public function getArticle($slug) 
    { 
     $article = $this->article->findBySlug($slug); 
     Events::fire('article.shown'); 

     return View::make('articles.single', compact('article')); 
    } 
} 

// some event subscriber 
class ArticleSubscriber { 

    public function onShown() 
    { 
     // why implementation is missed described bellow 
    } 

    public function subscribe($events) 
    { 
     $events->listen('article.shown', '[email protected]'); 
    } 

} 

W tym momencie ponownie zastanawiałem się, jak zaimplementować przetwarzanie zdarzeń. Nie mogę przekazać modelu $article bezpośrednio na wydarzenie, ponieważ znowu narusza zasady OOP i mój subskrybent będzie wiedział o istnieniu modelu artykułu. Tak więc, nie mogę zrobić:

// controllers/ArticlesController.php 
... 
\Events::fire('article.shown', $article); 
... 

// some event subscriber 
... 
public function onShown(Article $article) 
{ 
    $article->increment('views'); 
} 
... 

Z drugiej strony nie widzę żadnego sensu wprowadzać do subscriber repozytorium ArticleRepository (lub wstrzyknąć go w contructor abonenta), ponieważ pierwszy powinien się znaleźć artykuł, a następnie zaktualizować licznik, w końcu, ja dostać dodatkowe zapytania (spowodować poprzednio w konstruktorze zrobić to samo) do bazy danych:

// controllers/ArticlesController.php 
... 
Events::fire('article.shown', $slug); 
... 

// some event subscriber 
... 
private $article; 

public function __construct(ArticleRepository $articleRepository) 
{ 
    $this->article = $articleRepository; 
} 

public function onShown($slug) 
{ 
    $article = $this->articleRepository->findBySlug($slug); 
    $article->increment('views'); 
} 
... 

Ponadto, po Event obsługiwane (liczba oznacza wzrost wyświetleń) , konieczne jest, aby kontroler wiedział o zaktualizowanym modelu, ponieważ w widoku chcę wyświetlić zaktualizowane widoki licznik. Okazuje się, że jakoś nadal muszę zwrócić nowy model z Event, ale nie chciałbym, aby Event stał się popularną metodą przetwarzania określonej akcji (dla tego istnieje repozytorium) i zwraca jakąś wartość. Ponadto, można zauważyć, że mój ostatni onShow() metoda ponownie sprzeczne z zasadami Repository wzór, ale nie rozumiem, jak umieścić tę logikę do repozytorium:

public function onShown($slug) 
{ 
    $article = $this->articleRepository->findBySlug($slug); 
    // INCORRECT! because the Event shouldn't know that the model is able to implement Eloquent 
    // $article->increment('views'); 
} 

Mogę jakoś przekazać znaleziony modelu wróć do repozytorium i zwiększ jej licznik (czy jest to sprzeczne z tym podejściem do wzoru Repository?)? coś takiego:

public function onShown($slug) 
{ 
    $article = $this->articleRepository->findBySlug($slug); 
    $this->articleRepository->updateViews($article); 
} 

// ArticleRepository.php 
... 
public function updateViews(Article $article) { 
    $article->increment('views'); 
} 
... 

W rezultacie, postaram się sformułować wszystko bardziej kompaktowe:

  1. będę musiał odmówić modele przechodzą bezpośrednio do kontrolera i innych udogodnień przewidzianych przez DI , jeśli użyję wzoru Repository?

  2. Czy to możliwe, aby korzystać z repozytorium dla utrzymania stanu modelu i przekazać je pomiędzy podmiotami (np z filtra do kontrolera z kontrolera do Event iz powrotem) unikając obscenicznych ponawianych wezwań db i jest to podejście będzie poprawne (trwałość modelu)?

Takie rzeczy, to są moje pytania. Chciałbym usłyszeć odpowiedzi, myśli, komentarze. Może, niewłaściwe podejście do zastosowania wzoru? Teraz powoduje więcej bólów głowy niż rozwiązuje problem mapowania danych.

Również czytałem kilka artykułów na temat repozytorium realizacji:

  1. http://heera.it/laravel-repository-pattern#.VFaKu8lIRLe
  2. http://vegibit.com/laravel-repository-pattern

ale to nie rozwiąże mój nieporozumienie

Odpowiedz

0

Repozytorium wzór ma swoje plusy i przeciw.

Z mojego stosunkowo niedawnego przyjęcia wzorca pozwala on na znacznie łatwiejsze testowanie - szczególnie wtedy, gdy wykorzystuje się dziedziczenie i polimorfizm.

Poniżej znajduje się fragment umowy dotyczącej repozytorium, z którego korzystam.

interface EntityRepository 
{ 
    /** 
    * @param $id 
    * @return array 
    */ 
    public function getById($id); 

    /** 
    * @return array 
    */ 
    public function getAll(); 

    /** 
    * @param array $attr 
    * @return array 
    */ 
    public function save(array $attr); 

    /** 
    * @param $id 
    */ 
    public function delete($id); 

    /** 
    * Checks if a record with the given values exists 
    * @param array $attr 
    * @return bool 
    */ 
    public function exists(array $attr); 

    /** 
    * Checks if any records with any of these values exists and returns true or false 
    * @param array $attr 
    * @return bool 
    */ 
    public function unique(array $attr); 
} 

Przewidywane jest stosunkowo wymowne, save() zarządza zarówno wkładanie i podmioty aktualizacji (modele).

Stąd utworzę klasę abstrakcyjną, która implementuje wszystkie funkcje dla sprzedawców, których chcę użyć - takie jak Eloquent lub Doctrine.

Warto zauważyć, że umowa ta nie złapie wszystkiego, a ja jestem obecnie w trakcie tworzenia oddzielnej implementacji, która obsługuje wiele do wielu relacji, ale to już inna historia.

Aby utworzyć własne klasy repozytoriów, w tym celu tworzę kolejną umowę dla każdego repozytorium, która rozszerza EntityRepositoryContract i określa, która funkcja jest dla nich dostępna. W przypadku użytkownika - registerUser(...) i disableUser(...) itp.

Ostateczne zajęcia przedłużą następnie EloquentEntityRepository i wdrożą odpowiednią umowę dla repozytorium.Klasa podpis dla EloquentUserRepository byłoby coś, takich jak:

class EloquentUserRepository extends EloquentEntityRepository implements UserRepositoryContract 
{ 
... 
} 

W moim realizacji dokonania nazw klas mniej gadatliwe nazw ja dźwigni do aliasu każdą realizację tak:

use Repo\Eloquent\UserRepo; //For the Eloquent implementation 
use Repo\Doctrine\UserRepo; //For the Doctrine implementation 

I nie próbuj spakować wszystkie moje repozytoria i zamiast tego pogrupować moją aplikację według funkcji, aby moja struktura katalogów była mniej zagracona.

Pomijam wiele szczegółów, ale nie chcę zbyt dużo wrzucać, więc eksperymentuj z dziedziczeniem i polimorfizmem, aby zobaczyć, co można osiągnąć dla lepszego przepływu pracy z repozytoriami.

W moim bieżącym przepływie pracy moje testy mają własne klasy abstrakcyjne przeznaczone wyłącznie dla umowy z bazowym repozytorium, które wszystkie repozytoria jednostek sprawiają, że testowanie jest proste po kilku pierwszych utrudnieniach.

Powodzenia!

0

Działa dobrze!

api.php

Route::get('/articles/{article}', '[email protected]')->middleware('can:get,article'); 

ArticleController.php

class ArticleController extends BaseController { 

    protected $repo; 

    public function __construct(ArticleRepository $repository) { 

     $this->repo = $repository; 
    } 

    public function getArticle(int $id) 
    { 
     $articleRepo = $this->repo->find($id); 
     return View::make('article.show', compact('articleRepo')); 
    } 
}