• Czas czytania ~5 min
  • 14.03.2023

Odpowiadanie z aplikacji Laravel jest tym, co nazwałbym niezbędnym, szczególnie gdy tworzysz API. Rzućmy okiem na to, jak możemy wzmocnić nasze reakcje.

Wielu z nas zazwyczaj zaczyna od korzystania z funkcji pomocniczych w naszych aplikacjach, ponieważ dokumenty i wiele samouczków będzie z nich korzystać. Są łatwe do rozpoczęcia i robią dokładnie to, czego od nich oczekujesz. Rzućmy okiem na to, jak one wyglądają:

return response()->json(
    data: [],
    status: 200,
);

To nieco przesadzony przykład; zwykle wysyłasz dane i pomijasz kod stanu. Jednak dla mnie nawyki umierają ciężko!

Ten kod utworzy dla Ciebie nowy JsonResponse i przekaże dane i kod stanu gotowe do powrotu. To działa i nie ma nic złego w stosowaniu tego podejścia. Jeśli już tego używasz, sposobem na ulepszenie gry API jest dodanie kodu statusu, aby był bardziej deklaratywny w tym, co zwracasz.

Idąc dalej, możemy pominąć używanie funkcji pomocniczych i zacząć używać klasy bazowej, którą tworzą funkcje pomocnicze:

return new JsonResponse(
    data: [],
    status: 200,
);

Podoba mi się to podejście, ponieważ opiera się mniej na pomocnikach i jest bardziej deklaratywne. Patrząc na kod, wiesz dokładnie, co jest zwracane, ponieważ znajduje się tuż przed tobą, a nie za pomocnikiem. Możesz to wyrównać, używając stałej lub innego sposobu deklarowania samego kodu stanu - dzięki czemu jest to jeszcze bardziej przystępne do przeczytania i zrozumienia dla programistów, którzy mogą nie znać wszystkich kodów stanu na pamięć. Zobaczmy, jak to może wyglądać:

return new JsonResponse(
    data: [],
    status: JsonResponse::HTTP_OK,
);

Klasa JsonResponse rozszerza klasę Symfony Response o kilka warstw abstrakcji, dzięki czemu można to nazwać bezpośrednio - jednak twój analizator statyczny może narzekać na to. Zbudowałem pakiet o nazwie juststeveking/http-status-code, PHP Enum, który zwróci coś podobnego, a jego jedynym zadaniem jest zwracanie kodów stanu. Wolę to lżejsze podejście do takich rzeczy, ponieważ wiesz dokładnie, co się dzieje i co ta klasa lub pakiet może zrobić. Problem czasami polega na tym, że klasa, której używasz, robi tak wiele, że musisz załadować tę ogromną rzecz do pamięci, aby móc zwrócić wartość całkowitą. Nie ma to większego sensu, więc zalecam użycie dedykowanego pakietu lub klasy, aby samemu sobie z tym poradzić. Zobaczmy, jak to wygląda, gdy to zrobisz: Jest to znaczący krok naprzód pod względem jasności tego,

return new JsonResponse(
    data: [],
    status: Http::OK->value,
);

jak deklaratywny jest nasz kod. Łatwo jest odczytać i dokładnie zrozumieć, co się dzieje. Jednak za każdym razem tworzymy ten sam blok kodu, więc jak możemy to rozwiązać?

Odpowiedź jest dość prosta - klasy odpowiedzi. W Laravel istnieje kontrakt, którego możemy użyć o nazwie Responsable, który mówi nam, że nasza klasa musi mieć toResponse na sobie metodę. Możemy zwrócić to bezpośrednio od naszych kontrolerów, ponieważ Laravel przeanalizuje i zrozumie te klasy bez żadnych problemów. Spójrzmy na szybki podstawowy przykład tego, jak wyglądają te klasy:

class MyJsonResponse implements Responsable
{
    public function __construct(
        public readonly array $data,
        public readonly Http $status = Http::OK,
   ) {}
    public function toResponse($request): Response
    {
        return new JsonResponse(
            data: $this->data,
            status: $this->status->value,
        );
    }
}

To jest coś prostego w użyciu. Nie dodaje to jednak żadnej wartości do naszej aplikacji. To tylko abstrakcja wokół tego, co już jest. Spójrzmy na coś, co może dodać więcej wartości do naszej aplikacji.

class CollectionResponse implements Responsable
{
    public function __construct(
        public readonly JsonResourceCollection $data,
        public readonly Http $status = Http::OK,
    ) {}
    public function toResponse($request): Response
    {
        return new JsonResponse(
            data: $this->data,
            status: $this->status->value,
        );
    }
}

Teraz mamy klasę odpowiedzi, która obsłuży wszystkie kolekcje zasobów, przez które przechodzimy, dzięki czemu będzie można ją bardzo ponownie wykorzystać w naszej aplikacji. Spójrzmy, jak możemy zwrócić to w naszym kontrolerze:

return new CollectionResponse(
    data: UserResource::collection(
        resource: User::query()->get(),
    ),
);

jest czystszy, ma mniej duplikacji kodu i łatwo jest zastąpić domyślny status, jeśli zajdzie taka potrzeba. Daje nam to korzyści, które dały nam metody pomocnicze i klasa Json Response - ale pozwala nam na większy kontekst i przewidywalność.

Jednak obecnie mamy do czynienia z problemem duplikacji kodu w innych obszarach. W ramach samych naszych klas odpowiedzi. Wiele z nich wygląda podobnie, jedyną różnicą jest to, że właściwości konstruktora będą różnych typów. Chcemy zachować kontekst używania niestandardowych klas odpowiedzi, ale chcemy uniknąć tworzenia czegoś z ogromnym argumentem typu unii dla właściwości - kiedy równie dobrze możemy dodać mieszane i skończyć z tym.

W tej sytuacji możesz albo sięgnąć po klasę abstrakcyjną do rozszerzenia, albo cechę, aby dodać zachowanie do klas, które tego potrzebują. Osobiście jestem fanem kompozycji nad dziedziczeniem, więc użycie cechy ma dla mnie większy sens.

trait SendsResponse
{
    public function toResponse($request): Response
    {
        return new JsonResponse(
            data: $this->data,
            status: $this->status->value,
        );
    }
}

Największym problemem związanym z tym podejściem jest to, że analiza statyczna będzie narzekać na ten kod, ponieważ cecha musi mieć lub wiedzieć o właściwościach klasy. Jest to jednak łatwe rozwiązanie.

/**
 * @property-read mixed $data
 * @property-read Http $status
 */

Możemy dodać ten blok doc do cechy, aby był świadomy właściwości, do których ma dostęp.

Teraz nasze klasy odpowiedzi będą o wiele prostsze w użyciu i kompilowaniu, z mniejszą liczbą powtórzeń w naszym kodzie.

class MessageResponse implements Responsable
{
    use SendsResponse;

    public function __construct(
        public readonly array $data,
        public readonly Http $status = Http::OK,
    ) {}
}

Teraz możemy zbudować wszystkie potencjalne odpowiedzi, które musimy łatwo wysłać, utrzymując bezpieczeństwo typu i duplikację kodu.

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