Контроллер
Дата обновления перевода: 2023-01-19*
Контроллер
Контроллер - это созданная вами 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.
Управление сессией
Symfony предоставляет объект сессии, который вы можете использовать для хранения информации о пользователе между запросами. Сессии включены по умолчанию, но запустятся только, когда вы начнёте читать или записывать в них.
Хранение сессии и другую конфигурацию можно контролировать в
конфигурации framework.session в
config/packages/framework.yaml
.
Для получения доступа к сессии добавьте аргумент и обозначьте его тип как SessionInterface:
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\HttpFoundation\Session\SessionInterface;
// ...
public function index(SessionInterface $session): Response
{
// сохраняет атрибут для переиспользования позже в запросе пользователя
$session->set('foo', 'bar');
// получает атрибут установленный другим контроллером в другом запросе
$foobar = $session->get('foobar');
// использует значение по умолчению, если атрибут не существует
$filters = $session->get('filters', []);
// ...
}
Сохранённые атрибуты остаются в сессии до окончания сессии пользователя.
Чтобы узнать больше, см. Сессии.
Флеш-сообщения
Вы можете также сохранять специальные сообщения, которые называют флеш-сообщениями, в пользовательской сессии. Согласно замыслу, флеш-сообщения предполагается использовать ровно один раз: они автоматически исчезают из сессии, как только вы их возвращаете. Эта особенность делает флеш-сообщения особенно удобными для хранения пользовательских оповещений.
Для примера, представьте, что вы обрабатываете отправку формы:
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()) {
// do some sort of processing
$this->addFlash(
'notice',
'Your changes were saved!'
);
// $this->addFlash() is equivalent to $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 #}
{# прочитать и отобразить только один тип флеш-сообщения #}
{% for message in app.flashes('notice') %}
<div class="flash-notice">
{{ message }}
</div>
{% endfor %}
{# прочитать и отобразить несколько типов флеш-сообщений #}
{% for label, messages in app.flashes(['success', 'warning']) %}
{% for message in messages %}
<div class="flash-{{ label }}">
{{ message }}
</div>
{% endfor %}
{% endfor %}
{# прочитать и отобразить все флеш-сообщения #}
{% for label, messages in app.flashes %}
{% for message in messages %}
<div class="flash-{{ label }}">
{{ message }}
</div>
{% endfor %}
{% endfor %}
Обычно используют notice
, warning
и error
в качестве ключей для
разных типов flash-сообщений, но вы можете использовать любой ключ, который вам подходит.
Tip
В качестве альтернативы вы можете использовать метод peek() чтобы получить сообщение, не удаляя его.
Объекты Запрос и Ответ
Как упоминалось ранее , 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 26
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
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;
// создаёт простой Ответ со статус-кодом 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(): Response
{
$contentsDir = $this->getParameter('kernel.project_dir').'/contents';
// ...
}
Возвращение JSON-ответа
Чтобы вернуть JSON из контроллера, используйте метод json()
. Он возвращает
специальный объект JsonResponse
, который автоматически превращает данные в json:
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Component\HttpFoundation\Response;
// ...
public function index(): Response
{
// возвращает '{"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\Response;
// ...
public function download(): Response
{
// отправить содержание файла и заставить браузер скачать его
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(): Response
{
// загрузить файл из файловой системы
$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.