Шаг 6: Создание контроллера

5.0 version
Maintained

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

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

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

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

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

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

1
$ symfony composer req maker --dev

Так как бандл Maker полезен лишь в процессе разработки, не забудьте указать флаг --dev, чтобы пакет не загружался при развёртывании приложения в продакшене.

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

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

1
$ symfony console list make

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

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

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

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

Для работы с аннотациями нужно добавить ещё одну зависимость:

1
$ symfony composer req annotations

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

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

Создайте свой первый контроллер с помощью команды 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
17
18
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()
    {
        return $this->render('conference/index.html.twig', [
            'controller_name' => 'ConferenceController',
        ]);
    }
}

Аннотация @Route("/conference", name="conference") — это именно то, что делает метод index() контроллером (конфигурация находится рядом с кодом, который она настраивает).

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

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

patch_file
 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()
     {

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

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

patch_file
 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
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,6 +3,7 @@
 namespace App\Controller;

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

 class ConferenceController extends AbstractController
@@ -12,8 +13,13 @@ class ConferenceController extends AbstractController
      */
     public function index()
     {
-        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-запрос.

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

Для демонстрации того, как в ответе можно использовать информацию из запроса, давайте добавим небольшое пасхальное яйцо. При переходе на главную страницу, если в строке запроса мы находим что-то типа ?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
29
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,6 +3,7 @@
 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;

@@ -11,11 +12,17 @@ class ConferenceController extends AbstractController
     /**
      * @Route("/", name="homepage")
      */
-    public function index()
+    public function index(Request $request)
     {
+        $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.

Примечание

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -9,13 +9,19 @@ use Symfony\Component\Routing\Annotation\Route;
 class ConferenceController extends AbstractController
 {
     /**
-     * @Route("/", name="homepage")
+     * @Route("/hello/{name}", name="homepage")
      */
-    public function index()
+    public function index(string $name = '')
     {
+        $greet = '';
+        if ($name) {
+            $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
+        }
+
         return new Response(<<<EOF
 <html>
     <body>
+        $greet
         <img src="/images/under-construction.gif" />
     </body>
 </html>

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


  • « Previous Шаг 5: Поиск и устранение неисправностей
  • Next » Шаг 7: Подготовка базы данных

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.