Jak działają aplikacje?

Czytasz właśnie dokumentację rdzenia Nette. Poznasz całą zasadę działania aplikacji internetowych. Od A do Z, od momentu narodzin do ostatniego tchnienia skryptu PHP. Po przeczytaniu tego, będziesz wiedział:

  • jak to wszystko działa
  • czym jest Bootstrap, Presenter i kontener DI
  • jak wygląda struktura katalogów

Struktura katalogu

Otwórz szkieletowy przykład aplikacji internetowej o nazwie WebProject i w trakcie czytania możesz przyjrzeć się omawianym plikom.

Struktura katalogów wygląda mniej więcej tak:

web-project/
├── app/                      ← adresář s aplikací
│   ├── Presenters/           ← presentery a šablony
│   │   ├── HomePresenter.php ← třída presenteru Home
│   │   └── templates/        ← adresář se šablonami
│   │       ├── @layout.latte ← šablona layoutu
│   │       └── Home/         ← šablony presenteru Home
│   │           └── default.latte  ← šablona akce 'default'
│   ├── Router/               ← konfigurace URL adres
│   └── Bootstrap.php         ← zaváděcí třída Bootstrap
├── bin/                      ← skripty spouštěné z příkazové řádky
├── config/                   ← konfigurační soubory
│   ├── common.neon
│   └── services.neon
├── log/                      ← logované chyby
├── temp/                     ← dočasné soubory, cache, …
├── vendor/                   ← knihovny instalované Composerem
│   ├── ...
│   └── autoload.php          ← autoloading všech nainstalovaných balíčků
├── www/                      ← veřejný adresář neboli document-root projektu
│   ├── .htaccess             ← pravidla mod_rewrite
│   └── index.php             ← prvotní soubor, kterým se aplikace spouští
└── .htaccess                 ← zakazuje přístup do všech adresářů krom www

Możesz zmienić strukturę katalogów w dowolny sposób, zmienić nazwę lub przenieść foldery, a następnie po prostu edytować ścieżki do log/ i temp/ w pliku Bootstrap.php, a następnie ścieżkę do tego pliku w composer.json w sekcji autoload. Nic więcej, żadnych skomplikowanych rekonfiguracji, żadnych zmian w stałych. Nette posiada sprytną funkcję autodetekcji.

W przypadku nieco większych aplikacji możemy podzielić foldery prezentera i szablonu na dysku na podkatalogi, a klasy na przestrzenie nazw, które nazywamy modułami.

Katalog www/ jest tzw. katalogiem publicznym lub document-root projektu. Możesz zmienić jego nazwę bez konieczności ustawiania czegokolwiek innego po stronie aplikacji. Wystarczy skonfigurować hosting tak, aby document-root trafił do tego katalogu.

Możesz również pobrać WebProject bezpośrednio, w tym Nette, używając Composera:

composer create-project nette/web-project

W systemach Linux lub macOS ustaw katalogi log/ i temp/ na uprawnienia do zapisu.

Aplikacja WebProject jest gotowa do uruchomienia, nie jest wymagana żadna konfiguracja i można ją obejrzeć bezpośrednio w przeglądarce, wchodząc do folderu www/.

Żądanie HTTP

Wszystko zaczyna się w momencie, gdy użytkownik otwiera stronę w przeglądarce. Czyli wtedy, gdy przeglądarka kliknie na serwer z żądaniem HTTP. Żądanie jest kierowane do pojedynczego pliku PHP znajdującego się w katalogu publicznym www/, czyli do index.php. Załóżmy, że żądanie dotyczy https://example.com/product/123 i jest wykonywany.

Jego celem jest:

  1. inicjalizacja środowiska
  2. zdobyć fabrykę
  3. uruchomić aplikację Nette, która będzie obsługiwać żądanie

Jaka fabryka? Nie robimy traktorów, robimy strony internetowe! Trzymaj się, za chwilę się to wyjaśni.

