Development

Documentation/ru_RU/book/1.0/09-Links-and-the-Routing-System

You must first sign up to be able to contribute.

Version 14 (modified by BaBL, 7 years ago)
--

Глава 9 - Ссылки и система навигации

Единая точка входа приложения (фронт-контроллер) и помощники в шаблонах позволяют полностью разделить способ отображения URL и работу,которую они выполняют. Назовём это навигацией. Навигация позволяет создавать более удобные и безопасные приложения. Эта глава расскажет вам обо всём, что нужно знать, что бы обрабатывать URL в ваших приложениях:

  • Что такое навигация и как она работает
  • Как использовать помощники в шаблонах, что бы использовать преимущества навигации
  • Как настроить правила навигации, что бы изменить внешний вид URL

Так же вы познакомитесь с некоторыми приемами настройки производительности навигации.

Что такое навигация?

Навигация - это механизм, который переписывает URL, что они были более удобными. Что бы понять, почему это важно, задумайтесь об URL.

URL как инструкции для сервера

URL передают информацию от броузера к серверу, необходимую для вызова действия, желаемого пользователем. Например, традиционный URL содержит путь к файлу скрипта и параметры запроса, как в этом примере:

http://www.example.com/web/controller/article.php?id=123456&format_code=6532

Этот URL содержит информацию об архитектуре приложения и базы данных. Разработчики обычно прячут инфраструктуру приложения (например, они выбирают заглавия страниц вроде "Информация о пользователе", а не "QZ7.65"). Демонстрация внутренней структуры приложения в URL противоречит этому принципу и имеет отрицательные стороны:

  • Технические данные, засветившиеся в URL, создают потенциальные дыры в безопасности. Что случится с предыдущим примером, если недоброжелательный пользователь изменит параметр id ? Значит ли это, что приложение предоставляет прямой доступ к базе данных? Или пользователь попробует использовать другие имена сценариев, например, admin.php ? Как бы там ни было, подобные URL предоставляют легкий способ изучения структуры приложения, и с ними практически невозможно её скрыть.

  • Появление неразборчивых ссылок раздражает и делает менее выразительным впечатление от окружающего их контента. В наши дни URL появляются не только в адресной строке. Они появляются, когда пользователь проводит указателем мыши над ссылкой, а так же в результатах поиска. Когда пользователь ищет информацию, вы хотите дать ему понять, что же он нашел, а не сбивающий с толку URL, как на Рисунке 9-1.

Рисунок 9-1 - URL появляются во многих местах, например, в результатах запроса к поисковым серверам

URL появляются во многих местах, например, в результатах запроса к поисковым серверам

  • Если какой-либо URL должен быть изменен (например, изменилось имя скрипта или одного из его параметров), то каждая ссылка на эту страницу так же должна быть изменена. Это значит, что изменения в структуре контроллера требуют больших усилий и затрат, что неприемлемо для быстрой разработки.

Было бы намного хуже. если бы симфони не использовала принцип фронт-контроллера; это означало бы, что приложение состоит из большого количества скриптов, доступных из Интернет, находящихся в различных директориях, как, например, вот это:

http://www.example.com/web/gallery/album.php?name=my%20holidays 
http://www.example.com/web/weblog/public/post/list.php 
http://www.example.com/web/general/content/page.php?name=about%20us

В этом случае, разработчикам пришлось бы приводить в соответствие внешний вид всех URL с файловой структурой, что при каждом изменении структуры превратило бы поддержку сайта в кошмар.

URL как часть интерфейса

Идея навигации в том, что бы рассматривать URL как часть интерфейса. Приложение может сформировать URL таким образом, что бы предоставить пользователю информацию, а пользователь может использовать URL для обращения к ресурсам приложения.

Это возможно в приложениях симфони, поскольку URL, предоставляемая пользователю, не связана с инструкциями о том, как серверу следует обработать запрос. Напротив, она сотносится с запрашиваемой информацией, и может быть свободно отформатирована. Наример, симфони поймёт следующий URL и покажет ту же информацию, что и первый URL в этой главе:

http://www.example.com/articles/finance/2006/activity-breakdown.html

Преимущества таковы:

  • URL на самом деле что-то означают и могут помочь пользователям догадаться, что страница, находящаяся по ссылке, содержит то, что они ожидают. Это, в частности, полезно при просмотре результатов запроса к поисковому сайту. Помимо этого, URL иногда используются безо всякого упоминания о заглавии страницы, например, когда вы копируете URL в сообщение электронной почты, и в этом случае, URL должна означать что-то сама по себе. Посмотрите пример дружественной URL на Рисунке 9-2.

Рисунок 9-2 - URL может содержать дополнительную информацию о странице, например, дату публикации

