Контролер

Дата оновлення перекладу 2022-12-14

Контролер

Контролер - це створена вами 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.
  • Рядок 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
27
28
29
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;

// ...
public function index(): RedirectResponse
{
    // перенаправлення на маршрут "homepage"
    return $this->redirectToRoute('homepage');

    // redirectToRoute - скорочення для:
    // повернути новий RedirectResponse($this->generateUrl('homepage'));

    // робить постійне 301 перенаправлення
    return $this->redirectToRoute('homepage', [], 301);
    // якщо ви хочете, ви можете використати PHP-константи замість жорстко закодованих цифр
    return $this->redirectToRoute('homepage', [], Response::HTTP_MOVED_PERMANENTLY);

    // перенаправлення на маршрут з параметрами
    return $this->redirectToRoute('app_lucky_number', ['max' => 10]);

    // перенаправлення на маршрут та збереження оригінальних параметрів запиту
    return $this->redirectToRoute('blog_show', $request->query->all());

    // перенаправлення на поточний маршрут (наприклад, для патерну Post/Redirect/Get):
    return $this->redirectToRoute($request->attributes->get('_route'));

    // перенаправлення на зовнішній сайт
    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

Якщо вам необхідний контроль над точним значенням аргументу, ви можете use the #[Autowire] attribute:

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
// ...
    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!');
            // ...
        }
    }

Ви можете прочитати більше про цей атрибут у :ref:`autowire-attribute`.

.. versionadded:: 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 , запускайте:

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
{
    // добути об'єкт з бази даних
    $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('Щось пішло не так!');

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

Для налаштування сторінки помилки, що відображається користувачу, див. статтю Як налаштувати сторінки помилок.

Об'єкт Request в якості аргументу контролера

Що ви будете робити, якщо вам знадобиться дізнатися параметри запиту, заголовок запиту або отримати доступ до завантаженого файлу? Вся ця інформація в Symfony міститься в об'єкті Request. Щоб отримати доступ до цієї інформації в контролері, просто додайте його в якості аргументу та додайте тип 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);

    // ...
}

Продовжуйте читати для детальнішої інформації про використання об'єкту Запит.

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

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()) {
        // проведіть якусь обробку

        $this->addFlash(
            'notice',
            'Ваші зміни збережені!'
        );
        // $this->addFlash() еквівалентно $request->getSession()->getFlashBag()->add()

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

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

Після обробки запиту, контролер встановлює флеш-повідомлення в сесії, а потім виконує перенаправлення. Ключ до повідомлення (notice в цьому прикладі) може бути будь-яким: ви будете використовувати його для того, щоб отримати доступ до самого повідомлення.

В шаблоні наступної сторінки (або ще краще, в вашому базовому шаблоні), прочитайте будь-яке флеш-повідомлення з сесії, використовуючи метод 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 в якості ключів для різних типів флеш-повідомлень, але ви можете використовувати будь-який ключ, який вам підходить.

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

public function index(Request $request): Response
{
    $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(): 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. Не знайдено".

В інших статтях ви дізнаєтеся, як використовувати спеціальні сервіси всередині вашого контролера, що допоможе вам зберігати та отримувати об'єкти з бази даних, обробляти відправлені форми, працювати з кешем тощо.