Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#1 04.11.2014 21:42:35

likerRr

Имплементация Repository паттерна в Laravel

Всем привет!

С недавних пор изучаю `Laravel 4` и его возможности. Стала задача имплементировать паттерн Repository, чтобы вынести логику работы с бд туда. И вот тут столкнулся с рядом неудобств или непониманием, как правильно все организовать. Общий вопрос у меня звучит примерно так: **возможна ли реализация и применение этого паттерна в `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'));
        }
    }

    // некий subscriber, подписанный на события
    class ArticleSubscriber {
    
        public function onShown()
        {
            // почему нету реализации, описано ниже
        }
    
        public function subscribe($events)
        {
            $events->listen('article.shown', 'ArticleSubscriber@onShown');
        }
    
    }

На данном этапе я снова был озадачен тем, как правильно реализовать обработку события. Передать модель статьи `$article` в событие я не могу, т.к. это опять нарушает принципы ООП и мой subscriber будет знать о существовании модели article. То есть сделать так я не могу:

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

    // некий subscriber, подписанный на события
    ...
    public function onShown(Article $article)
    {
        $article->increment('views');
    }
    ...

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

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

    // некий 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);
        // НЕВЕРНО! т.к. Event не должен знать о том, что умеет модель в реализации Eloquent
        // $article->increment('views');
    }

Можно ли найденную модель передать обратно в репозиторий и уже там увеличить ей счетчик (имеется ввиду, не противоречит ли такой подход паттерну?)? Примерно так:

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

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

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

  1. При использовании паттерна `Repository` мне придется отказаться от передачи модели в контроллер и подобных удобностей?

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

Такие дела, такие вот у меня вопросы. Хотелось бы услышать ответы, мысли, комментарии. Быть может, я не так пытаюсь применить паттерн? Сейчас он вызывает больше головной боли, нежели решает проблему дата маппинга.

#2 05.11.2014 09:54:46

Re: Имплементация Repository паттерна в Laravel

По вопросам в конце.

1. Можно и передавать модель в контроллер, но мне не кажется, что это какая-то особенная удобность. От подобной магии, которая экономит пару строчек кода, но накладывает определенные ограничения, я стараюсь сторониться.
2. Нет, так как для хранения состояния у нас есть стандартные модели. Вообще, у нас для абстракции от БД у нас есть паттерн ActiveRecord и поэтому имплементировать Repository прям в точном виде и юзать только его, мне кажется, излишним. Репозитории, конечно, удобны и нужны для аггломераци методов получения данных из модели/моделей и методов операций с сущностями, но забывать из-за этого, что у нас есть потрясающий Eloquent и не прикасаться к нему, как к чумному, мне кажется - это стрелять в ногу.

В данном конкретном случае я бы сделал так, как в последнем примере кода, т.е. передал в репозиторий экземпляр модели. Events тут не нужны (они вообще мало где нужны, имхо, или мне просто не попадалось задач под них).

Не в сети

#3 05.11.2014 16:16:59

Re: Имплементация Repository паттерна в Laravel

Ответил Вам на хэшкоде, ибо сначала увидел ссылку в чате...

Не в сети

#4 06.11.2014 23:22:31

likerRr

Re: Имплементация Repository паттерна в Laravel

slider23 пишет:

По вопросам в конце.

1. Можно и передавать модель в контроллер, но мне не кажется, что это какая-то особенная удобность. От подобной магии, которая экономит пару строчек кода, но накладывает определенные ограничения, я стараюсь сторониться.
2. Нет, так как для хранения состояния у нас есть стандартные модели. Вообще, у нас для абстракции от БД у нас есть паттерн ActiveRecord и поэтому имплементировать Repository прям в точном виде и юзать только его, мне кажется, излишним. Репозитории, конечно, удобны и нужны для аггломераци методов получения данных из модели/моделей и методов операций с сущностями, но забывать из-за этого, что у нас есть потрясающий Eloquent и не прикасаться к нему, как к чумному, мне кажется - это стрелять в ногу.

В данном конкретном случае я бы сделал так, как в последнем примере кода, т.е. передал в репозиторий экземпляр модели. Events тут не нужны (они вообще мало где нужны, имхо, или мне просто не попадалось задач под них).

Спасибо за ответ. По поводу 2, то есть грубо говоря бд сущности мне необходимо наследовать от \Illuminate\Database\Eloquent\Model, а в качестве репозитория использовать унаследованную Eloquent реализацию? Просто не хочется раздувать модель методами доступа к данным, их модификации и пр.

Даже с реализацией Repository я не собираюсь отказываться от Eloquent, ведь мне ничто не мешает в конструктор репозитория инжектить eloquent реализацию модели, так ведь?

#5 06.11.2014 23:24:18

Re: Имплементация Repository паттерна в Laravel

SMGladkovskiy пишет:

Ответил Вам на хэшкоде, ибо сначала увидел ссылку в чате...

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

Не в сети

Подвал раздела