URL может содержать дополнительную информацию о странице, например, дату публикации

  • Если такие URL используются в бумажных документах, их легче напечатать и запомнить. Если веб-сайт вашей компании на вашей визитке обозначен как http://www.example.com/controller/web/index.jsp?id=ERD4 не ждите на сайте большого количества посетителей.

  • URL может стать инструментом адресной строки сама по себе, позволяя обращаться к информации интуитивно понятным способом. Приложения, предоставляющие такую возможность, удобны для продвинутых пользователей.

    // Список результатов: добавьте новый таг, что бы сократить список 
    http://del.icio.us/tag/symfony+ajax 
    // Страница профиля пользователя: измените имя, что бы перейти к профилю другого пользователя 
    http://www.askeet.com/user/francois
    
  • Вы можете изменять формат URL и имена действий и параметров независимо друг от друга и за одну операцию. Это значит, что сначала вы можете уделить внимание разработке, а затем отформатировать ваши URL, полностью избежав беспорядка в вашем приложении.

  • В случае реорганизации внутренней структуры приложения, для внешнего мира URL могут оставаться прежними. Это делает URL постоянными, что обязательно для закладок, и позволяет хранить в закладках ссылки на динамические страницы.

  • Иногда поисковые машины пропускают динамические страницы (с расширениями .php, .asp и т.п.), когда индексируют сайты. Вы можете отформатировать URL так, что бы заставить поисковых роботов считать, что они имеют дело со статическим содержимым, даже если они обращаются к динамическим страницам, что позволяет страницам вашего приложения быть проиндексированными лучшим образом.

  • Это более безопасно. Любой нераспознанный URL будет перенаправлен на страницу, определенную разработчиком, и пользователи не смогут просматривать файловую структуру сайта, перебирая различные URL. Реальный скрипт, вызываемый по запросу, так же как и его параметры, скрыты от внешнего мира.

Соответствие между URL, видимыми пользователем, и настоящим именем скрипта и параметрами запроса, достигается при помощи системы навигации, основанной на образцах, которые могут быть определены при настройке конфигурации приложения.

NOTE Изображения, каскадные таблицы стилей и файлы JavaScript находятся в подкаталогах каталога web/ и для создания ссылок на них нет нужды в системе навигации. Однако, вы по прежнему можете использовать динамически сгенерированные URI внутри этих хелперов, к примеру - для вывода динамически генерируемой картинки: image_tag(url_for('captcha/image?key='.$key)).

Как это работает

Симфони разделяет внешний URL и и его внутреннее представление URI. Соответствие между ними выполняется системой навигации. Что бы упростить обработку, симфони использует для внутренних URI синтаксис, подобный обычным URL.

Пример 9-1 - Внешний URL и внутренний URI

// Синтаксис внутреннего URI 
<module>/<action>[?param1=value1][&param2=value2][&param3=value3]... 

// Пример внутреннего URI, который никогда не показывается конечному пользователю 
article/permalink?year=2006&subject=finance&title=activity-breakdown 

// Пример внешнего URL, который видет пользователь 
http://www.example.com/articles/finance/2006/activity-breakdown.html

Система навигации использует специальный конфигурационный файл, называемый routing.yml, в котором вы можете определить правила навигации. Рассмотрите правило, показанное в Примере 9-2. Оно определяет образец, похожий на articles/*/*/*, и именует части URL, совпадающие с wildcard.

Пример 9-2 - Правило навигации

article_by_title: 
  url:    articles/:subject/:year/:title.html 
  param:  { module: article, action: permalink }

Каждый запрос к приложению симфони сначала анализируется системой навигации (это просто организовать, благодаря единой точке входа в приложение - фронт-контроллеру). Система навигации ищет соответствие между URL запроса и образцами, определенными в правилах навигации. Если соответствие найдено, поименованные wildcard становятся параметрами запроса и объединяются с теми, которые определены в ключе param: . Посмотрите, как это работает, в Примере 9-3.

Пример 9-3 - Система навигации интерпретирует URL входящих запросов

// Пользователь напечатал или кликнул внешний URL 
http://www.example.com/articles/finance/2006/activity-breakdown.html 

// Фронт-контроллер нашел, что URL соответствует правилу article_by_title 
// Система роутинга создала следующие параметры запроса 
  'module'  => 'article' 
  'action'  => 'permalink' 
  'subject' => 'finance' 
  'year'    => '2006' 
  'title'   => 'activity-breakdown'

TIP Расширение .html во внешнем URL - это всего лишь элемент украшения, и система навигации игнорирует его. Его единственное назначение - сделать динамические страницы похожими на статические. Далее в разделе "Настройка навигации" вы увидите, как активировать такое расширение.

Затем запрос передается действию permalink модуля article, вместе с параметрами, которые содержат всю необходимую информацию, для того что бы определить, какая статья должна быть показана.

Но этот механизм должен работать и в другую сторону. Что бы приложение могло показать внешние URL в форме ссылок, вы должны предоставить системе навигации достаточно данных, что бы можно было определить, какое правило к ним применить. Вам так же не следует записывать гиперссылки непосредственно при помощи тега <a> (это полностью обойдет систему навигации), а использовать специальный помощник, как показано в Примере 9-4.

Пример 9-4 - Система навигации формирует вывод URL в шаблонах

[php] 
// Помощник url_for() преобразует внутренний URI во внешний URL 
<a href="<?php echo url_for('article/permalink?subject=finance&year=2006&title=activity-breakdown') ?>">click here</a> 

