• Czas czytania ~5 min
  • 04.12.2022

DTO lub Domain Transfer Objects mogą być używane do tak wielu rzeczy. Od wydania PHP 8 tworzenie tych fantastycznych klas w projektach nigdy nie było łatwiejsze.


Od ucieczki od podstawowej konstrukcji tablicy do dodania bezpieczeństwa typu do tego, co kiedyś było zwykłą starą tablicą. W wersji pre-PHP 8 wszystko było możliwe; wymagało to znacznie więcej kodu standardowego i nigdy nie wydawało się opłacalne.

Wraz z pojawieniem się PHP 8.2 na horyzoncie, nasze opcje otwierają się coraz bardziej w ekosystemie PHP. Przydałaby się świetna książka do czytania Przewodnik po stylach projektowania obiektów autorstwa Matthiasa Nobacka, którą polecam wszystkim programistom przeczytać przynajmniej raz.

Jednak nie nazywam tych DTO, ponieważ nie używam ich tylko w kodzie mojej domeny. Zamiast tego nazywam te Obiekty Danych, ponieważ tym właśnie są. W pozostałej części tego samouczka będę odnosić się do nich jako do obiektów danych.

Podczas tworzenia obiektów danych lubię, aby wszystkie właściwości były tylko do odczytu, ponieważ powinny być tylko odczytywane, a nie zapisywane - to pokonuje ich sens. Daje mi to niezmienną strukturę, którą mogę przejść przez moją aplikację, aby zachować kontekst i bezpieczeństwo typu - co nazywam sytuacją korzystną dla obu stron.

Spójrzmy na przykład. Zapożyczę pomysł od Bootcamp Laravela i tworzyć Chirps. Nasz chirp ma dwie rzeczy, o które musi dbać, jego przesłanie i użytkownika, który go stworzył. Tworząc obecnie aplikacje, używam identyfikatorów UUID lub ULID, w zależności od aplikacji. W tym przypadku użyję identyfikatorów ULID.

Dlatego chcemy zrefaktoryzować bazę kodu Bootcamp, aby ułatwić zarządzanie na dłuższą metę - interfejsem WWW, API, CLI itp. Dlatego szukamy przejścia od logiki wbudowanej w naszej aplikacji do współdzielonych klas. Zobaczmy, jak to wygląda.

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

Możemy to zrefaktoryzować, abyśmy wykonali walidację w żądaniu formularza i przenieśli tworzenie do czegoś innego.

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

Tutaj wracamy i zajmujemy się wszystkim za jednym zamachem – może to trochę utrudnić czytanie, więc podzielmy to na części.

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

To jest w porządku i nie ma powodu, dla którego musisz iść dalej. Jeśli jednak chcesz zrobić więcej i zacząć dodawać kontekst, możesz zacząć dodawać Obiekty Danych, które - moim zdaniem - są przyjemne w użyciu.

Jak powinien wyglądać nasz chirp? Co by nam się przydało? Spójrzmy na to, czego użyłem i porozmawiajmy o procesie decyzyjnym.

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,
        ];
    }
}

Więc, w typowy dla Steve'a sposób, to jest ostatnia lekcja. Implementuje interfejs tzw DataObjectContract, który pochodzi z jednego z pakietów Laravel, które zwykle włączam do projektu. Każda właściwość jest publiczna i dostępna poza klasą, ale są one również tylko do odczytu, więc mój kontekst nie może się zmienić, gdy tylko obiekt zostanie utworzony. Mam wtedy metodę o nazwie doTablicy, co jest wymuszane przez interfejs i jest to dla mnie sposób na zaimplementowanie sposobu, w jaki ten obiekt powinien zostać wysłany do Eloquent.

Zastosowanie tego podejścia pozwala mi na użycie obiektu kontekstowego i dodanie dodatkowego zabezpieczenia typu do aplikacji. Oznacza to, że mogę spać spokojnie podczas przekazywania danych wokół mojej aplikacji. Jak teraz wygląda nasz kontroler?

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,
    );
}

Ten kod jest dla mnie idealny. Być może zechcemy umieścić nasz kod w bloku try-catch, aby wychwycić potencjalne problemy, ale nie o to teraz mi chodzi.

Jak dotąd największym problemem, jaki znalazłem, jest to, że tworzenie obiektów danych jest czasami trochę uciążliwe, zwłaszcza gdy stają się większe. Jeśli pracuję w większej aplikacji, w której obiekty danych są większe, zastosuję nieco inne podejście. W tym przykładzie nie użyłbym tego. Jednak w interesie pokazania Ci, jak możesz z niego korzystać - pokażę Ci teraz to:

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,
        );
    }
}

Stworzenie Fabryki Obiektów Danych pozwoli nam kontrolować sposób tworzenia Obiektów Danych i pozwoli nam przekształcić przychodzące żądanie w coś bliższego temu, jak chcemy pracować w naszej aplikacji. Przyjrzyjmy się, jak wyglądałaby fabryka obiektów danych.

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')),
        );
    }
}

Są to tylko proste klasy, które przyjmują tablicę żądań, aby przekształcić ją w obiekt, ale gdy ładunek żądania staje się większy, pomagają one oczyścić kod kontrolera.

Czy znalazłeś ekscytujące sposoby wykorzystania obiektów danych? Jak sobie radzisz z ich tworzeniem? Kiedyś dodawałem statyczne metody tworzenia do moich obiektów danych - ale wydawało mi się, że mieszam przeznaczenie samego obiektu danych. Daj nam znać, co myślisz na Twitterze!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297