Любите загадки? Событие еще доступно на сайте.
Inn_100_gramm

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

Паттерн "Обработчик" (Handler) с использованием DTO и VO

  1. Изоляция бизнес-логики: Бизнес-логика изолирована в обработчиках, что позволяет сделать код более организованным и легко поддерживаемым.
  2. Тестируемость: Обработчики легко тестируются отдельно, так как они не зависят от инфраструктурного кода (например, контроллеров).
  3. Переиспользование: Обработчики могут быть легко переиспользованы в различных частях приложения.
  4. Ясность и читаемость кода: Использование DTO и VO позволяет четко определить структуру передаваемых данных, что улучшает читаемость и понимание кода.
  5. Соблюдение принципов SOLID: Обработчики помогают соблюдать принципы единственной ответственности (SRP) и разделения интерфейсов (ISP).
  6. Иммутабельность VO: Значения VO не изменяются после создания, что помогает избежать непреднамеренных изменений и улучшает предсказуемость кода.

Рассмотрим на примере API маршрута, который создает товар

  1. Маршрут (Router) Создадим маршрут для обработки POST-запросов на создание нового товара.
<?php

use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;

Route::post('products', [ProductController::class, 'store'])
            ->name('products.store');
  1. Контроллер (Controller) Контроллер принимает запрос от клиента и вызывает соответствующий обработчик.
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Domains\Product\Core\Handlers\CreateProductHandler;
use App\Http\Requests\CreateProductRequest;
use Illuminate\Http\JsonResponse;

class ProductController extends Controller
{
    public function store(CreateProductRequest $request, CreateProductHandler $handler): JsonResponse
    {
        $product = $handler->handle($request->getDto());

        return response()->json([
            'message' => 'Product created successfully',
            'data' => $product
        ], 201);
    }
}
  1. Реквест (Request) Реквест используется для валидации входящих данных и создания DTO.
<?php

declare(strict_types=1);

namespace App\Http\Requests;

use App\Domains\Product\Core\DTO\CreateProductDTO;
use Illuminate\Foundation\Http\FormRequest;

class CreateProductRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true; // Здесь можно добавить логику авторизации
    }

    public function rules(): array
    {
        return [
            'product.name'        => 'required|string|max:255',
            'product.description' => 'required|string',
            'product.price'       => 'required|integer|min:0',
            'tags'                => 'array',
            'tags.*'              => 'integer|exists:tags,id',
            'images'              => 'array',
            'images.*'            => 'string',
        ];
    }

    public function getDto(): CreateProductDTO
    {
        return CreateProductDTO::fromArray($this->validated());
    }
}
  1. DTO (Data Transfer Object) DTO используется для передачи данных запроса между слоями приложения.
<?php

declare(strict_types=1);

namespace App\Domains\Product\Core\DTO;

use App\Domains\Product\Core\ValueObjects\ProductVO;
use Illuminate\Support\Arr;

final class CreateProductDTO
{
    public function __construct(
        public ProductVO $product
        public array $tags,
        public array $images
    ) {
    }

    public static function fromArray(array $data): self
    {
        return new self(
            ProductVO::fromArray(Arr::get($data, 'product')),
            Arr::get($data, 'tags', []),
            Arr::get($data, 'images', [])
        );
    }
}
  1. VO (Value Object) VO инкапсулирует небольшое количество данных.
<?php

declare(strict_types=1);

namespace App\Domains\Product\Core\ValueObjects;

use Illuminate\Support\Arr;

final class ProductVO
{
    public function __construct(
        public string $name,
        public string $description,
        public float $price
    ) {
    }

    public static function fromArray(array $data): self
    {
        return new self(
            Arr::get($data, 'name'),
            Arr::get($data, 'description'),
            null !== Arr::get($data, 'price') ? (float) Arr::get($data, 'price') : 0
        );
    }
}
  1. Обработчик (Handler) Обработчик содержит логику создания товара.
<?php

declare(strict_types=1);

namespace App\Domains\Product\Core\Handlers;

use App\Domains\Product\Core\DTO\CreateProductDTO;
use App\Models\Product;
use Illuminate\Support\Facades\DB;

final class CreateProductHandler
{
    public function handle(CreateProductDTO $dto): Product
    {
        return DB::transaction(function () use ($dto) {
            // Создание товара
            $product = new Product();
            $product->name = $dto->product->name;
            $product->description = $dto->product->description;
            $product->price = $dto->product->price;
            $product->save();

            // Привязка тегов
            $product->tags()->attach($dto->tags);

            // Сохранение изображений
            foreach ($dto->images as $image) {
                $product->images()->create(['path' => $image]);
            }

            return $product;
        });
    }
}

Пошаговое объяснение

  1. Маршрут (Router): В файле маршрутов Laravel определяем маршрут для POST-запросов на создание нового товара. Этот маршрут связывается с методом store контроллера ProductController.
  2. Контроллер (Controller): Контроллер ProductController принимает HTTP-запрос и вызывает метод getDto() реквеста CreateProductRequest для получения DTO. Затем он передает DTO обработчику CreateProductHandler для создания товара и возвращает JSON-ответ с данными о созданном товаре.
  3. Реквест (Request): CreateProductRequest используется для валидации входящих данных. Метод getDto() создает DTO из валидированных данных и возвращает его.
  4. DTO (Data Transfer Object): CreateProductDTO используется для передачи данных запроса. Он инкапсулирует объект ProductVO, массив тегов и массив изображений.
  5. VO (Value Object): ProductVO инкапсулирует данные о товаре (название, описание и цену). Он используется для структурирования данных внутри DTO.
  6. Обработчик (Handler): CreateProductHandler содержит логику создания товара. Он принимает DTO и создает товар, привязывает теги и сохраняет изображения в транзакции.

Для успешного использования паттерна “Обработчик” с DTO и VO программист должен иметь уровень подготовки Middle (средний).

1