// Помощник находит, что URI соответствует правилу article_by_title rule 
// Система навигации создает внешний URL согласно этому правилу 
 => <a href="http://www.example.com/articles/finance/2006/activity-breakdown.html">click here</a> 

// Помощник link_to() сразу выдает гиперссылку 
// Это позволяет избежать смешивания PHP и HTML 
<?php echo link_to( 
  'click here', 
  'article/permalink?subject=finance&year=2006&title=activity-breakdown' 
) ?> 

// link_to() вызовет url_for(), так что результат будет одинаковым 
=> <a href="http://www.example.com/articles/finance/2006/activity-breakdown.html">click here</a>

Навигация - это двусторонний механизм, но он работает только в том случае, если вы используете помощник link_to() для форматирования всех ваших ссылок.

URL Rewriting

Прежде чем глубоко погрузиться в систему навигации, необходимо кое-что прояснить. Во внутренних URI примеров предыдущего раздела не было и намёка на фронт-контроллер (index.php или myapp_dev.php). Окружение определяет, какой из фронт-контроллеров будет вызван. Так что все ссылки должны быть независимыми от окружения, и имя фронт-контроллера может никогда не появиться во внутренних ссылках.

В примерах сгенерированных URL вообще отсутствует имя скрипта. Это связано с тем, что URL, сгенерированные для продакшн-окружения, по умолчанию не содержат имени скрипта. Параметр no_script_name файла settings.yml позволяет управлять появлением названия скрипта фронт-контроллера в генерируемых URL. Установите его значение в off, как показано в Примере 9-5, и помощники ссылок будут выводить имя фронт-контроллера во всех ссылках.

Пример 9-5 - Как показывать в URL имя скрипта фронт-контроллера, apps/myapp/settings.yml

prod: 
  .settings 
    no_script_name:  off

Теперь генерируемые ссылки будут выглядеть следующим образом:

http://www.example.com/index.php/articles/finance/2006/activity-breakdown.html

Во всех окружениях, кроме первого (продакшн, production environment), параметр no_script_name по умолчанию установлен в off. Так что если вы рассматриваете ваше приложение, например, в окружении разработки (development environment), имя фронт-контроллера всегда фигурирует в URL.

http://www.example.com/myapp_dev.php/articles/finance/2006/activity-breakdown.html

В продакшне no_script_name установлено в on, так что URL содержат только информацию о навигации сайта, и поэтому они более дружественны. В этом случае они не содержат технической информации, такой, как имя скрипта.

http://www.example.com/articles/finance/2006/activity-breakdown.html

Но как приложение узнает, какой фронт-контроллер следует вызывать? Здесь в игру вступает переписывание URL (URL rewriting). Веб-сервер может быть настроен таким образом, что бы вызывать требуемый скрипт, когда его имя не указано в URL.

В веб-сервере Апач можно активировать расширение mod_rewrite. Каждый проект симфони содержит файл .htaccess в каталоге web/, который подключает настройки mod_rewrite к конфигурации вашего веб-сервера. Типичное содержание такого файла показано в Примере 9-6.

Пример 9-6 - Типовые настройки перезаписи URL в файле myproject/web/.htaccess

<IfModule mod_rewrite.c> 
  RewriteEngine On 

  # Пропускаем все файлы с расширениями, отличными от .html 
  RewriteCond %{REQUEST_URI} \..+$ 
  RewriteCond %{REQUEST_URI} !\.html$ 
  RewriteRule .* - [L] 

  # Проверяем, существует ли такой файл с расширением .html (кеширование) 
  RewriteRule ^$ index.html [QSA] 
  RewriteRule ^([^.]+)$ $1.html [QSA] 
  RewriteCond %{REQUEST_FILENAME} !-f 

  # Если нет, перенаправляем запрос к нашему фронт-контроллеру 
  RewriteRule ^(.*)$ index.php [QSA,L] 
</IfModule>

Веб-сервер исследует форму полученного ним URL. Если URL не содержит суффикс и кешированая версия страницы отсутствует (кеширование описано в Главе 12), то запрос обслуживается скриптом index.php.

Однако, каталог web/ проекта симфони является общим для всех приложений и окружений проекта. Это значит, что в каталоге обычно находится более чем один фронт-контроллер. Например, в проекте, который состоит из двух приложений, frontend и backend, и двух конфигураций, dev и prod, каталог web/ содержит четыре скрипта фронт-контроллеров:

index.php         // frontend in prod 
frontend_dev.php  // frontend in dev 
backend.php       // backend in prod 
backend_dev.php   // backend in dev

Настройки mod_rewrite указывают только одно имя скрипта по умолчанию. Если вы включите no_script_name для всех ваших приложений и окружений, все URL будут восприниматься как запросы к приложению frontend в окружении prod. Вот почему у вас может быть только одно приложение с одним окружением, которое может использовать преимущество перезаписываемых URL.

TIP Существует способ получить несколько приложений, которые будут вызываться без указания имени скрипта. Создайте подкаталоги в каталоге web\, и переместите фронт-контроллеры внутрь этих подкаталогов. Измените в них определение константы SF_ROOT_DIR соответствующим образом, и создайте файлы .htaccess с конфигурацией изменения URL для каждого такого приложения.

