Laravel – jak nie zepsuć architektury?

Laravel – jak nie zepsuć architektury?

Ahoj PHPiraci!

Dzisiejszy wpis chciałbym poświęcić tematowi jakim jest wytwarzanie aplikacji z użyciem frameworka Laravel autorstwa Tylera Otwella i błędów w projektowaniu. Mógłbym wszystko zamknąć w krótkim stwierdzeniu: „aby nie spieprzyć aplikacji napisanej z użyciem Laravela – nie używaj go i weź Symfony”. Otóż nie! Ten framework, jak i każdy inny ma swoje plusy i minusy, można go stosować do wielu różnych projektów – nawet tych większych (nie użyłem najwyższego poziomu stopniowania więc bez bicia proszę).

Niby wszystko zostało już powiedziane, ale jednak warto wtrącić swoje trzy grosze na temat odpowiedniego rozkładania odpowiedzialności w aplikacji i tego w jaki sposób można ominąć największe zagrożenia płynące z braku odpowiedniej wiedzy.

Na początek – czym tak właściwie jest MVC?

MVC to skrót od Model-View-Controller, jest to wzorzec architektoniczny aplikacji, który skupia się na jej trzech głównych elementach. Mowa tutaj o modelu, który prezentuje naszą encję wypełnioną danymi; warstwa widoku (view) prezentująca wynik działania aplikacji i… pozostaje nam kontroler. Odbiera on dane od użytkownika, przekazuje je w odpowiednie miejsca w celu przetworzenia i wydaje na świat w formie widoków, przekierowań czy innych tego typu zachowań systemu.

Jak się to ma do Laravela? A no tak, że ten framework już na starcie jest oparty o właśnie tę architekturę. Jest ona bardzo prosta do zrozumienia. Uważam, że nawet początkujący developerzy są w stanie zrozumieć podstawowe rozłożenie odpowiedzialności po tych częściach aplikacji. Problem polega na tym, że znam wielu developerów z większym jak nie nawet dużym doświadczeniem zawodowym, którzy robią to źle. Nie oznacza to oczywiście, że są złymi programistami – po prostu brakuje im odpowiedniej wiedzy z tego zakresu pracy.

Logo Laravel

Laravel w ujęciu MVC.

Jak wszyscy dobrze wiedzą – tematyczny framework mocno rozszerza funkcjonalności sugerowane do implementowania w architekturze opartej na widoku, kontrolerze i modelu. Także krótko przedstawię co powinno pozostać w konkretnych miejscach aplikacji.

Model

To trudny temat, ale z pewnością sobie poradzimy. Po pierwsze – model nie przeprowadza skomplikowanych operacji na danych, model je jedynie dostarcza do dalszej części systemu i odpowiada za ich reprezentację w logice aplikacji. Jakiekolwiek odwołania do niego poza repozytorium czy specyficznymi przypadkami powinny być karane biciem po łapach. Skoro mamy już Laravela to nie porzucajmy też całościowo Eloquentowych modeli – można je w bardzo fajny sposób wykorzystać. Umieść w nich swoje gettery, settery, relacje i scope-y. Tak – uważam, że to odpowiednie dla nich miejsce. Będą dotyczyły danych naszej encji, więc możemy działać sobie na nich w łatwy sposób, na przykład za pomocą repozytoriów.

View

Widok, widok, widok. Nie, nie ma dostępu do modeli, ogarnij się i nawet niech Ci nie przejdzie przez myśl taki głupi pomysł. Widoki we frameworku jakim jest Laravel (i każdym innym), to nasza warstwa prezentacyjna danych wcześniej przetworzonych. Nie ma tutaj miejsca na przetwarzanie danych wyplutych przez kontroler. Dostarczamy tutaj już przygotowane wcześniej informacje, które chcemy zaprezentować użytkownikowi wraz z odpowiednią wartością logiczną w postaci ukrywania, iterowania po elementach. Sam z siebie Laravel wspiera system jakim jest Blade – skorzystaj z niego. Nie wykonuj najlepiej w ogóle czystego PHP w widokach, to nie miejsce na takie cyrki. Blade dostarcza wiele różnych funkcjonalności, z których można w fajny sposób korzystać. Ba – dodatkowo możesz napisać swoje dyrektywy, które spełnią Twoje wymagania. Polecam przygotować sobie coś do budowania pól formularzy, będzie znacznie łatwiej.

