Дата обновления перевода: 2020-12-20

Контроллер

Контроллер - это созданная вами PHP-функция, которая смотрит на объект Request создает и возвращает объект Response. Ответ может быть HTML-страницей, JSON, XML, сохраняемым файлом, редиректом, ошибкой 404 или чем-то другим. Контроллер может запускать любую произвольную логику, которая нужна вашему приложению для отображения содержимого страницы.

Tip

Если вы еще не создали свою первую рабочую страницу, просмотрте главу создание страницы и потом возвращайтесь!

Простой контроллер

В то время как контроллер может быть любой PHP-сущностью (функцией, методом объекта или Closure), обычно контроллер - это метод внутри класса контроллера:

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

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class LuckyController
{
    /**
     * @Route("/lucky/number/{max}", name="app_lucky_number")
     */
    public function number($max)
    {
        $number = random_int(0, $max);

        return new Response(
            '<html><body>Lucky number: '.$number.'</body></html>'
        );
    }
}

Контроллер - это метод number(), который расположен внутри класса контроллера LuckyController.

Этот контроллер достаточно прямолинеен:

  • Строчка 2: Symfony использует преимущества пространства имён PHP, чтобы указать пространство имён для класса контроллера.
  • Строчка 4: Symfony снова использует преимущества пространства имён PHP: ключевое слово use импортирует класс Response, который должен вернуть контроллер.
  • Строчка 7: Технически, класс можно назвать как угодно, но по соглашению, он имеет суффикс Controller.
  • Строчка 12: Методу действия разрешено иметь аргумент $max благодаря символу подстановки в маршруте {max}.
  • Строчка 16: Контроллер создает и возвращает объект Response.

Связывание URL с контроллером

Для того, чтобы увидеть результат этого контроллера, вам понадобится привязать URL к нему с помощью маршрута. Это было сделано выше с помощью аннотации маршрута @Route("/lucky/number/{max}").

Чтобы увидеть вашу страницу, перейдите на этот URL в вашем браузере: http://localhost:8000/lucky/number/100

Для того, чтобы узнать больше о маршрутизации, см. главу Маршрутизация.

Базовый класс контроллера и сервисы

Для помощи в разработке, Symfony включает в себя два опциональный базовый класс AbstractController. Вы можете расширить его, чтобы получить доступ к методам-помощникам.

Добавьте выражение use сверху класса контроллера и измените LuckyController, чтобы расширить его:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// src/Controller/LuckyController.php
namespace App\Controller;

+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

- class LuckyController
+ class LuckyController extends AbstractController
{
    // ...
}

Вот и все! Теперь у вас есть доступ к таким методам как $this->render() и многим другим, о которых вы узнаете далее.

Генерирование URL

Метод generateUrl() - это просто метод-помощник, который генерирует URL для заданного маршрута:

1
$url = $this->generateUrl('app_lucky_number', ['max' => 10]);

Перенаправление

Если вы хотите перенаправить пользователя на другую страницу, используйте методы

redirectToRoute() и redirect():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Symfony\Component\HttpFoundation\RedirectResponse;

// ...
public function index()
{
    // редирект на путь "homepage"
    return $this->redirectToRoute('homepage');

    // redirectToRoute - это сокращение для:
    // return new RedirectResponse($this->generateUrl('homepage'));

    // делает постоянный - 301-й редирект
    return $this->redirectToRoute('homepage', [], 301);

    // редирект на путь с параметрами
    return $this->redirectToRoute('app_lucky_number', ['max' => 10]);

    // редирект на путь и сохранение оригинальный параметров запроса
    return $this->redirectToRoute('blog_show', $request->query->all());

    // редирект на внешний сайт
    return $this->redirect('http://symfony.com/doc');
}

Caution

Метод redirect() никак не проверяет место назначеня. Если вы перенаправляете по URL, предоставленному конечными пользователями, ваше приложение может быть открыто к уязвимости безопасности невалидированных редиректов.

Отображение шаблонов

Если вы выдаёте HTML, вам пригодится умение отображать шаблоны. Метод render() отображает шаблон и помещает его содержимое в объект Response для вас:

1
2
// отображает templates/lucky/number.html.twig
return $this->render('lucky/number.html.twig', ['number' => $number]);

Шаблонизирование и Twig обяснены детальнее в статье Создание и использование шаблонов.

Получение сервисов

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

Если вам нужен сервис в контроллере, укажите класс или интерфейс аргумента. Symfony автоматически передаст вам необходимый сервис:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use Psr\Log\LoggerInterface;
// ...

/**
 * @Route("/lucky/number/{max}")
 */
public function number($max, LoggerInterface $logger)
{
    $logger->info('We are logging!');
    // ...
}

Отлично!

Какие еще сервисы можно подключить с помощью указания типа? Чтобы увидеть их, запустите консольную команду debug:autowiring:

1
$ php bin/console debug:autowiring

Если вам необходим контроль над точным значением аргумента, вы можете cвязать аргумент с его именем:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    # config/services.yaml
    services:
        # ...
    
        # explicitly configure the service
        App\Controller\LuckyController:
            tags: [controller.service_arguments]
            bind:
                # for any $logger argument, pass this specific service
                $logger: '@monolog.logger.doctrine'
                # for any $projectDir argument, pass this parameter value
                $projectDir: '%kernel.project_dir%'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!-- config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <!-- ... -->
    
            <!-- Explicitly configure the service -->
            <service id="App\Controller\LuckyController">
                <tag name="controller.service_arguments"/>
                <bind key="$logger"
                    type="service"
                    id="monolog.logger.doctrine"
                />
                <bind key="$projectDir">%kernel.project_dir%</bind>
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // config/services.php
    use App\Controller\LuckyController;
    use Symfony\Component\DependencyInjection\Reference;
    
    $container->register(LuckyController::class)
        ->addTag('controller.service_arguments')
        ->setBindings([
            '$logger' => new Reference('monolog.logger.doctrine'),
            '$projectDir' => '%kernel.project_dir%'
        ])
    ;
    

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

Чтобы узнать больше о сервисах, см. статью Service Container.

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

Для экономии времени, вы можете установить Symfony Maker и сказать Symfony сгенерировать новый класс контроллера:

1
2
3
4
$ php bin/console make:controller BrandNewController

created: src/Controller/BrandNewController.php
created: templates/brandnew/index.html.twig

Если вы хотите сгенеритьвать полный CRUD с привязкой к Doctrine entity, запускайте:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ php bin/console make:crud Product

created: src/Controller/ProductController.php
created: src/Form/ProductType.php
created: templates/product/_delete_form.html.twig
created: templates/product/_form.html.twig
created: templates/product/edit.html.twig
created: templates/product/index.html.twig
created: templates/product/new.html.twig
created: templates/product/show.html.twig

New in version 1.2: Команда make:crud появилась в MakerBundle 1.2.

Управление ошибками и 404 страницами

Когда что-то не найдено, вы должны вернуть ответ 404. Чтобы сделать это, вызовите специальный тип исключения:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

// ...
public function index()
{
    // извлечь объект из DB
    $product = ...;
    if (!$product) {
        throw $this->createNotFoundException('The product does not exist');

        // вышенаписанное - просто сокращение для:
        // вызвать новый NotFoundHttpException('Продукт не существует');
    }

    return $this->render(...);
}

Метод createNotFoundException() - это лишь сокращение для создания специального объекта NotFoundHttpException, который в конечном счете запускает ответ 404 внутри Symfony.

Если вы бросите исключение, расширяющее HttpException, Symfony будет использовать соответствующий код статуса HTTP. Иначе ответ будет выдавать код статуса HTTP 500:

1
2
// это исключение сгенерирует ошибку с HTTP 500
throw new \Exception('Something went wrong!');

В обоих случаях, конечному пользователю отображается страница ошибки, а разработчику отображается полная страница отладки ошибки (например, когда вы в режиме "отладки" - см. Configuration Environments).

Для настройки страницы ошибки, отображаемую пользователю, см. статью How to Customize Error Pages.

Объект Request в качестве аргумента контроллера

