Symfony 2 Joboard: Маршрутизация

Если вы обратите внимание на URL домашней страницы Joboard, то в адресной строке вы увидите: /job/1/show. Наверняка, вы привыкли видеть адреса в формате /job.php?id=1. Так как же Symfony определяет какое действие необходимо выполнить исходя из заданного адреса? Почему переменная $idнаходится там, где должно указываться действие, а не параметр? Попробуем разобраться.

Следующий код вы уже видели в шаблоне src/App/JoboardBundle/Resources/views/Job/index.html.twig.

{{ path(‘app_job_show’, { ‘id’: entity.id }) }}

При помощи функции path Symfony генерирует необходимый нам путь для вывода статьи с идентификатором 1. app_job_show — имя маршрута заданное в настройках фреймворка, которые мы увидим чуть ниже.

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

Обычно, настройка маршрутов Symfony2 задается в файле app/config/routing.yml. В нем указывается ссылка на настройки маршрутизации определённого бандла Symfony2. В нашем случае это — src/App/JoboardBundle/Resources/config/routing.yml:

# app/config/routing.yml
app_joboard:
    resource: "@AppJoboardBundle/Resources/config/routing.yml"
    prefix:   /

Если вы обратите внимание на содержание файла routing.yml в бандле JoboardBundle, вы не сможете не заметить, что он в свою очередь импортирует другой файл настройки маршрутизации, а именно — настройки для определённого контроллера, а также определяет маршрут AppJoboardBundle_homepageдля адреса /hello/{name}:

# src/App/JoboardBundle/Resources/config/routing.yml
AppJoboardBundle_job:
    resource: "@AppJoboardBundle/Resources/config/routing/job.yml"
    prefix: /job
app_joboard_homepage:
    pattern:  /hello/{name}
    defaults: { _controller: AppJoboardBundle:Default:index }
# src/App/JoboardBundle/Resources/config/routing/job.yml
app_job:
    pattern:  /
    defaults: { _controller: "AppJoboardBundle:Job:index" }
app_job_show:
    pattern:  /{id}/show
    defaults: { _controller: "AppJoboardBundle:Job:show" }
app_job_new:
    pattern:  /new
    defaults: { _controller: "AppJoboardBundle:Job:new" }
app_job_create:
    pattern:  /create
    defaults: { _controller: "AppJoboardBundle:Job:create" }
    requirements: { _method: post }
app_job_edit:
    pattern:  /{id}/edit
    defaults: { _controller: "AppJoboardBundle:Job:edit" }
app_job_update:
    pattern:  /{id}/update
    defaults: { _controller: "AppJoboardBundle:Job:update" }
    requirements: { _method: post|put }
app_job_delete:
    pattern:  /{id}/delete
    defaults: { _controller: "AppJoboardBundle:Job:delete" }
    requirements: { _method: post|delete }

Давайте разберемся подробнее относительно маршрута app_job_show. Паттерн заданный маршрутом app_job_show определяет адрес /{id}/show, где знак {id} является идентификатором вакансии. Для адреса /1/show, id получает значение 1, который передаётся в качестве параметра контроллеру. Параметр _controller — задает имя контроллера, соответствующего маршруту. В нашем случает это showAction из JobController в AppJoboardBundle.

Следует обратить особое внимание на параметры (например {id}), так как именно к ним у нас будет доступ из метода контроллера.

<?php
public function showAction($id)
{
    // ...
}

Настройка маршрутизации в среде dev

В среде dev автоматически загружается файл app/config/routing_dev.yml, который среди прочих содержит в себе маршруты Web Debug Toolbar. Его загрузка объявлена в настройках dev окружения app/config/config_dev.yml.

Так по адресу http://joboard.local/app_dev.php расположена первая страница Symfony 2, которую вы видите после установки фреймворка. Давайте сделаем так, что в среде dev мы так же будем видеть те же страницы, что и в других средах. Откройте файл app/config/routing_dev.yml и удалите первые три маршрута, которые относятся к бандлу Symfony demo bundle. Вот содержимое файла после редактирования:

# app/config/routing_dev.yml
_wdt:
    resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml"
    prefix:   /_wdt
_profiler:
    resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml"
    prefix:   /_profiler
_configurator:
    resource: "@SensioDistributionBundle/Resources/config/routing/webconfigurator.xml"
    prefix:   /_configurator
_main:
    resource: routing.yml

Не забудьте очистить кэш после этого.

php app/console cache:clear --env=prod
php app/console cache:clear --env=dev

Детальная настройка маршрутов

На данный момент перейдя по адресу / мы получим ошибку 404. Так получилось, потому что с этим адресом не связан ни один маршрут. У нас есть маршрут app_joboard_homepage, который соответствует адресу /hello/joboard и методу indexAction из DefaultController. Направим этот маршрут на адрес /. Измените файл src/App/JoboardBundle/Resources/config/routing.yml в соответствии:

# src/App/JoboardBundle/Resources/config/routing.yml
# ...
app_joboard_homepage:
    pattern:  /
    defaults: { _controller: AppJoboardBundle:Job:index }