Przez “inicjalizację środowiska” rozumiemy na przykład, że włączona jest Tracy, która jest niesamowitym narzędziem do logowania lub wizualizacji błędów. Rejestruje błędy na serwerze produkcyjnym, a wyświetla je na serwerze deweloperskim. Tak więc inicjalizacja obejmuje podjęcie decyzji, czy witryna działa w trybie produkcyjnym czy rozwojowym. Aby to zrobić, Nette używa autodetekcji: jeśli uruchamiasz stronę na localhost, działa ona w trybie deweloperskim. Nie musisz niczego konfigurować, a aplikacja jest gotowa zarówno do rozwoju, jak i wdrożenia na żywo. Czynności te są wykonywane i szczegółowo opisane w rozdziale poświęconym klasie Bootstrap.

Trzeci punkt (tak, pominęliśmy drugi, ale do niego wrócimy) to uruchomienie aplikacji. Obsługą żądań HTTP zajmuje się w Nette klasa Nette\Application\Application (dalej Application), więc mówiąc o uruchomieniu aplikacji, mamy na myśli w szczególności wywołanie metody o odpowiedniej nazwie run() na obiekcie tej klasy.

Nette jest mentorem, który prowadzi Cię do pisania czystych aplikacji według sprawdzonych metodologii. A jeden z absolutnie najbardziej sprawdzonych nazywa się dependency injection, w skrócie DI. W tym momencie nie chcemy obarczać Cię wyjaśnianiem DI, jest na to osobny rozdział, sedno sprawy jest takie, że kluczowe obiekty będą zazwyczaj tworzone przez naszą fabrykę obiektów, która nazywa się DI container (w skrócie DIC). Tak, to jest ta fabryka, o której była mowa przed chwilą. A także uczyni nas obiektem Application, dlatego najpierw potrzebujemy kontenera. Uzyskamy go za pomocą klasy Configurator i każemy mu wytworzyć obiekt Application, wywołać na nim metodę run() i to uruchomi aplikację Nette. Dokładnie to samo dzieje się w pliku index.php.

Aplikacja Nette

Klasa Application ma tylko jedno zadanie: odpowiedzieć na żądanie HTTP.

Aplikacje napisane w Nette są podzielone na wiele prezenterów (w innych frameworkach możesz spotkać się z terminem controller, to to samo), czyli klas, z których każda reprezentuje konkretną stronę witryny: np. stronę główną; produkt w sklepie internetowym; formularz logowania; sitemap feed itp. Aplikacja może mieć od jednego do tysięcy prezenterów.

Aplikacja rozpoczyna się od zapytania tzw. routera o decyzję, do którego prezentera przekazać bieżące żądanie do przetworzenia. Router decyduje o tym, czyja to odpowiedzialność. Patrzy na wejściowy URL https://example.com/product/123, którego poprosi o wyświetlenie (show) produktu z id: 123 jako akcji . Dobrą praktyką jest zapisanie pary prezenter + akcja oddzielonej dwukropkiem jako Product:show.

W ten sposób router przekształca adres URL w parę Presenter:action + parametry, w naszym przypadku Product:show + id: 123. Jak wygląda taki router, można zobaczyć w pliku app/Router/RouterFactory.php, a szczegółowo opisujemy go w rozdziale Routing.

Ruszajmy dalej. Aplikacja zna teraz nazwę prezentera i może przejść dalej. Produkując obiekt klasy ProductPresenter, który jest kodem prezentera Product. Dokładniej, prosi kontener DI o wyprodukowanie prezentera, ponieważ jest tam, aby go wyprodukować.

Prezenter może wyglądać tak:

class ProductPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ProductRepository $repository,
	) {
	}

	public function renderShow(int $id): void
	{
		// získáme dane z modelu a předáme do šablony
		$this->template->product = $this->repository->getProduct($id);
	}
}

Prezenter przejmuje obsługę żądania. A zadanie jest jasne: wykonać akcję show z id: 123. Co w języku prezenterów oznacza wywołanie metody renderShow() i uzyskanie 123 w parametrze $id.

