Jak działają aplikacje?
Czytasz właśnie fundamentalny rozdział dokumentacji Nette. Poznasz kompletne zasady działania aplikacji webowych, od A do Z, od chwili narodzin żądania aż po zakończenie skryptu PHP. Po lekturze będziesz wiedzieć:
- jak to wszystko działa
- czym są Bootstrap, Presenter i kontener DI
- jak wygląda struktura katalogów
Struktura katalogów
Otwórz przykładowy szkielet aplikacji webowej o nazwie WebProject. Czytając, możesz zaglądać do omawianych plików.
Struktura katalogów wygląda mniej więcej tak:
web-project/ ├── app/ ← katalog aplikacji │ ├── Core/ ← klasy rdzenia niezbędne do działania │ │ └── RouterFactory.php ← konfiguracja adresów URL │ ├── Presentation/ ← presentery, szablony i spółka │ │ ├── @layout.latte ← szablon layoutu │ │ └── Home/ ← katalog presentera Home │ │ ├── HomePresenter.php ← klasa presentera Home │ │ └── default.latte ← szablon akcji default │ └── Bootstrap.php ← klasa startowa Bootstrap ├── assets/ ← zasoby (SCSS, TypeScript, obrazy źródłowe) ├── bin/ ← skrypty uruchamiane z wiersza poleceń ├── config/ ← pliki konfiguracyjne │ ├── common.neon │ └── services.neon ├── log/ ← zalogowane błędy ├── temp/ ← pliki tymczasowe, cache, … ├── vendor/ ← biblioteki zainstalowane przez Composera │ ├── ... │ └── autoload.php ← autoloading wszystkich zainstalowanych pakietów ├── www/ ← katalog publiczny, document root projektu │ ├── assets/ ← skompilowane pliki statyczne (CSS, JS, obrazy, ...) │ ├── .htaccess ← reguły mod_rewrite │ └── index.php ← plik początkowy uruchamiający aplikację └── .htaccess ← zabrania dostępu do wszystkich katalogów poza www
Strukturę katalogów możesz dowolnie zmieniać, zmieniać nazwy folderów albo je przenosić; jest całkowicie elastyczna. Nette dysponuje też sprytną autodetekcją i samo rozpoznaje położenie aplikacji wraz z jej bazowym URL.
W nieco większych aplikacjach możemy uporządkować foldery presenterów i szablonów w podkatalogi, a klasy pogrupować w przestrzenie nazw, które nazywamy modułami.
Katalog www/ reprezentuje katalog publiczny, czyli document-root projektu. Możesz zmienić jego nazwę bez
konieczności konfigurowania czegokolwiek po stronie aplikacji. Wystarczy skonfigurować hosting tak, aby
document-root wskazywał na ten katalog.
WebProject możesz też pobrać bezpośrednio, wraz z Nette, za pomocą Composera:
composer create-project nette/web-project
Na Linuksie albo macOS ustaw katalogom log/ i temp/ prawa do zapisu.
Aplikacja WebProject jest gotowa do uruchomienia; nie trzeba konfigurować absolutnie niczego i możesz obejrzeć ją od razu
w przeglądarce, wchodząc do folderu www/.
Żądanie HTTP
Wszystko zaczyna się, gdy użytkownik otworzy w przeglądarce stronę. Przeglądarka wysyła do serwera żądanie HTTP.
Żądanie to trafia do jednego pliku PHP leżącego w katalogu publicznym www/, czyli do index.php.
Załóżmy, że żądanie dotyczy adresu https://example.com/product/123. Dzięki odpowiedniej konfiguracji serwera
również ten URL mapowany jest na plik index.php, który następnie się wykonuje.
Jego zadaniem jest:
- zainicjować środowisko
- pozyskać fabrykę
- uruchomić aplikację Nette, która obsłuży żądanie
Jaką fabrykę? Przecież nie produkujemy traktorów, tylko robimy strony! Zaraz, zaraz, zaraz to wyjaśnimy.
Przez “inicjalizację środowiska” rozumiemy na przykład aktywowanie Tracy, czyli znakomitego narzędzia do logowania i wizualizowania błędów. Na serwerze produkcyjnym błędy loguje, a w środowisku deweloperskim wyświetla je bezpośrednio. Inicjalizacja obejmuje więc również ustalenie, czy witryna działa w trybie produkcyjnym, czy deweloperskim. Nette używa do tego sprytnej autodetekcji: jeśli uruchomisz witrynę na localhoście, działa ona w trybie deweloperskim. Nie musisz niczego konfigurować, a aplikacja jest od razu gotowa zarówno do tworzenia, jak i do wdrożenia na żywo. Te kroki wykonywane są i szczegółowo opisane w rozdziale o klasie Bootstrap.
Punkt trzeci (tak, drugi pominęliśmy, ale do niego wrócimy) to uruchomienie aplikacji. Za obsługę żądań HTTP w Nette
odpowiada klasa Nette\Application\Application (dalej Application). Gdy więc mówimy “uruchom
aplikację”, mamy konkretnie na myśli wywołanie na obiekcie tej klasy trafnie nazwanej metody run().
Nette jest mentorem, który prowadzi Cię do pisania czystych aplikacji według sprawdzonych metodologii. Jedną z najbardziej
ugruntowanych jest wstrzykiwanie zależności, w skrócie DI. Nie chcemy Cię teraz obciążać wyjaśnianiem DI, jest od
tego osobny rozdział. Istotną konsekwencją jest to,
że kluczowe obiekty tworzy zwykle fabryka obiektów zwana kontenerem DI (albo DIC). Tak, to właśnie ta fabryka,
o której była mowa wcześniej. Produkuje ona również obiekt Application, dlatego najpierw potrzebujemy
kontenera. Pozyskujemy go za pomocą klasy Configurator, każemy mu utworzyć obiekt Application,
wywołujemy na nim metodę run() i tym samym aplikacja Nette rusza. Dokładnie to dzieje się w pliku index.php.
Nette Application
Klasa Application ma jedno zadanie: odpowiedzieć na żądanie HTTP.
Aplikacje pisane w Nette dzielą się na wiele tak zwanych presenterów (w innych frameworkach możesz spotkać się z terminem “kontroler”, który w istocie oznacza to samo). To klasy, z których każda reprezentuje konkretną stronę witryny: np. stronę główną, produkt w sklepie internetowym, formularz logowania, feed z mapą strony itd. Aplikacja może mieć od jednego do tysięcy presenterów.
Application zaczyna od zapytania tak zwanego routera, który presenter ma obsłużyć bieżące żądanie. Router
rozstrzyga o odpowiedzialności. Analizuje wejściowy URL https://example.com/product/123 i na podstawie swojej
konfiguracji decyduje, że to zadanie należy na przykład do presentera Product, który ma wykonać
akcję show dla produktu o id: 123. Dobrą praktyką jest zapisywanie pary presenter + akcja
oddzielonych dwukropkiem, czyli Product:show.
Router zamienił więc URL na parę Presenter:akcja + parametry, w naszym przypadku Product:show +
id: 123. Jak taki router wygląda, zobaczysz w pliku app/Core/RouterFactory.php, a szczegółowo
opisujemy go w rozdziale Routing.
Idźmy dalej. Application zna już nazwę presentera i może działać dalej. Robi to, tworząc instancję klasy
ProductPresenter, która zawiera kod presentera Product. Ściślej mówiąc, prosi o utworzenie
presentera kontener DI, bo tworzenie obiektów to jego odpowiedzialność.
Presenter może wyglądać tak:
class ProductPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ProductRepository $repository,
) {
}
public function renderShow(int $id): void
{
// pobieramy dane z modelu i przekazujemy do szablonu
$this->template->product = $this->repository->getProduct($id);
}
}
Presenter przejmuje obsługę żądania. Zadanie jest jasne: wykonać akcję show z id: 123. W
terminologii presenterów oznacza to, że wywoływana jest metoda renderShow(), otrzymująca 123 w
parametrze $id.
Presenter może obsługiwać wiele akcji, czyli mieć wiele metod render<Akcja>(). Zalecamy jednak
projektować presentery z jedną albo możliwie niewieloma akcjami.
Wywołana została więc metoda renderShow(123). Jej kod to fikcyjny przykład, ale pokazuje, jak dane
przekazywane są do szablonu, konkretnie przez zapis do $this->template.
Następnie presenter zwraca odpowiedź. Może nią być strona HTML, obraz, dokument XML, wysłanie pliku z dysku, JSON albo
choćby przekierowanie na inną stronę. Co ważne, jeśli nie wskażemy jawnie, jak odpowiedzieć (a tak jest w przypadku
ProductPresenter), odpowiedzią będzie wyrenderowanie szablonu do strony HTML. Dlaczego? Bo w 99% przypadków chcemy
wyrenderować szablon. Presenter przyjmuje więc to zachowanie jako domyślne, aby ułatwić nam pracę. Na tym polega
istota Nette.
Nie musimy nawet wskazywać, który szablon wyrenderować; framework sam wywnioskuje ścieżkę. W przypadku akcji
show po prostu próbuje wczytać szablon show.latte leżący w tym samym katalogu co klasa
ProductPresenter. Próbuje też odnaleźć layout w pliku @layout.latte (więcej szczegółów w wyszukiwaniu szablonów).
Następnie szablony są renderowane. To kończy zadanie presentera i całej aplikacji. Jeśli szablon nie istnieje, zwracana jest strona błędu 404. Więcej o presenterach dowiesz się na stronie Presentery.
Dla pewności podsumujmy cały proces na nieco innym URL:
- URL to
https://example.com - aplikacja startuje, tworzony jest kontener DI i wykonywane
Application::run() - router dekoduje URL na parę
Home:default - tworzona jest instancja klasy
HomePresenter - wywoływana jest metoda
renderDefault()(jeśli istnieje) - renderowany jest szablon, np.
default.latte, wraz z layoutem, np.@layout.latte
Mogłeś właśnie spotkać się z wieloma nowymi pojęciami, ale wierzymy, że mają sens. Tworzenie aplikacji w Nette jest zdumiewająco proste.
Szablony
Skoro mowa o szablonach, Nette używa systemu szablonów Latte. Dlatego pliki
szablonów mają rozszerzenie .latte. Latte używane jest przede wszystkim dlatego, że to najbezpieczniejszy system
szablonów dla PHP, a zarazem najbardziej intuicyjny. Nie musisz uczyć się wiele nowego; wystarczy znajomość PHP i kilku
tagów. Wszystko, czego potrzebujesz, znajdziesz w dokumentacji.
W szablonie tworzysz odnośniki do innych presenterów i akcji tak:
<a n:href="Product:show $productId">szczegóły produktu</a>
Zamiast prawdziwego URL po prostu zapisujesz znajomą parę Presenter:akcja i dodajesz ewentualne parametry.
Sztuczka tkwi w n:href, które mówi Nette, aby ten atrybut przetworzyło. Wygeneruje wtedy:
<a href="/product/456">szczegóły produktu</a>
Generowaniem URL zajmuje się wspomniany wcześniej router. Routery w Nette są wyjątkowe, bo potrafią wykonać nie tylko
przekształcenie z URL na parę Presenter:akcja, ale też odwrotne: wygenerować URL z nazwy presentera, akcji
i parametrów. Dzięki temu w Nette możesz całkowicie zmienić postać URL w całej gotowej aplikacji bez zmieniania jednego
znaku w szablonach czy presenterach, po prostu modyfikując router. Umożliwia to również tak zwaną kanonizację, kolejną
unikalną cechę Nette, która poprawia SEO (Search Engine Optimization) przez automatyczne zapobieganie istnieniu zduplikowanej
treści pod różnymi URL. Wielu programistów uznaje tę możliwość za zdumiewającą.
Komponenty interaktywne
Musimy powiedzieć Ci o presenterach jeszcze jedno: mają wbudowany system komponentów. Osoby z większym doświadczeniem mogą przypomnieć sobie coś podobnego z Delphi albo ASP.NET Web Forms; React czy Vue.js zbudowane są na nieco pokrewnych koncepcjach. W świecie frameworków PHP jest to funkcja całkowicie unikalna.
Komponenty to niezależne jednostki wielokrotnego użytku, które osadzamy w stronach (czyli w presenterach). Mogą to być formularze, datagridy, menu, ankiety, w zasadzie wszystko, co warto wykorzystywać wielokrotnie. Możemy tworzyć własne komponenty albo skorzystać z ogromnego wyboru komponentów open source.
Komponenty zasadniczo wpływają na podejście do tworzenia aplikacji. Otwierają nowe możliwości składania stron z wcześniej przygotowanych jednostek. I mają też coś wspólnego z Hollywood.
Kontener DI i konfiguracja
Kontener DI, czyli fabryka obiektów, jest sercem całej aplikacji.
Bez obaw, nie jest to żadna magiczna czarna skrzynka, choć poprzednie wiersze mogłyby tak sugerować. W rzeczywistości to
całkiem przyziemna klasa PHP, którą generuje Nette i zapisuje w katalogu cache. Zawiera wiele metod nazwanych w rodzaju
createServiceAbcd(), z których każda potrafi utworzyć i zwrócić konkretny obiekt. Tak, jest tam również
metoda createServiceApplication__application(), produkująca instancję Nette\Application\Application,
której potrzebowaliśmy w index.php do uruchomienia aplikacji. Są też metody tworzące poszczególne
presentery itd.
Obiekty tworzone przez kontener DI z jakiegoś powodu nazywane są usługami.
Prawdziwie szczególne w tej klasie jest to, że jej nie programujesz – robi to framework. Faktycznie generuje kod PHP
i zapisuje go na dysk. Ty jedynie podajesz instrukcje, jakie obiekty kontener ma umieć tworzyć i dokładnie jak. Instrukcje te
zapisywane są w plikach
konfiguracyjnych, które używają formatu NEON i dlatego mają
rozszerzenie .neon.
Pliki konfiguracyjne służą wyłącznie do instruowania kontenera DI. Jeśli więc na przykład podasz w sekcji session opcję expiration: 14 days, kontener DI przy
tworzeniu obiektu Nette\Http\Session reprezentującego sesję wywoła jego metodę
setExpiration('14 days'), urzeczywistniając tym samym konfigurację.
Czeka na Ciebie cały rozdział opisujący, co można skonfigurować i jak zdefiniować własne usługi.
Gdy tylko zagłębisz się nieco w tworzenie usług, natkniesz się na pojęcie autowiring. To funkcja, która niesamowicie uprości Ci życie. Potrafi automatycznie przekazywać obiekty tam, gdzie ich potrzebujesz (na przykład do konstruktorów Twoich klas), bez konieczności robienia czegokolwiek. Odkryjesz, że kontener DI w Nette to mały cud.
Co dalej?
Omówiliśmy podstawowe zasady działania aplikacji Nette. Jak dotąd był to przegląd powierzchowny, ale wkrótce zagłębisz się bardziej i z czasem stworzysz wspaniałe aplikacje webowe. Gdzie dalej? Wypróbowałeś już samouczek Stwórz swoją pierwszą aplikację?
Poza tym, co opisano powyżej, Nette oferuje cały arsenał przydatnych klas, warstwę bazodanową itd. Poklikaj po dokumentacji. Albo odwiedź blog. Odkryjesz wiele ciekawych rzeczy.
Niech framework przyniesie Ci wiele radości 💙