Symfony 4: маршрутизация, контроллеры и шаблоны

Symfony 4 продолжает обретать репутацию высокоэффективного фреймворка в сообществе PHP. Даже те разработчики, которые не пробовали его ранее, теперь дают ему шанс. У Мэттью Сэттера есть отличная история о его опыте работы с Symfony 4.

В моих прошлых статьях я рассуждал о том, как установить Symfony 4 на Cloudways и создание системы рецептов с использованием Symfony Flex. В этот раз я исследую Symfony 4 глубже и буду рассуждать о создании маршрутов, контроллеров и шаблонов twig. Я также представлю новую структуру директорий Symfony 4 и покажу вам, как можно регистрировать бандлы и создавать файлы с папками для ваших шаблонов и контроллеров.

Структура директорий

Symfony 4.x имеет новую и более упрощенную структуру директорий, которая позволяет разработчикам писать приложения с более точной структурой. Symfony Flex также основан на ней. Вот как выглядит эта чудесно простая структура:

5137d23fb9791ad9782330c38b91.png

Обратите внимание на папку config, которая содержит bundles.php. Flex зарегистрирует все бандлы автоматически после установки. Папка plublic  обеспечивает доступ к приложению через index.php, тогда как папка src содержит контроллеры и объекты.

Создаём маршруты в Symfony 4

Если вы реализовывали маршрутизацию в стандартных версиях Symfony, то вам нужно было сделать работу вроде такой:

blog_list:
   path:     /blog
   defaults: { _controller: AppBundle:Blog:list }
blog_show:
   path:     /blog/{slug}
   defaults: { _controller: AppBundle:Blog:show }

В Symfony 3.x вы даёте маршруту имя и путь с соответствующими контроллерами и настройками по умолчанию. Настройки были определены как { _controller: AppBundle:Blog:show }.

В Symfony 4 же вам просто надо найти config/routes.yml и добавить маршруты к этому файлу. Давайте теперь посмотрим, как вы можете определить первый маршрут:

index:
   path: /
   controller: App\Controller\DefaultController::index

Вы можете просто определить имя, путь и контроллер, который должен быть вызван. Также обратите внимание на гораздо более упрощенную структуру контроллера [App\Controller\DefaultController::index], которая теперь определяет только  пространство имен и класс контроллера вместе с методом вызова. Да, это выглядит, как маршрутизация в Laravel. Аналогично, если вам надо передать параметры или сделать что-нибудь динамическое, вы можете создать маршрут типа:

blog_show:
   path: /blog/{slug}
   controller: App\Controller\DefaultController::show

Этот код будет брать параметры URL запроса и передавать их в метод контроллера show(). Очевидно, что вы определите функционал в этом методе. Если вы ранее определяли такие же маршруты, которые совпадают (такие, как /blog/{page} & /blog/{slug}), они будут совпадать с /blog/*, так что вы сможете определить требования для маршрута, которые содержат регулярные выражения.

Предположим, что /blog/{page} будет иметь дело с числами страницы. Вы можете определить ее так:

blog_list:
    path:    /blog/{page}
    controller: App\Controller\BlogController::list
    requirements:
        page: '\d+'

page: '\d+' определит параметр как число.

Создаем контроллер и маршруты с аннотациями в Symfony 4

Для создания маршрутов с аннотациями в Symfony 4 вам первым делом потребуется установить библиотеку Annotation. Используйте для этого Composer.

composer require annotations

Как только установка завершится, вы сможете создавать маршруты с аннотациями в файле DefaultController.php. Создайте файл в src/Controller и добавьте в него следующий код:

<?php
namespace App\Controller;
//необходимо добавить эти пространства имен
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class DefaultController
{
   /**
    * @Route("/", name="index")
    */
   public function index()
   {
       return new Response('This is the first Symfony4 page!');
   }
     /**
    * @Route("/blogs", , name="blog_list")
    */
   public function blogs()
   {
       return new Response('All blogs will show here');
   }
   /**
    * @Route("/blogs/{slug}", , name="blog_show")
    */
   public function show($slug)
   {
       return new Response(sprintf(
           'Future page to show the article: "%s"', $slug
       ));
   }
}

