Когда мы говорим о том, как сделать следующий шаг в качестве разработчика, мы должны понимать, что качество кода всегда является основным фокусом на этом пути.
Существует множество идей о том, как улучшить ваш код, например, KISS (Keep it Simple), DRY (Don't Repeat Yourself) и та, которая будет рассмотрена здесь: SOLID.
Существует множество рекомендаций по программированию, чтобы сделать наш код надежным, понятным и удобным для сопровождения. Принцип SOLID - один из них.
SOLID - это аббревиатура, которая расшифровывается как:
SOLID - это аббревиатура правил, которые облегчают жизнь тому, кто читает код для следующего сопровождения, поскольку они подобны "законам", которым вы должны следовать в объектно-ориентированном программировании для лучшей читаемости и управляемости кода.
Эти принципы были впервые представлены Робертом К. Мартином, он же Дядя Боб, в его работе 2000 года "Принципы проектирования и паттерны проектирования", позже эти принципы были перегруппированы, а аббревиатура SOLID была введена Мишелем Фезерсом.
Принципы SOLID не относятся только к PHP, они применимы к различным языкам OOPS. Это скорее принципы проектирования программного обеспечения. Это не функция, а способ мышления о том, как структурировать код, чтобы он был более удобным в обслуживании, понятным, гибким и легко расширяемым.
При работе над устаревшим кодом приходится много раз перечитывать код, чтобы добраться до той части, где требуется модификация. Становится очень трудно понять, что делает тот или иной метод, и чтение кода занимает слишком много времени, чем его написание.
Принцип единой ответственности гласит: "Класс должен делать что-то одно, и поэтому у него должна быть только одна причина для изменений".
Итак, допустим, у нас есть класс SaleReports.php внутри App\Solid.
<?php namespace App\Solid; use DB; class SaleReports { public function export() { $sales = DB::table('sales') ->latest() ->get(); return 'CSV format'; } }
В этом методе экспорта он выполняет две задачи: получает данные из базы данных и возвращает их в соответствующем формате. Таким образом, у этого метода есть две причины для изменения.
При разработке программного обеспечения требования бизнеса всегда меняются. Например, допустим, в будущем нам нужно будет добавить формат PDF для отчетов о продажах, тогда вышеприведенный класс будет выглядеть следующим образом
<?php namespace App\Solid; use DB; class SaleReports { public function export($format) { $sales = DB::table('sales') ->latest() ->get(); if ($format === 'pdf'){ return 'PDF format' } return 'CSV format'; } }
Здесь мы нарушаем SRP, потому что в приведенном выше классе много вещей происходит в одном классе. Теперь давайте реализуем SRP на вышеуказанном классе.
<?php namespace App\Solid; class PdfExport { public function export($data) { return 'PDF format'; } }
<?php namespace App\Solid; class CsvExport { public function export($data) { return 'CSV format'; } }
<?php namespace App\Solid; use DB; class SaleReports { public function export($format) { $sales = DB::table('sales') ->latest() ->get(); } }
Теперь вышеперечисленные классы делают именно то, что должны делать. Класс PdfExport будет экспортировать только данные, связанные с PDF, аналогично класс CsvExport будет экспортировать CSV.
При генерации PDF SaleReports мы можем использовать их следующим образом:
... $salesReports = new SaleReports(); $pdfExport = new PdfExport(); return $pdfExport->export($salesReports);
, а если мы хотим сгенерировать CSV SaleReports и так далее.
... $salesReports = new SaleReports(); $csvExport = new CsvExport(); return $csvExport->export($salesReports);
Представим себе сценарий, в котором у нас есть случайный чат и есть пользователь, который отправляет и получает сообщения. В этом сценарии мы собираемся сохранить все сообщения от нашего пользователя в базе данных после проверки.
Мы будем использовать экосистему Laravel, где входом запроса будет контроллер. В обязанности контроллера входит:
Вот сниппет:
namespace App\Http\Controllers; use DB; use Illuminate\Foundation\Http\Request; use App\Events\ChatMessage; class MessagesController extends Controller { public function postMessage(Request $request) { $this->validate($request, [ 'user_id' => 'required|exists:users,id', 'message' => 'required' ]); if ($this->getUserSpecificMessagesCount($data['message']) >= 5) { Log::alert('[User Alert] Flooding', $data) } $model = Message::create($request->all()); broadcast(new ChatMessage($model)); return response()->json(['message' => 'message created'], 201); } public function getUserSpecificMessagesCount(int $userId, string $message) { return DB::table('user_messages')->where([ ['user_id', '=', $userId], ['message', '=', $message], ])->count(); } }
Давайте перечислим, что можно увидеть в сниппете MessagesController
Теперь, думая об ответственности, которую должен нести контроллер, мы видим, что это было немного далеко от этого и не ожидалось.
Давайте приступим к рефакторингу сверху вниз, начиная с валидации. В экосистеме Laravel существует способ валидации запросов, при котором вы изолируете ответственность в классе FormRequest.
Используя команду php artisan make:request CreateMessageRequest, вы создадите класс FormRequest, который появится в папке/пространстве имен App\Http\Requests и будет нести УНИКАЛЬНУЮ ответственность за валидацию вашего запроса и ничего больше:
namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class CreateMessageRequest extends FormRequest { /** * 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 [ 'user_id' => 'required|exists:users,id', 'message' => 'required' ]; } }
Теперь мы собираемся реализовать решение на нашем фрагменте выше, и оно должно выглядеть следующим образом:
namespace App\Http\Controllers; use DB; use App\Http\Requests\CreateMessageRequest; use App\Events\ChatMessage; class MessagesController extends Controller { public function postMessage(CreateMessageRequest $request) { $data = $request->validated(); if ($this->getUserSpecificMessagesCount($data['message'])) { Log::alert('[User Alert] Flooding', $data) } $model = Message::create($data); broadcast(new ChatMessage($model)); return response()->json(['message' => 'message created'], 201); } public function getUserSpecificMessagesCount(int $userId, string $message) { return DB::table('user_messages')->where([ ['user_id', '=', $userId], ['message', '=', $message], ])->count(); } }
Итак, мы отделили валидацию нашей главной функции. Теперь нам нужно извлечь бизнес-правила на новый уровень абстракции, который известен как паттерн репозитория. Идея паттерна репозитория заключается в том, что у вас есть место для работы с методами/классами, которые взаимодействуют с базой данных, почтой и всем остальным, что вам нужно. Это буквально то место, куда вы помещаете всю бизнес-логику (если хотите).
Паттерн репозитория - это не последний слой абстракции, в реальности вы можете абстрагировать сколько угодно слоев, чтобы ваш код был более читабельным.
namespace App\Repositories; use App\Models\Message; use App\Events\ChatMessage; class MessageRepository { private $model; public function __construct() { $this->model = new Message(); } public function create(array $payload): bool { if ($this->checkFloodPossibility($data['message'])) { Log::alert('[User Alert] Flooding', $data) } $model = Message::create($payload); broadcast(new ChatMessage($model)); return true; } public function getUserSpecificMessagesCount(int $userId, string $message) { return DB::table('user_messages')->where([ ['user_id', '=', $userId], ['message', '=', $message], ])->count(); } }
После того, как мы создадим наше хранилище и бросим туда всю необходимую ответственность, у нас будет три способа вызвать его на нашем контроллере:
$repository = new MessageRepository();
2. Инжектирование зависимости в конструктор класса
class MessagesController { public $repository; public function __construct(MessageRepository $repository) { $this->repository = $repository; } }
3. Использование контейнеров Laravel
$repository = app(MessageRepository::class)->create();
В нашем коде мы будем использовать Dependency Injection, чтобы лучше видеть код. В частности, это тот способ, который имеет для меня больше смысла, поскольку мы должны поддерживать код как можно чище.
namespace App\Http\Controllers; use App\Http\Requests\CreateMessageRequest; use App\Repositories\MessageRepository; class MessagesController extends Controller { private $repository; public function __construct(MessageRepository $repository) { $this->repository = $repository; } public function postMessage(CreateMessageRequest $request) { $data = $request->validated(); $this->repository->create($data); return response()->json(['message' => 'message created'], 201); } }
Благодаря этому наш код стал намного более организованным и чистым для чтения. Каждая из обязанностей была распределена, а обязанности контроллера выполнялись так, как мы говорили в начале статьи: получение, обработка и ответ.
Далее >> O - Принцип Открытости-Закрытости (скоро будет)
20.08.2023 18:21
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в 2023-2024 годах? Или это полная лажа?".
20.08.2023 17:46
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
19.08.2023 18:39
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в частности, магию поплавков и гибкость flexbox.
19.08.2023 17:22
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для чтения благодаря своей простоте. Кроме того, мы всегда хотим проверить самые последние возможности в наших проектах!
18.08.2023 20:33
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий их языку и культуре.
14.08.2023 14:49
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип предназначен для представления неделимого значения.