Помощники ссылок

Как уже говорилось ранее, вам следует использовать в шаблонах помощники ссылок вместо обычных тегов <a>. Не воспринимайте этим методы как преграду, напротив, они позволяют вашему приложению оставаться простым и легким в обслуживании, кроме того, они позволяют использовать несколько техник, которые значительно облегчают жизнь разработчику.

Гиперссылки, кнопки и формы

Вы уже знаете о методе-помошнике "link_to()". Он принимает два параметра: кликабельный элемент и внутренний URI, на который будет вести ссылка, и выводит совместимую с XHTML ссылку. Если вместо ссылки вы хотите вывести кнопку - используйте метод "button_to()". Так же существует метод-помошник для форм, который позволяет управлять атрибутом "action" формы. Подробнее о формах вы сможете прочитать в следующей главе. В листинге 9-7 приведены некоторые примеры использования описанных выше методов.

Пример 9-7 - Link Helpers for <a>, <input>, and <form> Tags

[php] 
// строка-ссылка
<?php echo link_to('my article', 'article/read?title=Finance_in_France') ?> 
 => <a href="/routed/url/to/Finance_in_France">my article</a> 

// Изображение-ссылка
<?php echo link_to(image_tag('read.gif'), 'article/read?title=Finance_in_France') ?> 
 => <a href="/routed/url/to/Finance_in_France"><img src="/images/read.gif" /></a> 

// Кнопка
<?php echo button_to('my article', 'article/read?title=Finance_in_France') ?> 
 => <input value="my article" type="button"onclick="document.location.href='/routed/url/to/Finance_in_France';" /> 

// Тэг формы
<?php echo form_tag('article/read?title=Finance_in_France') ?> 
 => <form method="post" action="/routed/url/to/Finance_in_France" />

Все эти методы могут работать как с внутренними URI, так и с статичными URL (начинающимися с "http://", система маршрутизации их игнорирует) и с якорями (anchors). Тут важно понимать, что внутренние URI строятся с учетом некоторых динамических параметров. Следующий листинг демонстрирует это:

Пример 9-8 - URL, с которыми работают ссылочные методы-помошники

[php] 
// внутренний URI 
<?php echo link_to('my article', 'article/read?title=Finance_in_France') ?> 
 => <a href="/routed/url/to/Finance_in_France">my article</a> 

// внутренний URI с динамическими параметрами
<?php echo link_to('my article', 'article/read?title='.$article->getTitle()) ?> 

// внутренний URI с якорем
<?php echo link_to('my article', 'article/read?title=Finance_in_France#foo') ?> 
 => <a href="/routed/url/to/Finance_in_France#foo">my article</a> 

// статичный абсолютный URL
<?php echo link_to('my article', 'http://www.example.com/foobar.html') ?> 
 => <a href="http://www.example.com/foobar.html">my article</a>

Опции в ссылочных методах-помошниках

Как показано в главе 7, методы-помошники позволяют использовать домолнительный аргумент опций, который может быть ассоциативным массивом, или строкой. Это так и для ссылочных помошников, как показано в листинге 9-9.

Пример 9-9 - Ссылочные помошники позволяют использовать опции

[php] 
// Дополнительные опции в виде ассоциативного массива
<?php echo link_to('my article', 'article/read?title=Finance_in_France', array( 
  'class'  => 'foobar', 
  'target' => '_blank' 
)) ?> 

// Дополнительные опции в виде строки
<?php echo link_to('my article', 'article/read?title=Finance_in_France','class=foobar target=_blank') ?> 
 => <a href="/routed/url/to/Finance_in_France" class="foobar" target="_blank">my article</a>

Вы так же можете использовать специфичные для symfony опции: "confirm" и "popup". Первая использует JS-диалоговое окно в момент клика по ссылке для подтверждения перехода (применимо для кнопок удаления в CMS, к примеру), а вторая - открывает документ, на который ведет ссылка в новом окне, как показано в листинге 9-10.

Пример 9-10 - 'confirm' и 'popup' опции

[php] 
<?php echo link_to('delete item', 'item/delete?id=123', 'confirm=Are you sure?') ?> 
 => <a onclick="return confirm('Are you sure?');" 
       href="/routed/url/to/delete/123.html">add to cart</a> 

<?php echo link_to('add to cart', 'shoppingCart/add?id=100', 'popup=true') ?> 
 => <a onclick="window.open(this.href);return false;" 
       href="/fo_dev.php/shoppingCart/add/id/100.html">add to cart</a> 

<?php echo link_to('add to cart', 'shoppingCart/add?id=100', array( 
  'popup' => array('Window title', 'width=310,height=400,left=320,top=0') 
)) ?> 
 => <a onclick="window.open(this.href,'Window title','width=310,height=400,left=320,top=0');return false;" 
       href="/fo_dev.php/shoppingCart/add/id/100.html">add to cart</a>

Все эти опции можно использовать совместно.

Неверное использование GET и POST

Достаточно часто разработчик создает GET запрос в то время, как на самом деле подразумевается POST. Взгляните на этот пример:

http://www.example.com/index.php/shopping_cart/add/id/100

