Создание URL-ссылок
Создавать ссылки в Nette так же просто, как показать пальцем. Достаточно прицелиться, а всю работу сделает фреймворк. Мы покажем:
- как создавать ссылки в шаблонах и не только
- как отличить ссылку на текущую страницу
- что делать с некорректными ссылками
Благодаря двунаправленной маршрутизации вам никогда не придётся жёстко прописывать URL вашего приложения в шаблонах или коде, ведь они могут позже измениться или оказаться сложными в сборке. В ссылке достаточно указать презентер и действие, передать параметры, а URL фреймворк породит сам. По сути это очень похоже на вызов функции. Вам понравится.
В шаблоне презентера
Чаще всего мы создаём ссылки в шаблонах, и отличный помощник здесь –
атрибут n:href:
<a n:href="Product:show">detail</a>
Обратите внимание, что вместо HTML-атрибута href мы использовали n:атрибут n:href. Его значением
служит не URL, как было бы у атрибута href, а имя презентера и
действия.
Щелчок по ссылке, попросту говоря, похож на вызов метода
ProductPresenter::renderShow(). А если у него есть параметры в сигнатуре, мы
можем вызвать его с аргументами:
<a n:href="Product:show $product->id, $product->slug">product detail</a>
Можно передавать и именованные параметры. Следующая ссылка передаёт
параметр lang со значением en:
<a n:href="Product:show $product->id, lang: en">product detail</a>
Если у метода ProductPresenter::renderShow() нет $lang в сигнатуре, он
может получить значение параметра через $lang = $this->getParameter('lang')
или из свойства.
Если параметры хранятся в массиве, их можно развернуть оператором
...:
{var $args = [$product->id, lang => en]}
<a n:href="Product:show, ...$args">product detail</a>
Так называемые постоянные параметры тоже передаются в ссылках автоматически.
Атрибут n:href очень удобен для HTML-тегов <a>. Если мы
хотим вывести ссылку в другом месте, например в тексте, мы используем
{link}:
URL is: {link Home:default}
В коде
Для создания ссылки в презентере служит метод link():
$url = $this->link('Product:show', $product->id);
Параметры можно передать и массивом, где можно указать и именованные параметры:
$url = $this->link('Product:show', [$product->id, 'lang' => 'en']);
Ссылки можно создавать и без презентера, с помощью LinkGenerator и его метода link().
Иногда нужно создать ссылку сейчас, а сам URL породить только позже.
Для этого есть метод lazyLink(), возвращающий объект
Nette\Application\UI\Link. Преимущество в том, что этот объект можно
передать дальше, например в шаблон, и до его отрисовки ещё изменить его
параметры методом setParameter(). Сам URL собирается только при
преобразовании объекта в строку:
$link = $this->lazyLink('Product:show', $id);
// ...
echo $link; // URL порождается только здесь
Ссылки на презентер
Если целью ссылки служит презентер и действие, синтаксис такой:
[//] [[[[:]module:]presenter:]action | this] [#fragment]
Этот формат поддерживают все теги Latte и все методы презентера,
работающие со ссылками, то есть n:href, {link}, {plink},
link(), lazyLink(), isLinkCurrent(), redirect(),
redirectPermanent(), forward(), canonicalize(), а также LinkGenerator. Так что, хотя в примерах используется
n:href, на его месте могла бы быть любая из этих функций.
Основная форма, таким образом, – Презентер:действие:
<a n:href="Home:default">home page</a>
Если мы ссылаемся на действие текущего презентера, его имя можно опустить:
<a n:href="default">home page</a>
Если целевое действие – default, его можно опустить, но двоеточие
должно остаться:
<a n:href="Home:">home page</a>
Ссылки могут указывать и на другие модули. Здесь ссылки
различаются как относительные к вложенному подмодулю или абсолютные.
Принцип аналогичен путям на диске, только вместо слешей используются
двоеточия. Если считать, что текущий презентер входит в модуль
Front, мы написали бы:
<a n:href="Shop:Product:show">link to Front:Shop:Product:show</a>
<a n:href=":Admin:Product:show">link to Admin:Product:show</a>
Особый случай – ссылка на саму себя, где мы
указываем целью this.
<a n:href="this">refresh</a>
Мы можем сослаться на определённую часть страницы через так
называемый фрагмент после знака решётки #:
<a n:href="Home:#main">link to Home:default and fragment #main</a>
Фрагмент можно задать и динамически, аргументом с ключом
#. Его значение автоматически кодируется и имеет приоритет над
фрагментом, указанным в цели:
$this->link('Home:default', ['#' => $fragment]);
Абсолютные пути
Ссылки, порождаемые через link() или n:href, всегда являются
абсолютными путями (то есть начинаются с /), но не абсолютными URL
с протоколом и доменом вроде https://domain.
Чтобы породить абсолютный URL, добавьте в начале два слеша (например,
n:href="//Home:"). Как вариант, вы можете переключить презентер на
порождение только абсолютных ссылок, задав $this->absoluteUrls = true.
В шаблоне для преобразования относительного пути в абсолютный можно
использовать и фильтр |absoluteUrl.
Ссылка на текущую страницу
Цель this создаёт ссылку на текущую страницу:
<a n:href="this">refresh</a>
При этом передаются все параметры, указанные в сигнатуре метода
action<Action>() или render<View>() (если action<Action>() не
определён). Так что если мы находимся на странице Product:show с
id: 123, ссылка на this передаст и этот параметр.
Разумеется, параметры можно указать и напрямую:
<a n:href="this refresh: 1">refresh</a>
Функция isLinkCurrent() проверяет, совпадает ли цель ссылки с текущей
страницей. Это можно использовать, например, в шаблоне, чтобы выделять
ссылки и подобное.
Параметры те же, что и у метода link(), но вместо конкретного
действия можно использовать подстановочный знак *, означающий
любое действие данного презентера.
{if !isLinkCurrent('Admin:login')}
<a n:href="Admin:login">Login</a>
{/if}
<li n:class="isLinkCurrent('Product:*') ? active">
<a n:href="Product:">...</a>
</li>
В сочетании с n:href в одном элементе можно использовать
сокращённую форму:
<a n:class="isLinkCurrent() ? active" n:href="Home:">...</a>
Подстановочный знак * можно использовать только вместо
действия, но не вместо презентера.
Чтобы определить, находимся ли мы в определённом модуле или его
подмодуле, используйте метод isModuleCurrent(moduleName).
<li n:class="isModuleCurrent('Forum:Users') ? active">
<a n:href="Product:">...</a>
</li>
Изменение базы ссылок
По умолчанию относительные ссылки отсчитываются от текущего
презентера. Это можно изменить с помощью {linkBase}:
{linkBase Admin:Dashboard}
<a n:href="Product:show">product detail</a>
Ссылка приведёт на Admin:Dashboard:Product:show. Затрагиваются только
относительные ссылки: абсолютные, начинающиеся с двоеточия, и ссылки
на текущий презентер (this, show) остаются без изменений.
{linkBase} действует на весь шаблон и особенно полезен в шаблонах
макетов, где обеспечивает единообразные ссылки независимо от
вызывающего презентера. Тег должен стоять в начале шаблона, иначе он
выбросит CompileException.
Ссылки на сигнал
Целью ссылки может быть не только презентер и действие, но и сигнал (он вызывает метод
handle<Signal>()). Тогда синтаксис такой:
[//] [sub-component:]signal! [#fragment]
Сигнал, таким образом, отличается восклицательным знаком:
<a n:href="click!">signal</a>
Можно создать и ссылку на сигнал подкомпонента (или под-подкомпонента):
<a n:href="componentName:click!">signal</a>
Ссылки в компоненте
Поскольку компоненты –
самостоятельные переиспользуемые единицы, которые не должны быть
связаны с окружающими презентерами, ссылки здесь работают немного
иначе. Атрибут Latte n:href и тег {link}, а также методы
компонента вроде link() и другие всегда считают целью ссылки имя
сигнала. Поэтому восклицательный знак даже не нужен:
<a n:href="click">signal, not an action</a>
Если бы мы хотели сослаться в шаблоне компонента на презентеры, мы
использовали бы тег {plink}:
<a href={plink Home:default}>home</a>
или в коде
$this->getPresenter()->link('Home:default')
Псевдонимы
Иногда бывает полезно назначить паре “презентер:действие” легко
запоминающийся псевдоним. Например, назвать главную страницу
Front:Home:default просто home, а Admin:Dashboard:default –
admin.
Псевдонимы задаются в конфигурации под ключом
application › aliases:
application:
aliases:
home: Front:Home:default
admin: Admin:Dashboard:default
sign: Front:Sign:in
В ссылках они затем записываются со знаком собаки, например:
<a n:href="@admin">administration</a>
Они поддерживаются и во всех методах, работающих со ссылками, таких
как redirect() и подобные.
Некорректные ссылки
Может случиться, что мы создадим некорректную ссылку: либо потому,
что она ведёт на несуществующий презентер, либо потому, что передаёт
больше параметров, чем принимает в сигнатуре целевой метод, либо когда
URL для целевого действия породить нельзя. Как обходиться с
некорректными ссылками, задаётся в презентере через
$this->invalidLinkMode. Он может принимать сочетание этих значений
(констант):
Presenter::InvalidLinkSilent– молчаливый режим, возвращает в качестве URL символ #Presenter::InvalidLinkWarning– выдаётся предупреждение E_USER_WARNING, которое в производственном режиме попадёт в лог, но не прервёт выполнение скриптаPresenter::InvalidLinkTextual– наглядное предупреждение, выводит ошибку прямо в ссылкуPresenter::InvalidLinkException– выбрасывает InvalidLinkException
По умолчанию задано InvalidLinkWarning в производственном режиме и
InvalidLinkWarning | InvalidLinkTextual в режиме разработки. InvalidLinkWarning в
производственной среде не прерывает скрипт, но предупреждение
попадает в лог. В среде разработки его перехватывает Tracy и показывает синий экран. InvalidLinkTextual
работает так, что возвращает как URL сообщение об ошибке, начинающееся с
символов #error:. Чтобы такие ссылки бросались в глаза с первого
взгляда, добавьте в свой CSS:
a[href^="#error:"] {
background: red;
color: white;
}
Если мы не хотим, чтобы в среде разработки возникали предупреждения, мы можем подавить их прямо в конфигурации.
application:
silentLinks: true
LinkGenerator
Как создавать ссылки с таким же удобством, как методом link(), но
без презентера? Для этого и существует Nette\Application\LinkGenerator.
LinkGenerator – сервис, который вы можете получить через конструктор и
затем создавать ссылки его методом link().
По сравнению с презентерами есть отличие. LinkGenerator создаёт все ссылки
сразу как абсолютные URL. Кроме того, “текущего презентера” нет,
поэтому нельзя указать целью только имя действия link('default') или
использовать относительные пути к модулям.
Некорректные ссылки всегда выбрасывают
Nette\Application\UI\InvalidLinkException.