Создание 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.

версия: 4.x