Что вы будете делать, если вам понадобится узнать параметры запроса, заголовок запроса или получить доступ к загруженному файлу? Вся эта информация в Symfony содержится в объекте Request. Чтобы получить доступ к этой информации в контроллере, просто добавьте его в качестве аргумента и добавьте тип Request:

1
2
3
4
5
6
7
8
use Symfony\Component\HttpFoundation\Request;

public function index(Request $request, $firstName, $lastName)
{
    $page = $request->query->get('page', 1);

    // ...
}

Продолжайте читать для более детальной информации об использовании объекта Request.

Управление сессиями

Symfony предоставляет сервис сессии, который вы можете использовать для хранения информации о пользователе между запросами. Сессии включены по умолчанию, но запустятся только, когда вы начнёте читать или записывать в них.

Хранение сессии и другую конфигурацию можно контролировать в конфигурации framework.session в config/packages/framework.yaml.

Для получения доступа к сессии добавьте аргумент и обозначьте его тип как SessionInterface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use Symfony\Component\HttpFoundation\Session\SessionInterface;

public function index(SessionInterface $session)
{
    // сохраняет атрибут для переиспользования позже в запросе пользователя
    $session->set('foo', 'bar');

    // получает атрибут установленный другим контроллером в другом запросе
    $foobar = $session->get('foobar');

    // использует значение по умолчению, если атрибут не существует
    $filters = $session->get('filters', []);
}

Сохранённые атрибуты будут храниться в сессии до окончания сессии пользователя.

Чтобы узнать больше, см. Sessions.

Flash-сообщения

Вы можете также сохранять специальные сообщения, которые называют flash-сообщениями, в пользовательской сессии. Согласно замыслу, flash-сообщения предполагается использовать ровно один раз: они автоматически исчезают из сессии, как только вы их возвращаете. Эта особенность делает flash-сообщения особенно удобными для хранения пользовательских оповещений.

Для примера представьте, что вы обрабатываете отправку формы:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use Symfony\Component\HttpFoundation\Request;

public function update(Request $request)
{
    // ...

    if ($form->isSubmitted() && $form->isValid()) {
        // какая-то обработка

        $this->addFlash(
            'notice',
            'Ваши изменения сохранены!'
        );
        // $this->addFlash() - это эквивалент $request->getSession()->getFlashBag()->add()

        return $this->redirectToRoute(...);
    }

    return $this->render(...);
}

После обработки запроса контроллер устанавливает flash-сообщение в сессии и затем выполняет редирект. Ключ к сообщению (notice в этом примере) может быть любым: вы будете его использовать для того, чтобы получить доступ к самому сообщению.

