Контроллер

Вы все еще здесь после первых двух частей? Вы уже становитесь фанатом Symfony! Без дальнейших разглагольствований, узнайте, что могут сделать для вас контроллеры.

Возвращение сырых ответов

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

До этих пор, все действия, показанные в этом туториале, использовали метод шортката контроллера $this->render(), чтобы вернуть отображенный шаблон в качестве результата. В случае, если он вам нужен, вы также можете создать сырой объект Response для возврата любого содержания текста:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// src/AppBundle/Controller/DefaultController.php
namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction()
    {
        return new Response('Welcome to Symfony!');
    }
}

Параметры маршрута

В большинстве случаев, URL приложений содержат в себе переменные части. Если вы создаете, к примеру, приложение-блог, URL для отображения записей должен содержать их заголовок или какой-то другой уникальный идентификатор, чтобы дать приложению знать, какой именно пост надо отобразить.

В приложении Symfony переменные части маршрутов заключаются в фигурные скобки (например, /blog/read/{article_title}/). Каждая переменная часть имеет уникальное имя, которое можно использовать позже в контроллере для получения каждого значения.

Давайте создадим новое действие с переменными маршрута, чтобы показать эту функцию в действии. Откройте файл src/AppBundle/Controller/DefaultController.php и добавьте новый метод под названием helloAction() со следующим содержанием:

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

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller
{
    // ...

    /**
     * @Route("/hello/{name}", name="hello")
     */
    public function helloAction($name)
    {
        return $this->render('default/hello.html.twig', array(
            'name' => $name
        ));
    }
}

Откройте ваш браузер и зайдите по URL http://localhost:8000/hello/fabien, чтобы увидеть результат выполнения этого нового действия. Вместо результата действия, вы увидите страницу ошибки. Как вы наверное уже догадались, причиной этой ошибки является то, что вы пытаетесь отобразить щаблон (default/hello.html.twig), который пока еще не существует.

Создайте новый шаблон app/Resources/views/default/hello.html.twig со следующим содержанием:

1
2
3
4
5
6
{# app/Resources/views/default/hello.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
    <h1>Hi {{ name }}! Welcome to Symfony!</h1>
{% endblock %}

Зайдите опять по URL http://localhost:8000/hello/fabien, и вы увидите этот новый шаблон отображенный с информацией, переданной контроллером. Если вы измените последнюю часть URL (например, http://localhost:8000/hello/thomas) и перезагрузите ваш браузер, страница отобразит другое сообщение. А если вы уберете последнюю чась URL (например, http://localhost:8000/hello), Symfony отобразит ошибку, так как маршрут ожидает имя, которое вы не предоставили.

Использование форматов

Сегодня веб-приложение должно быть способно давать больше, чем просто HTML-страницы. От XML-каналов для RSS или веб-сервисов, до запросов JSON для Ajax, существует множество форматов, из которых можно выбрать. Поддержка этих форматов в Symfony очень прямолинейны блягодаря специальной переменной _format, которая сохраняет формат, запрошенный пользователем.

Измените маршурт hello путем добавления новой переменной _format с html в качестве значения по умолчанию:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/AppBundle/Controller/DefaultController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

// ...

/**
 * @Route("/hello/{name}.{_format}", defaults={"_format"="html"}, name="hello")
 */
public function helloAction($name, $_format)
{
    return $this->render('default/hello.'.$_format.'.twig', array(
        'name' => $name
    ));
}

Очевидно, что если вы поддерживаете несколько форматов запросов, вам нужно будет предоставить шаблон для каждого поддерживаемого формата. В этом случае, вам нужно создать новый шлаблон hello.xml.twig:

1
2
3
4
<!-- app/Resources/views/default/hello.xml.twig -->
<hello>
    <name>{{ name }}</name>
</hello>

Теперь, когда вы зайдете на http://localhost:8000/hello/fabien, вы увидите обычную HTML-страницу, потому что html - формат по умолчанию. При посещенни http://localhost:8000/hello/fabien.html вы снова получите HTML-страницу, в этот раз потому, что вы ясно попросили html формат. И последнее, если вы зайдете на http://localhost:8000/hello/fabien.xml, то вы увидите новый XML-шаблон, отображенный в вашем браузере.

Вот и все. Для стандартных форматов, Symfony также автоматически выберет наилучший заголовок``Content-Type`` для ответа. Для ограничения форматов, поддерживаемых заданным действием, используйте опцию requirements аннотации @Route():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// src/AppBundle/Controller/DefaultController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

// ...

/**
 * @Route("/hello/{name}.{_format}",
 *     defaults = {"_format"="html"},
 *     requirements = { "_format" = "html|xml|json" },
 *     name = "hello"
 * )
 */
public function helloAction($name, $_format)
{
    return $this->render('default/hello.'.$_format.'.twig', array(
        'name' => $name
    ));
}

Действие hello теперь будет совпадать с URL вроде /hello/fabien.xml или /hello/fabien.json, но оно будет отображать ошибку 404, если вы попробуете получить URL вроде /hello/fabien.js, потому что значение переменной _format не соответствет его требованиям.

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// src/AppBundle/Controller/DefaultController.php
class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction()
    {
        return $this->redirectToRoute('hello', array('name' => 'Fabien'));
    }
}

