Struktura aplikacji w Laravel 5

Witam po krótkiej przerwie w tworzeniu poradnika dla Laravela. Dzisiejszy wpis chciałbym poświęcić projektowaniu, czy zaplanowaniu tak trywialnej czynności jaką jest obsługa formularza.

Problem wydaje się banalny i przy tym został już opisany w poprzednich wpisach o Laravelu. Mimo wszystko, poprzednie wpisy miały za zadanie zajmować się czysto funkcjonalnym podejściem, dziś chciałbym skupić się jak te elementy aplikacji typu CRUD można ułożyć, by było czysto, by można było łatwo naszą aplikację rozbudować, a przede wszystkim gruntownie zautomatyzować testy. W tym odcinku, z premedytacją nie podaję całego kodu, nie próbuję tworzyć gotowej aplikacji, a wręcz celowo będę stosował więcej pseudokodu. Chcę by wpis był raczej opisem struktury prostej aplikacji crudowej, a nie jego kompletnym przykładem.

Czego potrzebujemy na starcie? Oczywiście zainstalowanego i poprawnie skonfigurowanego Laravela. Zakładam, że środowisko zostało postawione, mamy połączenie z bazą, artisan działa poprawnie, możemy podążać dalej.

Jak napisałem w wstępie najbardziej zależy mi na utworzeniu kodu, który mimo swej prostoty będzie w jakiś sposób porządkował nasz projekt. Chciałbym więc, by najczęściej wykonywane operacje można było wykonywać w jak najprostszy i czysty sposób. Przykładem takiej operacji są właśnie operacje CRUD. W nawet niezbyt mocno rozbudowanej aplikacji często tworzy się mnóstwo obiektów, będących odwzierciedleniem rekordów bazy. Logika ich tworzenia jest zazwyczaj ta sama:

  • wyświetl formularz z dodawaniem nowego obiektu
  • po wysłaniu formularza do obsługi przeprowadź walidację
  • w przypadku poprawności danych umieść obiekt w bazie danych + wyświetl komunikat typu success
  • w przypadku braku poprawności danych, powróć do formularza, wyświetl komunikat typu failure

Tak zazwyczaj wygląda funkcjonalność dodawania nowego rekordu, a możemy mówić jeszcze o pozostałych operacjach: aktualizowaniu rekordów, czy ich usuwaniu. Pomimo faktu, iż część obiektów może posiadać pewne dodatkowe funkcje (usunięcie zdjęcia z galerii będzie skutkowało nie tylko usunięciem rekordu z bazy „Galeria” ale również trzeba będzie fizycznie usunąć zdjęcie z dysku), to jednak główny element pozostanie ten sam – usunięcie rekordu. Spróbujmy dodać uniwersalne kontrolery obsługujące dodawanie, edycję i usuwanie rekordów. Oprócz tego proponuję również dodanie kontrolera Search, który realizowałby inny z podstawowych funkcji, czyli przeszukiwanie rekordów:

Zacznijmy od operacji „Create”. Czego będziemy potrzebowali aby ją wykonać?

  • modelu obiektu do zapisu (czyli danych, które chcemy zapisać)
  • obiektu, który będzie realizował walidację
  • obiektu, który pozwoli nam na zapisanie naszej encji w bazie

Pamiętamy przy tym, iż zapisywanie obiektów do bazy w kontrolerze nie jest dobrą praktyką, dlatego też samo zapisywanie rekordu będzie realizowane za pomocą specjalnie przeznaczonego do tego obiektu.

Stwórzmy więc pomocnicze obiekty:

  • tworzymy katalog Savers w katalogu app
  • tworzymy katalog Models w katalogu app
  • w katalogu Savers tworzymy plik Saver.php (czyli nasz ogólny saver)
  • modyfikujemy plik composer.json, dodając autoload dla naszej nowej klasy:
  • Uruchamiamy composera: composer dump-autoload (czym composer dump-autoload różni się od composer install lub composer update? Install powoduje ściągnięcie zależności i utworzenie composer.lock, jeśli nie został jeszcze stworzony, Update zawsze na nowo tworzy composer.lock i ściąga zależności, natomiast dump-autoload tworzy nową listę klas dostępnych w aplikacji)

Mamy już szkielet aplikacji:

  • app/savers/Saver.php
  • app/Http/Controllers/Crud/Create.php
  • app/Http/Controllers/Crud/Update.php
  • app/Http/Controllers/Crud/Delete.php
  • app/Http/Controllers/Crud/Search.php

Przyjrzyjmy się poszczególnym plikom:
app/savers/Saver.php

