Jobeet. День пятый. Маршрутизация.
Приветствуем!
Хотите что-то написать?Нужно назвать себя.
Если вы пришли в первый раз,
то нужно зарегистрироваться.
Переводы → Jobeet. День пятый. Маршрутизация.
Если вы осилили 4 день, вы должны быть знакомы с шаблном MVC и он должен все больше должен казаться наиболее естественным способом программирования. Потратьте немного времени, и вы не станете оглядываться назад. Вчера на практике мы разнообразили страницы Jobeet и, в процессе этого, рассмотрели некоторые концепции symfony,такие как layout, хелперы (helpers), и слоты (slots).
Сегодня мы будем погружаться в удивительный мир маршрутизации symfony.
URLы
Если вы нажмете на работу на главной странице Jobeet, URL будет выглядеть так:/job/show/id/1. Если вы уже разрабатывали веб-сайты на PHP, вы,возможно, больше привыкли к URLам вида /job.php?id=1. Как это работает наsymfony? Как symfony определяет действие по такому URLу?Почему id этой работы получается командой $request->getParameter(‘id’)?Сегодня мы ответим на все эти вопросы.
Но для начала давайте поговорим об URLах, и что конкретно они из себя представляют. В контексте вебаURL — уникальный идентификатор веб-ресурса. Когда вы переходите на URL, выпросите браузер извлечь ресурс по этому URLу. Так как URL являетсяинтерфейсом между веб-сайтом и пользователем, он должен передавать некоторуюинформацию о ресурсе, который содержит. Но «традиционные» URLы вовсе неописывают ресурс, они показывают внутренюю структуруприложения. Пользователю совершенно не интересно, что ваш сат разработанна PHP или что работа имеет определенный идентификатор в базе данных.Показывать внутренюю работу вашего приложения так же плохо из соображенийбезопасности: что если пользователь попытается угадать URL для ресурсов,к которым не имеет доступ? Конечно разработчик должен защитить их должным образом,но вам лучше скрыть эту информацию.
URLы настолько важны в symfony, что целый фреймворк выделенпод их управление: routing framework. Маршрутизация управляется внутреннимиURI и внешними URL. Когда приходит запрос, маршрутизация парсит URLи конвертирует его во внутренний URI.
Вы уже видели внутренний URI страницы работы вшаблоне showSuccess.php:
‘job/show?id=’.$job->getId()
Хелпер url_for() конвертирует этот внутренний URI в соответствующий URL:
/job/show/id/1
Внутренний URI состоит из нескольких частей: модуль job, экшн showи строка дополнительных параметров, передаваемых в экшн. Общийшаблон для внутреннего URI:
MODULE/ACTION?key=value&key_1=value_1&…
Так как в symfony маршрутизация двухстороняя, вы можете изменить URLы безизменения технической реализации. Это одно из главных преимуществшаблона проектирования front-controller.
Настройка маршрутизации
Сопостовление внутренних URI и внешних URLов выполняется вконфигурационном файле routing.yml:
# apps/frontend/config/routing.ymlhomepage: url: / param: { module: default, action: index } default_index: url: /:module param: { action: index } default: url: /:module/:action/*
Файл routing.yml описывает маршруты. Маршрут содержит имя (homepage),шаблон (/:module/:action/*) и некоторые параметры (под ключем param).
Когда приходит запрос, маршрутизация пытается подставить полученый URLв шаблон. Первый подошедший по порядку в routing.ymlважнее. Давайте рассмотрим несколько примеров, чтобы лучше понять как этоработает.
Когда вы запрашиваете главную страницу Jobeet с URLом /job, первыйподходящий маршрут будет default_index. В шаблоне слово начинающеесяс двоеточия (:) — переменная, так шаблон /:module значит, что после /что-то есть. В нашем примере переменная module будет содержать значение job.Это значение может быть получено командой$request->getParameter(‘module’) в экшне. Этот маршрут также определяетзначение по умолчанию для переменной action. Таким образом, для всех URLов подходящим под этот маршрут,запрос так же будет содержать параметр action со значением index.
Если запросить страницу /job/show/id/1, symfony подберет такой шаблон шаблон:/:module/:action/*. В шаблоне звездочка (*) означает коллекциюпар переменная-значение, разделенных слешем (/):
| module | job |
| action | show |
| id | 1 |
Переменные module и action специальные — они используются symfony для определения запускаемого экшна.
URL /job/show/id/1 можно создать из шаблона, используя хелпер url_for():
url_for(‘job/show?id=’.$job->getId())
Вы также можете использовать имя маршрута с префиксом @:
url_for(‘@default?module=job&action=show&id=’.$job->getId())
Оба вызова эквивалентны, но последний более быстр, т.к. не нужно парситьвсе маршруты, чтобы найти совпадение, и меньше привязанк реализации (модуль и экшн не содержатся во внутреннем URI).
Настройка маршрута
Теперь, когда вы запросили в браузере URL /, вы по умолчанию получитестраницу поздравлений symfony. Это происходит, потому что этот URL совпадает с маршрутом homepage.Но имеет смысл изменить её на стартовую Jobeet. Чтобы сделать это,измените переменную module маршрута homepage на job:
# apps/frontend/config/routing.ymlhomepage: url: / param: { module: job, action: index }
Теперь мы можем изменить ссылку с логотипа Jobeet в layout, используямаршрут homepage:
<! — apps/frontend/templates/layout.php — ><h1> <a href=«<?php echo url_for(‘@homepage’) ?>»> <img src=«/images/jobeet.gif» alt=«Jobeet Job Board» /> </a></h1>
Это просто! Для чего то более стоящего давайте изменим URL страницы работына что-нибудь более значимое:
/job/sensio-labs/paris-france/1/web-developer
Не зная о Jobeet и не видя страницу, выможете понять из URLа, что Sensio Labs ищет веб-разработчика дляработы в Париже во Франции.
Красивые URLы важны, потому что они передают информацию пользователю. Они так же полезны при копи-пасте в email или для оптимизации вашего веб-сайта в поисковых системах.
Такой URL совпадает с шаблоном:
/job/:company/:location/:id/:position
Отредактируйте файл routing.yml и добавьте маршрут job_show_user в началофайла:
job_show_user: url: /job/:company/:location/:id/:position param: { module: job, action: show }
Если обновить главную страницу Jobeet, ссылки на работу не изменились,потому что для генерации маршрута, вы должны указать все необходимые переменные. Таким образом,вам нужно изменить вызов url_for() в indexSuccess.php на:
url_for(‘job/show?id=’.$job->getId().‘&company=’.$job->getCompany(). ‘&location=’.$job->getLocation().‘&position=’.$job->getPosition())
Внутренний URI так же может быть представлен как массив:
url_for(array( ‘module’ => ‘job’, ‘action’ => ‘show’, ‘id’ => $job->getId(), ‘company’ => $job->getCompany(), ‘location’ => $job->getLocation(), ‘position’ => $job->getPosition(),))
Требования
В течение первого дня мы говорили, что валидация и обработкаошибок является хорошим тоном. В система маршрутизации есть встроеная валидация.Каждая переменная шаблона может быть проверена регулярным выражением,определенным пользователем в пункте requirements в описании маршрута:
job_show_user: url: /job/:company/:location/:id/:position param: { module: job, action: show } requirements: id: \d+
Приведенный выше пункт requirements говорит, что id должно быть числовым значением.Если нет — маршрут не подходит.
Класс маршрута
Каждый маршрут, определенный в routing.yml, внутри преобразуется в объекткласса sfRoute. Этот классможет быть изменен определением пункта class в описании маршрута. Если вызнакомы с протоколом HTTP, вы знаете, что он определяет несколько «методов»,таких как GET, POST, HEAD, DELETE и PUT. Первые триподдерживаются всеми браузерами, два других — нет.
Чтобы ограничить маршрут только для некоторых методов, вы можете изменитькласс маршрута на sfRequestRoute идобавить требования для виртуальной переменной sf_method:
job_show_user: url: /job/:company/:location/:id/:position class: sfRequestRoute param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
Требования маршрута ограничиться некоторыми HTTP-методами не полностью эквивалентно использованию sfWebRequest::isMethod() в ваших экшнах, потому что маршрутизация продолжит поиск маршрута, если метод не подходит.
Объект класса маршрута
Новый внутренний URI для работы довольно долго и утомительно писать(url_for(‘job/show?id=’.$job->getId().‘&company=’.$job->getCompany().‘&location=’.$job->getLocation().‘&position=’.$job->getPosition())),но, как мы только что узнали в предыдущем разделе, класс маршрута может бытьизменен. Для маршрута job_show_user, будет лучше использоватьsfPropelRoute каккласс, оптимизированный для маршрутов, которые представляют объекты Propel или коллекциюобъектов Propel:
job_show_user: url: /job/:company/:location/:id/:position class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
Пункт options настраивает поведение маршрута. Здесь опция modelопределяет класс модели Propel (JobeetJob), связанной с маршрутом, иопция type определяет, что этот маршрут связан с один объектом (вы так же можете указатьlist, если маршрут представляет коллекцию объектов).
Маршрут job_show_user теперь связан с JobeetJob и таким образоммы можем проще вызывать url_for():
url_for(array(‘sf_route’ => ‘job_show_user’, ‘sf_subject’ => $job))
или просто:
url_for(‘job_show_user’, $job)
Первый вариант предпочтительней, когда вам нужно передать больше параметров, чем просто объект.
Это сработает, потому что для всех переменных в маршруте есть соответсвующий аксессор вклассе JobeetJob (например, переменная company приметзначение функции getCompany()).
Посмотрите на сгенерированые URLы, они не совсем такие, как мы хотим:
http://jobeet.localhost/frontend_dev.php/job/Sensio+Labs/Paris%2C+France/1/Web+Developer
Нам нужно обработать (slugify) столбец значений, заменив все не ASCII-символына -. Откройте файл JobeetJob и добавьте следущий методв класс:
// lib/model/JobeetJob.phppublic function getCompanySlug(){ return Jobeet::slugify($this->getCompany());} public function getPositionSlug(){ return Jobeet::slugify($this->getPosition());} public function getLocationSlug(){ return Jobeet::slugify($this->getLocation());}
Затем создадим файл lib/Jobeet.class.php и добавим в него метод slugify:
// lib/Jobeet.class.phpclass Jobeet{ static public function slugify($text) { // replace all non letters or digits by - $text = preg_replace(‘/\W+/’, ‘-’, $text); // trim and lowercase $text = strtolower(trim($text, ‘-’)); return $text; }}
В этом учебнике мы никогда не пишем открывающий тег <?php в примерах, которые содержат только чистый PHP код, чтобы сэкономить место и сохранить несколько деревьев. Вы, очевидно, должны не забывать добавлять его при создании нового PHP-файла.
Мы определили три новых «виртуальных» аксессора: getCompanySlug(),getPositionSlug() и getLocationSlug(). Они возвращают значение соответствующегостолбца после обработки методом slugify(). Теперь вы можете заменитьнастоящие имена столбцов на виртуальные в маршруте job_show_user:
job_show_user: url: /job/:company_slug/:location_slug/:id/:position_slug class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
Перед обновлением главной страницы Jobeet, вам нужно почистить свой кэш, т.к. мыдобавили новый класс (Jobeet):
$ php symfony cc
Теперь у вас ожидемые URLы:
http://jobeet.localhost/frontend_dev.php/job/sensio-labs/paris-france/1/web-developer
Но это лишь половина истории. Маршрут способен не только генерировать URL, основаныйна объекте, но и находить объект, относящийся к URLу. Связаныйобъект можно получить методом getObject() объекта маршрута.При обработке входящего запроса, маршрутизация хранит объект совпавшего маршрутадля использования вами в экшнах. Таким образом, изменим метод executeShow(),используя объект маршрута для получения объекта Jobeet:
class jobActions extends sfActions{ public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); $this->forward404Unless($this->job); } // …}
Если вы попытаетесь получить работу с неизвестным id, вы увидете страницу офибки 404, носообщение об ошибке изменилось:

Это произошло потому, что ошибка 404 была выброшена автоматически из методаgetRoute(). Таким образом, мы можем упростить метод executeShow ещё больше:
class jobActions extends sfActions{ public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); } // …}
Если вы не хотите, чтоб маршрут генерировал ошибку 404, вы можете установить опцию маршрутизации allow_empty в значение true.
Связаный объект загружается лениво, т.е. он будет получен из базы данных только если вызвать метод getRoute().
Маршрутизация в экшнах и шаблонах
В шаблоне хелпер url_for() преобразует внутренний URI во внешний URL. Некоторыедругие хелперы symfony также принимают внутренний URI в качестве аргумента, напримерхелпер link_to(), которые генерирует тэг <a>:
<?php echo link_to($job->getPosition(), ‘job_show_user’, $job) ?>
Он сгенерирует такой HTML-код:
<a href=«/job/sensio-labs/paris-france/1/web-developer»>Web Developer</a>
url_for() и link_to() так же могут генерировать абсолютные URLы:
url_for(‘job_show_user’, $job, true); link_to($job->getPosition(), ‘job_show_user’, $job, true);
Еси вы хотите сгенерировать URL из экшна, вы можете использовать метод generateUrl():
$this->redirect($this->generateUrl(‘job_show_user’, $job));
Семейство методов «redirect»
Во вчерашнем уроке мы говорили о «forward» методах. Эти методы перенаправляют текущий запрос в другой экшн без редиректа.
Методы «redirect» перенаправляют пользователя на другой URL. Как в случае с forward, вы можете использовать метод redirect() или короткие методы redirectIf() и redirectUnless().
Класс коллекции маршрута
Для модуля job у нас уже настроен маршрут для экшна show, ноURLы для других методов (index, new, edit, create, update,и delete) всё ещё управляются маршрутом default:
default: url: /:module/:action/*
Маршрут default — отличный способ начать программировать без обьявления множествамаршрутов. Но маршрут «ловит-всё» и не может быть настроен дляконкретный нужд.
Так как все экшны job связаны с классом модели JobeetJob, мы можем легкоопределить своё маршрут sfPropelRoute для каждого, как мы уже сделали для экшнаshow. Но так как модуль job определяет классические семь возможных действийдля модели, мы можем так же использовать классsfPropelRouteCollection.Откройте файл routing.yml и измените его следущим образом:
# apps/frontend/config/routing.ymljob: class: sfPropelRouteCollection options: { model: JobeetJob } job_show_user: url: /job/:company_slug/:location_slug/:id/:position_slug class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: show } requirements: id: \d+ sf_method: [get] # default ruleshomepage: url: / param: { module: job, action: index } default_index: url: /:module param: { action: index } default: url: /:module/:action/*
Маршрут job выше на самом деле просто ярлык, который автоматически сгенерируетследущие семь маршрутов sfPropelRoute:
job: url: /job.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: list } param: { module: job, action: index, sf_format: html } requirements: { sf_method: get } job_new: url: /job/new.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: new, sf_format: html } requirements: { sf_method: get } job_create: url: /job.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: create, sf_format: html } requirements: { sf_method: post } job_edit: url: /job/:id/edit.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: edit, sf_format: html } requirements: { sf_method: get } job_update: url: /job/:id.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: update, sf_format: html } requirements: { sf_method: put } job_delete: url: /job/:id.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: delete, sf_format: html } requirements: { sf_method: delete } job_show: url: /job/:id.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: show, sf_format: html } requirements: { sf_method: get }
Некоторые маршруты, которые сгенерировал sfPropelRouteCollection, имеют одинаковые URLы. Маршрутизация по прежнему будет использовать их все, поскольку все они имеют разные HTTP-методы в требованиях.
Маршруты job_delete и job_update требуют HTTP-методы, которые не поддерживаютбраузеры (DELETE и PUT соответственно). Но они работают, потому чтоsymfony симулирует их. Откройте шаблон _form.php и посмотрите пример:
// apps/frontend/modules/job/templates/_form.php<form action=«…» …><?php if (!$form->getObject()->isNew()): ?> <input type=«hidden» name=«sf_method» value=«PUT» /><?php endif; ?> <?php echo link_to( ‘Delete’, ‘job/delete?id=’.$form->getObject()->getId(), array(‘method’ => ‘delete’, ‘confirm’ => ‘Are you sure?’)) ?>
Всем хелперам symfony можно сказать симулировать нужный HTTP-методиспользуя специальный параметр sf_method.
В symfony есть много других специальных параметров, таких как sf_method, все начинаются с префикса sf_. В сгенерированых маршрутах выше, вы можете увидить ещё один: sf_format, который будет рассмотрен в последующих днях.
Отладка маршрута
При использовании коллекции маршрутов, иногда бывает полезно посмотреть сгенерированыемаршруты. Команда app:routes выводит все маршруты для приложения:
$ php symfony app:routes frontend
Вы также можете посмотреть много отладочной информации для маршрута, указав егоимя в качестве дополнительного аргумента:
$ php symfony app:routes frontend job_edit
Маршруты по умолчанию
Определение маршрута для всех ваших URLов является хорошим тоном. Так как маршрут jobопределяет все маршруты, необходимые для описания приложения Jobeet, вернёмся и удалим или закомментируем маршруты по умолчанию из конфигурационного файла routing.yml:
# apps/frontend/config/routing.yml#default_index:# url: /:module# param: { action: index }##default:# url: /:module/:action/*
Приложение Jobeet должно так же работать как и раньше.
Увидимся завтра
Сегодня было много новой информации. Вы узнали, как использовать фрейворк маршрутизации в symfony и как отделить ваши URLы оттехнической реализации.
Завтра мы не будем вводить какие-либо новые понятия, а потратим время науглубление в уже пройденое.
Коментарии:
Спасибо большое за пояснение использования фрейворка маршрутизации в symfony. Бьюсь над єтим уже вторую неделю, накачал себе кучу инфы и обучалок с рапидшары , но самому в них разобраться довольно-таки сложно… У вас как-то все попроще и ближе стандартному человеческому уму!
ответитьС выходом второй версии фреймворка готовится новый материал. В новой версии куча изменений, в том числе и в маршрутизации. Следите за нашим блогом! :)
ответить