Prezenter może obsługiwać wiele akcji, czyli mieć wiele metod render<Akce>(). Zalecamy jednak projektowanie prezenterów z jedną lub jak najmniejszą ilością akcji.

Wywołana jest więc metoda renderShow(123), której kod jest fikcyjnym przykładem, ale widać, jak przekazać dane do szablonu, czyli pisząc do $this->template.

Następnie prezenter zwraca odpowiedź. Może to być strona HTML, obraz, dokument XML, wysłanie pliku z dysku, JSON, a nawet przekierowanie na inną stronę. Co ważne, o ile nie powiemy wprost prezenterowi, jak ma odpowiedzieć (co ma miejsce w przypadku ProductPresenter), odpowiedzią będzie renderowanie szablonu ze stroną HTML. Dlaczego? Ponieważ w 99% przypadków chcemy renderować szablon, więc prezenter przyjmuje to zachowanie jako domyślne i chce ułatwić nam pracę. O to właśnie chodzi w Nette.

Nie musimy nawet określać, jaki szablon ma być renderowany, sam wywnioskuje ścieżkę do niego za pomocą prostej logiki. W przypadku prezentera Product i akcji show, spróbuje sprawdzić, czy istnieje jeden z tych plików szablonów przechowywanych względnie z katalogu klasy ProductPresenter:

  • templates/Product/show.latte
  • templates/Product.show.latte

Spróbuje również prześledzić układ w pliku @layout.latte, a następnie wyrenderować szablon. W ten sposób kończy się zadanie prezentera i aplikacji, a praca zostaje zakończona. Jeśli szablon nie istnieje, zwrócona zostanie strona błędu 404. Więcej o prezenterach można przeczytać na stronie Prezenterzy.

Aby być bezpiecznym, spróbujmy podsumować cały proces z nieco innym adresem URL:

  1. Adres URL będzie następujący https://example.com
  2. uruchamiamy aplikację, tworzymy kontener i uruchamiamy Application::run()
  3. router dekoduje adres URL jako parę Home:default
  4. tworzony jest obiekt klasy HomePresenter
  5. wywoływana jest metoda renderDefault() (jeśli istnieje)
  6. wyrenderować szablon np. templates/Home/default.latte z układem np. templates/@layout.latte

Teraz być może spotkałeś się z wieloma nowymi pojęciami, ale wierzymy, że mają one sens. Tworzenie aplikacji w Nette to ogromna bułka z masłem.

Szablony

Mówiąc o szablonach, Nette używa systemu szablonów Latte. Stąd .latte zakończenie dla szablonów. Latte jest używany częściowo dlatego, że jest to najbezpieczniejszy system szablonowania dla PHP, a także najbardziej intuicyjny. Nie musisz uczyć się wielu nowych rzeczy, możesz sobie poradzić z PHP i kilkoma tagami. Wszystkiego można się dowiedzieć w dokumentacji.

Szablon łączy się z innymi prezenterami i wydarzeniami w następujący sposób:

<a n:href="Product:show $productId">detail produktu</a>

Wystarczy wpisać znaną parę Presenter:action zamiast prawdziwego adresu URL i podać dowolne parametry. Sztuczka to n:href, która mówi, że ten atrybut jest obsługiwany przez Nette. I wygenerować ją:

<a href="/product/456">detail produktu</a>

Za generowanie adresu URL odpowiada wspomniany wcześniej router. Routery w Nette są wyjątkowe, ponieważ mogą nie tylko wykonywać transformacje z URL do pary prezenter:akcja, ale także w drugą stronę, czyli z nazwy prezentera + akcji + parametrów do wygenerowania URL. Dzięki temu Nette może całkowicie zmienić kształty adresów URL w całej gotowej aplikacji, nie zmieniając ani jednego znaku w szablonie lub prezenterze. Wystarczy zmodyfikować router. Sprawia również, że działa tak zwana kanonizacja, kolejna unikalna funkcja Nette, która przyczynia się do lepszego SEO (optymalizacji pod kątem wyszukiwarek) poprzez automatyczne zapobieganie istnieniu zduplikowanych treści na różnych adresach URL. Wielu programistów uważa to za przytłaczające.

