Часто, описывая модель разработки MVC (MVP, MVVM или другие M**), на контроллер возлагаются необоснованно большие задачи. Получение параметров, бизнес логика, авторизация и ответ.
Конечно, в статьях и книгах это описывается в качестве примера, но часто воспринимается как призыв к действию в рабочих проектах. Такой подход неизбежно приведет к неконтролируемому разрастанию класса и сильно усложнит поддержку кода.
Принцип единой ответственности
Для примера возьмем довольно грубый, но часто встречающийся тип «толстого» контроллера.
class OrderController extends Controller { public function create(Request $request) { $rules = [ 'product_id' => 'required|max:10', 'count' => 'required|max:10', ]; $validator = Validator::make($request->all(), $rules); if ($validator->fails()) { return response()->json($validator->errors(), 400); } $order = Order::create($request->all()); // Резервирование товаров // Отправка sms сообщения покупателю с подтверждением заказа return response()->json($order, 201); } }
В данном примере видно, что контроллер слишком много знает о «размещении заказа», так же на него возложена задача оповестить покупателя и зарезервировать товар.
Мы будем стремиться к тому, что контроллеру предстоит только контролировать весь процесс и никакой бизнес логики.
Начнем с того, что вынесем валидацию параметров в отдельный Request класс.
class OrderRequest extends Request { public function rules(): array { return [ 'product_id' => 'required|max:10', 'count' => 'required|max:10', ]; } }
А всю бизнес логику переместим в класс OrderService
public class OrderService { public function create(Request $request) { // Создание заказа // Резервирование товаров $sms = new RuSms(); // Устанавливаем номер и текст сообщения и уникальный идентификатор приложения $sms->send($number, $text, $appId); } }
Используя Сервис Контейнер внедрим наш сервис в контроллер.
В итоге имеем «тонкий» контроллер.
class OrderController extends Controller { protected $service; public function __construct(OrderService $service) { $this->service = $service; } public function create(OrderRequest $request) { $this->service->create(request); return response()->json($order, 201); } }
Уже лучше. Вся бизнес логика перемещена в сервис. Контроллер знает только о классах OrderRequest и OrderService — минимальном наборе информации, необходимом для выполнения своей работы.
Но теперь наш сервис так же нуждается в рефакторинге. Вынесем логику отправки sms в отдельный класс.
class SmsRu { public function send($number, $text): void { // Описываем здесь логику отправки смс } }
И внедрим его через конструктор
class OrderService { private $sms; public function __construct() { $this->sms = new SmsRu(); } public function create($number, $text): void { // Создание заказа // Резервирование товаров $this->sms->send($nubmer, $text); } }
Уже лучше, но класс OrderService до сих пор знает слишком много об отправке сообщений. Возможно нам потребуется в будущем заменить провайдера рассылки сообщений или добавить модульные тесты. Продолжим рефакторинг добавив интерфейс SmsSender, а сам SmsRu укажем через провайдер SmsServiceProvider.
interface SmsSenderInterface { public function send($number, $text): void; } class SmsServiceProvider extends ServiceProviderInterface { public function register(): void { $this->app->singleton(SmsSenderInterface::class, function ($app) { return new SmsRu($params['app_id'], $params['url']); }); } }
Теперь сервис так же освобожден от ненужных деталей
class OrderService { private $sms; public function __construct(SmsSenderInterface $sms) { $this->sms = $sms; } public function create(): void { // Создание заказа // Резервирование товаров $this->sms->send($nubmer, $text); } }
Событийно-управляемая архитектура
Отправка сообщений не является частью основного процесса создания заказа. Заказ создастся вне зависимости от отправки сообщения, так же возможно в будущем будет добавлена опция отмены sms оповещения. Для того чтобы не перегружать OrderService ненужными подробностями оповещения, можно использовать Laravel Observers. Класс который будет отслеживать события при определенном поведении нашей модели Order и возложить на него всю логику оповещения покупателя.
class OrderObserver { private $sms; public function __construct(SmsSenderInterface $sms) { $this->sms = $sms; } /** * Handle the Order "created" event. */ public function created(Order $order) { $this->sms->send($nubmer, $text); }
Не забываем зарегистрировать OrderObserver в AppServiceProvider.
Вывод
Я понимаю, что описываю в этом посте довольно банальные вещи, но хотелось показать, как этот принцип реализуется на фреймворке Laravel.
Принцип SPR один из важнейших и открывает дорогу к применению остальных. Обязателен к применению, тем более что фреймворк Laravel предоставляет все необходимые инструменты.
ссылка на оригинал статьи https://habr.com/ru/post/489174/
Добавить комментарий