Этот запрос изменит данные, хранящиеся в приложении, добавив товар в корзину, которая хранится в сессии или базе данных. Такой URL может быть внесен в закладки, может быть закэширован, или проиндексирован поисковыми машинами. Только представьте все последствия такого для ваший базы данных! На самом деле этот запрос должен передаваться как POST. Хотя бы потому, что поисковые боты не делают POST запросов и не индексируют их.

Symfony предоставляет нам возможность изменить поведение "link_to()" и "button_to()" так, чтобы они использовали POST. Для этого необходимо добавить опцию 'post=true', как показано в листинге 9-11.

Пример 9-11 - Делаем POST из простой ссылки

[php] 
<?php echo link_to('go to shopping cart', 'shoppingCart/add?id=100', 'post=true') ?> 
 => <a onclick="f = document.createElement('form'); document.body.appendChild(f); 
                f.method = 'POST'; f.action = this.href; f.submit();return false;" 
       href="/shoppingCart/add/id/100.html">go to shopping cart</a>

Когда происходит клик по ссылке, динамически создается форма с "action=post" и совершается переход по ней, а переход по исходной ссылке отменяется. Как видите, у этого тага есть аттрибут "href", и, если у пользователя отключен Javascript, или если по ссылке пройдет поисковый бот, он выполнит запрос GET по умолчанию. Чтобы избежать описанной выше ситуации вам необходимо ограничить ваш action таким образом, чтобы он отвечал только на POST запросы:

[php] 
$this->forward404If($request->getMethod() != sfRequest::POST);

У этого подхода есть одно ограничение - нельзя использовать ссылки с "post=true" внутри форм, так как такая ссылка при клике создает собственную форму, что приведет к некорректной работе исходной формы.

Использовать ссылки с POST тогда, когда они действительно выполнают post считается хорошим тоном.

Принудительное представление параметров запроса как GET переменных

В системе маршрутизации переменные, используемые в качестве аргументов для "link_to()" преобразовываются в шаблоны. Если сформированный внутренний URI не подпадает ни под одно правило, используется маршрут по умолчанию "module/action?key=value" => "module/action/key/value", как показано в листинге 9-12.

Пример 9-12 - Правило маршрутизации по умолчанию

[php] 
<?php echo link_to('my article', 'article/read?title=Finance_in_France') ?> 
=> <a href="/article/read/title/Finance_in_France">my article</a>

Если вам нужно использовать синтакс GET и сохранить формат ?key=value для ваших параметров - вам необходимо использовать эти параметры вне передаваемого URL, в опции "query_string". Эту опцию поддерживают все ссылочные помощники.

Пример 9-13 - Используем GET параметры в query_string

[php] 
<?php echo link_to('my article', 'article/read', array( 
  'query_string' => 'title=Finance_in_France' 
)) ?> 
=> <a href="/article/read?title=Finance_in_France">my article</a>

Переменные, переданные в качестве GET параметров могут быть использованы в скриптах на стороне клиента и в переменных $_GET и $_REQUEST на стороне сервера.

SIDEBAR Методы помошники для статических ресурсов

В седьмой главе вы познакомились с методами "image_tag()", "stylesheet_tag()", и "javascript_include_tag()", которые позволяют покдлючать к документу изображение, файл стиля или javasctipt файл. Пути в таких хэлперах не обрабатываются системой маршрутизации, так как они указывают на ресурсы, расположенные в публично доступной директории.

Нет необходимости указывать расширение файла, Symfony автоматически использует ".png", ".js", ".css" для подключения изображения, js-файла или стиля. Более того, по умолчанию Symfony будет искать указанные файлы в директориях "web/images", "web/css", "web js". Однако, если вы хотите использовать нестандартное расширение файла, или файл из какой-то нестандартной директории, вы можете указать путь к файлу. И не беспокойтесь о атрибуте "alt" выших изображений, symfony определит его автоматически.

[php] 
<?php echo image_tag('test') ?> 
<?php echo image_tag('test.gif') ?> 
<?php echo image_tag('/my_images/test.gif') ?> 
 => <img href="/images/test.png" alt="Test" /> 
    <img href="/images/test.gif" alt="Test" /> 
    <img href="/my_images/test.gif" alt="Test" />

Чтобы изменить размер изображения, используйте атрибут "size", он принимает ширину и высоту, разделенную символом x.

[php] 
<?php echo image_tag('test', 'size=100x20')) ?> 
 => <img href="/images/test.png" alt="Test" width="100" height="20"/>

Если вы хотите использовать стиль или js файл в секции документа - используйте методы "use_stylesheet()" и "use_javascript" в шаблонах, вместо "*_tag()" в обрамлениях. Их использование приведет к вставке ссылки на необходимые файлы до тега "".

Использование абсолютных путей

По умолчанию генерируемые ссылки используют относительные пути. Если по той или иной причине вам надо работать с абслютными, используйте опцию "absolete=true". Такой подход бывает полезен при работе с Email, API или RSS лентами.

Пример 9-14 - Использование абсолютных путей

[php] 
<?php echo url_for('article/read?title=Finance_in_France') ?> 
 => '/routed/url/to/Finance_in_France' 