В шаблоне следующей страницы (или ещё лучше, в вашем базовом шаблоне), прочитайте любое flash-сообщение из сессии используя метод flashes() предоставляемый глобальной переменной app в Twig:

 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
{# templates/base.html.twig #}

{# read and display just one flash message type #}
{% for message in app.flashes('notice') %}
    <div class="flash-notice">
        {{ message }}
    </div>
{% endfor %}

{# read and display several types of flash messages #}
{% for label, messages in app.flashes(['success', 'warning']) %}
    {% for message in messages %}
        <div class="flash-{{ label }}">
            {{ message }}
        </div>
    {% endfor %}
{% endfor %}

{# read and display all flash messages #}
{% for label, messages in app.flashes %}
    {% for message in messages %}
        <div class="flash-{{ label }}">
            {{ message }}
        </div>
    {% endfor %}
{% endfor %}

Обычно используют notice, warning и error в качестве ключей для разных типов flash-сообщений, но вы можете использовать любой ключ, который вам подходит.

Tip

В качестве альтернативы вы можете использовать метод peek() чтобы получить сообщение, не удаляя его.

Объекты Request и Response

Как упоминалось ранее, Symfony передаст объект Request любому аргументу контроллера, который будет типизирован по классу Request:

 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
use Symfony\Component\HttpFoundation\Request;

public function index(Request $request)
{
    $request->isXmlHttpRequest(); // это запрос Ajax?

    $request->getPreferredLanguage(['en', 'fr']);

    // извлекает переменные GET и POST соответственно
    $request->query->get('page');
    $request->request->get('page');

    // извлекает переменные глобальной переменной SERVER
    $request->server->get('HTTP_HOST');

    // извлекает объект UploadedFile по ключу foo
    $request->files->get('foo');

    // извлекает значение COOKIE
    $request->cookies->get('PHPSESSID');

    // извлекает заголовок запроса HTTP с нормализированными ключами строчными буквами
    $request->headers->get('host');
    $request->headers->get('content-type');
}

У класса Request есть несколько общедоступных свойств и методов, которые возвращают любую нужную вам информацию о запросе.

Как и у Request, у объекта Response также есть публичное свойство headers. Это объект класса ResponseHeaderBag, который содержит методы для чтения и изменения заголовков ответов. Имена заголовков нормализованны. Таким образом, Content-Type эквивалентно именам content-type и даже content_type.

Единственное, что требуется от контроллера - это возвращать объект Response:

1
2
3
4
5
6
7
8
use Symfony\Component\HttpFoundation\Response;

// создаёт простой Response со статус-кодом 200 (по умолчанию)
$response = new Response('Hello '.$name, Response::HTTP_OK);

// создаёт CSS-ответ со статус-кодом 200
$response = new Response('<style> ... </style>');
$response->headers->set('Content-Type', 'text/css');

Существуют специальные классы, которые облегчают некоторые виды ответов. Некоторые из них описаны ниже. Чтобы узнать больше о Request и Response (и специальных классах Response), см. документацию компонента HttpFoundation.

Доступ к параметрам конфигурации

Для получения значений любых параметров конфигурации из контроллера, используйте метод getParameter():

1
2
3
4
5
6
// ...
public function index()
{
    $contentsDir = $this->getParameter('kernel.project_dir').'/contents';
    // ...
}

Возвращение JSON-ответа

Чтобы вернуть JSON из контроллера, используйте метод json(). Он возвращает специальный объект JsonResponse, который автоматически превращает данные в json:

1
2
3
4
5
6
7
8
9
// ...
public function index()
{
    // возвращает '{"username":"jane.doe"}' и устанавливает правильный заголовок Content-Type
    return $this->json(['username' => 'jane.doe']);

    // сокращение определяет три необязательных аргумента
    // return $this->json($data, $status = 200, $headers = [], $context = []);
}

Если в вашем приложении включен сервис сериализации, то он будет использован для сериализации данных в JSON. Иначе будет использована функция json_encode.

Потоковая передача ответов файла

Вы можете использовать метод file(), чтобы выдавать файл из контроллера:

1
2
3
4
5
public function download()
{
    // отправить содержание файла и заставить браузер скачать его
    return $this->file('/path/to/some_file.pdf');
}

Метод file() предоставляет некоторые аргументы, чтобы сконфигурировать его поведение:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;

public function download()
{
    // загрузить файл из файловой системы
    $file = new File('/path/to/some_file.pdf');

    return $this->file($file);

    // переименовать скачанный файл
    return $this->file($file, 'custom_name.pdf');

    // отобразить содержание файла в браузере вместо скачивания
    return $this->file('invoice_3241.pdf', 'my_invoice.pdf', ResponseHeaderBag::DISPOSITION_INLINE);
}

Заключение

В Symfony контроллер - это обычно метод класса, который используется для приёма запросов и выдачи объекта Response. Если связать его с URL, контроллер становится доступным и его ответ можно увидеть.

Для помощи в разработке контроллеров, Symfony предоставляет AbstractController. Он может быть использован для расширения класса контроллера давая доступ к часто используемым функциям такие как render() и redirectToRoute(). AbstractController также предоставляет метод createNotFoundException(), который используется для возврата ответа "404. Не найдено"

В других статьях, вы узнаете, как использовать спецаильные сервисы изнутри вашего контроллера, что поможет вам сохранять и получать объекты из базы данных, обрабатывать отправленные формы, работать с кэшем и т.д.

Продолжайте!

Далее, узнайте всё об Отображении шаблонов с помощью Twig.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.