Skip to content

Создание контроллера

Наш проект гостевой книги уже работает в продакшене, однако мы немного схитрили. У проекта пока ещё нет ни одной веб-страницы, а на главной странице по-прежнему отображается ошибка 404. Давайте исправим это.

Когда приходит HTTP-запрос, например, на главную страницу (http://localhost:8000/), Symfony пытается найти маршрут , соответствующий пути запроса (/ в данном случае). Маршрут — это связующее звено между путём запроса и callback-функцией PHP, которая создаёт ответ HTTP для этого запроса.

Эти callback-функции PHP называются "контроллерами". В Symfony большинство контроллеров реализовано в виде PHP-классов. Конечно, вы можете вручную создать такой класс, но для быстроты разработки давайте посмотрим, как Symfony в этом может нам помочь.

Ускорение разработки с помощью бандла Maker

Для лёгкой генерации контроллеров мы можем использовать пакет symfony/maker-bundle, который был установлен как часть пакета webapp.

Бандл Maker поможет вам сгенерировать множество различных классов. Мы будем использовать его на протяжении всей книги. Каждый "генератор" определён в отдельной команде, при этом все команды принадлежат одному и тому же пространству имён команды make.

Компонент Symfony Console имеет встроенную команду list, которая выводит список всех команд в указанном пространстве имен; используйте её, чтобы посмотреть все доступные генераторы бандла Maker:

1
$ symfony console list make

Выбор формата конфигурации

Перед созданием нашего первого контроллера в проекте, нам необходимо определиться с используемыми форматами для написания конфигураций. По умолчанию Symfony поддерживает YAML, XML, PHP и PHP-атрибуты.

YAML идеален для конфигурации пакетов. Этот формат будет использоваться в файлах директории config/. Зачастую, когда вы устанавливаете новый пакет, рецепт этого пакета добавляет новый файл с расширением .yaml в эту директорию.

Для конфигурации, связанной с PHP-кодом, лучше всего подойдут атрибуты, поскольку они указываются рядом с кодом. Сейчас объясню на примере. Когда приходит запрос, определённая конфигурация должна передать Symfony, что текущий путь запроса должен обрабатываться соответствующим контроллером (то есть PHP-классом). При использовании конфигурационных форматов на YAML, XML или PHP потребуется включать два файла (сам конфигурационный файл и PHP-файл контроллера). В то же время с помощью атрибутов можно всё сконфигурировать непосредственно в самом классе контроллера.

Вам может быть интересно узнать, какой именно пакет нужно установить, чтобы добавить поддержку той или иной функциональной возможности? Как правило, вам не нужно этого знать. Поскольку в большинстве случаев Symfony показывает отсутствующий пакет в сообщении об ошибке. К примеру, выполнение команды symfony make:message без установленного пакета messenger выбросит исключение с подсказкой о том, какой пакет следует установить.

Генерация контроллера

Создайте свой первый контроллер с помощью команды make:controller:

1
$ symfony console make:controller ConferenceController

Команда создает класс ConferenceController в директории src/Controller/. Сгенерированный класс содержит немного шаблонного кода, который может быть изменён:

src/Controller/ConferenceController.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ConferenceController extends AbstractController
{
    #[Route('/conference', name: 'conference')]
    public function index(): Response
    {
        return $this->render('conference/index.html.twig', [
            'controller_name' => 'ConferenceController',
        ]);
    }
}

С помощью атрибута #[Route('/conference', name: 'conference')] метод index() становится контроллером (его объявление вместе с конфигурацией находится непосредственно над кодом).

При переходе в браузере по пути /conference выполняется контроллер, который возвращает HTTP-ответ.

Настройте маршрут так, чтобы он соответствовал главной странице:

1
2
3
4
5
6
7
8
9
10
11
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -8,7 +8,7 @@ use Symfony\Component\Routing\Annotation\Route;

 class ConferenceController extends AbstractController
 {
-    #[Route('/conference', name: 'conference')]
+    #[Route('/', name: 'homepage')]
     public function index(): Response
     {
         return $this->render('conference/index.html.twig', [

Параметр маршрута name пригодится нам, когда понадобится получить ссылку на главную страницу. Таким образом, вместо жёстко заданного в коде пути /, мы будем использовать имя маршрута.

Теперь вернём обычный HTML-код вместо шаблона по умолчанию:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -11,8 +11,13 @@ class ConferenceController extends AbstractController
     #[Route('/', name: 'homepage')]
     public function index(): Response
     {
-        return $this->render('conference/index.html.twig', [
-            'controller_name' => 'ConferenceController',
-        ]);
+        return new Response(<<<EOF
+<html>
+    <body>
+        <img src="/images/under-construction.gif" />
+    </body>
+</html>
+EOF
+        );
     }
 }

Перезагрузите главную страницу в браузере:

/

Основная задача контроллера — вернуть объект Response в качестве ответа на HTTP-запрос.

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

1
2
$ git add .
$ git commit -m'Add the index controller'

Добавление пасхального яйца

Для демонстрации того, как в ответе можно использовать информацию из запроса, давайте добавим небольшое пасхальное яйцо. При переходе на главную страницу, если в строке запроса мы находим что-то типа ?hello=Fabien, то показываем приветственное сообщение.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,17 +3,24 @@
 namespace App\Controller;

 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Annotation\Route;

 class ConferenceController extends AbstractController
 {
     #[Route('/', name: 'homepage')]
-    public function index(): Response
+    public function index(Request $request): Response
     {
+        $greet = '';
+        if ($name = $request->query->get('hello')) {
+            $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
+        }
+
         return new Response(<<<EOF
 <html>
     <body>
+        $greet
         <img src="/images/under-construction.gif" />
     </body>
 </html>

Посмотреть данные запроса в Symfony можно через объект Request. Если в объявлении типа для аргумента контроллера указать объект запроса, Symfony автоматически передаст его вам. Далее используем этот объект, чтобы получить параметр name из строки запроса и добавляем его в заголовок <h1>.

Чтобы увидеть получившийся результат, сначала в браузере перейдите на главную страницу (/), а после по пути /?hello=Fabien.

Note

Обратите внимание на вызов функции htmlspecialchars(), который нужен, чтобы защититься от XSS-атак. Когда мы начнём использовать шаблонизатор, он автоматически защитит нас от подобных проблем с безопасностью.

Также отмечу, что имя можно сделать частью URL-адреса:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -9,11 +9,11 @@ use Symfony\Component\Routing\Annotation\Route;

 class ConferenceController extends AbstractController
 {
-    #[Route('/', name: 'homepage')]
-    public function index(Request $request): Response
+    #[Route('/hello/{name}', name: 'homepage')]
+    public function index(string $name = ''): Response
     {
         $greet = '';
-        if ($name = $request->query->get('hello')) {
+        if ($name) {
             $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
         }

Составная часть маршрута {name} — это динамический параметр маршрута. Он работает так же, как и знак подстановки. Теперь можно перейти в браузере по пути /hello, а затем на /hello/Fabien — результат будет одинаковый. Значение параметра {name} можно получить, добавив имя аргумента в контроллер (то есть $name).

Отмените изменения, которые мы только что внесли:

1
$ git checkout src/Controller/ConferenceController.php
1
2
$ git reset HEAD src/Controller/ConferenceController.php
$ git checkout src/Controller/ConferenceController.php

Отладочные переменные

Отличным помощником в отладке является функция Symfony dump(). Она всегда доступна и позволяет отображать сложные переменные в красивом интерактивном формате.

Временно измените src/Controller/ConferenceController.php для отображения объекта Request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,14 +3,17 @@
 namespace App\Controller;

 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Annotation\Route;

 class ConferenceController extends AbstractController
 {
     #[Route('/', name: 'homepage')]
-    public function index(): Response
+    public function index(Request $request): Response
     {
+        dump($request);
+
         return new Response(<<<EOF
 <html>
     <body>

При обновлении страницы обратите внимание на новый значок "target" на панели инструментов; он позволяет осмотреть дамп. Щёлкните по нему, чтобы перейти на детальную страницу, где навигация упрощена:

/

Отмените изменения, которые мы только что внесли:

1
$ git checkout src/Controller/ConferenceController.php
1
2
$ git reset HEAD src/Controller/ConferenceController.php
$ git checkout src/Controller/ConferenceController.php
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version