• Время чтения ~1 мин
  • 04.12.2022

DTO, или объекты переноса домена, можно использовать для очень многих целей. С момента выпуска PHP 8 создание этих фантастических классов в ваших проектах стало еще проще.


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

С приближением PHP 8.2 наши возможности в экосистеме PHP открываются все больше и больше. Отличная книга для чтения была бы Руководство по стилю объектного дизайна от Matthias Noback, который я рекомендую всем разработчикам прочитать хотя бы раз.

Однако я не называю эти DTO, поскольку я не просто использую их в коде своего домена. Вместо этого я называю эти Объекты Данных, поскольку именно они и есть. В оставшейся части этого руководства я буду называть их объектами данных.

При создании объектов данных мне нравится делать все свойства доступными только для чтения, поскольку их следует только читать, а не записывать — это лишает их смысла. Это дает мне неизменяемую структуру, которую я могу передать через свое приложение, чтобы сохранить контекст и безопасность типов, что я называю беспроигрышной ситуацией.

Давайте посмотрим на пример. Я позаимствую идею из Учебный лагерь Laravel и создайте чириканье. У нашего чирика есть две вещи, о которых ему нужно заботиться: его сообщение и пользователь, который его создал. В наши дни при создании приложений я использую либо 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,
        ];
    }
}

Итак, в типичной манере Стива, это последний урок. Он реализует интерфейс, называемый Контракт Объекта Данных, который поступает из одного из пакетов 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,
        );
    }
}

Создание фабрики объектов данных позволит нам контролировать, как создаются объекты данных, и позволит нам преобразовать входящий запрос во что-то более близкое к тому, как мы хотим работать в нашем приложении. Давайте посмотрим, как будет выглядеть фабрика объектов данных.

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