• Час читання ~1 хв
  • 04.12.2022

DTO, або об’єкти передачі домену, можна використовувати для багатьох. З моменту виходу PHP 8 створення цих фантастичних класів у ваших проектах ніколи не було таким простим.

 

Від відмови від базової конструкції масиву до додавання безпеки типу до того, що раніше було звичайним старим масивом. До PHP 8 все було можливо; для цього знадобилося набагато більше шаблонного коду, і він ніколи не здавався вартим.

З появою PHP 8.2 на горизонті наші можливості відкриваються все більше й більше в екосистемі PHP. Була б чудова книга для читання Посібник зі стилю об’єктного дизайну від Матіаса Нобака, який я рекомендую всім розробникам прочитати принаймні один раз.

Однак я не називаю ці DTO, оскільки використовую їх не лише в коді свого домену. Натомість я називаю ці об’єкти даних, оскільки вони так і є. У решті цього підручника я буду називати їх об’єктами даних.

Під час створення об’єктів даних я люблю робити всі властивості доступними лише для читання, оскільки їх слід лише читати, а не записувати – це перешкоджає їхній суті. Це дає мені незмінну структуру, яку я можу передати через свою програму, щоб зберегти контекст і безпеку типів, що я називаю безпрограшною ситуацією.

Давайте розглянемо приклад. Ідею запозичу у Навчальний табір Laravel і створити Chirps. У нашого chirp є дві речі, про які він повинен дбати: його повідомлення та користувач, який його створив. У ці дні під час створення додатків я використовую або UUID, або ULID, залежно від програми. У цьому я буду використовувати ULID.

Тож ми хочемо змінити кодову базу Bootcamp, щоб полегшити керування в довгостроковій перспективі – веб-інтерфейс, API, CLI тощо. Тому ми хочемо перейти від вбудованої логіки в нашій програмі до спільних класів. Давайте подивимось, як це виглядає.

$validated = $request->validate([
    'message' => 'required|string|max:255',
]);
 
$request->user()->chirps()->create($validated);
 
return redirect(route('chirps.index'));

Ми можемо змінити це таким чином, щоб виконати перевірку у запиті форми та перенести створення на щось інше.

public function __invoke(StoreRequest $request): Response
{
    return new JsonResponse(
        data: $this->command->handle(
            chirp: $request->validated(),
        ),
        status: Http::CREATED->value,
    );
}

Тут ми повертаємось і обробляємо все одним рухом - це може ускладнити читання, тому давайте розділимо це.

$chirp = $this->command->handle(
    chirp: $request->validated(),
);

Це добре, і немає причин, щоб ви йшли далі. Однак, якщо ви хочете зробити більше і почати додавати контекст, ви можете почати додавати об’єкти даних, які, на мій погляд, приємні у використанні.

Як має виглядати наше цвірінькання? Що б нам допомогло? Давайте подивимося, що я використав, і поговоримо про процес прийняття рішення.

final class ChirpObject implements DataObjectContract
{
    public function __construct(
        public readonly string $user,
        public readonly string $message,
    ) {}
 
    public function toArray(): array
    {
        return [
            'message' => $this->message,
            'user_id' => $this->user,
        ];
    }
}

Отже, у типовій манері Стіва, це останній урок. Він реалізує інтерфейс під назвою DataObjectContract, який походить з одного з пакетів Laravel, які я зазвичай використовую в проекті. Кожна властивість є загальнодоступною та доступною за межами класу, але вони також доступні лише для читання, тому мій контекст не може змінитися, щойно об’єкт буде створено. Потім у мене є метод під назвою toArray, який забезпечується інтерфейсом, і це спосіб для мене реалізувати, як цей об’єкт слід надсилати до Eloquent.

Використання цього підходу дозволяє мені використовувати контекстний об’єкт і додавати додатку додаткову безпеку типу. Це означає, що я можу бути спокійним, коли передаю дані навколо своєї програми. Як тепер виглядає наш контролер?

public function __invoke(StoreRequest $request): Response
{
    return new JsonResponse(
        data: $this->command->handle(
            chirp: new ChirpObject(
                user: strval(auth()->id()),
                message: strval($request->get('message')),
            ),
        ),
        status: Http::CREATED->value,
    );
}

Цей код, як на мене, ідеальний. Можливо, ми захочемо загорнути наш код у блок try-catch, щоб виявити можливі проблеми, але це не те, про що я зараз намагаюся пояснити.

Наразі найбільша проблема, яку я виявив, полягає в тому, що створення об’єктів даних іноді доставляє трохи болю, особливо коли вони стають більшими. Якщо я працюю у більшій програмі, де об’єкти даних більші, я використовуватиму дещо інший підхід. У цьому прикладі я б не використовував це. Однак, щоб показати вам, як ви можете ним користуватися, я покажу вам це зараз:

final class StoreController
{
    public function __construct(
        private readonly ChirpFactoryContract $factory,
        private readonly CreateNewChirpContract $command,
    ) {}
 
    public function __invoke(StoreRequest $request): Response
    {
        return new JsonResponse(
            data: $this->command->handle(
                chirp: $this->factory(
                    data: [
                        ...$request->validated(),
                        'user' => strval(auth()->id()),
                    ]
                ),
            ),
            status: Http::CREATED->value,
        );
    }
}

Створення фабрики об’єктів даних дозволить нам контролювати, як створюються об’єкти даних, і перетворити вхідний запит на щось ближче до того, як ми хочемо працювати в нашій програмі. Давайте подивимося, як буде виглядати Data Object Factory.

final class ChirpFactory implements ChirpFactoryContract
{
    public function make(array $data): DataObjectContract
    {
        return new ChirpObject(
            user: strval(data_get($data, 'user')),
            message: strval(data_get($data, 'message')),
        );
    }
}

Це лише прості класи, які приймають масив запитів, щоб перетворити його на об’єкт, але коли корисне навантаження запитів стає більшим, вони допомагають очистити код контролера.

Чи знайшли ви захоплюючі способи використання об’єктів даних? Як ви справляєтеся з їх створенням? Раніше я додавав статичні методи створення до своїх об’єктів даних, але здавалося, що я змішую мету самого об’єкта даних. Поділіться своїми думками в Twitter!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297