Итак, все маршруты работают нормально. Вы даже можете создать более продвинутые и сложные для разных вариантов использования.

Просмотр всех маршрутов

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

php bin/console debug:route

Создаём шаблоны twig в Symfony 4 и работаем с ними

В примере выше я вернул простой ответ в виде строки представлению контроллера в Symfony 4. Вы можете вернуть HTML-представления с twig-шаблонным движком.

Twig — это любовь

Вы можете сделать целую кучу интересных вещей с помощью twig. Вам нужно будет установить библиотеку Twig для создания шаблонов в Symfony 4. Запустите следующую команду Composer:

composer require twig

Вы можете увидеть, что Flex установил (и зарегистрировал бандл в bundles.php) twig-бандл.

Также, если вы обратите внимание на корневую директорию проекта, вы заметите новую папку, названную templates, в которой есть файл base.html.twig.  Также был добавлен файл twig.yml  в config/packages/twig.yaml.

Теперь давайте добавим новый twig-файл в папку templates, назовем его article.html.twig и добавим в него следующим простой код:

<h1>{{ title }}</h1>
<div>
   <p>
       Bacon ipsum dolor amet filet mignon picanha kielbasa jowl hamburger shankle biltong chicken turkey pastrami cupim pork chop. Chicken andouille prosciutto capicola picanha, brisket t-bone. Tri-tip pig pork chop short ribs frankfurter pork ham. Landjaeger meatball meatloaf, kielbasa strip steak leberkas picanha swine chicken pancetta pork loin hamburger pork.
   </p>
   <p>
       Kielbasa pork belly meatball cupim burgdoggen chuck turkey buffalo ground round leberkas cow shank short loin bacon alcatra. Leberkas short loin boudin swine, ham hock bresaola turducken tail pastrami picanha pancetta andouille rump landjaeger bacon. Pastrami swine rump meatball filet mignon turkey alcatra. Picanha filet mignon ground round tongue ham hock ball tip tri-tip, prosciutto leberkas kielbasa short loin short ribs drumstick. Flank pig kielbasa short loin jerky ham hock turducken prosciutto t-bone salami pork jowl.
   </p>
   <p>
       Pastrami short loin pork chop, chicken kielbasa swine turducken jerky short ribs beef. Short ribs alcatra shoulder, flank pork chop shankle t-bone. Tail rump pork chop boudin pig, chicken porchetta. Shank doner biltong, capicola brisket sausage meatloaf beef ribs kevin beef rump ribeye t-bone. Shoulder cupim meatloaf, beef kevin frankfurter picanha bacon. Frankfurter bresaola chuck kevin buffalo strip steak pork loin beef ribs prosciutto picanha shankle. Drumstick prosciutto pancetta beef ribs.
   </p>
</div>

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

/**
   * @Route("/articles/{slug}", name="blog_show")
   */
  public function myView($slug) {
    return $this->render('articles/first.html.twig', [
          'title' => ucwords(str_replace('-', ' ', $slug)),
      ]);
  }

Этот метод уберет «-» из URL и покажет заголовок переменной в теге <title>. Давайте запустим маршрут в браузере:

9e977b5bd53eefbdced0318ed96b.png

Он работает хорошо, но выглядит немного грубовато. Теперь вы можете отрендерить ваши шаблоны twig в Symfony 4. Я сейчас интегрирую шаблон base.html.twig в first.view.html. Поскольку некоторые функциональные возможности шаблонов общие, вы можете объявить его однажды в базовом шаблоне и отрендерить их в каждом дочернем шаблоне.

Добавьте следующую строчку на самый верх first.html.twig:

{% extends 'base.html.twig' %}

В базовом шаблоне компоненты дочернего главным образом показываются в блоке body, так что вам надо запретить дочернему шаблону отображаться в блоке базового. Это можно сделать, перегрузив шаблон в блоке body:

{% block body %}
// ваш шаблон будет здесь
{% endblock %}

Теперь можно интегрировать Bootstrap, чтобы придать HTML страницам хороший вид. Вы можете легко добавить его в блок stylesheet, который будет добавлен в тег <head>:

{% block stylesheets %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css">
{% endblock %}