Controller

Cytując Macieja Aniserowicza z blogu devstyle „kontroler jest jak wyrostek” muszę się po prostu zgodzić. Niestety, sam nie znalazłem pełnego zamiennika warstwy kontrolera jaką dostarcza nam MVC. Podobno ciekawą alternatywą jest architektura oparta o ADR (Action-Domain-Responder) aczkolwiek nie miałem jeszcze okazji napisania żadnego projektu za pomocą tej techniki – wydzielamy tam każdą akcję kontrolera jako osobną klasę i przygotowujemy specyficzne respondery i pozostawiamy część domenową w przeznaczonym dla niej miejscu. MVC i pojęcie kontrolera to przede wszystkim spełnienie funkcji odbioru danych i wymuszenia wyświetlenia odpowiedniej warstwy prezentacyjnej. Zarówno zwrotka JSON-owa, widok, pobranie pliku to krańce tego, co powinien zwrócić kontroler.

Niestety, największy minus kontrolera zamiast wydzielonej pojedynczej akcji to to, że łamie on podstawową zasadę SOLID – czyli Single Responsibility Principle. Dlaczego? Ponieważ każda metoda kontrolera spełnia inną funkcję w systemie. Metoda index() przykładowo wyświetla listę użytkowników, a zaś metoda create() przygotowuje i wyświetla formularz dodawania nowego użytkownika. Mamy już dwie odpowiedzialności i zasada jest w bardzo prosty sposób złamana, smutek.

Najlepszym sposobem wydzielenia rzeczy, którymi kontroler nie powinien się zajmować jest utworzenie wielu pomocniczych warstw aplikacji. Po pierwsze – repozytoria. Odpuśćmy sobie pracę na zwykłych modelach, to nie ma większego sensu, później może być problem z podmianą drivera danych czy też nową implementacją. Jeżeli oprzemy naszą warstwę modelu o repozytoria, które będą implementacją ich interfejsów – z łatwością będziemy mieli możliwość łatwego zarządzania danymi. Jeżeli chodzi o filtrowanie i walidację – wynieśmy ją do osobnej klasy. Idealnym miejscem, które dostarcza Laravel jest klasa Requestu. Tak czy siak musimy ją wstrzykiwać do metod odpowiedzialnych za konkretne akcje. Czemu by tam nie zawrzeć tych elementów aplikacji? Dodatkowo warto zaprząc Laravelowe Joby do wykonywania konkretnych operacji na danych i inpucie zebranym od użytkownika aplikacji. Co ciekawe – nasz framework w kosmicznie prosty sposób umożliwia nam przekazanie takich zadań do wykonania przez kolejki – wystarczy jedynie je „dispatchować”. Tutaj więcej informacji.

Zajmijmy się wyrostkiem!

Przychodzi pora na pocięcie tego, co robił kontroler dotychczas i stworzenie poprawnie wyglądającej i działającej klasy. Oddzielimy tutaj walidację, filtrowanie danych a także wyniesiemy komendy do innego miejsca.

Spójrzcie na nasz skopany kontroler zawierający zwykłe, proste akcje dostępne na każdym blogu. Załóżmy, że jest to kontroler o nazwie PostController.

Dobrze, także możemy zabierać się za refactoring i poprawienie tego, aby ten trywialny kontroler wyglądał jak należy. Aktualnie wiele osób pewnie nie zauważy potrzeby tworzenia bardziej skomplikowanej architektury, aczkolwiek pamiętajcie, że ten przykład jest niesamowicie prosty.

Po pierwsze tworzymy repozytorium postów.

Na samym początku musimy przygotować repozytorium odpowiedzialne za pracę nad postami na blogu. Przygotowujemy sobie klasę w odpowiedniej dla PSR-4 strukturze App\Repositories\PostRepository oraz właściwy dla niej interfejs App\Repositories\Contracts\PostRepositoryInterface. To bardzo fajna baza do rozpoczęcia prac porządkujących nasz nieskomplikowany kontroler. Co ważne – repozytorium należy zbindować, aby aplikacja sama wybierała odpowiednią implementację na nasze potrzeby. Później, gdybyśmy nagle chcieli przejść na inny typ bazy, czy stworzyć całkowicie inne implementacje – wystarczy podmienić bindowanie na nową klasę i voile la!

