Я изучал мозги других разработчиков по понятию «толстые модели, тощие контроллеры» после прочтения:
Большинство респондентов используют то, что я считаю контроллерами жира.
В то время как тема возникла при переполнении стека, я не нашел подробного описания метода на практике.
Я просто нашел здесь старый вопрос .
То, что вы увидите в PHP (ваниль или Laravel или Symfony), все больше и больше – самые скудные контроллеры. Это то, что вы уже видите в Rails, и люди также начинают называть его (с некоторыми другими практиками) шестиугольными. Одна строка кода – это все, что вам нужно в вашем контроллере, на самом деле они говорят, что это должно быть целью для всех ваших методов. Это пример, да, немного больше, чем это, но все еще тощий:
<?php class PostController extends Controller { private $repository; public function __construct(PostRepositoryInterface $repository) { $this->repository = $repository; } public function store() { try { $this->repository->create(Input::all()); } catch (ValidationException $e) { return Redirect::back()->withInput()->withErrors($e->all()); } return Redirect::route('posts'); } }
Контроллер представляет собой мост между HTTP-запросами, бизнес-логикой и уровнем представления. Поэтому он должен получить один запрос, отправить его на инъецируемый объект, который будет обрабатывать его и перенаправлять на маршрут (или визуализировать представление), ответственный за отправку отзывов клиенту (или пользователю). Все остальное, включая проверку, должно происходить в ваших репозиториях, службах, моделях (MVC, yay!) И т. Д.
Но мы могли бы реорганизовать этот контроллер в гексагональной форме, чтобы достичь цели с одной строкой на один метод:
<?php class PostController extends Controller { private $repository; public function __construct(PostRepositoryInterface $repository) { $this->repository = $repository; } public function store() { return $this->repository->create(Input::all(), $this); } public function createSucceeded() { return Redirect::route('posts'); } public function createFailed() { return Redirect::back()->withInput()->withErrors($e->all()); } }
В основном ваши классы репозитория будут использовать собственный вызывающий ( $this
), чтобы запустить succeeded
и failed
методы.
Модели слишком связаны с вашими данными, иногда они являются вашим ORM и напрямую говорят на вашем сервере базы данных, поэтому в наши дни вы увидите, что люди используют репозитории и сервисы в виде слоев между ними.
Репозиторий – это класс, который, непосредственно разговаривая с вашими моделями, обрабатывает и собирает информацию, необходимую вашему приложению. Ваше приложение не должно знать, что необходимо для выбора некоторой информации в вашей базе данных, выбирать, где, порядок, группировать, это вещи, о которых иногда должны знать только ваши модели, поэтому это репозиторий:
class PostRepository implements PostRepositoryInterface { private $model; public function __construct(PostInterface $model) { $this->model = $model; } public function create($input) { return $this->model->create($input); } public findBySlug($slug) { return $this->model->where('slug', $slug)->first(); } }
Все, что не принадлежит непосредственно вашей бизнес-логике, главным образом внешним службам, наиболее удаленным от вашего кода приложения, тем более развязанным вы их создаете, тем лучше. Создание внешних пакетов (пакетов Composer) для этих сервисов – хороший способ развязать их от всего остального, и если вы сделаете их агностиками фреймворка, вы получите 10 очков осетровых . В Laravel вы можете создавать сервисы, интегрируя три типа классов:
1) Service Class (ы): ответственный за то, что делает ваша служба, все ваши логики обслуживания идут здесь.
2) Поставщик услуг: ответственен за загрузку вашего сервиса и добавление его в контейнер IoC Laravel, поэтому он может быть готов к использованию в любое время, но обратите внимание, что Laravel будет создавать экземпляры ваших классов обслуживания только тогда, когда ваше приложение действительно их использует.
3) Фасад: позволяет получить доступ к вашему сервису из любого места в приложении, используя статический (: 🙂 синтаксис:
Mailer::send($user->id, 'Thanks for registering', 'emails.registered');
Это служба Mailer:
<?php namespace ACR\Services\Mailer; use Illuminate\Mail\Mailer as IlluminateMailer; use Sentry; class Service { public function __construct(IlluminateMailer $mailer) { $this->mailer = $mailer; } public function send($userId, $subject, $view, $data = []) { return $this->mailer->queue($view, $data, function($message) use ($userId, $subject) { $user = Sentry::findUserById($userId); $message->to($user->email, $user->name); $message->subject($subject); }); } }
<?php namespace ACR\Services\Mailer; use Illuminate\Support\ServiceProvider as IlluminateServiceProvider; use ACR\Services\Mailer\Service as Mailer; class ServiceProvider extends IlluminateServiceProvider { /** * Indicates if loading of the provider is deferred. * * @var bool */ protected $defer = true; /** * Register the service provider. * * @return void */ public function register() { $this->app->bind('acr.mailer', function($app) { return new Mailer($app->make('mailer')); }); } /** * Get the services provided by the provider. * * @return array */ public function provides() { return array('acr.mailer'); } }
<?php namespace ACR\Services\Mailer; use Illuminate\Support\Facades\Facade as IlluminateFacade; class Facade extends IlluminateFacade { protected static function getFacadeAccessor() { return 'acr.mailer'; } }
Эти ребята должны быть сильно заменяемыми, сегодня вы можете использовать Eloquent как ваш ORM, сохраняя данные в базе данных, но вам может потребоваться изменить его на что-то еще, некоторые фоки хранят 100% своих данных в Redis, так что вам лучше будьте готовы к подобным изменениям, используя слой интерфейса (контракт) между вашим ORM и вашим доменным протоколом и действительно развивайтесь для своих интерфейсов, а не для ваших конкретных классов. Тейлор Отуэлл в своей книге даже говорит, что вы должны полностью удалить папку с вашими моделями.
interface PostInterface { public function all(); public function find($id); } class DbPost extends Eloquent implements PostInterface { } class RedisPost extends Eloquent implements PostInterface { }
Идея заключается в том, чтобы легко сменять реализации, поэтому в Laravel вы можете использовать контейнер IoC, чтобы сообщить Laravel, какую реализацию вы используете:
App::bind('PostInterface', 'DbPost');
Итак, если у вас в репозитории используется PostInterface:
class PostRepository implements PostRepositoryInterface { private $model; public function __construct(PostInterface $model) { $this->model = $model; } }
Контейнер Laravel IoC автоматически создает экземпляр этого репозитория с экземпляром DbPost. И если вам когда-либо понадобится изменить его на Redis, вам просто нужно изменить одну строку:
App::bind('PostInterface', 'RedisPost');
Самый неистовый вид.
Представления должны отвечать только за отображение информации. Представления не должны знать о ваших моделях, службах, репозиториях или чем-либо еще в вашей системе. Представления должны быть доступны для редактирования веб-дизайнерами, поэтому, чем больше у вас кода, тем больше ошибок, которые добавит их разработчик не-php-программист. Ваш контроллер должен собрать информацию из ваших репозиториев и передать их вашим представлениям:
<?php class PostController extends Controller { private $repository; public function __construct(PostRepositoryInterface $repository) { $this->repository = $repository; } public function index() { return View::make('posts.index')->with('posts', $this->repository->getPaginated()); } }
И единственная ответственность вашего взгляда должна заключаться в том, что данные:
@extends('layout') @section('contents') <ul> @foreach($posts as $post) <li> {{ $post->title }} - {{ $post->author }} - {{ $post->published_at }} </li> @endforeach </ul> {{ $users->links() }} @stop
Как вы отформатируете свои данные? Вы пишете сырые свойства в своих представлениях, но вы должны за кулисами использовать презентаторов, да, представить свои данные. Ведущие обычно используют шаблон оформления Decorator для форматирования ваших данных, которые будут представлены на ваших страницах. Это пример использования LaravelAutoPresenter от Shawn McCool:
<?php namespace App\Presenters; use McCool\LaravelAutoPresenter\BasePresenter; class Post extends BasePresenter { public function __construct(UserModel $user) { $this->resource = $user; } public function author() { return $this->resource->author->name; } public function published_at() { return $this->date($this->resource->created_at); } public function dateTime($date) { return \Carbon\Carbon::createFromFormat('dm-Y', $date, 'Sao_Paulo/Brazil') ->toFormattedDateString(); } }
Ларавель Тейлора Отуэлла: от ученика до ремесленника
Реставрация Ларавеля Криса Фидао