Реализация шаблона репозитория с помощью Laravel

Недавно я начал учиться Laravel 4 и это возможности. Я хочу реализовать шаблон репозитория для перемещения логики модели туда. И в этот момент я столкнулся с рядом неудобств или недопониманием того, как это организовать. Общий вопрос у меня звучит примерно так: Можно ли реализовать и применить этот шаблон в Laravel без головной боли и стоит ли?

Вопрос будет разделен на несколько частей, что привело меня в замешательство.

1) Laravel предоставляет удобный способ связать модель в качестве параметра контроллера, например, я делаю это так:

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

Route::get('articles/{article}', 'ArticlesController@getArticle');

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

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

Если я хочу использовать Repository шаблон, то я не могу использовать этот подход, так как в этом случае контроллер будет четко знать о существовании моделей Article? Будет ли правильно переписать этот пример, используя шаблон Repository таким образом:

// routes.php
Route::get('articles/{slug}', 'ArticlesController@getArticle');

// 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) Предположим, мой код выше с использованием Repository верно. Теперь я хочу увеличивать счетчик просмотров статей каждый раз, когда он будет показан, однако я хочу сделать эту обработку в Event, То есть код выглядит следующим образом:

// routes.php
Route::get('articles/{slug}', 'ArticlesController@getArticle');

// 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', 'ArticleSubscriber@onShown');
}

}

В этот момент я снова озадачился тем, как реализовать обработку событий. Я не могу пройти $article модель непосредственно к событию, потому что, опять же, это нарушает принципы ООП, и мой подписчик узнает о существовании модели статьи. Итак, я не могу сделать это:

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

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

С другой стороны, я не вижу смысла вводить в subscriber хранилище ArticleRepository (или внедрить его в конструктор подписчика), потому что сначала я должен найти статью, а затем обновить счетчик, в конце я получу дополнительный запрос (потому что ранее в конструкторе я делал то же самое) к базе данных:

// 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');
}
...

Более того, после Event обработано (то есть увеличено число просмотров), необходимо, чтобы контроллер знал об обновленной модели, потому что в представлении я хочу отобразить счетчик обновленных просмотров. Оказывается, мне как-то нужно вернуть новую модель из Event, но я бы не хотел Event стал распространенным методом для обработки определенного действия (для этого есть хранилище) и возврата некоторого значения. Кроме того, вы можете заметить, что мой последний onShow() Метод опять противоречит правилам Repository шаблон, но я не понимаю, как поместить эту логику в хранилище:

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

Можно ли как-то передать найденную модель обратно в хранилище и увеличить ее счетчик (противоречит ли это подходу к Repository шаблон?)? Что-то вроде этого:

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

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

В результате я постараюсь сформулировать все более компактно:

  1. Мне придется отказаться от передачи моделей непосредственно на контроллер и другие удобства, предоставляемые DI, если я буду использовать Repository шаблон?

  2. Можно ли использовать репозиторий для сохранения состояния модели и передавать его между сущностями (например, от фильтра к контроллеру, от контроллера к Event и обратно) избегать непристойных повторных обращений к БД, и будет ли этот подход правильным (постоянство модели)?

Такие вещи, это мои вопросы. Я хотел бы услышать ответы, мысли, комментарии. Может быть, я неверно подхожу к применению шаблона? Теперь это вызывает больше головной боли, чем решает проблему отображения данных.

Также я прочитал несколько статей о реализации репозитория:

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

но это не решает мое недоразумение

7

Решение

У репозитория есть свои плюсы и минусы.

Из моего сравнительно недавнего принятия шаблона он позволяет гораздо легче испытывать опыт, особенно когда используются наследование и полиморфизм.

Ниже приведена выдержка из почти полного контракта с репозиторием, который я использую.

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

Договор относительно понятен, save() управляет как вставкой, так и обновлением сущностей (моделей).

Отсюда я создам абстрактный класс, который реализует все функциональные возможности для поставщиков, которых я хочу использовать, таких как Eloquent или Doctrine.

Стоит отметить, что этот контракт не уловил бы все, и сейчас я нахожусь в процессе создания отдельной реализации, которая обрабатывает многие отношения, но это уже другая история.

