Як працюють додатки?
Зараз ви читаєте основний документ документації Nette. Ви дізнаєтеся всі принципи роботи веб-додатків. Усі дрібниці від А до Я, від моменту народження до останнього подиху PHP-скрипта. Після прочитання ви будете знати:
- як усе це працює
- що таке Bootstrap, Presenter і DI контейнер
- який вигляд має структура каталогів
Структура каталогу
Відкрийте скелетний приклад веб-додатка під назвою WebProject, і ви зможете спостерігати, як відбувається запис файлів.
Структура каталогів виглядає приблизно так:
web-project/ ├── app/ ← каталог с приложением │ ├── Core/ ← основні необхідні класи │ │ └── RouterFactory.php ← конфігурація URL-адрес │ ├── UI/ ← презентатори, шаблони та інше │ │ ├── @layout.latte ← шаблон спільного макета │ │ └── Home/ ← домашній каталог доповідачів │ │ ├── HomePresenter.php ← клас головного доповідача │ │ └── default.latte ← шаблон дії default │ └── Bootstrap.php ← загрузочный класс Bootstrap ├── bin/ ← скрипты командной строки ├── config/ ← файлы конфигурации │ ├── common.neon │ └── services.neon ├── log/ ← журналы ошибок ├── temp/ ← временные файлы, кэш, … ├── vendor/ ← библиотеки, установленные через Composer │ ├── ... │ └── autoload.php ← автозагрузчик библиотек, установленных через Composer ├── www/ ← публичный корневой каталог проекта │ ├── .htaccess ← правила mod_rewrite и т. д. │ └── index.php ← начальный файл, запускающий приложение └── .htaccess ← запрещает доступ ко всем каталогам, кроме www
Ви можете змінити структуру каталогів будь-яким способом,
перейменувати або перемістити папки, а потім просто відредагувати
шляхи до log/
і temp/
у файлі Bootstrap.php
і шлях до цього
файлу в composer.json
у секції autoload
. Нічого більше, жодного
складного переналаштування, жодних постійних змін. Nette має інтелектуальне
автовизначення.
Для трохи більших додатків ми можемо розділити папки з ведучими і шаблонами на підкаталоги (на диску) і на простори імен (у коді), які ми називаємо модулями.
Публічний каталог www/
може бути змінений без необхідності
встановлювати що-небудь ще. Насправді, часто буває, що через специфіку
вашого хостингу вам доведеться перейменувати його або, навпаки,
встановити так званий 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
і буде
виконано.
Його завдання полягає в наступному:
- ініціалізація середовища
- отримання фабрики
- запуск програми Nette, яка обробляє запит
Що за фабрика? Ми виробляємо не трактори, а веб-сайти! Зачекайте, зараз усе буде пояснено.
Під “ініціалізацією середовища” мається на увазі, наприклад, що активовано сервіс Tracy, який є дивовижним інструментом для реєстрації або візуалізації помилок. Він реєструє помилки на робочому сервері та відображає їх безпосередньо на сервері розробки. Тому під час ініціалізації також необхідно вирішити, чи працює сайт у виробничому режимі або в режимі розробника. Для цього Nette використовує автовизначення: якщо ви запускаєте сайт на localhost, він працює в режимі розробника. Вам не потрібно нічого налаштовувати, і додаток готовий як для розробки, так і для виробничого розгортання. Ці кроки виконуються і детально описуються в розділі Bootstrap.
Третій пункт (так, ми пропустили другий, але ми до нього
повернемося) – це запуск програми. Обробкою HTTP-запитів у Nette
займається клас Nette\Application\Application
(далі Application
), тому коли
ми говоримо “запустити застосунок”, ми маємо на увазі виклик методу з
ім'ям run()
на об'єкті цього класу.
Nette – це наставник, який спрямовує вас до написання чистих додатків
за перевіреними методологіями. І найбільш перевірена з них
називається впровадження залежностей, скорочено DI. Наразі ми не
хочемо обтяжувати вас поясненням DI, оскільки цьому присвячено окремий розділ, тут важливим є
те, що ключові об'єкти зазвичай створюються фабрикою об'єктів, яка
називається DI-контейнер (скорочено DIC). Так, це та сама фабрика, про
яку йшлося деякий час тому. І вона також створює для нас об'єкт
Application
, тому спочатку нам потрібен контейнер. Ми отримуємо його
за допомогою класу Configurator
і дозволяємо йому створити об'єкт
Application
, викликаємо метод run()
і це запускає додаток Nette.
Саме це і відбувається у файлі index.php.
Додаток Nette
Клас Application має єдине завдання: для відповіді на HTTP-запит.
Додатки, написані на Nette, розділені на безліч так званих презентерів (в інших фреймворках ви можете зустріти термін контролер, що те саме), які є класами, що представляють конкретну сторінку сайту: наприклад, домашня сторінка; товар в електронному магазині; реєстраційна форма; rss-карта і т. д. У додатку може бути від одного до тисячі презентерів.
Додаток починає роботу з того, що просить так званий маршрутизатор
вирішити, якому з презентерів передати поточний запит на обробку.
Маршрутизатор вирішує, чия це відповідальність. Він переглядає
вхідний URL https://example.com/product/123
, який хоче показать
продукт
із id: 123
як дію. Доброю звичкою є написання пар презентер + дія,
розділених двокрапкою: Продукт:показать
.
Тому маршрутизатор перетворив URL у пару Presenter:action
+ параметри, у
нашому випадку Product:show
+ id
:
123. Вы можете увидеть, как выглядит маршрутизатор в файле `app/Core/RouterFactory.php
,
і ми детально опишемо його в розділі Маршрутизація.
Давайте рухатися далі. Додаток уже знає ім'я презентера і може
продовжити роботу. Шляхом створення об'єкта 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 і кілька тегів Latte.
Ви дізнаєтеся все в документації.
У шаблоні ми створюємо посилання на інших презентерів і дії таким чином:
<a n:href="Product:show $productId">страница товара</a>
Просто напишіть знайому пару Presenter:action
замість реального URL і
ввімкніть будь-які параметри. Хитрість полягає в n:href
, який
говорить, що цей атрибут буде оброблятися Nette. І він буде генерувати:
<a href="/product/456">страница товара</a>
Раніше згаданий маршрутизатор відповідає за генерацію URL. Фактично, маршрутизатори в Nette унікальні тим, що вони можуть виконувати не тільки перетворення з URL у пару презентер:дія, а й навпаки – генерувати URL з імені презентер + дія + параметри. Завдяки цьому в Nette ви можете повністю змінити форму URL у всьому готовому застосунку, не змінюючи жодного символу в шаблоні або презентері, просто модифікувавши маршрутизатор. І завдяки цьому працює так звана канонізація – ще одна унікальна особливість Nette, яка покращує SEO шляхом автоматичного запобігання існування дубльованого контенту на різних URL. Багато програмістів знаходять це дивовижним.
Інтерактивні компоненти
Ми хочемо розповісти вам ще дещо про презентери: вони мають вбудовану систему компонентів. Ті, хто старший, можуть пам'ятати щось подібне з Delphi або ASP.NET Web Forms. React або Vue.js побудовані на чомусь віддалено схожому. У світі PHP-фреймворків це абсолютно унікальна функція.
Компоненти – це окремі багаторазово використовувані блоки, які ми поміщаємо в сторінки (тобто в презентери). Це можуть бути форми, сітки даних, меню, опитування, загалом, усе, що має сенс використовувати багаторазово. Ми можемо створювати власні компоненти або використовувати деякі з величезної кількості компонентів з відкритим вихідним кодом.
Компоненти докорінно змінюють підхід до розробки додатків. Вони відкриють нові можливості для створення сторінок із заздалегідь заданих блоків. І в них є щось спільне з Голлівудом.
Контейнер DI та конфігурація
DI-контейнер (фабрика для об'єктів) – це серце всього застосунку.
Не хвилюйтеся, це не магічний чорний ящик, як може здатися з
попередніх слів. Насправді, це один доволі нудний PHP-клас, згенерований
Nette, який зберігається в каталозі кешу. Він має безліч методів, названих
createServiceAbcd()
, і кожен з них створює і повертає об'єкт. Так, є також
метод createServiceApplication()
, який створює Nette\Application\Application
, який
нам знадобився у файлі index.php
для запуску програми. Існують також
методи підготовки індивідуальних презентерів. І так далі.
Об'єкти, які створює контейнер DI, з якоїсь причини називаються сервісами.
Особливість цього класу в тому, що він програмується не вами, а
фреймворком. Він фактично генерує PHP-код і зберігає його на диску. Ви
просто даєте інструкції про те, які об'єкти і як саме має виробляти
контейнер. І ці інструкції записані в конфігураційних
файлах у форматі NEON і тому мають
розширення .neon
.
Конфігураційні файли використовуються виключно для навчання
DI-контейнера. Так, наприклад, якщо я вкажу expiration: 14 days
у секції session, контейнер DI при створенні
об'єкта Nette\Http\Session
, що представляє сесію, викличе його метод
setExpiration('14 days')
, і таким чином конфігурація стане реальністю.
Для вас підготовлено цілий розділ, який описує, що можна налаштувати і як визначити свої власні сервіси.
Щойно ви перейдете до створення сервісів, ви зіткнетеся зі словом autowiring (автопідключення). Це гаджет, який неймовірно полегшить ваше життя. Він може автоматично передавати об'єкти туди, куди вам потрібно (наприклад, до конструкторів ваших класів), не маючи потреби що-небудь робити. Ви побачите, що контейнер DI в Nette – це маленьке диво.
Що далі?
Ми розглянули основні принципи роботи додатків у Nette. Поки що дуже поверхнево, але незабаром ви зануритеся в глибини і зрештою створите чудові веб-додатки. Де продовжити? Ви пробували вивчити підручник Створіть свій перший додаток?
На додаток до перерахованого вище, Nette має цілий арсенал корисних класів, шар бази даних тощо. Спробуйте цілеспрямовано просто переглянути документацію. Або відвідайте блог. Ви відкриєте для себе багато цікавого.
Нехай цей фреймворк принесе вам багато радості 💙.