SOLID
SOLID — принципы программирования, следуя которым можно добиться легко-масштабируемого и легко-поддерживаемого кода.
Controller Service Repository
Controller Service Repository — архитектурный паттерн, необходимый для разделения ответственности и помогающий соблюдать принципы SOLID в фреймворке Laravel
Контроллеры
Контроллеры — классы, отвечающие за обработку запросов. Таким образом ответственность контроллера — это формирование ответа на пользовательские запросы.
1. Принять запрос (request)
2. Запустить метод сервиса
3. Обработать исключения или возвращенное сервисом значение
4. Ответить (response) в нужном формате
<?php
namespace App\Http\Controllers;
use App\Exceptions\UserNotFoundException;
use App\Http\Controllers\Controller;
use App\Http\Requests\ShowUserRequest;
use App\Services\UserService;
use Illuminate\View\View;
final class UserController extends Controller
{
public function show(ShowUserRequest $request, UserService $service): View
{
try {
$user = $service->getUser($request->getUserId());
} catch (UserNotFoundException $exception) {
return view('user.not_found', ['user_id' => $request->getUserId()]);
}
return view('user.profile', ['user' => $user]);
}
}
Laravel дает возможность конвертировать исключения в Http ответы, добавив метод render, но это нарушает принципы SOLID, как минимум S — принцип единственной ответственности и не рекомендуется к применению.
<?php
namespace App\Exceptions;
use Illuminate\Http\Request;
use Illuminate\View\View;
final class UserNotFoundException extends Exception
{
public function report(): ?bool
{
//
}
public function render(Request $request): View
{
return view('user.not_found', ['user_id' => $request->getUserId()]);
}
}
Исключение не должно определять какой контент получит пользователь. Негативные последствия такой реализации в том, что при необходимости получения разных ответов в разных методах контроллеров, одно и то же исключение может быть преобразовано по разному.
В таких случаях:
1. Метод render будет содержать различные условия, проверяющие из какого именно контроллера было выброшено исключение
2. Будет не очевидно, какие ответы какой контроллер возвращает в той или иной ситуации
То же самое справедливо и для преобразования исключений в методе Handler::register()
Сервисы
Сервисы — классы, отвечающие за бизнес логику. В Laravel по умолчанию не создана директория сервисов. Вы должны создать её самостоятельно.
<?php
namespace App\Services;
final class UserService
{
public function __construct(public readonly UserRepository $userRepository)
{
}
public function getUser(int $userId): User
{
//Выполняем различные бизнес проверки
...
$user = $this->userRepository->find($userId);
if ($user === null) {
throw new UserNotFoundException("User {$userId} not found.');
}
return $user;
}
}
В теле метода сервиса не выполняются запросы к базе данных. Только бизнесовая логика.
Репозитории
Репозитории — классы, отвечающие за сохранение и извлечение некоторого набора данных. В репозиториях нет сложной и тем более бизнесовой логики. В методах репозитория должны лишь формироваться и выполняться запросы. В Laravel по умолчанию не создана директория репозиториев. Вы должны создать её самостоятельно.
<?php
namespace App\Repositories;
final class UserRepository
{
public function find(int $userId): ?User
{
return User::find($userId);
}
}
В идеале репозиторий не должен возвращать или принимать объекты класса Model. Лучше создать собственные entity или dto.
Такой код может показаться излишне сложным. Но с опытом вы поймете, что разделение ответственности, делает работу с таким кодом и его поддержку значительно легче.