MVC (Laravel), где добавить логику

Скажем, всякий раз, когда я выполняю операцию CRUD или модифицирую отношения определенным образом, я также хочу сделать что-то еще. Например, всякий раз, когда кто-то публикует сообщение, я также хочу сохранить что-то в таблице для аналитики. Может быть, не лучший пример, но в целом есть много этой «сгруппированной» функциональности.

Обычно я вижу, что этот тип логики помещается в контроллеры. Все в порядке, денди, пока вы не захотите воспроизвести эту функциональность во многих местах. Когда вы начинаете попадать в частичные, создавая API и создавая фиктивный контент, это становится проблемой с сохранением вещей DRY.

Способы, которые я видел для управления, это события, репозитории, библиотеки и добавление к моделям. Вот мое понимание каждого из них:

Услуги: именно там большинство людей, вероятно, поместили бы этот код. Моя основная проблема с услугами заключается в том, что иногда трудно найти в них определенную функциональность, и я чувствую, что они забываются о том, когда люди сосредоточены на использовании Eloquent. Как я узнаю, что мне нужно вызвать метод publishPost() в библиотеке, когда я могу просто сделать $post->is_published = 1 ?

Единственное условие, по которому я вижу, что это хорошо работает, – это ТОЛЬКО использовать сервисы (и в идеале сделать Eloquent недоступным каким-то образом из контроллеров все вместе).

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

Хранилища: из того, что я понимаю, это в основном как сервис, но есть интерфейс, поэтому вы можете переключаться между ORM, которые мне не нужны.

События: Я считаю это самой элегантной системой в некотором смысле, потому что вы знаете, что ваши модельные события всегда будут вызваны методами «Красноречивый», поэтому вы можете написать свои контроллеры, как обычно. Я вижу, что они становятся грязными, и если у кого-то есть примеры больших проектов, использующих события для критической связи, я бы хотел это увидеть.

Модели: Традиционно у меня были бы классы, которые выполняли CRUD, а также обрабатывали критическую связь. Это действительно облегчило работу, потому что вы знали всю функциональность вокруг CRUD +, что бы ни было, с ней было.

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

Каковы преимущества / недостатки каждого метода? Я что-то упускаю?

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

Для того, где добавить логику, я считаю, что важно упомянуть принцип единой ответственности . Кроме того, в моем ответе вы полагаете, что работаете над средним / крупным проектом. Если это проект throw-something-on-a-page , забудьте этот ответ и добавьте все его в контроллеры или модели.

Короткий ответ: где это имеет смысл для вас (с услугами) .

Длинный ответ:

Контроллеры : в чем ответственность Контроллеров? Конечно, вы можете поместить всю свою логику в контроллер, но это ответственность диспетчера? Я так не думаю.

Для меня контроллер должен получать запрос и возвращать данные, и это не место для размещения проверок, методов вызова db и т. Д.

Модели : Это хорошее место, чтобы добавить логику, например, отправить приветственное письмо, когда пользователь регистрирует или обновляет количество голосов в почте? Что делать, если вам нужно отправить тот же адрес электронной почты из другого места в вашем коде? Создаете ли вы статический метод? Что делать, если эти электронные письма нуждаются в информации из другой модели?

Я думаю, что модель должна представлять сущность. С Laravel я использую только класс модели, чтобы добавлять такие вещи, как fillable , guarded , table и отношения (это потому, что я использую шаблон репозитория, иначе модель также будет иметь методы save , update , find и т. Д.).

Репозитории (шаблон хранилища) : Вначале я был очень смущен этим. И, как и вы, я подумал: «Ну, я использую MySQL, и это так».

Тем не менее, я сравнил плюсы и минусы использования шаблона репозитория, и теперь я использую его. Я думаю, что сейчас , в этот самый момент, мне нужно будет только использовать MySQL. Но, если через три года мне нужно перейти на нечто вроде MongoDB, большая часть работы будет выполнена. Все за счет одного дополнительного интерфейса и $app->bind(«interface», «repository») .