Na te potrzeby najlepiej utworzyć nowy Service Provider, który będzie zajmował się jedynie bindowaniem naszych klas repozytoriów do ich interfejsów, a później dołączyć go w naszym pliku config/app.php.

W naszym repozytorium będziemy potrzebować trzy metody odpowiedzialne za wyciąganie aktywnych postów, tworzenie nowych, oraz usuwanie istniejących. Na taką potrzebę należy przygotować odpowiedni interfejs i klasę właściwą.

Wydzielamy walidację i filtrowanie.

Nadeszła pora na wydzielenie filtrowania oraz walidacji do pobocznej klasy, która będzie rozszerzała Illuminate\Foundation\Http\FormRequest. To w niej umieścimy naszą walidację i filtrowanie poprawnych danych, które będzie można wyciągnąć za pomocą odpowiednich metod. Od tego momentu wszystko, co będzie dostarczane do kontrolera – będzie już odpowiednio przygotowane i niczym dane w ValueObject – będzie poprawne. Laravel daje naprawdę spore możliwości jeżeli mówimy o takim pre-przetwarzaniu danych.

Tworzymy komendę zadań (Joba).

Laravel dostarcza developerowi funkcjonalność nazywaną jobami, które można bardzo łatwo delegować do kolejek, opóźniać czy odpowiednio przygotowywać do inicjalizacji. Są takimi komendami, które wykonują się w odpowiednich dla aplikacji momentach. Można po nich odpalać różnego rodzaju eventy. Przykładowo po wykonaniu zadania Create na blogowym wpisie – możemy powiadomić aplikację, że zadanie się zakończyło i umożliwić zapis odpowiednio logu, czy chociażby notyfikacji dla autora. Przykładowa komenda tworzenia wpisu wygląda jak poniżej. Warto zauważyć, że do metody handle() wstrzykiwany dynamicznie jest interfejs odpowiedniego repozytorium dzięki naszemu bindowaniu.

Do tego wszystkiego dodamy komendę, która zajmie się tylko i wyłącznie usuwaniem użytkownika. Takie wydzielenie nawet najmniejszych komend da nam ogromne możliwości rozbudowy aplikacji, oraz wykonywania konkretnych jobów gdziekolwiek tylko byśmy chcieli. Wiele kontrolerów może wykonywać jednego joba, przez co nie ma potrzeby powielania kodu.

Zapewne padnie pytanie: dlaczego utworzyłem metodę fromRequest(args): self. Przygotowuje ją za każdym razem, gdy wiem, że jest możliwość, że wpadnie większa ilość danych do joba, a dodatkowo mam możliwość kontroli tego co i w jaki sposób zostanie ustawione. Jest oczywiście możliwość ręcznego przygotowania konstruktora, aczkolwiek jednak sugerowałbym korzystanie ze statycznej metody, do której po prostu przekażemy odpowiednia klasa requestu. Dzięki temu w dalszym ciągu wszystkie dane będą odpowiednio zwalidowane i przefiltrowane.

Kontroler oczyszczony.

Także mamy już uporządkowany i wyczyszczony kontroler. Co prawda w tak trywialnym przypadku ciężko jest pokazać jakie możliwości daje Laravel, aczkolwiek z odrobiną wyobraźni będziecie w stanie wyobrazić sobie jak ogromne możliwości da nam taki podział aplikacji. Poniżej prezentuje jak wygląda taki kontroler, którego obowiązki zostały oddelegowane w inne miejsca. Teraz jest on właściwie tylko klientem, odbiera dane (już poprawne), wykonuje odpowiednie komendy i zwraca wynik.

Deser?

Zaprezentowałem oczywiście jedną z wielu możliwości tworzenia aplikacji w Laravelu. Warto poznać także tematy takie jak Event Observers czy poszerzyć wiedzę z zakresu Service Containera, który daje potężne możliwości. Wystarczy jedynie poczytać, przemyśleć i zacząć działać. Laravel to świetne narzędzie do szybkiego wytwarzania aplikacji, oczywiście są momenty, gdy kończy się jego przydatność i warto pomyśleć nad Symfony, ale … zresztą najlepiej wybrać samemu! 🙂

  • Świetny artykuł! Na pewno wykorzystam te techniki w swoim projecie 🙂

  • Bartłomiej Płaza

    Świetny temat. Po przebrnięciu przez podstawową wiedzę z Laravela poszerza to spojrzenie, tego szukałem 😉

Comments are closed.