Контроллер

Дата обновления перевода 2023-07-25

Контроллер

Контроллер - это созданная вами 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
// 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(int $max): Response
    {
        $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.
  • Строка 10: Методу действия разрешено иметь аргумент $max благодаря символу подстановки в маршруте {max}.
  • Строка 14: Контроллер создает и возвращает объект 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
24
25
26
use Symfony\Component\HttpFoundation\RedirectResponse;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;

// ...
public function index(): RedirectResponse
{
    // перенаправляет по пути "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
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
// ...

#[Route('/lucky/number/{max}')]
public function number(int $max, LoggerInterface $logger): Response
{
    $logger->info('We are logging!');
    // ...
}

Отлично!

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

1
$ php bin/console debug:autowiring

Tip

Если вам нужен контроль над точным значением аргумента, или потребовать параметр, вы можете использовать атрибут #[Autowire]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ...
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Response;

class LuckyController extends AbstractController
{
    public function number(
        int $max,

        // внедрить конкретный сервис логгера
        #[Autowire(service: 'monolog.logger.request')]
        LoggerInterface $logger,

        // или внедрить значения параметра
        #[Autowire('%kernel.project_dir%')]
        string $projectDir
    ): Response
    {
        $logger->info('We are logging!');
        // ...
    }
}

Вы можете прочитать больше об этом атрибуте в .

6.1

Атрибут #[Autowire] был представлен в Symfony 6.1.

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

Чтобы узнать больше о сервисах, см. статью Сервис-контейнер.

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

Для экономии времени, вы можете установить 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

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

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

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

// ...
public function index(): Response
{
    // извлечь объект из 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('Что-то пошло не так!');

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

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

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

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

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ...

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

    // ...
}

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

Управление сессией

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

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

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

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

    if ($form->isSubmitted() && $form->isValid()) {
        // сделать какую-то обработку

        $this->addFlash(
            'notice',
            'Your changes were saved!'
        );
        // $this->addFlash() эквивалентно $request->getSession()->getFlashBag()->add()

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

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

Прочитайте это , чтобы узнать больше об использовании сессий.

Объект Request и Response

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

use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpFoundationResponse;

public function index(Request $request): Response { $request->isXmlHttpRequest(); // is it an Ajax request?

$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.

В Symfony контроллер должен возвращать объект Response:

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

// создаёт простой Response со статус-кодом (по умолчанию)
$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(): Response
{
    $contentsDir = $this->getParameter('kernel.project_dir').'/contents';
    // ...
}

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

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

1
2
3
4
5
6
7
8
9
10
11
use Symfony\Component\HttpFoundation\JsonResponse;
// ...

public function index(): JsonResponse
{
    // возвращает '{"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
6
7
8
use Symfony\Component\HttpFoundation\BinaryFileResponse;
// ...

public function download(): BinaryFileResponse
{
    // отправить содержание файла и заставить браузер скачать его
    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
17
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
// ...

public function download(): BinaryFileResponse
{
    // загрузить файл из файловой системы
    $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. Не найдено"

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