События ( шаблон наблюдателя ): события полезны для вещей, которые могут быть брошены в любой класс в любое время. Подумайте, например, о отправке уведомлений пользователю. Когда вам нужно, вы запускаете событие для отправки уведомления в любом классе вашего приложения. Затем вы можете иметь класс, такой как UserNotificationEvents который обрабатывает все ваши запущенные события для уведомлений пользователей.

Услуги : до сих пор у вас есть выбор, чтобы добавить логику к контроллерам или моделям. Для меня имеет смысл добавить логику в Services . Посмотрим правде в глаза, Services – это причудливое имя для классов. И у вас может быть столько классов, сколько имеет смысл для вас в ваших приложениях.

Возьмем этот пример: недавно я разработал нечто вроде Google Forms. Я начал с CustomFormService и закончил с CustomFormService , CustomFormRender , CustomFieldService , CustomFieldRender , CustomAnswerService и CustomAnswerRender . Зачем? Потому что это имело смысл для меня. Если вы работаете с командой, вы должны поместить свою логику там, где это имеет смысл для команды.

Преимущество использования Services vs Controllers / Models заключается в том, что вы не ограничены одним контроллером или одной моделью. Вы можете создать как можно больше услуг на основе дизайна и потребностей вашего приложения. Добавьте к этому преимущество вызова службы в любом классе вашего приложения.

Это длится долго, но я хотел бы показать вам, как я структурировал свое приложение:

 app/ controllers/ MyCompany/ Composers/ Exceptions/ Models/ Observers/ Sanitizers/ ServiceProviders/ Services/ Validators/ views (...) 

Я использую каждую папку для определенной функции. Например, каталог Validators содержит класс BaseValidator отвечающий за обработку валидации, на основе $rules и $messages определенных валидаторов (обычно по одной для каждой модели). Я мог бы легко поместить этот код в Сервис, но для меня имеет смысл иметь определенную папку для этого, даже если он используется только в службе (пока).

Я рекомендую вам прочитать следующие статьи, поскольку они могут объяснить вам немного лучше:

Ломать пресс-форму от Dayle Rees (автор CodeBright): Вот где я собрал все это вместе, хотя я изменил несколько вещей, чтобы соответствовать моим потребностям.

Развязка кода в Laravel с использованием репозиториев и сервисов Chris Goosey: в этом сообщении хорошо объясняется, что такое «Сервис» и «Шаблон репозитория» и как они сочетаются друг с другом.

У Laracasts также есть Упрощенная и Единая Ответственность Хранилища, которые являются хорошими ресурсами с практическими примерами (хотя вы должны платить).

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

  1. Контроллер запрашивает действие пользователя и отправляет параметры и делегирует все в класс обслуживания.
  2. Класс обслуживания выполняет всю логику, связанную с операцией: проверка ввода, ведение журнала событий, операции с базой данных и т. Д.
  3. Модель содержит информацию о полях, преобразовании данных и определениях валидации атрибутов.

Вот как я это делаю:

Это метод контроллера для создания чего-то:

 public function processCreateCongregation() { // Get input data. $congregation = new Congregation; $congregation->name = Input::get('name'); $congregation->address = Input::get('address'); $congregation->pm_day_of_week = Input::get('pm_day_of_week'); $pmHours = Input::get('pm_datetime_hours'); $pmMinutes = Input::get('pm_datetime_minutes'); $congregation->pm_datetime = Carbon::createFromTime($pmHours, $pmMinutes, 0); // Delegates actual operation to service. try { CongregationService::createCongregation($congregation); $this->success(trans('messages.congregationCreated')); return Redirect::route('congregations.list'); } catch (ValidationException $e) { // Catch validation errors thrown by service operation. return Redirect::route('congregations.create') ->withInput(Input::all()) ->withErrors($e->getValidator()); } catch (Exception $e) { // Catch any unexpected exception. return $this->unexpected($e); } } 

Это класс обслуживания, который выполняет логику, связанную с операцией:

 public static function createCongregation(Congregation $congregation) { // Log the operation. Log::info('Create congregation.', compact('congregation')); // Validate data. $validator = $congregation->getValidator(); if ($validator->fails()) { throw new ValidationException($validator); } // Save to the database. $congregation->created_by = Auth::user()->id; $congregation->updated_by = Auth::user()->id; $congregation->save(); } 

И это моя модель:

 class Congregation extends Eloquent { protected $table = 'congregations'; public function getValidator() { $data = array( 'name' => $this->name, 'address' => $this->address, 'pm_day_of_week' => $this->pm_day_of_week, 'pm_datetime' => $this->pm_datetime, ); $rules = array( 'name' => ['required', 'unique:congregations'], 'address' => ['required'], 'pm_day_of_week' => ['required', 'integer', 'between:0,6'], 'pm_datetime' => ['required', 'regex:/([01]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5][0-9]/'], ); return Validator::make($data, $rules); } public function getDates() { return array_merge_recursive(parent::getDates(), array( 'pm_datetime', 'cbs_datetime', )); } } 

Для получения дополнительной информации об этом пути я использую для организации своего кода для приложения Laravel: https://github.com/rmariuzzo/Pitimi

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

Я закончил использование существующей структуры, которую предоставляет Laravel, что означает, что я сохранил файлы в основном как Model, View и Controller. У меня также есть папка Libraries для повторно используемых компонентов, которые на самом деле не являются моделями.

Я НЕ ПЛАНИРУЕТ МОИ МОДЕЛИ В УСЛУГАХ / БИБЛИОТЕКАХ . Все изложенные причины не помогли мне на 100% воспользоваться услугами. Хотя я могу ошибаться, насколько я вижу, они просто приводят к количеству лишних почти пустых файлов, которые мне нужно создавать и переключаться между ними при работе с моделями, а также действительно уменьшать преимущество использования красноречивых (особенно когда речь заходит о моделях RETRIEVING , например, с использованием разбивки на страницы, области и т. д.).

Я поставил бизнес-логику в МОДЕЛИ и получил доступ к красноречивому прямо от моих контроллеров. Я использую ряд подходов, чтобы убедиться, что бизнес-логика не обошла стороной:

  • Аксессоры и мутаторы: у Laravel есть отличные аксессоры и мутаторы. Если я хочу выполнить действие всякий раз, когда сообщение перемещается из черновика в опубликованное, я могу вызвать это, создав функцию setIsPublishedAttribute и включив туда логику
  • Переопределение создания / обновления и т. Д. Вы всегда можете переопределить методы Eloquent в своих моделях, чтобы включить пользовательские функции. Таким образом, вы можете вызывать функциональные возможности любой операции CRUD. Изменить: я думаю, что есть ошибка с переопределением создания в новых версиях Laravel (поэтому я использую события, зарегистрированные сейчас в процессе загрузки)
  • Проверка. Я также проверяю правильность моей проверки, например, я буду запускать проверку путем переопределения функций CRUD, а также аксессуаров / мутаторов, если это необходимо. См. Esensi или dwightwatson / подтверждение для получения дополнительной информации.
  • Магические методы: я использую методы __get и __set для своих моделей, чтобы подключаться к функциям, когда это необходимо
  • Расширение Eloquent: если есть действие, которое вы хотите использовать для всех обновлений / создания, вы можете даже увеличить красноречивость и применить его к нескольким моделям.
  • События: это прямое и согласованное место для этого. Самый большой недостаток событий я думаю, что исключения трудно отследить (возможно, это не новый случай с новой системой событий Laravel). Мне также нравится группировать свои события тем, что они делают, а не когда они вызывают … например, есть подписчик MailSender, который слушает события, отправляющие почту.
  • Добавление Pivot / BelongsToMany Events: Одна из вещей, с которой я боролся с самыми длинными, – это то, как приложить поведение к модификации отношений ownToMany. Например, выполнение действия всякий раз, когда пользователь присоединяется к группе. Для этого я почти закончил создание специальной библиотеки. Я еще не опубликовал его, но он функциональный! Попытайтесь отправить ссылку в ближайшее время. EDIT Я закончил тем, что сделал все свои повороты в нормальные модели, и моя жизнь была намного проще …

Решение проблем людей с использованием моделей:

  • Организация: Да, если вы добавите больше логики в модели, они могут быть длиннее, но в целом я обнаружил, что 75% моих моделей все еще довольно малы. Если я решил организовать более крупные, я могу сделать это с помощью свойств (например, создать папку для модели с дополнительными файлами, такими как PostScopes, PostAccessors, PostValidation и т. Д. По мере необходимости). Я знаю, что это не обязательно то, что характерно, но эта система работает без проблем.

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

КОГДА ИСПОЛЬЗОВАТЬ УСЛУГИ : В этой статье очень хорошо излагаются БОЛЬШИЕ примеры того, когда использовать услуги ( подсказка: это не очень часто ). Он говорит в основном, когда ваш объект использует несколько моделей или моделей в странных частях своего жизненного цикла, имеет смысл. http://www.justinweiss.com/articles/where-do-you-put-your-code/

На мой взгляд, у Laravel уже есть много вариантов для хранения вашей бизнес-логики.

Короткий ответ:

  • Используйте объекты Request laravel для автоматической проверки ввода, а затем сохраняйте данные в запросе (создайте модель). Поскольку все входные данные пользователя доступны непосредственно в запросе, я считаю, что имеет смысл выполнить это здесь.
  • Используйте объекты Job larvel для выполнения задач, требующих отдельных компонентов, а затем просто отправляйте их. Я думаю, что занятия включают классы обслуживания. Они выполняют задачу, такую ​​как бизнес-логика.

Длинные (er) ответы:

Использовать репозитории, когда необходимо: Хранилища должны быть переизолированы, и большую часть времени они просто используются в качестве accessor к модели. Я чувствую, что они определенно имеют какую-то пользу, но если вы не разрабатываете массивное приложение, которое требует такой гибкости, чтобы вы могли полностью расколоть laravel, не вмешиваться в хранилища. Вы поблагодарите себя позже, и ваш код будет намного более прямым.

Спросите себя, есть ли вероятность, что вы собираетесь менять фреймворки PHP или тип базы данных, который не поддерживается laravel.

Если ваш ответ «Вероятно, нет», тогда не реализуйте шаблон репозитория.

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

Употребляйте сервисы экономно: классы обслуживания для меня – это просто место для хранения бизнес-логики для выполнения конкретной задачи с заданными зависимостями. У Laravel есть эти из коробки, называемые «Джобс», и они обладают гораздо большей гибкостью, чем пользовательский класс обслуживания.

Я чувствую, что у Laravel есть хорошо округленное решение для логики MVC . Это всего лишь вопрос или организация.

Пример:

Запрос :

 namespace App\Http\Requests; use App\Post; use App\Jobs\PostNotifier; use App\Events\PostWasCreated; use App\Http\Requests\Request; class PostRequest extends Request { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'title' => 'required', 'description' => 'required' ]; } /** * Save the post. * * @param Post $post * * @return bool */ public function persist(Post $post) { if (!$post->exists) { // If the post doesn't exist, we'll assign the // post as created by the current user. $post->user_id = auth()->id(); } $post->title = $this->title; $post->description = $this->description; // Perform other tasks, maybe fire an event, dispatch a job. if ($post->save()) { // Maybe we'll fire an event here that we can catch somewhere else that // needs to know when a post was created. event(new PostWasCreated($post)); // Maybe we'll notify some users of the new post as well. dispatch(new PostNotifier($post)); return true; } return false; } } 

Контроллер :

 namespace App\Http\Controllers; use App\Post; use App\Http\Requests\PostRequest; class PostController extends Controller { /** * Creates a new post. * * @return string */ public function store(PostRequest $request) { if ($request->persist(new Post())) { flash()->success('Successfully created new post!'); } else { flash()->error('There was an issue creating a post. Please try again.'); } return redirect()->back(); } /** * Updates a post. * * @return string */ public function update(PostRequest $request, $id) { $post = Post::findOrFail($id); if ($request->persist($post)) { flash()->success('Successfully updated post!'); } else { flash()->error('There was an issue updating this post. Please try again.'); } return redirect()->back(); } } 

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

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