Jak działają aplikacje?
Właśnie czytasz podstawowy dokument dokumentacji Nette. Poznasz całą zasadę działania aplikacji internetowych. Krok po kroku, od A do Z, od momentu powstania aż do ostatniego tchnienia skryptu PHP. Po przeczytaniu będziesz wiedzieć:
- jak to wszystko działa
- co to jest Bootstrap, Presenter i kontener DI
- jak wygląda struktura katalogów
Struktura katalogów
Otwórz przykład szkieletu aplikacji internetowej o nazwie WebProject i podczas czytania możesz patrzeć na pliki, o których jest mowa.
Struktura katalogów wygląda mniej więcej tak:
web-project/ ├── app/ ← katalog z aplikacją │ ├── Core/ ← podstawowe klasy niezbędne do działania │ │ └── RouterFactory.php ← konfiguracja adresów URL │ ├── Presentation/ ← presentery, szablony & 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 ├── bin/ ← skrypty uruchamiane z wiersza poleceń ├── config/ ← pliki konfiguracyjne │ ├── common.neon │ └── services.neon ├── log/ ← logowane błędy ├── temp/ ← pliki tymczasowe, cache, … ├── vendor/ ← biblioteki zainstalowane przez Composer │ ├── ... │ └── autoload.php ← autoloading wszystkich zainstalowanych pakietów ├── www/ ← katalog publiczny czyli document-root projektu │ ├── .htaccess ← reguły mod_rewrite │ └── index.php ← pierwszy plik, którym uruchamia się aplikacja └── .htaccess ← zabrania dostępu do wszystkich katalogów oprócz www
Strukturę katalogów można dowolnie zmieniać, foldery przemianować lub przenieść, jest całkowicie elastyczna. Nette dodatkowo dysponuje inteligentną autodetekcją i automatycznie rozpozna lokalizację aplikacji, w tym jej bazę URL.
W nieco większych aplikacjach możemy foldery z presenterami i szablonami podzielić na podkatalogi i klasy na przestrzenie nazw, które nazywamy modułami.
Katalog www/
reprezentuje tzw. katalog publiczny czyli document-root projektu. Można go przemianować bez
konieczności ustawiania czegokolwiek dodatkowego po stronie aplikacji. Trzeba tylko skonfigurować hosting tak, aby
document-root wskazywał na ten katalog.
WebProject można również od razu pobrać wraz z Nette za pomocą Composera:
composer create-project nette/web-project
Na Linuksie lub macOS ustaw katalogom log/
i temp/
prawa do zapisu.
Aplikacja WebProject jest gotowa do uruchomienia, nie trzeba niczego konfigurować i można ją od razu wyświetlić w
przeglądarce, uzyskując dostęp do folderu www/
.
Żądanie HTTP
Wszystko zaczyna się w chwili, gdy użytkownik w przeglądarce otwiera stronę. Czyli gdy przeglądarka puka do serwera
z żądaniem HTTP. Żądanie kieruje się na jeden plik PHP, który znajduje się w publicznym katalogu www/
, a jest
nim index.php
. Powiedzmy, że chodzi o żądanie na adres https://example.com/product/123
. Dzięki
odpowiedniemu ustawieniu serwera
również ten URL mapuje się na plik index.php
i ten się wykonuje.
Jego zadaniem jest:
- zainicjalizowanie środowiska
- uzyskanie fabryki
- uruchomienie aplikacji Nette, która obsłuży żądanie
Jaką fabrykę? Przecież nie produkujemy traktorów, ale strony internetowe! Poczekajcie, zaraz się to wyjaśni.
Słowami „inicjalizacja środowiska” rozumiemy na przykład to, że aktywuje się Tracy, co jest niesamowitym narzędziem do logowania lub wizualizacji błędów. Na serwerze produkcyjnym błędy loguje, na deweloperskim od razu wyświetla. Dlatego do inicjalizacji należy również decyzja, czy strona działa w trybie produkcyjnym czy deweloperskim. Do tego Nette używa inteligentnej autodetekcji: jeśli stronę uruchamiasz na localhost, działa w trybie deweloperskim. Nie musisz więc nic konfigurować, a aplikacja jest od razu gotowa zarówno do rozwoju, jak i ostrego wdrożenia. Te kroki są wykonywane i szczegółowo opisane w rozdziale o klasie Bootstrap.
Trzecim punktem (tak, drugi pominęliśmy, ale wrócimy do niego) jest uruchomienie aplikacji. Obsługą żądań HTTP w Nette
zajmuje się klasa Nette\Application\Application
(dalej Application
), więc gdy mówimy uruchomić
aplikację, mamy na myśli konkretnie wywołanie metody o wymownej nazwie run()
na obiekcie tej klasy.
Nette jest mentorem, który prowadzi Cię do pisania czystych aplikacji według sprawdzonych metodyk. A jedna z tych
absolutnie najsprawdzonych nazywa się dependency injection, w skrócie DI. W tej chwili nie chcemy Cię obciążać
wyjaśnianiem DI, od tego jest osobny rozdział, istotny
jest skutek, że kluczowe obiekty będzie nam zazwyczaj tworzyć fabryka obiektów, której mówi się kontener DI (w
skrócie DIC). Tak, to ta fabryka, o której była przed chwilą mowa. I wyprodukuje nam również obiekt
Application
, dlatego potrzebujemy najpierw kontenera. Uzyskamy go za pomocą klasy Configurator
i pozwolimy mu wyprodukować obiekt Application
, wywołamy na nim metodę run()
i tym samym uruchomi
się aplikacja Nette. 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 mnóstwo tzw. presenterów (w innych frameworkach można spotkać się z terminem controller, chodzi o to samo), które są klasami, z których każda reprezentuje jakąś konkretną stronę internetową: np. stronę główną; produkt w e-sklepie; formularz logowania; kanał sitemap itp. Aplikacja może mieć od jednego do tysięcy presenterów.
Application
zaczyna od tego, że prosi tzw. router, aby zdecydował, któremu z presenterów przekazać aktualne
żądanie do obsłużenia. Router decyduje, czyja to odpowiedzialność. Spogląda na wejściowy URL
https://example.com/product/123
i na podstawie tego, jak jest ustawiony, decyduje, że to praca np. dla
presentera Product
, od którego będzie chciał jako akcję wyświetlenie (show
) produktu
o id: 123
. Parę presenter + akcja jest dobrym zwyczajem zapisywać oddzieloną dwukropkiem jako
Product:show
.
Zatem router przekształcił URL na parę Presenter:action
+ parametry, w naszym przypadku
Product:show
+ id: 123
. Jak taki router wygląda, można zobaczyć w pliku
app/Core/RouterFactory.php
i szczegółowo opisujemy go w rozdziale Routingu.
Idźmy dalej. Application
już zna nazwę presentera i może kontynuować. Tworząc obiekt klasy
ProductPresenter
, który jest kodem presentera Product
. Dokładniej mówiąc, prosi kontener DI, aby
wyprodukował presenter, ponieważ od produkowania jest on.
Presenter może wyglądać na przykład 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);
}
}
Obsługę żądania przejmuje presenter. A zadanie brzmi jasno: wykonaj akcję show
z id: 123
. Co w
języku presenterów oznacza, że wywołana zostanie metoda renderShow()
i w parametrze $id
otrzyma
123
.
Presenter może obsługiwać więcej akcji, czyli mieć więcej metod render<Akcja>()
. Ale zalecamy
projektowanie presenterów z jedną lub jak najmniejszą liczbą akcji.
Zatem, wywołano metodę renderShow(123)
, której kod jest wprawdzie wymyślonym przykładem, ale można na nim
zobaczyć, jak przekazuje się dane do szablonu, czyli zapisem do $this->template
.
Następnie presenter zwraca odpowiedź. Może to być strona HTML, obrazek, dokument XML, wysłanie pliku z dysku, JSON lub na
przykład przekierowanie na inną stronę. Ważne jest, że jeśli jawnie nie powiemy, jak ma odpowiedzieć (co jest przypadkiem
ProductPresenter
), odpowiedzią będzie wyrenderowanie szablonu ze stroną HTML. Dlaczego? Ponieważ w 99%
przypadków chcemy wyrenderować szablon, dlatego presenter to zachowanie traktuje jako domyślne i chce nam ułatwić pracę. To
jest sens Nette.
Nie musimy nawet podawać, jaki szablon wyrenderować, ścieżkę do niego wywnioskuje sam. W przypadku akcji show
po prostu spróbuje załadować szablon show.latte
w katalogu z klasą ProductPresenter
. Podobnie
spróbuje odnaleźć layout w pliku @layout.latte
(więcej o wyszukiwaniu szablonów).
A następnie szablony wyrenderuje. Tym samym zadanie presentera i całej aplikacji jest zakończone, a dzieło jest ukończone. Jeśli szablon by nie istniał, zwrócona zostanie strona z błędem 404. Więcej o presenterach przeczytasz na stronie Presentery.
Dla pewności, spróbujmy podsumować cały proces z nieco innym URL:
- URL będzie
https://example.com
- uruchamiamy aplikację, tworzy się kontener i uruchamia
Application::run()
- router dekoduje URL jako parę
Home:default
- tworzy się obiekt klasy
HomePresenter
- wywoływana jest metoda
renderDefault()
(jeśli istnieje) - renderowany jest szablon np.
default.latte
z layoutem np.@layout.latte
Być może spotkałeś się teraz z dużą ilością nowych pojęć, ale wierzymy, że mają sens. Tworzenie aplikacji w Nette to ogromna przyjemność.
Szablony
Skoro już mowa o szablonach, w Nette używa się systemu szablonów Latte. Dlatego
też te końcówki .latte
przy szablonach. Latte używa się po pierwsze dlatego, że jest to najlepiej zabezpieczony
system szablonów dla PHP, a jednocześnie system najbardziej intuicyjny. Nie musisz uczyć się wielu nowych rzeczy, wystarczy
znajomość PHP i kilku znaczników. Wszystko dowiesz się w
dokumentacji.
W szablonie tworzy się linki do innych presenterów i akcji w ten sposób:
<a n:href="Product:show $productId">szczegóły produktu</a>
Po prostu zamiast rzeczywistego URL wpisujesz znaną parę Presenter:action
i podajesz ewentualne parametry.
Sztuczka tkwi w n:href
, które mówi, że ten atrybut przetworzy Nette. I wygeneruje:
<a href="/product/456">szczegóły produktu</a>
Generowaniem URL zajmuje się już wcześniej wspomniany router. Otóż routery w Nette są wyjątkowe tym, że potrafią wykonywać nie tylko transformacje z URL na parę presenter:action, ale także odwrotnie, czyli z nazwy presentera + akcji + parametrów wygenerować URL. Dzięki temu w Nette możesz całkowicie zmienić kształty URL w całej gotowej aplikacji, nie zmieniając ani jednego znaku w szablonie czy presenterze. Tylko przez modyfikację routera. Również dzięki temu działa tzw. kanonizacja, co jest kolejną unikalną cechą Nette, która przyczynia się do lepszego SEO (optymalizacji dla wyszukiwarek internetowych) przez automatyczne zapobieganie istnieniu duplikatów treści pod różnymi URL. Wielu programistów uważa to za niesamowite.
Komponenty interaktywne
O presenterach musimy Ci jeszcze zdradzić jedną rzecz: mają w sobie wbudowany system komponentów. Coś podobnego mogą pamiętać weterani z Delphi lub ASP.NET Web Forms, na czymś zdalnie podobnym opiera się React czy Vue.js. W świecie frameworków PHP jest to absolutnie unikalna sprawa.
Komponenty to samodzielne, wielokrotnego użytku całości, które wstawiamy do stron (czyli presenterów). Mogą to być formularze, siatki danych, menu, ankiety, właściwie cokolwiek, co ma sens używać wielokrotnie. Możemy tworzyć własne komponenty lub używać niektórych z ogromnej oferty komponentów open source.
Komponenty zasadniczo wpływają na podejście do tworzenia aplikacji. Otworzą Ci nowe możliwości składania stron z gotowych jednostek. A ponadto mają coś wspólnego z Hollywoodem.
Kontener DI i konfiguracja
Kontener DI czyli fabryka obiektów jest sercem całej aplikacji.
Nie obawiaj się, nie jest to żaden magiczny black box, jak mogłoby się wydawać z poprzednich linijek. Właściwie jest to
jedna dość nudna klasa PHP, którą generuje Nette i zapisuje w katalogu z cache. Ma mnóstwo metod nazwanych jak
createServiceAbcd()
i każda z nich potrafi wyprodukować i zwrócić jakiś obiekt. Tak, jest tam również
metoda createServiceApplication()
, która wyprodukuje Nette\Application\Application
, którego
potrzebowaliśmy w pliku index.php
do uruchomienia aplikacji. I są tam metody produkujące poszczególne
presentery. I tak dalej.
Obiektom, które tworzy kontener DI, z jakiegoś powodu mówi się usługi.
Co jest w tej klasie naprawdę specjalnego, to że nie programujesz jej Ty, ale framework. On rzeczywiście generuje kod PHP
i zapisuje go na dysku. Ty tylko dajesz instrukcje, jakie obiekty ma umieć kontener produkować i jak dokładnie. A te
instrukcje są zapisane w plikach
konfiguracyjnych, dla których używa się formatu NEON i dlatego mają
też rozszerzenie .neon
.
Pliki konfiguracyjne służą czysto do instruowania kontenera DI. Więc gdy na przykład podam w sekcji sesji opcję expiration: 14 days
, to kontener DI
przy tworzeniu obiektu Nette\Http\Session
reprezentującego sesję wywoła jego metodę
setExpiration('14 days')
i tym samym konfiguracja stanie się rzeczywistością.
Jest tu dla Ciebie przygotowany cały rozdział opisujący, co wszystko można konfigurować i jak definiować własne usługi.
Jak tylko trochę zagłębisz się w tworzenie usług, natkniesz się na słowo autowiring. To jest bajer, który niesamowicie uprości Ci życie. Potrafi automatycznie przekazywać obiekty tam, gdzie ich potrzebujesz (na przykład w konstruktorach Twoich klas), bez konieczności robienia czegokolwiek. Odkryjesz, że kontener DI w Nette to mały cud.
Dokąd dalej?
Przeszliśmy przez podstawowe zasady aplikacji w Nette. Na razie bardzo powierzchownie, ale wkrótce zagłębisz się w temat i z czasem stworzysz wspaniałe aplikacje internetowe. Dokąd kontynuować dalej? Czy wypróbowałeś już tutorial Pisanie pierwszej aplikacji?
Oprócz wyżej opisanego, Nette dysponuje całym arsenałem przydatnych klas, warstwą bazy danych, itd. Spróbuj sobie po prostu przeklikać dokumentację. Lub blog. Odkryjesz mnóstwo ciekawych rzeczy.
Niech framework przynosi Ci mnóstwo radości 💙