Як працюють застосунки?
Ви читаєте основний документ документації Nette. Ви дізнаєтеся весь принцип роботи веб-застосунків. Гарно від А до Я, від моменту народження до останнього подиху PHP-скрипта. Після прочитання ви будете знати:
- як це все працює
- що таке Bootstrap, Presenter та DI-контейнер
- як виглядає структура каталогів
Структура каталогів
Відкрийте приклад скелета веб-застосунку під назвою WebProject і під час читання можете дивитися на файли, про які йдеться.
Структура каталогів виглядає приблизно так:
web-project/ ├── app/ ← каталог із застосунком │ ├── Core/ ← базові класи, необхідні для роботи │ │ └── RouterFactory.php ← конфігурація URL-адрес │ ├── Presentation/ ← презентери, шаблони та ін. │ │ ├── @layout.latte ← шаблон layout │ │ └── Home/ ← каталог презентера Home │ │ ├── HomePresenter.php ← клас презентера Home │ │ └── default.latte ← шаблон дії default │ └── Bootstrap.php ← завантажувальний клас Bootstrap ├── bin/ ← скрипти, що запускаються з командного рядка ├── config/ ← конфігураційні файли │ ├── common.neon │ └── services.neon ├── log/ ← залоговані помилки ├── temp/ ← тимчасові файли, кеш, … ├── vendor/ ← бібліотеки, встановлені Composer │ ├── ... │ └── autoload.php ← автозавантаження всіх встановлених пакетів ├── 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, поділяються на безліч так званих презентерів (в інших фреймворках ви можете зустріти термін контролер, це те саме), що є класами, кожен з яких представляє якусь конкретну сторінку веб-сайту: наприклад, головну сторінку; продукт в інтернет-магазині; форму входу; 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 у файлі @layout.latte
(детальніше про пошук шаблонів).
І потім шаблони відобразить. Тим самим завдання презентера та всього застосунку виконано, і робота завершена. Якби шаблон не існував, повернулася б сторінка з помилкою 404. Більше про презентери ви дізнаєтеся на сторінці Презентери.
Для певності, спробуймо підсумувати весь процес з трохи іншим URL:
- URL буде
https://example.com
- завантажуємо застосунок, створюється контейнер і
запускається
Application::run()
- маршрутизатор декодує URL як пару
Home:default
- створюється об'єкт класу
HomePresenter
- викликається метод
renderDefault()
(якщо існує) - відображається шаблон, наприклад,
default.latte
з layout, наприклад,@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, меню, опитування, власне все, що має сенс використовувати повторно. Ми можемо створювати власні компоненти або використовувати деякі з величезної пропозиції компонентів з відкритим кодом.
Компоненти суттєво впливають на підхід до створення застосунків. Вони відкриють вам нові можливості складання сторінок з готових одиниць. І до того ж мають щось спільне з Голлівудом.
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 має цілий арсенал корисних класів, шар бази даних, тощо. Спробуйте просто проклацати документацію. Або блог. Ви відкриєте багато цікавого.
Нехай фреймворк приносить вам багато радості 💙