Чтобы создать мои отдельные классы репозитория, для этого я создаю другой контракт для каждого репозитория, который расширяет EntityRepositoryContract и утверждает, что функциональность является эксклюзивной для них. В случае пользователя — registerUser(...) а также disableUser(...) и т. д.

Заключительные классы затем расширят EloquentEntityRepository и выполнить соответствующий контракт для хранилища. Подпись класса для EloquentUserRepository было бы что-то вроде:

class EloquentUserRepository extends EloquentEntityRepository implements UserRepositoryContract
{
...
}

В моей собственной реализации, чтобы сделать имена классов менее многословными, я использую пространства имен для псевдонимов каждой реализации следующим образом:

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

Я стараюсь не объединять все свои репозитории и вместо этого группировать приложения по функциям, чтобы структура каталогов была менее загромождена.

Я пропускаю много деталей, но я не хочу вдаваться в подробности, поэтому поэкспериментируйте с наследованием и полиморфизмом, чтобы увидеть, чего можно достичь для улучшения рабочего процесса с репозиториями.

С моим текущим рабочим процессом мои тесты имеют свои собственные абстрактные классы исключительно для базового контракта с репозиториями, которые все хранилища сущностей реализуют, делая тестирование быстрым после первых нескольких препятствий.

Удачи!

0

Другие решения

Работает нормально!

api.php

Route::get('/articles/{article}', 'ArticleController@getArticle')->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'));
}
}
0

@likerRr вы спросили:

Будет ли правильно переписать этот пример с использованием шаблона репозитория следующим образом:

Прежде всего, вы должны подумать, почему мы используем шаблоны проектирования и, в частности, шаблон репозитория? Мы используем шаблон Repository для реализации принципов SOLID (все или несколько).
Во-первых, никто не должен обращаться к источнику данных / базе данных в контроллерах. Делая это, вы:

  1. нарушая Single Responsibility Principle (S in SOLID), Ваш контроллер не должен знать об источнике данных. Ответственным является только ответ на запрос HTTP или медитация ч / б вашего приложения и HTTP.
  2. вы нарушаете принцип подстановки Лискова
  3. вы нарушаете принцип инверсии зависимости.

Вот почему вы должны использовать не только шаблоны репозитория, но и внедрять принципы SOLID.
как сделать то? Оберните свой доступ к источникам данных где-нибудь еще, и репозиторий — лучшее место.
Предположим, вы получаете пользователя, используя следующий код:

User::where('id', $id)->where('company_id', $companyId)->get();

Если вы напишите этот код на всех ваших контроллерах, где вам нужно, вы не сможете сделать следующее:

  1. Вы не можете изменить свой источник данных без изменения этой строки
  2. Вы не можете проверить свой код
  3. и в конце концов это будет трудно поддерживать

2: Могу ли я как-то передать найденную модель обратно в хранилище и увеличить ее счетчик (противоречит ли это подходу к шаблону хранилища?)

Вы делаете правильно в своем фрагменте.
На самом деле, вы хотите получить как легкость, предоставляемую Laravel, так и преимущества паттернов.
Вы, наверное, знаете, что вам нужно пожертвовать чем-то ради другого. Drivers driving on the easy to drive roads can not become good drivers, Итак, я предлагаю вам следовать шаблонам проектирования и принципам SOLID и оставить «легкость», обеспечиваемую Laravel. В противном случае эта так называемая «легкость» создаст вам столько проблем, что вы даже не сможете поддерживать свой проект, и все исчезнет.

Последние вещи об использовании событий:

События — это не что иное, как Наблюдатель. В вашем случае, кажется, нет необходимости использовать шаблоны наблюдателей, поэтому вам следует избегать этого.

Наилучшим кандидатом на участие в наблюдениях / событиях будет то, что вы снимаете деньги со своего клиента, и после успешного снятия средств вы хотите отправить подробные сведения о сумме начисленной в настоящее время суммы вместе с предыдущим сложным расчетом по электронной почте. Поскольку это займет время, и вы не хотите показывать пользователю перезагрузку страницы, пока выполняется вся эта тяжелая обработка. Таким образом, вы можете зарядить пользователя, запустить событие, перенаправить пользователя на ваш сайт с сообщением об успехе и позволить обработчику событий выполнить тяжелую работу, а ваш пользователь может делать другие вещи.

Вы можете задать любые другие вопросы, если хотите!

0