Как работают приложения?
Сейчас вы читаете основной документ документации 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
. Благодаря соответствующим настройкам server settings, этот URL
также сопоставлен с файлом index.php
и будет выполнен.
Его задача состоит в следующем:
- инициализация среды
- получение фабрики
- запуск приложения 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
и, основываясь на том,
как он настроен, решает, что это задание, например, для презентера
Product
, который хочет показать
продукт с 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
{
// we obtain data from the model and pass it to the template
$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 имеет целый арсенал полезных классов, слой базы данных и т. д. Попробуйте целенаправленно просто просмотреть документацию. Или посетите блог. Вы откроете для себя много интересного.
Пусть этот фреймворк принесёт вам много радости 💙.