Недавно я начал учиться 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');
}
...
В результате я постараюсь сформулировать все более компактно:
Мне придется отказаться от передачи моделей непосредственно на контроллер и другие удобства, предоставляемые DI, если я буду использовать Repository
шаблон?
Можно ли использовать репозиторий для сохранения состояния модели и передавать его между сущностями (например, от фильтра к контроллеру, от контроллера к Event
и обратно) избегать непристойных повторных обращений к БД, и будет ли этот подход правильным (постоянство модели)?
Такие вещи, это мои вопросы. Я хотел бы услышать ответы, мысли, комментарии. Может быть, я неверно подхожу к применению шаблона? Теперь это вызывает больше головной боли, чем решает проблему отображения данных.
Также я прочитал несколько статей о реализации репозитория:
но это не решает мое недоразумение
У репозитория есть свои плюсы и минусы.
Из моего сравнительно недавнего принятия шаблона он позволяет гораздо легче испытывать опыт, особенно когда используются наследование и полиморфизм.
Ниже приведена выдержка из почти полного контракта с репозиторием, который я использую.
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
Я стараюсь не объединять все свои репозитории и вместо этого группировать приложения по функциям, чтобы структура каталогов была менее загромождена.
Я пропускаю много деталей, но я не хочу вдаваться в подробности, поэтому поэкспериментируйте с наследованием и полиморфизмом, чтобы увидеть, чего можно достичь для улучшения рабочего процесса с репозиториями.
С моим текущим рабочим процессом мои тесты имеют свои собственные абстрактные классы исключительно для базового контракта с репозиториями, которые все хранилища сущностей реализуют, делая тестирование быстрым после первых нескольких препятствий.
Удачи!
Работает нормально!
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'));
}
}
@likerRr вы спросили:
Будет ли правильно переписать этот пример с использованием шаблона репозитория следующим образом:
Прежде всего, вы должны подумать, почему мы используем шаблоны проектирования и, в частности, шаблон репозитория? Мы используем шаблон Repository для реализации принципов SOLID (все или несколько).
Во-первых, никто не должен обращаться к источнику данных / базе данных в контроллерах. Делая это, вы:
Single Responsibility Principle (S in SOLID)
, Ваш контроллер не должен знать об источнике данных. Ответственным является только ответ на запрос HTTP или медитация ч / б вашего приложения и HTTP. Вот почему вы должны использовать не только шаблоны репозитория, но и внедрять принципы SOLID.
как сделать то? Оберните свой доступ к источникам данных где-нибудь еще, и репозиторий — лучшее место.
Предположим, вы получаете пользователя, используя следующий код:
User::where('id', $id)->where('company_id', $companyId)->get();
Если вы напишите этот код на всех ваших контроллерах, где вам нужно, вы не сможете сделать следующее:
2: Могу ли я как-то передать найденную модель обратно в хранилище и увеличить ее счетчик (противоречит ли это подходу к шаблону хранилища?)
Вы делаете правильно в своем фрагменте.
На самом деле, вы хотите получить как легкость, предоставляемую Laravel, так и преимущества паттернов.
Вы, наверное, знаете, что вам нужно пожертвовать чем-то ради другого. Drivers driving on the easy to drive roads can not become good drivers
, Итак, я предлагаю вам следовать шаблонам проектирования и принципам SOLID и оставить «легкость», обеспечиваемую Laravel. В противном случае эта так называемая «легкость» создаст вам столько проблем, что вы даже не сможете поддерживать свой проект, и все исчезнет.
Последние вещи об использовании событий:
События — это не что иное, как Наблюдатель. В вашем случае, кажется, нет необходимости использовать шаблоны наблюдателей, поэтому вам следует избегать этого.
Наилучшим кандидатом на участие в наблюдениях / событиях будет то, что вы снимаете деньги со своего клиента, и после успешного снятия средств вы хотите отправить подробные сведения о сумме начисленной в настоящее время суммы вместе с предыдущим сложным расчетом по электронной почте. Поскольку это займет время, и вы не хотите показывать пользователю перезагрузку страницы, пока выполняется вся эта тяжелая обработка. Таким образом, вы можете зарядить пользователя, запустить событие, перенаправить пользователя на ваш сайт с сообщением об успехе и позволить обработчику событий выполнить тяжелую работу, а ваш пользователь может делать другие вещи.
Вы можете задать любые другие вопросы, если хотите!