Теперь очистите кэш (для prod и dev окружения и если возникает необходимость чистить кэш, то всегда делайте это для всех существующих сред окружения), перейдите по адресу http://joboard.local/app_dev.php/ и вы увидите свою домашнюю страницу. Давайте воспользуемся преимуществами маршрутизации и изменим адрес логотипа в файле шаблона. Найдите блок <div class="col-lg-4"> и поменяйте его на следующий код:

<!-- src/Resources/views/base.html.twig -->
<!-- ... -->
<div class="col-lg-4">
    <a id="logo" href="{{ path('app_joboard_homepage') }}">
       Joboard
    </a>
</div>
<!-- ... -->

Теперь изменим сам адрес на /job/company-name/location-name/1/position-name. Зачем мы изменили адрес? Такой адрес является более информативным, как для человека так и для поисковых систем (SEO friendly). Поправьте маршрут app_job_show из файла src/App/JoboardBundle/Resources/config/routing/job.yml

# src/App/JoboardBundle/Resources/config/routing/job.yml
# ...
app_job_show:
    pattern:  /{company}/{location}/{id}/{position}/
    defaults: { _controller: "AppJoboardBundle:Job:show" }

Нам необходимо передать все параметры заявленные в маршруте app_job_show. Замените маршрут {{ path('app_job_show', { 'id': entity.id }) }} на следующий:

<!-- src/App/JoboardBundle/Resources/views/Job/index.html.twig -->
<!-- ... -->
<a href="{{ path('app_job_show', { 'id': entity.id, 'company': entity.company, 'location': entity.location, 'position': entity.position }) }}">
  {{ entity.position }}
</a>
<!-- ... -->

И все-таки адреса пока ещё не приобрели нужного нам формата, на данный момент они выглядят так: http://joboard.local/app_dev.php/job/ООО Компания/Москва/1/Web Разработчик/

Нам нужно заменить все пробелы на символ . Если мы этого не сделаем, то адрес будет выглядеть вот так: http://joboard.local/app_dev.php/job/ООО%20Компания/Москва/1/Web%20Разработчик/, т.е. браузер заменит пробелы на символы %20 и это будет выглядеть совершенно не читабельно.

Откройте файл src/App/JoboardBundle/Entity/Job.php и добавьте следующие методы.

<?php
// src/App/JoboardBundle/Entity/Job.php
// ...
use App\JoboardBundle\Utils\Joboard as Joborad;
class Job
{
    // ...
    public function getCompanySlug()
    {
        return Joborad::slugify($this->getCompany());
    }
    public function getPositionSlug()
    {
        return Joborad::slugify($this->getPosition());
    }
    public function getLocationSlug()
    {
        return Joborad::slugify($this->getLocation());
    }
}

Создайте файл src/App/JoboardBundle/Utils/Joboard.php и добавьте в него следующий метод

<?php
// src/App/JoboardBundle/Utils/Joboard.php
namespace App\JoboardBundle\Utils;
class Joboard
{
    static public function slugify($text)
    {
        // Замена пробелов на тире
        $text = preg_replace('/ +/', '-', $text);
        // Приведение текста к нижнему регистру
        $text = mb_strtolower(trim($text, '-'), 'utf-8');
        return $text;
    }
}

Мы определили три новых статичных метода: getCompanySlug(), getPositionSlug(), и getLocationSlug(). Они возвращают соответствующие значение столбца после применения метода slugify(). Теперь мы можем заменить настоящие названия колонок на только что созданные.

<!-- src/App/JoboardBundle/Resources/views/Job/index.html.twig -->
<!-- ... -->
<a href="{{ path('app_job_show', { 'id': entity.id, 'company': entity.companyslug, 'location': entity.locationslug, 'position': entity.positionslug}) }}">
  {{ entity.position }}
</a>
<!-- ... -->

Немного поясню, что у нас происходит в коде выше. entity — представляет собой объект модели вакансии (Job.php). Этот объект создаётся в контроллёре (JobController метод showAction) и передаётся в шаблон вакансии. entity.id означает, что вызывается метод getId() класса Job.php, соответственно entity.companyslug вызывает метод companySlug и т.д. В результате имеем: http://joboard.local/app_dev.php/job/ооо-компания/москва/1/web-разработчик/

Требования маршрута

Маршрутизация содержит в себе систему проверки параметров. Каждый параметр можно проверить при помощи регулярных выражений заданных в параметре маршрута requirements.

# src/App/JoboardBundle/Resources/config/routing/job.yml
# ...
app_job_show:
    pattern:  /{company}/{location}/{id}/{position}
    defaults: { _controller: "AppJoboardBundle:Job:show" }
    requirements:
        id:  \d+

По этому правилу маршрут в качестве параметра id примет только целочисленное значение.

Отладка маршрутов

После добавления большого количества маршрутов полезно просмотреть всю информацию о них. Отличный способ — консольная команда router:debug. Попробуйте выполнить её:

php app/console router:debug app_job_show

Напоследок

На сегодня достаточно. Если у вас остались вопросы, то ответы на них вы найдете прочитав главу Маршрутизация из документации или спрашивайте в комментариях.