<?php echo url_for('article/read?title=Finance_in_France', true) ?> 
 => 'http://www.example.com/routed/url/to/Finance_in_France' 

<?php echo link_to('finance', 'article/read?title=Finance_in_France') ?> 
 => <a href="/routed/url/to/Finance_in_France">finance</a> 
<?php echo link_to('finance', 'article/read?title=Finance_in_France','absolute=true') ?> 
 => <a href=" http://www.example.com/routed/url/to/Finance_in_France">finance</a> 

// The same goes for the asset helpers 
<?php echo image_tag('test', 'absolute=true') ?> 
<?php echo javascript_include_tag('myscript', 'absolute=true') ?>

SIDEBAR Метод-помошник Mail.
В наши дни по вебу гуляет туча ботов, собирающих опубликованные email адреса, и, опубликовав где-либо свой адрес, вы рискуете стать жертвой спамеров уже через несколько дней. Вот почему в symfony разработан специальный помошник "mail_to()". Этот хелпер принимает в параметры два аргумента - настоящий email адрес и строку, которую надо вывести, так же можно использовать опцию "encode=true", которая превратит адрес во что-то нечитаемое. Ссылку поймет браузер, но не поймет спамерский бот:

[php] 
<?php echo mail_to('myaddress@mydomain.com', 'contact') ?> 
 => <a href="mailto:myaddress@mydomain.com'>contact</a> 
<?php echo mail_to('myaddress@mydomain.com', 'contact', 'encode=true') ?> 
 => <a href="&#109;&#x61;... &#111;&#x6d;">&#x63;&#x74;... e&#115;&#x73;</a>

Эта технология позволяет заменять символы алфавита числовыми последовательностями, и, на данный момент, большинство ботов не понимают такого шифра, но, к сожалению, спамерз технолоджис развиваются слишком быстро...

Настройка маршрутизации

Система маршрутизации выполняет следующие процедуры:

  • Интерпретирует входящие абсолютные URL, определяя внутренний URI, модуль, действие (action) и параметры запроса.
  • Форматирует внутренние URI, указанные в методах-помошниках во внешние URL.

Преобразования выполняются по правилам, указанным в конфигурационном файле "routing.yml", который вы найдете в директории "config" приложения. В листинге 9-15 показан дефолтный файл конфигурации маршрутизации.

Пример 9-15 - Файл конфигурации маршрутизации по-умолчанию, он находится в myapp/config/routing.yml

# default rules 
homepage: 
  url:   / 
  param: { module: default, action: index } 