Метод redirectToRoute() в качестве аргументом берет имя маршрута и необязательный массив параметров, и перенаправляет пользователя по URL, сгенерированной с этими аргументами.

Отображение страниц ошибок

Ошибки неминуемо будут возникать во время выполнения каждого веб-приложения. В случае ошибок 404, Symfony имеет краткую форму, которую вы можете использовать в ваших контроллерах:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/AppBundle/Controller/DefaultController.php
// ...

class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction()
    {
        // ...
        throw $this->createNotFoundException();
    }
}

Для ошибок 500, просто используйте обычное PHP-исключение внутри контроллера, и Symfony транформирует его в правильную страницу ошибки 500:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/AppBundle/Controller/DefaultController.php
// ...

class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction()
    {
        // ...
        throw new \Exception('Something went horribly wrong!');
    }
}

Получение информации из запроса

Иногда вашим контроллерам может понадобиться получить доступ к информации, относящейся к запросу пользователя, например, предпочитаемый ими язык, IP-адрес или параметры URL-запроса. Чтобы получить доступ к этой информации, добавьте новый аргумент типа Request в действие. Имя этого нового аргумента не имеет значения, но оно должно быть упреждено типом Request, чтобы работать (не забудьте добавить новое выражение use, которое импортирует класс 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
27
// src/AppBundle/Controller/DefaultController.php
namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        // это запрос Ajax?
        $isAjax = $request->isXmlHttpRequest();

        // какой предпочитаемый язык пользователя?
        $language = $request->getPreferredLanguage(array('en', 'fr'));

        // получить значение параметра $_GET
        $pageName = $request->query->get('page');

        // получить значение параметра $_POST
        $pageName = $request->request->get('page');
    }
}

В шаблоне вы также можете получить доступ к объекту Request с помощью специальной переменной app.request, автоматически предоставляемой Symfony:

1
2
3
{{ app.request.query.get('page') }}

{{ app.request.request.get('page') }}

Сохранение данных в сессии

Даже если HTTP-протокол не имеет запоминания состояния, Symfony предоставляет хороший объект сессии, который представляет клиента (будь это настоящий человек, использующий браузер, или веб-сервис). Между двумя запросами, Symfony сохраняет атрибуты в cookies, используя родные PHP-сессии.

Сохранения и получения информации из сессии можно легко достичь из любого контроллера:

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

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

    // получить значение атрибута сессии
    $foo = $session->get('foo');

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

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

1
2
3
4
5
6
7
public function indexAction()
{
    // ...

    // сохранить сообщение для каждого следующего запроса
    $this->addFlash('notice', 'Congratulations, your action succeeded!');
}

И вы можете отобразить flash-сообщение в шаблоне вот так:

1
2
3
4
5
{% for message in app.flashes('notice') %}
    <div class="flash-notice">
        {{ message }}
    </div>
{% endfor %}

New in version 3.3: Функция Twig app.flashes() была представлена в Symfony 3.3. До этого вам нужно было использовать app.session.flashBag().

Заключение

Вот и все, что можно рассказать, и я даже не уверен, что вы потратили целых 10 минут. Вы кратко ознакомились с пакетами в первой части, и все свойства, о которых вы уже узнали, являются частью базового FrameworkBundle. Однако благодаря пакетам, все в Symfony может быть расширено или заменено. Это тема следующей части этого туториала.

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