Nasz Saver nie jest jakoś specjalnie rozbudowany, po prostu wypełnia model danymi, a następnie go zapisuje.

Zajrzyjmy do create:
app/Http/Controllers/Crud/Create.php

W powyższym kodzie na pierwszy miejscu uwidacznia się konstruktor, dzięki któremu możemy wstrzyknąć obiekty request, response, model, saver a także adresy, do których mamy się udać w przypadku gdy dane zostaną zapisane lub nie. W publicznej metodzie action() wywołujemy nasz saver i zapisujemy dane w bazie. Poprawne zapisanie rekordu warunkuje wynik wywołanej funkcji – redirect.

W tym miejscu spróbujmy stworzyć przykładowy model, a następnie dodać kontrolery i walidatory, najpierw model – niech przykładem posłuży prosta tabela cars posiadająca 4 pola:

Sprawdźamy, czy mamy nasz plik app/cars.php, powinien tam znaleźć się nasz model, który zresztą przenosimy do App/Models i od razu modyfikujemy:

Mając model musimy teraz zadbać o następne kilka rzeczy:

  • kontroler do obsługi modelu
  • walidator do obsługi walidacji modelu
  • saver do obsługi zapisu
  • nowe routes do naszego kontrolera
  • możemy również pokusić się o widoki

Zacznijmy od walidatora, jak pisałem wcześniej, w tym celu użyjemy Form Requests:

Szybko przechodzimy do folderu app/http/Requests/Validators/ i pliku carrequest.php, po czym dodajemy nasze reguły:

W ramach walidacji nie musimy nic więcej robić, nasz kontroler otrzyma tylko i wyłącznie poprawne dane, w przypadku błędu walidacji, Laravel wróci na stronę skąd przyszło żądanie, a także w sesji umieści uzyskane błędy.

Stwórzmy teraz nasz plik CarSaver w katalogu „savers”:

W przypadku zapisywania naszego samochodu, nie potrzebujemy żadnych dodatkowych funkcjonalności (nie musimy zapisywać zdjęć, itp), nasza klasa dziedzicząca nie potrzebuje więc żadnego dodatkowego kodu.

Kolejny etap to utworzenie kontrolera:

I zawartość:

Widzimy, iż w konstruktorze przekazujemy konkretny Request – samochodu, a także saver samochodu. W ciele konstruktora wywołujemy konstruktor klasy nadrzędnej również z żadaniem typu CarRequest, Modelem samochodu, a także naszym saverem. Pamiętamy również o adresach, do których ma podążyć strona w przypadku poprawnego zapisania, a także w przypadku błędu zapisu w bazie.

Pozostaje nam już tylko dodać odpowiedni routing:

Oznacza to, iż wszystkie żądania ‚car/create’ będą obsługiwane przez stworzony przez nas kontroler. Oczywiście do pełnego działania aplikacji potrzebujemy dodatkowych podstron, ale nie powinny one mieć wpływu na ogólną logikę.

Podsumowując, schemat stworzonych plików:

  • app/savers/Saver.php
  • app/models/Car.php
  • app/savers/CarSaver.php
  • app/validators/CarValidator.php
  • app/Http/Controllers/Crud/Create.php
  • app/Http/Controllers/Crud/Update.php
  • app/Http/Controllers/Crud/Delete.php
  • app/Http/Controllers/Crud/Search.php
  • app/Http/Controllers/Car/Create.php

Dodanie nowej tabeli, ogranicza się do:
– stworzenia nowej tabeli w bazie
– stworzenia formularza
– dodania plików z Modelem, pliku Saver, pliku z Walidacją

W tym momencie niektórzy mogą zapytać, po co to wszystko, dlaczego to robimy? Najlepszą odpowiedzią jest jedno słowo – test. Tak stworzony kod jest prosty w testowaniu, użycie Dependency Injection umożliwia nam  dowolne mockowanie obiektów, a przy tym nadal możemy łatwo nasz kod rozszerzać, tworzyć indywidualne sposoby na zapisywanie określonych obiektów.

Co więcej, znacząco oddzielamy warstwę danych od warstwy kontrolera, nie są to sztywne powiązania, nasz saver jest swoistym DAO, kontrolera nie powinno obchodzić jak dane zostaną zapisane, on chce mieć tylko dostęp do interfejsu, który mu to umożliwi.

Poza tym unikamy zjawiska ciężkiego kontrolera, czyli umieszczanie absolutnie wszystkiego w kontrolerze.

 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

This site uses Akismet to reduce spam. Learn how your comment data is processed.