SRP (принцип единственной ответственности) гласит, что у класса должна быть только одна причина для изменения. Это не означает один метод или одну строку кода. Это означает одну задачу. Если изменение базы данных, новый формат JSON и новое правило валидации заставляют вас редактировать один и тот же класс, значит, у этого класса слишком много обязанностей.
Проблема
Рассмотрим следующий класс:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Wallet;
use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class WalletBalanceController extends Controller
{
/**
* Return the authenticated user's current balance.
*/
public function __invoke(Request $request): JsonResponse
{
$wallet = $request->user()->wallet()->firstOrCreate([], [
'balance' => 0,
'currency' => Wallet::CURRENCY,
]);
return ApiResponse::make([
'balance' => $wallet->balance,
'currency' => $wallet->currency,
]);
}
}
У контроллера выше две задачи: обработка HTTP-запроса и запись в базу данных путем создания кошелька, если он отсутствовал.
Решение: одна задача на класс
1. Контроллер обрабатывает только HTTP
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Wallet;
use App\Services\WalletService;
use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class WalletBalanceController extends Controller
{
/**
* Return the authenticated user's current balance.
*/
public function __invoke(Request $request, WalletService $wallets): JsonResponse
{
$wallet = $wallets->findOrCreate($request->user());
return ApiResponse::make([
'balance' => $wallet->balance,
'currency' => $wallet->currency ?? Wallet::CURRENCY,
]);
}
}
Теперь контроллер только считывает запрос, получает кошелек из сервиса и возвращает ответ. Ничего лишнего.
2. Сервис отвечает за бизнес-логику
<?php
namespace App\Services;
use App\Exceptions\InsufficientFundsException;
use App\Models\LedgerEntry;
use App\Models\User;
use App\Models\Wallet;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class WalletService
{
/**
* Return the user's wallet, creating an empty rial wallet when one does not exist.
*/
public function findOrCreate(User $user): Wallet
{
return $user->wallet()->firstOrCreate([], [
'balance' => 0,
'currency' => Wallet::CURRENCY,
]);
}
WalletService получает кошелек пользователя и создает пустой кошелек, если он еще не существует.
Быстрый тест
Опишите класс одним предложением без союза «и». Вариант «Этот контроллер возвращает баланс пользователя» проходит проверку. Вариант «Этот контроллер валидирует перевод, и обновляет кошельки, и форматирует ответ» — нет.
Заключение
Благодаря разделению этих обязанностей контроллеры остаются компактными, правила работы с кошельками находятся в одном месте, а каждую часть можно протестировать отдельно. А как вы применяете SRP в своих проектах на Laravel?
Комментарии (0)
Пока нет комментариев — будьте первым.