Elementy interaktywne

O prezenterach można powiedzieć jeszcze jedno: mają wbudowany system komponentów. Możesz pamiętać coś podobnego z Delphi lub ASP.NET Web Forms, a React lub Vue.js opiera się na czymś zdalnie podobnym. W świecie frameworków PHP jest to rzecz zupełnie wyjątkowa.

Komponenty to samodzielne jednostki wielokrotnego użytku, które osadzamy na stronach (czyli prezenterach). Mogą to być formularze, datagridy, menu, ankiety, w rzeczywistości wszystko, co ma sens, aby używać wielokrotnie. Możemy stworzyć własne komponenty lub skorzystać z któregoś z ogromnej gamy komponentów open source.

Komponenty w zasadniczy sposób wpływają na podejście do budowania aplikacji. Otwierają one nowe możliwości komponowania stron z gotowych jednostek. Do tego mają coś wspólnego z Hollywood.

Kontener DI i konfiguracja

Kontener DI, czyli fabryka obiektów, to serce aplikacji.

Nie martw się, nie jest to magiczna czarna skrzynka, jak mogłoby się wydawać z poprzednich wierszy. Jest to właściwie jedna dość nudna klasa PHP, którą Nette generuje i przechowuje w katalogu cache. Ma garść metod nazwanych jak createServiceAbcd() i każda z nich może wytworzyć i zwrócić obiekt. Tak, istnieje metoda createServiceApplication(), która wyprodukuje Nette\Application\Application, który był nam potrzebny w pliku index.php do uruchomienia aplikacji. A są metody, które produkują indywidualnych prezenterów. I tak dalej.

Obiekty, które tworzy kontener DI, nie bez powodu nazywane są usługami.

To co jest naprawdę wyjątkowe w tej klasie to fakt, że nie programujesz jej, robi to framework. W rzeczywistości generuje on kod PHP i przechowuje go na dysku. Po prostu instruujesz, jakie obiekty kontener powinien być w stanie wyprodukować i jak dokładnie. A instrukcje te zapisywane są w plikach konfiguracyjnych, dla których wykorzystywany jest format NEON, a więc mają rozszerzenie .neon.

Pliki konfiguracyjne służą wyłącznie do instruowania kontenera DI. Tak więc, na przykład, jeśli określę opcję expiration: 14 days w sekcji sesji, kontener DI wywoła swoją metodę setExpiration('14 days'), gdy utworzy obiekt Nette\Http\Session reprezentujący sesję, urzeczywistniając konfigurację.

Jest cały rozdział opisujący, co można skonfigurować i jak zdefiniować niestandardowe usługi.

Gdy zagłębimy się nieco w tworzenie usług, natrafimy na słowo autowiring. To gadżet, który w niesamowity sposób uprości Twoje życie. Może automatycznie przekazywać obiekty tam, gdzie ich potrzebujesz (jak w konstruktorach twoich klas) bez konieczności robienia czegokolwiek. Przekonasz się, że kontener DI w Nette to małe cudo.

Gdzie dalej?

Przeszliśmy przez podstawowe zasady działania aplikacji Nette. Jak na razie bardzo powierzchownie, ale wkrótce dostaniesz się do nitty gritty i ostatecznie zbudujesz kilka wspaniałych aplikacji internetowych. Gdzie dalej? Czy próbowałeś tutoriala Pisanie pierwszej aplikacji?

Oprócz powyższego Nette posiada cały arsenał przydatnych klas, warstwę bazodanową itp. Spróbuj celowo przeklikać się przez dokumentację. Albo blog. Odkryjesz wiele ciekawych rzeczy.

Niech ramy przyniosą Wam wiele radości 💙

wersja: 4.0