Русская документация Symfony2 на SymfonyGuru
Дата последнего обновления: 2012-05-20.
Дата последнего обновления: 2012-05-20.
Контроллер - это PHP-функция, которую вы создаёте, чтобы получить информацию из HTTP запроса и на её основе создать HTTP ответ в виде объекта Response. Ответ может быть HTML страницей, XML-документом, сериализованным JSON-массивом, изображением, перенаправлением, ошибкой 404, всем чем угодно, о чём вы только могли мечтать. Контроллер содержит любую логику вашего приложения, необходимую для того, чтобы отобразить содержимое страницы.
Для того чтобы увидеть, насколько просто этого можно добиться, давайте рассмотрим контроллер Symfony2 в действии. Следующий контроллер отобразит страницу, которая всего-навсего напечатает Hello world!:
<?php
use Symfony\Component\HttpFoundation\Response;
public function helloAction()
{
return new Response('Hello world!');
}
Цель у контроллера всегда одна: создать и вернуть объект Response. Следуя этой цели, контроллер может читать информацию из запроса, загружать ресурсы из базы данных, отправлять email или же записывать информациюю в сессию пользователя. Но всегда, в конечном итоге, контроллер вернёт объект Response, который будет отправлен клиенту.
Здесь нет никакой магии или других требований, о которых стоило бы беспокоиться! Вот несколько типичных примеров:
Каждый запрос, обрабатываемый проектом Symfony2, следует одному и тому же простому жизненному циклу. Фреймворк берёт на себя повторяющиеся задачи и, в конце концов выполняет контроллер, который содержит код вашего приложения:
Создание страницы - это по сути создание контроллера (#3) и маршрута, который ставит в соответствие контроллеру некий URL (#2).
Примечание
Не смотря на то что “фронт-контроллер” и “контроллер” названы похожим образом, они сильно различаются - об этом мы еще поговорим чуть позже в этой главе. Фронт-контроллер - это короткий PHP-файл, который находится в web-директории и который обрабатывает все входящие запросы. Типичное приложение имеет продуктовый контроллер (prod, как правило app.php) и контроллер для разработки (dev, как правило app_dev.php). И вам скорее всего никогда не придется модифицировать или вообще задумываться о фронт-контроллерах в вашем приложении.
В то время как контроллер может быть любой PHP-сущностью, которую можно вызвать (функцией, методом объекта, или же замыканием (Closure)), в Symfony2 контроллер - это как правило некий метод объекта контроллера. Контроллеры также называются действиями (actions).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?php
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction($name)
{
return new Response('<html><body>Hello '.$name.'!</body></html>');
}
}
|
Совет
Обратите внимание, что контроллер - это метод indexAction, который расположен внутри класса контроллера (HelloController). Смотрите не путайтесь: класс контроллера - это просто удобный способ сгруппировать несколько контроллеров/действий вместе. Обычно класс контроллера содержит несколько контроллеров/действий (например updateAction, deleteAction и т.д.).
Этом контроллере нет ничего сложного, но давайте разберём подробнее:
Новый контроллер возвращает простую HTML-страницу. Для того чтобы увидеть эту страницу в вашем браузере, вам надо создать маршрут, который устанавливает соответствие между некоторым шаблоном URL и контроллером:
# app/config/routing.yml
hello:
pattern: /hello/{name}
defaults: { _controller: AcmeHelloBundle:Hello:index }
<!-- app/config/routing.xml -->
<route id="hello" pattern="/hello/{name}">
<default key="_controller">AcmeHelloBundle:Hello:index</default>
</route>
// app/config/routing.php
$collection->add('hello', new Route('/hello/{name}', array(
'_controller' => 'AcmeHelloBundle:Hello:index',
)));
Теперь при запросе URI /hello/ryan теперь выполняется контроллер HelloController::indexAction() и присваивает переменной $name значение ryan. Создание страницы по сути подразумевает всего лишь создание метода контроллера и соответствующего маршрута.
Обратите внимание на синтаксис, при помощи которого маршрут ссылается на контроллер: AcmeHelloBundle:Hello:index. Symfony2 использует простую строковую нотацию для создания ссылок на различные контроллеры. Этот очень простой синтаксис сообщает Symfony2 что класс контроллера с именем HelloController расположен в пакете AcmeHelloBundle. Затем выполняется метод indexAction().
Более подробно о формате строк, используемых для создания ссылок на различные контроллеры можно почитать здесь: Шаблон Именования Контроллера.
Примечание
В этом примере конфигурация маршрутизатора выполняется непосредственно в директории app/config/. На практике более удобен способ, когда ваши маршруты размещаются в пакете, которому соответствуют. Более подробно этот способ рассматривается здесь: Подключение внешних ресурсов для маршрутизации.
Совет
Подробно вопросы маршрутизации рассматриваются в главе Маршрутизация.
Вы уже знаете, что параметр _controller со значением AcmeHelloBundle:Hello:index ссылается на метод HelloController::indexAction(), который расположен в пакете AcmeHelloBundle. Также интерес представляют аргументы, которые передаются в этот метод:
<?php
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloController extends Controller
{
public function indexAction($name)
{
// ...
}
}
Контроллер имеет единственный аргумент - $name, который соответствует параметру {name} из маршрута (в нашем примере - ryan). Фактически, когда контроллер выполняется, Symfony2 каждому аргументу контроллера ставит в соответствие параметр из маршрута. Взгляните на пример:
# app/config/routing.yml
hello:
pattern: /hello/{first_name}/{last_name}
defaults: { _controller: AcmeHelloBundle:Hello:index, color: green }
<!-- app/config/routing.xml -->
<route id="hello" pattern="/hello/{first_name}/{last_name}">
<default key="_controller">AcmeHelloBundle:Hello:index</default>
<default key="color">green</default>
</route>
<?php
// app/config/routing.php
$collection->add('hello', new Route('/hello/{first_name}/{last_name}', array(
'_controller' => 'AcmeHelloBundle:Hello:index',
'color' => 'green',
)));
Контроллер для этого примера принимает несколько аргументов:
<?php
public function indexAction($first_name, $last_name, $color)
{
// ...
}
Обратите внимание, что оба заполнителя для переменных ({first_name}, {last_name}), как и переменная по умолчанию color - доступны в качестве аргументов в контроллере. Когда совпадает маршрут, заполнители переменных объединяются с defaults в один массив, который становится доступен в вашем контроллере.
Настройка соответствия параметров маршрута аргументам контроллера проста, нужно лишь следовать нижеперечисленным рекомендациям во время разработки:
Порядок аргументов контроллера не имеет значения
Symfony в состоянии установить соответствие между именами параметров маршрута и сигнатурой метода в контроллере. Другими словами, это работает таким образом, что параметр {last_name} соответствует аргументу $last_name. Аргументы контроллера менять местами и он всё равно будет работать:
<?php public function indexAction($last_name, $color, $first_name) { // .. }
Каждый обязательный аргумент контроллера должен соответствовать параметру маршрута
Следующий пример вызовет исключение RuntimeException, так как в маршруте не определён параметр foo:
<?php public function indexAction($first_name, $last_name, $color, $foo) { // .. }Для того чтобы это работало, нужно сделать параметр опциональным. Следующий пример не будет вызывать исключительной ситуации:
<?php public function indexAction($first_name, $last_name, $color, $foo = 'bar') { // .. }
Параметры маршрута не обязательно должны быть представлены в виде аргументов контроллера
Если, к примеру, параметр last_name не нужен в контроллере, его можно опустить:
<?php public function indexAction($first_name, $color) { // .. }
Совет
Каждый маршрут имеет специализировнный параметр _route, который содержит значение равное его имени (например hello). Обычно это значение не используется, но, тем не менее, этот параметр также доступен в качестве аргумента контроллера.
Для большего удобства, вы также можете передать объект Request в качестве аргумента в ваш контроллер. Это особенно удобно при работе с формами:
<?php
use Symfony\Component\HttpFoundation\Request;
public function updateAction(Request $request)
{
$form = $this->createForm(...);
$form->bindRequest($request);
// ...
}
Symfony2 включает базовый класс Controller, который оказывает помощь в выполнении наиболее типичных задач контроллера и предоставляет вашему контроллеру доступ к любому ресурсу, который может портребоваться. Осуществляя наследование от класса Controller вы получите в своё распоряжение некоторое число методов-помощников.
Добавьте выражение use в начале класса контроллера и модифицируйте HelloController, чтобы он наследовался от Controller:
<?php
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController extends Controller
{
public function indexAction($name)
{
return new Response('<html><body>Hello '.$name.'!</body></html>');
}
}
Эти изменения на самом деле ничего не меняют в логике работы вашего контроллера. В следующей секции вы узнаете о тех методах-помощниках, которые предоставляет базовый класс. Эти методы по сути являются обёртками для базового функционала Symfony2 который доступен вам в любом случае - с использованием базового класса Controller или же без него. Самый лучший путь для того чтобы увидеть базовые функции в действии - заглянуть в код класса Symfony\Bundle\FrameworkBundle\Controller\Controller самостоятельно.
Совет
Наследование от базового класса совершенно не обязательно в Symfony2. Этот касс содержит удобные методы-ярлыки, но ничего обязательного. Вы также можете отнаследоваться от класса Symfony\Component\DependencyInjection\ContainerAware. Объект service container’а будет доступен черз свойство container.
Примечание
Вы также можете объявить контроллер в качестве сервиса: </cookbook/controller/service>.
Хотя, виртуально контроллер ничего делать не обязан, в основном контроллеры выполняют одни и те же задачи снова и снова. Эти задачи, такие как перенаправление, переадресация, отображение шаблона и доступ к основным сервисам, в Symfony2 выполнять очень легко.
Если вы хотите перенаправить пользователя на другую страницу, используйте метод redirect():
<?php
public function indexAction()
{
return $this->redirect($this->generateUrl('homepage'));
}
Метод generateUrl(), это всего-лишь функция помощник, которая генерирует URL для заданного маршрута. Более подробно этот вопрос рассматривается в главе Маршрутизация.
По умолчанию, метод redirect() выполняет перенаправление с HTTP статус-кодом 302 (временное перенаправление). Для того, чтобы выполнить постоянное перенаправление (со статус-кодом 301), необходимо добавить второй аргумент:
<?php
public function indexAction()
{
return $this->redirect($this->generateUrl('homepage'), 301);
}
Совет
Метод redirect() - это просто ярлычок для операции создания объекта Response, который специализируется на перенаправлении пользователя. Он эквивалентен следующему коду:
<?php
use Symfony\Component\HttpFoundation\RedirectResponse;
return new RedirectResponse($this->generateUrl('homepage'));
Вы также легко можете переадресовать запрос на другой контроллер внутри системы, используя метод forward(). Вместо того, чтобы выполнить перенаправление браузера пользователя, этот метод выполняет внутренний подзапрос и вызывает указанный контроллер. Метод forward() возвращает объект Response, который возвращает контроллер, на который осуществлялась переадресация:
<?php
public function indexAction($name)
{
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green'
));
// Здесь можно модифицировать $response или же сразу вернуть его пользователю
return $response;
}
Обратите внимание, что метод forward() использует для указания контроллера тот же формат строки, который используется в конфигурации маршрутов. Таким образом, целью переадресации будет HelloController из пакета AcmeHelloBundle. Массив, передаваемый методу в качестве параметра, будет конвертирован в параметры целевого контроллера. Такой же интерфейс используется при встраивании контроллеров в шаблоны (см. Внедрение контроллеров). Метод целевого контроллера должен выглядеть следующим образом:
<?php
public function fancyAction($name, $color)
{
// ... create and return a Response object
}
И, как и в случае создания контроллера для маршрута, порядок аргументов для fancyAction не имеет значения. Symfony2 устанавливает соответствие по именам ключей (например name) и именам параметров (например $name). Если вы изменяете порядок следования аргументов, Symfony2 также будет присваивать верные значения каждой переменной.
Совет
Как и прочие методы базового контроллера, метод forward - это просто ярлык к базовому функционалу Symfony2. Переадресация может быть выполнена напрямую через сервис http_kernel. При переадресации возвращается объект Response:
<?php
$httpKernel = $this->container->get('http_kernel');
$response = $httpKernel->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green',
));
Хотя это и не является требованием, большинство контроллеров в конце концов будут отображать (рендерить) шаблон, который отвечает за генерацию HTML (или данных в другом формате) для контроллера. Метод renderView() рендерит шаблон и возвращает его содержимое. Контент из шаблона может быть использован для создания объекта Response:
<?php
$content = $this->renderView('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));
return new Response($content);
Эти операции могут быть выполнены за один шаг при помощи метода render(), который возвращает объект Response, содержащий контент шаблона:
В обоих случаях, будет отображен шаблон Resources/views/Hello/index.html.twig из пакета AcmeHelloBundle.
Шаблонизатор Symfony более подробно рассматривается в главе о Шаблонах
Совет
Метод renderView - это по сути ярлык для быстрого использования шаблонизатора. Шаблонизатор также можно использовать напрямую:
<?php
$templating = $this->get('templating');
$content = $templating->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));
При наследовании от базового контроллера, вы можете получить доступ к любому сервису Symfony2 при помощи метода get(). Ниже представлены основные сервисы, которые вам могут быть полезны:
$request = $this->getRequest();
$templating = $this->get('templating');
$router = $this->get('router');
$mailer = $this->get('mailer');
В Symfony2 по умолчанию определена куча сервисов и вы вольны определить ещё столько же собственных. Для того чтобы отобразить список доступных сервисов, используйте консольную команду container:debug:
php app/console container:debug
Больше данных о сервисах вы можете почерпнуть из главы Service container.
Когда что-либо не может быть найдено, вы должны вернуть статус-код 404. Для того чтобы это сделать, вы можете сгенерировать особый тип исключения. Если вы унаследовали контроллер от базового, выполните следующее:
<?php
public function indexAction()
{
$product = // тут получаем объект из базы данных
if (!$product) {
throw $this->createNotFoundException('Продукт не существует');
}
return $this->render(...);
}
Метод createNotFoundException() создаёт особый объект NotFoundHttpException, который в конечном итоге провоцирует возврат HTTP 404 внутри Symfony.
Конечно, вы вольны вызывать любую исключительную ситуацию в вашем контроллере - Symfony2 автоматически вернёт HTTP статус-код 500.
throw new \Exception('Что-то пошло не так!');
В любом случае, пользователь увидит страницу с той или иной ошибкой, а разработчику (при использовании dev-окружения) будет показана страница с полной отладочной информацией. Эти страницы ошибок могут быть изменены. Более подробно об этом написано в “книге рецептов”: “Как создать собственные страницы ошибок”.
Symfony2 предоставляет вам объект, для работы с сессиями, который вы можете использовать для хранения информации о пользователе (если он реальный человек, автоматический бот или же веб-сервис) между запросами. По умолчанию, Symfony2 сохраняет атрибуты в куках (cookie), используя нативные сессии PHP.
Сохранение и получение информации из сессии можно использовать из любого контроллера:
$session = $this->getRequest()->getSession();
// store an attribute for reuse during a later user request
$session->set('foo', 'bar');
// in another controller for another request
$foo = $session->get('foo');
// set the user locale
$session->setLocale('fr');
Эти атрибуты будут соответствовать конкретному пользователю, пока существует его сессия.
Вы также можете сохранять небольшие сообщения, которые сохраняются в пользовательской сессии между двумя запросами. Эти сообщения удобно использовать при обработке форм: вы хотите выполнить перенаправление и отобразить особое сообщение при следующем запросе. Такие сообщения называются flash-сообщениями.
Например, представьте, что вы обрабатываете отправку формы:
<?php
public function updateAction()
{
$form = $this->createForm(...);
$form->bindRequest($this->getRequest());
if ($form->isValid()) {
// do some sort of processing
$this->get('session')->setFlash('notice', 'Your changes were saved!');
return $this->redirect($this->generateUrl(...));
}
return $this->render(...);
}
После обработки запроса контроллер устанавливает flash-сообщение notice и выполняет перенаправление. Имя (notice) не устанавливается жёстко - это лишь обозначение типа сообщения.
В шаблоне следующего действия вы можете использовать следующий код для отображения сообщения notice:
{% if app.session.hasFlash('notice') %}
<div class="flash-notice">
{{ app.session.flash('notice') }}
</div>
{% endif %}
<?php if ($view['session']->hasFlash('notice')): ?>
<div class="flash-notice">
<?php echo $view['session']->getFlash('notice') ?>
</div>
<?php endif; ?>
По умолчанию, flash-сообщения должны жить ровно один запрос. Они разработаны именно для того, чтобы использоваться во время перенаправлениями так как показано в этом примере.
К контроллеру предъявляется лишь одно требование - вернуть объект Response. Класс Symfony\Component\HttpFoundation\Response представляет собой PHP-абстракцию HTTP-ответа - текстового сообщения, состоящего из HTTP-заголовков и контента, который возвращается клиенту:
// создаётся простой объект Response со статус-кодом 200 (по умолчанию)
$response = new Response('Hello '.$name, 200);
// создаётся JSON-ответ со статус-кодом 2000
$response = new Response(json_encode(array('name' => $name)));
$response->headers->set('Content-Type', 'application/json');
Совет
headers - это объект Symfony\Component\HttpFoundation\HeaderBag, содержащий методы для чтения и изменения заголовков ответа Response. Имена заголовков нормализованы, так что Content-Type, content-type и даже content_type эквивалентны.
Помимо значений заполнителей из маршрута, контроллер также имеет доступ к объекту Request, когда он является наследником базового класса Controller:
$request = $this->getRequest();
$request->isXmlHttpRequest(); // is it an Ajax request?
$request->getPreferredLanguage(array('en', 'fr'));
$request->query->get('page'); // get a $_GET parameter
$request->request->get('page'); // get a $_POST parameter
Подобно объекту Response, заголовки запроса хранятся в объекте HeaderBag и также легко доступны.
Когда вы создаёте страницу, в конечном итоге должны написать код, который содержит логику этой страницы. В Symfony эта логика называется “контроллером”, и представляет собой PHP-функцию, которая выполняет все необходимые действия для того чтобы вернуть объект Response, который будет отправлен пользователю.
Для того, чтобы сделать жизнь легче, вы можете отнаследоваться от класса Controller, который содержит методы для типичных задач, решаемых контроллером. Например, так как вы должны вернуть HTML код - вы можете использовать метод render() и вернуть контент шаблона.
В других главах вы узнаете как контроллер может быть использован для сохранения и получения объектов из базы данных, обрабатывать отправку форм, работать с кэшем и многое другое.