Как работят приложенията?
Току-що прочетохте основния документ на документацията на Nette. Ще научите целия принцип на работа на уеб приложенията. От А до Я, от момента на създаването до последния дъх на PHP скрипта. След като прочетете, ще знаете:
- как работи всичко
- какво е Bootstrap, Presenter и DI контейнер
- как изглежда директорийната структура
Директорийна структура
Отворете примера за скелет на уеб приложение, наречен WebProject, и докато четете, можете да разглеждате файловете, за които става въпрос.
Директорийната структура изглежда приблизително така:
web-project/ ├── app/ ← директория с приложението │ ├── Core/ ← основни класове, необходими за работа │ │ └── RouterFactory.php ← конфигурация на URL адреси │ ├── Presentation/ ← презентери, шаблони и др. │ │ ├── @layout.latte ← шаблон на лейаута │ │ └── Home/ ← директория на презентера Home │ │ ├── HomePresenter.php ← клас на презентера Home │ │ └── default.latte ← шаблон на действието default │ └── Bootstrap.php ← зареждащ клас Bootstrap ├── bin/ ← скриптове, стартирани от командния ред ├── config/ ← конфигурационни файлове │ ├── common.neon │ └── services.neon ├── log/ ← логвани грешки ├── temp/ ← временни файлове, кеш, … ├── vendor/ ← библиотеки, инсталирани от Composer │ ├── ... │ └── autoload.php ← autoloading на всички инсталирани пакети ├── www/ ← публична директория или document-root на проекта │ ├── .htaccess ← правила mod_rewrite │ └── index.php ← първоначален файл, с който се стартира приложението └── .htaccess ← забранява достъпа до всички директории освен www
Директорийната структура можете да променяте както искате, да преименувате или премествате папки, тя е напълно гъвкава. Nette освен това разполага с умна автодетекция и автоматично разпознава местоположението на приложението, включително неговата URL основа.
При малко по-големи приложения можем да разделим папките с презентери и шаблони на поддиректории и класовете на именни пространства, които наричаме модули.
Директорията www/
представлява т.нар. публична директория или
document-root на проекта. Можете да я преименувате без нужда от каквото и да
било друго настройване от страна на приложението. Само е необходимо да конфигурирате
хостинга така, че document-root да сочи към тази директория.
WebProject можете също така директно да изтеглите, включително Nette, с помощта на Composer:
composer create-project nette/web-project
На Linux или macOS задайте на директориите log/
и temp/
права за запис.
Приложението WebProject е готово за стартиране, не е необходимо изобщо
нищо да се конфигурира и можете директно да го покажете в браузъра,
като достъпите папката www/
.
HTTP заявка
Всичко започва в момента, когато потребителят отвори страница в
браузъра. Тоест, когато браузърът почука на сървъра с HTTP заявка.
Заявката сочи към единствен PHP файл, който се намира в публичната
директория www/
, и това е index.php
. Да кажем, че става въпрос за
заявка към адреса https://example.com/product/123
. Благодарение на подходящо настройване на
сървъра, и този URL се мапва към файла index.php
и той се
изпълнява.
Неговата задача е:
- да инициализира средата
- да получи фабриката
- да стартира Nette приложението, което ще обработи заявката
Каква фабрика? Не произвеждаме трактори, а уеб страници! Изчакайте, веднага ще се изясни.
С думите „инициализация на средата“ имаме предвид например това, че се активира Tracy, което е страхотен инструмент за логване или визуализация на грешки. На продукционен сървър той логва грешки, на сървър за разработка ги показва директно. Следователно към инициализацията принадлежи и решението дали уебсайтът работи в продукционна или развойна среда. За това Nette използва умна автодетекция: ако стартирате уебсайта на localhost, той работи в развойна среда. Не е необходимо нищо да конфигурирате и приложението е веднага готово както за разработка, така и за реално внедряване. Тези стъпки се извършват и са подробно описани в главата за клас Bootstrap.
Третата точка (да, прескочихме втората, но ще се върнем към нея) е
стартирането на приложението. Обработката на HTTP заявки в Nette се
извършва от класа Nette\Application\Application
(наричан по-нататък
Application
), така че когато казваме стартиране на приложението,
имаме предвид конкретно извикване на метода със знаковото име
run()
върху обекта на този клас.
Nette е ментор, който ви води към писането на чисти приложения според
доказани методики. И една от тези абсолютно най-доказани се нарича
dependency injection, съкратено DI. В този момент не искаме да ви натоварваме с
обяснение на DI, за това има отделна глава, същественото
последствие е, че ключовите обекти обикновено ще ни ги създава фабрика
за обекти, която се нарича DI контейнер (съкратено DIC). Да, това е
фабриката, за която стана дума преди малко. И тя ще ни произведе и
обекта Application
, затова първо се нуждаем от контейнера. Получаваме
го с помощта на класа Configurator
и го караме да произведе обекта
Application
, извикваме върху него метода run()
и така се стартира
Nette приложението. Точно това се случва във файла index.php.
Nette Application
Класът Application има една-единствена задача: да отговори на HTTP заявка.
Приложенията, написани на Nette, се разделят на много т.нар. презентери (в други фреймуърци може да срещнете термина controller, става въпрос за същото), които са класове, всеки от които представлява някаква конкретна страница на уебсайта: напр. начална страница; продукт в електронен магазин; формуляр за вход; sitemap feed и т.н. Приложението може да има от един до хиляди презентери.
Application започва с това, че моли т.нар. рутер да реши на кой от
презентерите да предаде текущата заявка за обработка. Рутерът решава
чия е отговорността. Поглежда входния URL https://example.com/product/123
и въз
основа на това как е настроен, решава, че това е работа напр. за
презентера Product
, от който ще иска като действие
показване (show
) на продукта с id: 123
. Двойката презентер +
действие е добър навик да се записва, разделена с двоеточие, като
Product:show
.
Следователно рутерът трансформира URL в двойка Presenter:action
+
параметри, в нашия случай Product:show
+ id: 123
. Как изглежда
такъв рутер можете да видите във файла app/Core/RouterFactory.php
и го
описваме подробно в главата Маршрутизация.
Да продължим нататък. Application вече знае името на презентера и може да
продължи напред. Като произведе обект от класа ProductPresenter
, което е
кодът на презентера Product
. По-точно казано, моли DI контейнера да
произведе презентера, защото производството е негова работа.
Презентерът може да изглежда например така:
class ProductPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ProductRepository $repository,
) {
}
public function renderShow(int $id): void
{
// получаваме данни от модела и ги предаваме на шаблона
$this->template->product = $this->repository->getProduct($id);
}
}
Обработката на заявката се поема от презентера. И задачата е ясна:
извърши действието show
с id: 123
. Което на езика на
презентерите означава, че се извиква методът renderShow()
и в
параметъра $id
получава 123
.
Презентерът може да обслужва повече действия, т.е. да има повече
методи render<Action>()
. Но препоръчваме да проектирате презентери
с едно или възможно най-малко действия.
Така че, извика се методът renderShow(123)
, чийто код е измислен
пример, но можете да видите на него как се предават данни към шаблона,
т.е. със запис в $this->template
.
Впоследствие презентерът връща отговор. Той може да бъде HTML страница,
изображение, XML документ, изпращане на файл от диска, JSON или например
пренасочване към друга страница. Важно е, че ако изрично не кажем как
трябва да отговори (което е случаят с ProductPresenter
), отговорът ще
бъде рендиране на шаблон с HTML страница. Защо? Защото в 99% от случаите
искаме да рендираме шаблон, следователно презентерът приема това
поведение като подразбиращо се и иска да ни улесни работата. Това е
смисълът на Nette.
Не е необходимо дори да посочваме кой шаблон да се рендира, пътят до
него се извежда сам. В случай на действие show
просто се опитва да
зареди шаблона show.latte
в директорията с класа ProductPresenter
.
Също така се опитва да намери лейаут във файла @layout.latte
(по-подробно за намиране на
шаблони).
И впоследствие рендира шаблоните. С това задачата на презентера и на цялото приложение е изпълнена и делото е завършено. Ако шаблонът не съществува, се връща страница с грешка 404. Повече за презентерите ще прочетете на страницата Презентери.
За всеки случай, нека опитаме да рекапитулираме целия процес с малко по-различен URL:
- URL ще бъде
https://example.com
- зареждаме приложението, създава се контейнер и се
стартира
Application::run()
- рутерът декодира URL като двойка
Home:default
- създава се обект от класа
HomePresenter
- извиква се методът
renderDefault()
(ако съществува) - рендира се шаблон напр.
default.latte
с лейаут напр.@layout.latte
Може би сега сте се сблъскали с голям брой нови понятия, но вярваме, че те имат смисъл. Създаването на приложения в Nette е огромно удоволствие.
Шаблони
Когато вече стана дума за шаблони, в Nette се използва шаблониращата
система Latte. Затова и тези разширения .latte
при шаблоните. Latte се използва от една страна, защото е най-добре
защитената шаблонираща система за PHP, а същевременно и
най-интуитивната система. Не е необходимо да учите много нови неща,
достатъчно е да познавате PHP и няколко тага. Всичко ще научите в документацията.
В шаблона се създават връзки към други презентери и действия по следния начин:
<a n:href="Product:show $productId">детайл на продукта</a>
Просто вместо реален URL напишете познатата двойка Presenter:action
и
посочете евентуални параметри. Трикът е в n:href
, което казва, че
този атрибут ще бъде обработен от Nette. И ще генерира:
<a href="/product/456">детайл на продукта</a>
Генерирането на URL се извършва от вече споменатия рутер. Всъщност рутерите в Nette са изключителни с това, че могат да извършват не само трансформации от URL към двойка presenter:action, но и обратно, т.е. от името на презентера + действието + параметрите да генерират URL. Благодарение на това в Nette можете напълно да промените формите на URL в цялото готово приложение, без да променяте нито един знак в шаблона или презентера. Само като промените рутера. Също така благодарение на това работи т.нар. канонизация, което е друга уникална характеристика на Nette, която допринася за по-добро SEO (оптимизация за намиране в интернет), като автоматично предотвратява съществуването на дублирано съдържание на различни URL адреси. Много програмисти смятат това за изумително.
Интерактивни компоненти
За презентерите трябва да ви разкрием още нещо: те имат вградена компонентна система. Нещо подобно може да е познато на ветераните от Delphi или ASP.NET Web Forms, на нещо отдалечено подобно са базирани React или Vue.js. В света на PHP фреймуърците това е абсолютно уникално явление.
Компонентите са самостоятелни цялости за многократна употреба, които вмъкваме в страниците (т.е. презентерите). Могат да бъдат формуляри, datagrid-ове, менюта, анкети за гласуване, всъщност всичко, което има смисъл да се използва многократно. Можем да създаваме собствени компоненти или да използваме някои от огромното предлагане на open source компоненти.
Компонентите фундаментално влияят на подхода към създаването на приложения. Ще ви отворят нови възможности за сглобяване на страници от предварително подготвени единици. И освен това имат нещо общо с Холивуд.
DI контейнер и конфигурация
DI контейнерът, или фабриката за обекти, е сърцето на цялото приложение.
Не се притеснявайте, това не е никаква магическа черна кутия, както
може би изглежда от предишните редове. Всъщност това е един доста
скучен PHP клас, който Nette генерира и съхранява в директорията с кеша. Има
много методи, наречени като createServiceAbcd()
, и всеки от тях може да
произведе и върне някакъв обект. Да, там има и метод
createServiceApplication()
, който произвежда Nette\Application\Application
, който
ни беше необходим във файла index.php
за стартиране на приложението.
И има методи, произвеждащи отделните презентери. И така нататък.
Обектите, които DI контейнерът създава, по някаква причина се наричат сървиси.
Това, което е наистина специално в този клас, е, че не го програмирате
вие, а фреймуъркът. Той наистина генерира PHP код и го съхранява на диска.
Вие само давате инструкции какви обекти трябва да може да произвежда
контейнерът и как точно. И тези инструкции са записани в конфигурационни
файлове, за които се използва форматът NEON
и следователно имат и разширение .neon
.
Конфигурационните файлове служат чисто за инструктиране на DI
контейнера. Така че, когато например посоча в секцията session опцията expiration: 14 days
, DI
контейнерът при създаването на обекта Nette\Http\Session
, представляващ
сесията, ще извика неговия метод setExpiration('14 days')
и така
конфигурацията ще стане реалност.
Има подготвена за вас цяла глава, описваща какво всичко може да се конфигурира и как да се дефинират собствени сървиси.
Щом малко навлезете в създаването на сървиси, ще се сблъскате с думата autowiring. Това е хитринка, която по невероятен начин ще ви улесни живота. Може автоматично да предава обекти там, където ги имате нужда (например в конструкторите на вашите класове), без да е необходимо да правите каквото и да било. Ще откриете, че DI контейнерът в Nette е малко чудо.
Накъде да продължим?
Преминахме през основните принципи на приложенията в Nette. Засега много повърхностно, но скоро ще навлезете в дълбочина и с времето ще създадете прекрасни уеб приложения. Накъде да продължим? Опитахте ли вече урока Пишем първото приложение?
Освен описаното по-горе, Nette разполага с цял арсенал от полезни класове, слой за работа с бази данни и т.н. Опитайте просто да прегледате документацията. Или блога. Ще откриете много интересно.
Нека фреймуъркът ви носи много радост 💙