default_symfony: 
  url:   /symfony/:action/* 
  param: { module: default } 

default_index: 
  url:   /:module 
  param: { action: index } 

default: 
  url:   /:module/:action/*

Правила и шаблоны

Правила маршрутизации это бинарные отношения между внутренним URI и внешним URL. Они состоят из:

  • Имя маршрута - уникальная метка, она может быть использована в методах-помошниках.
  • Ключ "url" - шаблон внешнего URL, по которому сверяется URL.
  • Массив параметров запроса (ключ param)

Шаблон может включать в себя групповые символы ( "*" к примеру) и проименованные групповые символы, которые начинаются с двоеточия. Совпадающие проименованные групповые символы преобразуются в параметры запроса. В качестве примера, в маршруте по умолчанию (:module/:action) при запросе "/foo/bar" параметр "action" примет значение "bar", а "module" => "foo", а в маршруте "default_symfony" в качестве модуля будет использоваться "symfony".

Система шаблонов сверяет URL запроса со всеми правилами сверху вниз и останавливается при первом совпадении, по этому ваши более конкретизированные правила надо указывать выше более общих.

Пример 9-16 - Правила проверяются сверху вниз.

my_rule: 
  url:   /foo/:bar 
  param: { module: mymodule, action: myaction } 

# default rules 
default: 
  url:   /:module/:action/*

NOTE Когда вы создаете новое действие (action), вам не обязательно менять что-либо в системе маршрутизации, если правило по умолчанию вас устраивает. Если же вам необходимо изменить внешний URL действия - необходимо будет создать маршрут к нему в routing.yml.

Пример 9-17 показывает, как сменить внешний формат URL для действия "article/read"

Пример 9-17 - Меняем формат внешнего URL для "article/read"

[php] 
<?php echo url_for('my article', 'article/read?id=123) ?> 
 => /article/read/id/123       // Default formatting 

// To change it to /article/123, add a new rule at the beginning 
// of your routing.yml 
article_by_id: 
  url:   /article/:id 
  param: { module: article, action: read }

В этом примере есть одна проблема: маршрут "article_by_id" перекрывает все маршруты, которые используют модуль "article". Фактически, при запросе "article/delete" symfony будет использовать действие "read" и id = delete. Чтобы обойти такое поведение, надо использовать шаблонное ограничение для правила, указывающее, что ":id" должно быть числом.

Шаблонные ограничения

В случае, если URL подпадает под несколько правил, вам придется переназначить правила путем добавления ограничений или требований к шаблону. Требование - это набор регулярных выражений, с которыми должны совпадать групповые символы (wildcards) URL чтобы оно подпадало под данное правило.

К примеру, чтобы добавить ограничение к правилу "article_by_id", надо дописать следующую строчку:

Пример 9-18 - Добавляем требование к правилу.

article_by_id: 
  url:   /article/:id 
  param: { module: article, action: read } 
  requirements: { id: \d+ }

Теперь при запросе "article/delete" действие "read" вызвано не будет, так как не выполняется требование.

SIDEBAR Постоянные ссылки ака пермалинки

Хорошей практикой по улучшению безопасности вашего сайта в плане маршрутизации является использование уникальных строк вместо первичных ключей в URL. К прмеру, вы можете использовать метод для чтения статей по названию статьи, а не по ее идентификатору:

http://www.example.com/article/Finance_in_France

Вы можете использовать и оба подхода вместе следующим образом:

article_by_id: 
  url:          /article/:id 
  param:        { module: article, action: read } 
  requirements: { id: \d+ } 

article_by_slug: 
  url:          /article/:slug 
  param:        { module: article, action: permalink }

Действие "permalink" будет возвращать статью по ее названию, а значит у вашей модели должен быть соответствущий метод.

[php] 
public function executePermalink() 
{ 
  $article = ArticlePeer::retrieveBySlug($this->getRequestParameter('slug'); 
  $this->forward404Unless($article);  // Display 404 if no article matches slug 
  $this->article = $article;          // Pass the object to the template 
}

Так же вам надо будет заменить действие "read" на "permalink" в ваших ссылочных методах-помошниках.

[php] 
// Replace 
<?php echo link_to('my article', 'article/read?id='.$article->getId()) ?> 

// With 
<?php echo link_to('my article', 'article/permalink?slug='.$article->getSlug()) ?>

Благодаря системе требований, url article/bla_bla_bla будет подпадать под правило article_by_slug, несмотря на то, что article_by_id написано выше него.

Для оптимизации быстродействия БД, при использовании метода получения статьи по названию вам не мешало бы проиндексировать вашу таблицу по полю title.

Установка значений по умолчанию

Вы можете указать переменным (wildcards) правила значения по умолчанию для того, чтобы правило сработало даже при отсутствии некоторых параметров, указывайте значения по умолчанию в массиве "params".

К примеру, правило "article_by_id" не сработает, если не указан "id", но это можно обойти, форсировав значение "id", как показано в листинге 9-19:

Пример 9-19 - Установка значения по умолчанию для wildcard.

article_by_id: 
  url:          /article/:id 
  param:        { module: article, action: read, 
id: 1 
 }

Параметрам по умолчанию не обязательно присутствовать в шаблоне. Таким образом вы можете управлять параметрами запроса:

Пример 9-20 - Установка значения по умолчанию для параметра запроса:

article_by_id: 
  url:          /article/:id 
  param:        { module: article, action: read, id: 1, display: true }

TIP Вы можете назначить один и тот же парамет для всех правил системы маршрутизации! Для этого надо назначить переменную конфигурации "sf_routing_defaults". К примеру, если вы хотите, чтобы все ваши маршруты имели параметр "theme" равный "default" по умолчанию, вы можете добавить строчку "sfConfig::set("sf_routing_defaults", array("theme" => "default"))" в ваш config.php.

Ускорение системы маршрутизации средствами использования имени маршрута


Методы-помошники для генерации ссылок поддерживают использование имени маршрута вместо пары модуль/действие, если перед ним стоит знак "@". Пример:

Пример 9-21 - Использование имени маршрута

[php] 
<?php echo link_to('my article', 'article/read?id='.$article->getId()) ?> 

// can also be written as 
<?php echo link_to('my article', '@article_by_id?id='.$article->getId()) ?>

Вот все за и против этой технологии:

  • Система маршрутизации мгновенно найдет нужный маршрут, а не будет его искать, что даст ощутимый прирост в производительности, если на странице находится большое количество ссылок.
  • Использование имени маршрута помогает разграничить логику и представление. Если вам понадобится сменить имя действия, но оставить URL прежним - будет достаточно изменить действие в файле routing.yml. Все вызовы "link_to()" будут работать как прежде.
  • Логика функции-помощника более очевидна при использовании имени маршрута.

Но у этого подхода есть и свои минусы. Один из них - ссылки становятся более завуалированными, вам постоянно приходится ссылаться на routing.yml.

Что лучше - решать вам. Во многом это зависит от конкретного проекта.

TIP При тестировании в вашем "dev" окружении, если вы хотите узнать, совпал ли с шаблоном данный запрос в браузере, откройте вкладку "logs and msgs" в тулбаре отладки и найдите строку "matched route XXX". Вы можете прочитать об отладке маршрутов подробнее в 16ой главе.

Добавляем расширение .html

Взгляните на следующие адреса:

http://myapp.example.com/article/Finance_in_France 
http://myapp.example.com/article/Finance_in_France.html

Даже если за ними скрывается одна и та же страница, пользователи (и поисковые боты) будут воспринимать их по разному только из-за адреса. Второй требует хорошо организованной структуры старических веб-страниц, а это как раз то, что очень здорово индексируется поисковиками.

Чтобы приписать ".html" ко всем внешним URL, которые генерируют ваши методы, установите переменную "suffix" в файле "settings.yml" конфиги приложения так, как это показано в примере 9-22.

Пример 9-22 - Установка суффикса для всех адресов в myapp/config/settings.yml

prod: 
  .settings 
    suffix:         .html

По умолчанию суффикс установлен в точку ("."), что означает, что система маршрутов не будет использовать суффиксы, если вы их не назначите дополнительно.

Иногда бывает необходимость назначить суффикс только для одного правила - тогда вы можете добавить его прямо в строчку "url" в описании правила в "routing.yml", глобальный суффикс в таком случае будет проигнорирован.

Пример 9-23 - Установка суффикса для одного адреса, in myapp/config/routing.yml

article_list: 
  url:          /latest_articles 
  param:        { module: article, action: list } 

article_list_feed: 
  url:          /latest_articles.rss 
  param:        { module: article, action: list, type: feed }

Создаем правила без routing.yml

Как и большинство других конфигурационных файлов, routing.yml - не единственный метод создать правила системы маршрутов. Вы можете создавать правила в PHP - либо в config.php, либо в контроллере до вызова "dispatch()", так как он определяет, какой модуль и действие вызвать исходя из уже существующих маршрутов. Создание маршрутов в PHP позволяет вам создавать правила динамически с учетом конфигураций либо каких-то других факторов.

Обработкой маршрутов занимается объект класса sfRouting (модель factory). Он доступен в любом месте кода посредствам вызова sfContext::getInstance()->getRouting(). Его метод prependRoute() создает маршрут и "кладет" его поверх уже существующих в файле routing.yml. Он принимает четыре параметра, которые совпадают с параметрами, необходимыми для создания правила: название маршрута, его шаблон, массив значений по умолчанию и массив ограничений / требований. К примеру, определение маршрута из листинга 9-18 эквивалентно определению в 9-24.

Новое в devel версии

NOTE Класс маршрутизации можно сменить в файле конфигурации factories.yml (подробности в 17ой главе). В этой главе говорится о sfPatternRouting, это класс, используемый для маршрутизации по умолчанию.

Пример 9-24 - Создаем маршрут в PHP.

[php] 
sfContext::getInstance()->getRouting()->prependRoute( 
  'article_by_id',                                  // Route name 
  '/article/:id',                                   // Route pattern 
  array('module' => 'article', 'action' => 'read'), // Default values 
  array('id' => '\d+'),                             // Requirements 
);

Класс sfRouting имеет еще несколько методов, полезных при работе с маршрутами вручную: clearRoutes(), hasRoutes() и так далее. Подробнее об этом можно прочитать в документации API(http://www.symfony-project.com/api/symfony.html).

TIP Когда вы полностью поймете то, о чем говорится в этой книге, мы рекомендуем вам почитать symfony API, или просмотреть исходные коды, что было бы даже лучше, так как не обо всем мы смогли написать в книге, многие вещи описаны в API.

Работа с маршрутами в действиях (actions)

Если вам необходимо работать с текущим маршрутом в действии, к примеру, для создания ссылки "назад к странице XXX", вы должны работать с объектом класса sfRouting. Используйте адрес, возвращаемый методом getCurrentInternalUri() в качестве аргумента для link_to(), как показано в листинге 9-25:

Пример 9-25 - Используем sfRouting для получения информации о текущем маршруте

[php] 
// If you require a URL like 
http://myapp.example.com/article/21 

$routing = sfContext::getInstance()->getRouting(); 

// Use the following in article/read action 
$uri = $routing->getCurrentInternalUri(); 
 => article/read?id=21 

$uri = $routing->getCurrentInternalUri(true); 
 => @article_by_id?id=21 

$rule = $routing->getCurrentRouteName(); 
 => article_by_id 

// If you just need the current module/action names, 
// remember that they are actual request parameters 
$module = $this->getRequestParameter('module'); 
$action = $this->getRequestParameter('action');

Если вам нужно получить внешний url из внутреннего так же, как это делает url_for() в шаблоне - используейте genUrl() - метод класса sfController:

Пример 9-26 - Используем sfController для трансформации url в действиях.

[php] 
$uri = 'article/read?id=21'; 

$url = $this->getController()->genUrl($uri); 
 => /article/21 

$url = $this->getController()->genUrl($uri, true); 
=> http://myapp.example.com/article/21

Итог

Маршрутизация - двунаправленный механизм, который позволяет преобразовывать адреса таким образом, что они становятся более удобочитаемы. URL rewriting необходим для того, чтобы опускать имя контроллера ("index.php", к примеру) в ваших адресах. Если вы хотите, чтобы маршрутизация работала в обоих направлениях - вам необходимо использовать методы-помощники для вывода ссылок в ваших шаблонах. Файл routing.ymlконфигурирует правила маршрутизации и использует порядок появления правил и требования / ограничения к правилам для поиска совпадений. Файл settings.yml содержит дополнительные настройки, которые позволяют регулировать присутствие имени контроллера и суффикса во внешних адресах.

Перевод Алексея Гоголева и Ната (natts) Гаджибалаева.