Development

Documentation/ru_RU/book/1.0/06-Inside-the-Controller-Layer

You must first sign up to be able to contribute.

Глава 6 - Внутри контроллера

В symfony controller, который содержит код связующий деловую логику и представление данных, поделен на несколько, используемых для разных целей, частей:

  • Фронт-контроллер это единственная точка входа в приложение. Он загружает конфигурацию и определяет какое действие (action) должно выполнятся.
  • Действия (action) содержит логику. Тут проверяется целостность запроса и подготавливаются данные для view.
  • Специальные объекты (представляющие сессию, пакеты запроса и ответа) предоставляют доступ к параметрам запроса, заголовкам пакета ответа и данным пользователя. В контроллере (controller) эти объекты используются очень часто.
  • Фильтры — порции кода, которые выполняются при каждом запросе, до или после выполнения действия (action). Примером могут служить, часто использующийся в веб приложениях, фильтр безопасности (security filter) и фильтр валидации. Вы можете расширить возможности фреймворка создав свои собственные фильтры.

Эта глава описывает все вышеперечисленные компоненты. Не пугайтесь того, что их так много. Для элементарной странички вам скорей всего понадобится написать всего только несколько строчек в классе действия (action class). Другие части контроллера используются только в особых случаях.

Фронт-контроллер

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

Принимая запрос, фронт-контроллер использует систему роутинга, чтоб установить соответствие между набранным пользователем URL и именами действия (action) и модуля. Например, следующий URL вызывает скрипт index.php (это фронт-контроллер), и будет понят как вызов к действию myAction модуля mymodule:

http://localhost/index.php/mymodule/myAction

Если вы не желаете понимать подробности внутренней работы symfony, то это все что вам необходимо знать о фронт-контроллере. Это обязательный компонент MVC архитектуры symfony, но вам редко понадобится изменять его. Так что, если вы не хотите узнать о начинке фронт-контроллера, вы можете перейти к следующей секции.

Детально о работе фронт-контроллера

Фронт-контроллер организовывает выполнение запроса, это означает немного больше, чем просто задать действие (action) для выполнения. В действительности, выполняется общий для всех действий код, включающий в себя такие задачи:

  1. Определение констант ядра.
  2. Определение местонахождения библиотек symfony.
  3. Загрузка и инициализация классов ядра фреймворка.
  4. Загрузка конфигурации.
  5. Расшифровка URL, определение действия для выполнения и параметров запроса.
  6. В случае, если действия не существует, осуществляется перенаправление на действие 404 error.
  7. Активация фильтров (к примеру, если запрос требует аутентификации).
  8. Выполнение фильтров, первая часть.
  9. Выполнение действия (action) и обработка (render) view.
  10. Выполнение фильтров, вторая часть.
  11. Вывод ответа (response).

Стандартный фронт-контроллер

Стандартный фронт-контроллер index.php находится в директории проекта web/, и являет собой простой PHP файл, приведенный в листинге 6-1.

Листинг 6-1 – Стандартный фронт-контроллер рабочего режима

[php]
<?php

define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'myapp');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

sfContext::getInstance()->getController()->dispatch();

Определение констант соответствует первому пункту списка из предыдущей секции. Далее фронт-контроллер подключает config.php проекта, который позаботится о пунктах 2 и 4. Вызов метода dispatch() объекта sfController (это объект ядра соответствующий части controller MVC архитектуры) передает обработку запроса дальше, попутно выполнив пункты 5 и 7. Оставшейся шаги обрабатываются последовательностью фильтров, эта работа поясняется позже в этой главе.

Связь между режимом и фронт-контроллером

На каждый режим существует по одному фронт-конроллеру. На самом деле само наличие фронт-контроллера определяет режим. Режим задан в константе SF_ENVIRONMENT.

Для того чтобы сменить режим, в котором вы просматривайте приложение, просто выберете другой фронт-контроллер. При создании приложения с помощью команды symfony init-app, появляются два стандартных фронт-контроллера — index.php соответствующий рабочему режиму (production environment) и myapp_dev.php соответствующий режиму разработки (подразумевается, что приложение называется myapp). Согласно настройкам по умолчанию, mod_rewrite использует index.php, если URL не содержит имя скрипта фронт-контроллера. Таким образом, в рабочем режиме оба следующих адреса указывают на одну и ту же страничку mymodule/index:

http://localhost/index.php/mymodule/index
http://localhost/mymodule/index

А этот URL отображает эту же страницу в режиме разработки:

http://localhost/myapp_dev.php/mymodule/index

Создать новый режим (environment) так же просто как и создать новый фронт-контроллер. К примеру, вам может понадобиться режим демонстрации (staging environment), чтоб заказчик мог протестировать приложение перед запуском рабочей версии. Чтоб создать режим демонстрации скопируйте содержимое файла web/myapp_dev.php в новый файл web/myapp_staging.php, и установите значение staging для константы SF_ENVIRONMENT. Теперь, во всех конфигурационных файлах вы можете добавить новую секцию staging:, чтоб задать специфические настройки для нового режима. Пример приведен в листинге 6-2.

Листинг 6-2 – Пример индивидуальных настроек для режима демонстрации в файле app.yml

staging:
  mail:
    webmaster:    dummy@mysite.com
    contact:      dummy@mysite.com
all:
  mail:
    webmaster:    webmaster@mysite.com
    contact:      contact@mysite.com

Если вы желаете посмотреть на приложение в новом режиме, вызовите соответствующий фронт-контроллер:

http://localhost/myapp_staging.php/mymodule/index

Batch файлы

Возможно вам понадобится выполнить скрипт из командной строки (или посредством cron таблицы), с доступом ко всем классам и возможностям symfony. К примеру это может пригодится для обработки писем или для периодического обновления модели c помощью ресурсоемких вычислений. В таком скрипте нужно добавить несколько строчек в начало, по аналогии с фронт-контроллером. Листинг 6-3 содержит пример начала batch скрипта.

Листинг 6-3 – Пример batch скрипта

[php]
<?php

define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'myapp');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

// добавьте свой код тут

Единственное отличие по сравнению с фронт-контроллером — отсутствие вызова метода dispatch() объекта sfController, который может использоваться только при вызове через веб сервер, но не в batch процессе. Вы получаете доступ к конкретной конфигурации, благодаря тому, что задано приложение и режим. Подключение файла приложения config.php инициирует контекст (context) и автоподключение.

TIP CLI symfony предоставляет команду init-batch, которая автоматически создает в директории batch/ базовый файл (скелет) похожий на приведенный в листинге 6-3. Нужно только задать в качестве аргументов имя приложения, режима, и самого batch файла.

Действия (actions)

Действия это сердце приложения, поскольку они содержат всю его логику. Они используют model и задают переменные для view. Когда вы делайте веб запрос к приложению построенному на symfony, URL определяет действие (action) и параметры запроса (request parameters).

Класс действия (Action Class)

Действия это методы класса, который является потомком класса sfActions. Методы именуются по схеме executeActionName, а класс имеет имя вида moduleNameActions. Каждый модуль имеет такой класс действий, он хранится в файле actions.class.php директории actions/.

Листинг 6-4 содержит пример файла actions.class.php, с единственным на весь модуль действием index.

Листинг 6-4 – Пример класса действия, в файле apps/myapp/modules/mymodule/actions/actions.class.php

[php]
class mymoduleActions extends sfActions
{
  public function executeIndex()
  {

  }
}

CAUTION Symfony чувствителен к регистру имен методов, в отличии от PHP. Не забывайте о том, что первая часть имени действия (execute) начинается с маленькой буквы, а вторая, содержащая собственно имя, с большой.

Для того чтобы запросить действие (action), нужно вызвать скрипт фронт-контроллера с именами модуля и действия в качестве параметров. По умолчанию, это можно сделать дописав пару module_name/action_name к скрипту. Это означает, что действие из листинга 6-4, может быть вызвано с помощью такого URL:

http://localhost/index.php/mymodule/index

Чтоб добавить еще несколько действий нужно просто добавить в класс действий методы execute, как показано в листинге 6-5.

Листинг 6-5 – Класс действий с двумя действиями, в файле myapp/modules/mymodule/actions/actions.class.php

[php]
class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    ...
  }

  public function executeList()
  {
    ...
  }
}

Если размер класса действия становится слишком большим, возможно следует провести refactoring, и перенести часть кода в модель (model). Чаще всего действия должны быть короткими (не более чем несколько строчек), и вся деловая логика обычно должна содержаться в модели (model).

Несмотря на это, количество действий в модуле может побудить вас разделить его на два модуля.

SIDEBAR Стандарты кодирования symfony

Вы должно быть заметили, что в примерах этой книги открывающие и закрывающие фигурные скобки ({ и }) находятся на одной вертикальной линии. Благодаря этому правилу код выигрывает в читаемости.

Среди стандартов кодирования фрейморка есть правило согласно которому, отступы следует делать двумя пробелами; табуляция не используется. Это объясняется тем, что в разных текстовых редакторах, tab имеет разное значение в пробелах. К тому же, если код содержит отступы с табуляцией и пробелами вперемешку, его невозможно читать.

PHP файлы ядра и файлы генерируемые symfony обычно не заканчиваются закрывающим тегом ?>. В этом нет реальной необходимости. Также использование закрывающих тегов может создать проблемы с выводом, если после тега будут пробелы.

Если вы очень внимательны, то вы заметите, что в symfony строка никогда не заканчивается пробелом. Причина на этот раз более прозаична: такие строчки безобразно выглядят в текстовом редакторе Фабьена.

Альтернативный способ организовать действия

Есть альтернативный способ организовать действия (action). Можно разместить каждое действие в отдельном файле, по одному действию на файл. В этом случае, каждый класс действия именуется по схеме actionNameAction, и является потомком класса sfAction (а не sfActions). Соответствующий же действию метод называется просто execute. Имя файла будет таким же как и имя класса. Итак, два файла, представленные в листингах 6-6 и 6-7, являются эквивалентом листинга 6-5.

Листинг 6-6 – Отдельный файл действия, в myapp/modules/mymodule/actions/indexAction.class.php

[php]
class indexAction extends sfAction
{
  public function execute()
  {
    ...
  }
}

Листинг 6-7 – Отдельный файл действия, в myapp/modules/mymodule/actions/listAction.class.php

[php]
class listAction extends sfAction
{
  public function execute()
  {
    ...
  }
}

Получение информации в действии

Класс действий предоставляет методы для доступа к классам ядра symfony и к информации, связанной с контроллером. Листинг 6-8 показывает как их использовать.

Листинг 6-8 – Распространенные методы класса sfActions

[php]
class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    // Получение параметра запроса
    $password    = $this->getRequestParameter('password');

    // Получение информации по контроллеру
    $moduleName  = $this->getModuleName();
    $actionName  = $this->getActionName();

    // Получение объектов ядра фреймворка
    $request     = $this->getRequest();
    $userSession = $this->getUser();
    $response    = $this->getResponse();
    $controller  = $this->getController();
    $context     = $this->getContext();

    // Определение переменных действия для передачи информации в шаблон
    $this->setVar('foo', 'bar');
    $this->foo = 'bar';            // Короткий синтаксис

  }
}

SIDEBAR singleton класс контекста

Вы уже видели вызов sfContext::getInstance() в фронт-контроллере. В действии вызов этого метода вернет тот же singleton. Этот очень полезный объект хранит ссылки на все связанные с данным запросом объекты ядра symfony, и предоставляет методы для доступа к каждому из них:

  • sfController: объект контроллера (->getController())
  • sfRequest: объект запроса (->getRequest())
  • sfResponse: объект пакета ответа (->getResponse())
  • sfUser: объект сессии пользователя (->getUser())
  • sfDatabaseConnection: соединение с базой данных (->getDatabaseConnection())
  • sfLogger: объект журнала событий (->getLogger())
  • sfI18N: объект интернационализации (->getI18N())

Вы можете обратиться к этому singleton классу (sfContext::getInstance()) из любого участка кода.

Завершение действия

Под конец выполнения действия возможны различные варианты дальнейшего поведения. Возвращенное действием (action) значение определяет как обрабатывать view. Чтоб узнать какой шаблон нужно использовать для отображения результата действия, используются константы класса sfView.

Если вы хотите чтоб view обрабатывался стандартно (это наиболее распространенный случай), действие должно заканчиваться так:

[php]
return sfView::SUCCESS;

В таком случае symfony будет искать шаблон с названием actionNameSuccess.php. Это поведение по умолчанию. Даже если вы упустили return в действии, symfony все равно будет использовать шаблон actionNameSuccess.php. Пустые действия (action) ведут себя также. Взгляните на пример успешного завершения действия в листинге 6-9.

Листинг 6-9 – Действия вызовут шаблоны indexSuccess.php и listSuccess.php

[php]
public function executeIndex()
{
  return sfView::SUCCESS;
}

public function executeList()
{
}

Если нужно вызвать соответствующий действию шаблон ошибки (error view), действие должно заканчиваться так:

[php]
return sfView::ERROR;

В таком случае symfony будет использовать шаблон actionNameError.php.

Вы можете придумать свое итоговое состояние (как например SUCCESS или ERROR), и применить его:

[php]
return 'MyResult';

После такого окончания действия (action) symfony будет искать шаблон actionNameMyResult.php.

Если представление данных не нужно (к примеру, в случае если действие используется в batch процессе), то действие должно заканчиваться так:

[php]
return sfView::NONE;

В этом случае шаблон обрабатываться не будет. Таким образом вы можете обойти часть View MVC архитектуры, и выводить HTML код напрямую из действия. Для этого случая в symfony есть специальный метод renderText() (см. листинг 6-10). Такой подход может пригодиться, если крайне важна быстрота выполнения действия (action), как например в Ajax интерфейсах, которые будут рассмотрены подробнее в главе 11.

Листинг 6-10 – View пропущен с помощью вывода ответа напрямую и возвращения sfView::NONE

[php]
public function executeIndex()
{
  echo "<html><body>Hello, World!</body></html>";

  return sfView::NONE;
}

// Код выше эквивалентен следующему:
public function executeIndex()
{
  return $this->renderText("<html><body>Hello, World!</body></html>");
}

В некоторых случаях нужно отослать пустой ответ, содержащий некоторые заголовки (особенно заголовок X-JSON). В следующей главе рассказывается об объекте sfResponse, с помощью которого можно задавать заголовки (header) ответа. Как вернуть только заголовки показано в листинге 6-11.

Листинг 6-11 – View не обрабатывается, будут отосланы только заголовки (header)

[php]
public function executeRefresh()
{
  $output = '<"title","My basic letter"],["name","Mr Brown">';
  $this->getResponse()->setHttpHeader("X-JSON", '('.$output.')');

  return sfView::HEADER_ONLY;
}

Если вы хотите в действии (action) непосредственно указать какой шаблон использовать, то следует применять метод setTemplate() вместо return:

[php]
$this->setTemplate('myCustomTemplate');

Переход к другому действию (action)

В некоторых случаях, действие (action) заканчивается запросом другого действия. Например действие, обрабатывающие данные отосланные из формы методом POST, обычно перенаправляет (redirect) запрос на другое действие после обновления базы данных. Или другой пример: действие index часто должно в итоге выводить список (list), и поэтому весь код сводится к перенаправлению на действие list.

Класс действия (action class) предоставляет два метода для перехода к выполнению другого действия:

  • Действие передает вызов другому действию:

    [php]
    $this->forward('otherModule', 'index');
    
  • Результатом действия будет переадресация (web redirection):

    [php]
    $this->redirect('otherModule/index');
    $this->redirect('http://www.google.com/');
    

NOTE Код идущий после методов forward или redirect не будет выполнен. В этом плане они эквивалентны выражению return. И forward и redirect выдают ошибку sfStopException чтоб остановить выполнение действия; позже symfony получит эту ошибку и попросту ее проигнорирует.

Выбор между forward и redirect иногда немного хитроват. Для того чтоб выбрать правильно нужно помнить, что forward действует только внутри приложения, и его применение невидимо для пользователя. В силу этого, URL в браузере останется таким же, несмотря на перенаправление на другое действие. В отличии от forward, redirect это сообщение браузеру пользователя, получив которое браузер делает новый запрос по другому URL.

Если действие вызывается формой с методом “post”, то вы всегда должны делать redirect. Основное преимущество в этом случае — если пользователь обновит результирующую страницу, данные из формы не будут отосланы еще раз. К тому же, кнопка “Back” (“Назад”) браузера будет работать правильно и вернет пользователя на страницу с формой, а не выдаст предупреждение о повторной отсылке POST запроса.

Есть один особый вид метода forward, который очень часто используется. Метод forward404() перенаправляет на действие «страница не найдена» ("page not found" action). Как правило этот метод используется в случае, если необходимый для выполнения действия параметр отсутствует в запросе (таким образом выявляются неправильно набранные URL).

Листинг 6-12 содержит пример действия show, для выполнения которого нужен параметр id.

Листинг 6-12 – Использование метода forward404()

[php]
public function executeShow()
{
  $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
  if (!$article)
  {
    $this->forward404();
  }
}

TIP Вы найдете действие 404 и соответствующий ему шаблон в директории $sf_symfony_data_dir/modules/default/. Вы можете создать свою страничку 404, добавив в приложение модуль default (который заменит собой аналогичный модуль самого фреймворка), и создав в нем действие error404 и шаблон error404Success. Также можно использовать уже существующие действие в качестве действия 404, для этого нужно просто задать константы error_404_module и error_404_ action в файле settings.yml.

Опыт показывает, что чаще всего forward или redirect используется в действии (action) после какой-то проверки, как например в листинге 6-12. Именно поэтому класс sfActions предоставляет еще несколько методов: forwardIf(), forwardUnless(), forward404If(), forward404Unless(), redirectIf(), и redirectUnless(). Эти методы используют дополнительный параметр, который запускает перенаправление если значение параметра true (методы вида xxxIf()) или false (методы вида xxxUnless()). Пример приведен в Листинге 6-13.

Листинг 6-13 – Использование метода forward404If()

[php]
// Это действие эквивалентно действию из листинга 6-12
public function executeShow()
{
  $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
  $this->forward404If(!$article);
}

// Также как и это действие
public function executeShow()
{
  $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
  $this->forward404Unless($article);
}

Благодаря этим методам код стал коротким и более читаемым.

TIP Когда действие вызывает forward404() или другой подобный метод, symfony выдает (throw) ошибку sfError404Exception, которая и заботится об отправке ответа 404 (404 response). Таким образом, если вам понадобится откуда-то вывести сообщение 404 и при этом вы не захотите обратится к контроллеру, то достаточно просто выдать такую ошибку.

Повторение кода для нескольких действий модуля

Договоренности (convention) об именах действий executeActionName() (в случае класса sfActions) и execute() (в случае класса sfAction) гарантируют, что symfony найдет соответствующий действию метод. Благодаря этой договоренности вы можете добавить методы, которые не будут рассматриваться как действия (action). Для этого название метода просто не должно начинаться на execute.

Существует еще одна удобная договоренность (convention) на случай, если в начале каждого действия нужно выполнить какой-то код одинаковый для всех действий. Вы можете вынести повторяющейся код в метод preExecute() класса действий. Если же этот код должен выполнятся после выполнения действия, его следует поместить в метод postExecute(). Как выглядят эти методы в классе действий демонстрирует листинг 6-14.

Листинг 6-14 – Методы preExecute, postExecute, и пользовательский метод в классе действий

[php]
class mymoduleActions extends sfActions
{
  public function preExecute()
  {
    // Код размещенный здесь выполняется в начале каждого действия
    ...
  }

  public function executeIndex()
  {
    ...
  }

  public function executeList()
  {
    ...
    $this->myCustomMethod();  // Методы класса действий доступны
  }

  public function postExecute()
  {
    // Код размещенный здесь выполняется в конце каждого действия
    ...
  }

  protected function myCustomMethod()
  {
    // Вы также можете добавить свои методы — главное чтоб их имена не начинались с "execute"
    // Такие методы лучше объявлять как protected или как private
    ...
  }
}

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

Вы уже знакомы с тем, как с помощью метода getRequestParameter('myparam') получать значения параметров запроса. Стоит отметить, что этот метод является методом-посредником (proxy method), который через цепочку вызовов обращается к контейнеру параметров запроса getRequest()->getParameter('myparam'). Через метод getRequest() класс действий может получить доступ к объекту запроса (который называется sfWebRequest в symfony) и всем его методам. Таблица 6-1 содержит список наиболее полезных методов sfWebRequest.

Таблица 6-1 – Методы объекта sfWebRequest

Имя | Что возвращает | Пример возвращенного значения -------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------- Информация о запросе | | getMethod() | Метод запроса | Возвращает константу sfRequest::GET или sfRequest::POST getMethodName() | Имя метода запроса | 'POST' getHttpHeader('Server') | Значение данного HTTP заголовка (header) | 'Apache/2.0.59 (Unix) DAV/2 PHP/5.1.6' getCookie('foo') | Значение заданного cookie | 'bar' isXmlHttpRequest()* | Это ajax запрос? | true isSecure() | Это SSL запрос? | true Параметры запроса | | hasParameter('foo') | Есть ли такой параметр в запросе? | true getParameter('foo') | Значение данного параметра | 'bar' getParameterHolder()->getAll() | Массив всех параметров запроса | Связанная с URI информация | | getUri() | Полный URI | 'http://localhost/myapp_dev.php/mymodule/myaction' getPathInfo() | Путь вида /модуль/действие | '/mymodule/myaction' getReferer() | Реферер | 'http://localhost/myapp_dev.php/' getHost() | Имя хоста | 'localhost' getScriptName() | Название фронт-контроллера и путь к нему | 'myapp_dev.php' Информация о браузере клиента | | getLanguages() | Массив допустимых языков | Array( [0] => fr [1] => fr_FR [2] => en_US [3] => en ) getCharsets() | Массив допустимых кодировок | Array( [0] => ISO-8859-1 [1] => UTF-8 [2] => * ) getAcceptableContentTypes() | Массив допустимых типов контента | Array( [0] => text/xml [1] => text/html

  • Работает только с prototype

Иногда блокируется прокси серверами

Класс sfActions предоставляет несколько методов-посредников (proxy method) для более быстрого доступа к вышеперечисленным методам. Листинг 6–15 содержит пример их использования.

Листинг 6–15 – Доступ к методам объекта sfRequest из действия

[php]
class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    $hasFoo = $this->getRequest()->hasParameter('foo');
    $hasFoo = $this->hasRequestParameter('foo');  // более короткий вариант
    $foo    = $this->getRequest()->getParameter('foo');
    $foo    = $this->getRequestParameter('foo');  // более короткий вариант
  }
}

Пользователь может отправить запрос с прикрепленным к нему файлом. Для таких случаев sfWebRequest предоставляет специальные методы, с помощью которых можно сохранить файл. Листинг 6–16 показывает как работать с полученными файлами.

Листинг 6–16 – Обработка полученных файлов с помощью объекта sfWebRequest

[php]
class mymoduleActions extends sfActions
{
  public function executeUpload()
  {
    if ($this->getRequest()->hasFiles())
    {
      foreach ($this->getRequest()->getFileNames() as $fileName)
      {
        $fileSize  = $this->getRequest()->getFileSize($fileName);
        $fileType  = $this->getRequest()->getFileType($fileName);
        $fileError = $this->getRequest()->hasFileError($fileName);
        $uploadDir = sfConfig::get('sf_upload_dir');
        $this->getRequest()->moveFile('file', $uploadDir.'/'.$fileName);
      }
    }
  }
}

Вам не нужно беспокоится о том, поддерживает ли ваш сервер переменную $_SERVER или $_ENV, о значениях по умолчанию, и о проблемах совместимости с сервером — за вас все это сделает методы sfWebRequest. Имена методов говорят сами за себя, и вам больше не нужно копаться в документации по PHP для того чтоб получить информацию из запроса.

Сессия

Symfony автоматически управляет сессиями пользователей и может хранить данные пользователя на протяжении всех его запросов. Фреймворк использует встроенные в PHP механизмы обработки сессий, улучшая их так, что они становятся более податливы настройке и просты в использовании.

Доступ к сессии пользователя

Доступ к объекту сессии текущего пользователя из действия можно получить с помощью метода getUser(), сам же объект является экземпляром класса sfUser. Этот класс содержит контейнер параметров, который позволяет хранить любые атрибуты пользователя. Эти данные будут доступны для других запросов вплоть до конца сессии пользователя, что проиллюстрировано в листинге 6–17. Атрибутом пользователя может быть что угодно (строка, массив, ассоциативный массив). Атрибут можно задать индивидуально для любого пользователя, даже если пользователь не идентифицирован.

Листинг 6–17 – Объект sfUser может хранить любые атрибуты пользователя на протяжении его цепочки запросов

[php]
class mymoduleActions extends sfActions
{
  public function executeFirstPage()
  {
    $nickname = $this->getRequestParameter('nickname');

    // сохранение данных в сессии пользователя
    $this->getUser()->setAttribute('nickname', $nickname);
  }

  public function executeSecondPage()
  {
    // получение данных из сессии с определением значения по умолчанию
    $nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward');
  }
}

CAUTION Вы можете сохранять в сессии объекты, но это настоятельно не рекомендуется. Поскольку между запросами объект преобразовывается в последовательную форму (serialize) и сохраняется в файле. Когда же данные восстанавливаются из файла, класс, экземпляр которого хранится в сессии, должен быть уже загружен, а это не всегда так.

Как и многие другие «getter» методы, getAttribute() использует второй параметр, чтоб определить значение параметра по умолчанию (для случая если параметр не задан). Чтоб проверить задан ли для пользователя какой либо атрибут используйте метод hasAttribute(). Все атрибуты хранятся в контейнере параметров, который можно получить с помощью метода getAttributeHolder(). С помощью методов этого контейнера параметров очень просто удалять атрибуты и очищать (cleanup) контейнер полностью. На пример того как удалять данные можно глянуть в листинге 6–18.

Листинг 6–18 – Удаление данных из сессии

[php]
class mymoduleActions extends sfActions
{
  public function executeRemoveNickname()
  {
    $this->getUser()->getAttributeHolder()->remove('nickname');
  }

  public function executeCleanup()
  {
    $this->getUser()->getAttributeHolder()->clear();
  }
}

Как показано в листинге 6–19, атрибуты сессии можно получить и в шаблоне, с помощью переменной $sf_user, которая хранит текущий объект sfUser.

Листинг 6–19 – Доступ к атрибутам сессии из шаблона

[php]
<p>
  Hello, <?php echo $sf_user->getAttribute('nickname') ?>
</p>

NOTE Если вам нужно сохранить информацию только на время текущего запроса (чтобы к примеру передать информацию через цепочку вызовов действий), вы можете воспользоваться классом sfRequest, который также имеет методы getAttribute() и setAttribute(). Только атрибуты объекта sfUser сохраняются от запроса к запросу.

Флеш атрибут (Flash Attributes)

NOTE flash — (англ.) вспышка, миг, мгновение и любое обозначение очень короткого промежутка времени.

Очистка сессии это постоянная проблема с атрибутами пользователей. После того как атрибут стал не нужен, его необходимо удалить. К примеру, вы захотите выводить подтверждение об успехе после обновления данных через форму. Так как механизм обработки форм делает переадресацию (redirect) на другое действие, то единственный способ передать данные из действия в действие это сохранить их в сессии. Но после того, как выведено подтверждение об успешности операции, нужно очистить атрибут; иначе он останется в сессии до конца.

Флеш атрибут (flash attribute) можно задать и больше о нем не заботится, поскольку он исчезнет после следующего запроса. Таким образом сессия останется чистой. В действии (action) задавать атрибуты следует так:

[php]
$this->setFlash('attrib', $value);

Шаблон будет обработан и отправлен пользователю, который отошлет новый запрос к другому действию. И в этом действии можно получить значение флеш атрибута:

[php]
$value = $this->getFlash('attrib');

Затем забудьте о нем. После того как пользователь получит вторую страничку, атрибут attrib перестанет существовать. И даже если вы не воспользовались им во втором действии, флеш атрибут все равно исчезнет из сессии.

Если нужно получить флеш атрибут в шаблоне используйте объект $sf_falsh:

[php]
<?php if ($sf_flash->has('attrib')): ?>
  <?php echo $sf_flash->get('attrib') ?>
<?php endif; ?>

Или просто:

[php]
<?php echo $sf_flash->get('attrib') ?>

Флеш атрибут это чистый путь для передачи информации в следующий запрос.

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

Обработка сессий в symfony полностью скрывает от разработчика хранение сессии на стороне клиента и на серверной стороне. Однако, вы можете при желании изменить стандартное поведение механизмов управления сессиями. Эта возможность предусмотрена прежде всего для продвинутых пользователей фреймворка.

На стороне клиента сессии обрабатываются с помощью cookie. Cookie сессии symfony называется symfony, но вы можете поменять это название в файле factories.yml (см. листинг 6–20):

Листинг 6–20 – Изменение имени для cookie в apps/myapp/config/factories.yml

all:
  storage:
    class: sfSessionStorage
    param:
      session_name: my_cookie_name

TIP Сессия запускается (с помощью PHP функции session_start()) только если в файле factories.yml параметр auto_start равен true (а по умолчанию так и есть). Если вы хотите самостоятельно запускать сессию, выключите эту опцию.

Обработка сессий в symfony основана на PHP сессиях. А значит, если вы хотите чтоб на стороне клиента управление осуществлялось через параметры URL вместо cookie, нужно просто поменять значение параметра use_trans_sid в php.ini (но помните, что это не рекомендуется):

session.use_trans_sid = 1

На серверной стороне symfony по умолчанию хранит сессии в файлах. Вы можете хранить сессии в базе данных, для этого нужно просто изменить в factories.yml опцию class как показано в листинге 6–21.

Листинг 6–21 – Изменение способа хранения сессий на серверной стороне apps/myapp/config/factories.yml

all:
  storage:
    class: sfMySQLSessionStorage
    param:
      db_table: SESSION_TABLE_NAME      # Имя таблицы в которой будут храниться сессии
      database: DATABASE_CONNECTION     # Имя соединения с базой, которое будет использоваться

Классы доступные для хранения сессий: sfMySQLSessionStorage, sfPostgreSQLSessionStorage, и sfPDOSessionStorage; последний наиболее предпочтителен. Дополнительный параметр database определяет, какое соединение с базой использовать; далее symfony обратится к databases.yml (см. главу 8) чтоб получить параметры этого соединения (хост, имя базы, логин, и пароль).

Сессия заканчивается автоматически по истечении времени заданного в параметре sf_timeout. Эта опция по умолчанию равна 30 минутам, задать это значение можно в файле settings.yml индивидуально для каждого режима. Листинг 6–22 содержит пример.

Листинг 6–22 – Изменение продолжительности сессии в файле apps/myapp/config/settings.yml

default:
  .settings:
    timeout:     1800           # Время жизни сессии в секундах

Безопасность действий

Доступ к некоторым действиям (action) может быть ограничен для определенного круга пользователей имеющих некие права. Symfony предоставляет инструменты для создания таких защищенных приложений, где только аутентифицированные пользователи (почитать об аутентификации) имеют доступ к некоторым возможностям и частям приложения. Защита приложения состоит из двух частей: определение требований безопасности для каждого действия и вход (logging in) пользователей с определенными правами, позволяющим получить доступ к защищенным действиям.

Ограничение доступа

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

  • Защищенные действия требуют аутентификации пользователя
  • Привилегии (сredential) это конкретные права доступа, позволяющие организацию защиты по признаку принадлежности к той или иной группе пользователей.

Чтоб ограничить доступ к действию достаточно просто сделать YAML файл security.yml в директории модуля config/. В этом файле вы можете задать требования, которым должен соответствовать пользователь для доступа к действиям. Эти требования можно определить для всех действий вместе, так и индивидуально для каждого. Листинг 6-23 содержит пример файла security.yml.

Листинг 6-23 – Ограничения прав доступа в файле apps/myapp/modules/mymodule/config/security.yml

read:
  is_secure:   off       # Все пользователи могут выполнить это действие

update:
  is_secure:   on        # Действие "update" доступно только для аутентифицированных пользователей

delete:
  is_secure:   on        # Только для аутентифицированных пользователей 
  credentials: admin     # Только для пользователей с правами администратора

all:
  is_secure:  off        # off это значение по умолчанию

По умолчанию действия не защищены. Поэтому доступ к действию имеет каждый, если действие не фигурирует в security.yml. Если security.yml существует, symfony будет искать в нем запрашиваемое действие. И если найдет — проверит соответствие пользователя требованиям защиты. Что случится при попытке обратится к защищенному действию зависит от привилегий пользователя:

  • Если пользователь аутентифицирован и у него есть нужные привилегии, то действие будет выполнено.
  • Если пользователь не идентифицирован, он будет перенаправлен на действие login (страницу с формой входа).
  • Если пользователь идентифицирован, но не имеет нужных привилегий, он будет перенаправлен на стандартное действие secure, результат которого показан на рисунке 6-1.

Страницы стандартных действий secure и login очень просты, и возможно вы захотите изменить их. Вместо них можно задать другие действия, изменив установки в файле settings.yml, как показано в листинге 6-24.

Рисунок 6-1 – Стандартная страничка действия secure

Стандартная страничка действия secure

Листинг 6-24 – Стандартные действия защиты заданы в apps/myapp/config/settings.yml

all:
  .actions:
    login_module:           default
    login_action:           login

    secure_module:          default
    secure_action:          secure

Предоствление доступа

Чтоб полущить доступ к защищенным действиям, пользователь должен быть аутентифицирован и/или иметь некие привилегии (credential). С помощью объекта sfUser вы можете предоставить пользователю больше прав. Воспользовавшись методом setAuthenticated() можно придать пользователю статус аутентифицированного. Листинг 6-25 содержит простой пример аутентификации пользователя.

Листинг 6-25 – Придание пользователю статуса аутентифицированного

[php]
class myAccountActions extends sfActions
{
  public function executeLogin()
  {
    if ($this->getRequestParameter('login') == 'foobar')
    {
      $this->getUser()->setAuthenticated(true);
    }
  }

  public function executeLogout()
  {
    $this->getUser()->setAuthenticated(false);
  }
}

С привилегиями (credential) работать немного сложнее, поскольку вы можете проверять, добавлять, отнимать или полностью очищать привилегии. Листинг 6-26 познакомит вас с методами класса sfUser для работы с привилегиями (credential).

Листинг 6-26 – Работа с привилегиями (credential) в действии

[php]
class myAccountActions extends sfActions
{
  public function executeDoThingsWithCredentials()
  {
    $user = $this->getUser();

    // Добавление одной или нескольких привилегий
    $user->addCredential('foo');
    $user->addCredentials('foo', 'bar');

    // Проверка, имеет ли пользователь данную привилегию
    echo $user->hasCredential('foo');                      =>   true

    // Проверка, имеет ли пользователь обе привилегии 
    echo $user->hasCredential(array('foo', 'bar'));        =>   true

    // Проверка, имеет ли пользователь одну из привилегий
    echo $user->hasCredential(array('foo', 'bar'), false); =>   true

    // Отнятие привилегии
    $user->removeCredential('foo');
    echo $user->hasCredential('foo');                      =>   false

    // Полная очистка привилегий, отнимаются все привилегии (полезено для выхода (logout) из приложения)
    $user->clearCredentials();
    echo $user->hasCredential('bar');                      =>   false
  }
}

Если у пользователя есть привилегия «foo», он имеет доступ к действиям, для которых в security.yml требуется эта привилегия. В листинге 6-27 показано как, используя привилегии в шаблоне, некоторые части страницы можно сделать видимыми только для пользователей имеющих соответственные права.

Листинг 6-27 – Работа с привилегиями в шаблоне

[php]
<ul>
  <li><?php echo link_to('section1', 'content/section1') ?></li>
  <li><?php echo link_to('section2', 'content/section2') ?></li>
  <?php if ($sf_user->hasCredential('section3')): ?>
  <li><?php echo link_to('section3', 'content/section3') ?></li>
  <?php endif; ?>
</ul>

Привилегии, как и статус аутентифицированного, обычно придаются пользователю при входе (логине, login). Именно поэтому в объект sfUser часто добавляют методы login и logout. Таким образом, статус и привилегии пользователя определяются в одном месте.

TIP Один из плагинов (plug-in) symfony, а именно sfGuardPlugin добавляет в класс сессии специальные возможности для авторизации и аутентификации. Используя расширенный класс, создавать вход (login) и выход (logout) проще. Глава 17 содержит больше информации по этой теме.

Сложные привилегии (credential)

Используя синтаксис YAML в файле security.yml, можно разрешить доступ к какому либо действию только пользователям, имеющим определенную комбинацию привилегий (credential). Для создания таких комбинаций используются связи типа AND или OR. С помощью таких связей можно построить сложную систему управления привилегиями. К примеру, админитерфейс доступен пользователям с привилегией (credential) admin, статьи могут редактировать только пользователи с привилегией editor, а публиковать статьи можно только с привилегией publisher. Листинг 6-28 демонстрирует как описать такую систему прав доступа.

Листинг 6-28 – Комбинации привилегий

editArticle:
  credentials: [ admin, editor ]              # admin AND editor

publishArticle:
  credentials: [ admin, publisher ]           # admin AND publisher

userManagement:
  credentials: [[ admin, superuser ]]         # admin OR superuser

Чтоб поменять связь между привилегиями с AND на OR или наоборот, достаточно просто еще раз заключить их в квадратные скобки. Таким образом можно создать очень сложную комбинацию привилегий, к примеру такую:

credentials: [[root, [supplier, [owner, quasiowner]], accounts]]
             # root OR (supplier AND (owner OR quasiowner)) OR accounts

Валидация и методы обработки ошибок

Валидация входящих данных действия (в основном параметров запроса) это нудная и повторяющейся задача. Symfony предоставляет встроенную систему валидации запроса, которая использует методы класса действий.

Начнем с примера. Когда пользователь делает запрос к действию myAction, symfony сперва ищет метод validateMyAction(). Если такой метод находится, фреймворк выполняет его. Возвращенное значение этого валидационного метода и определяет какой метод выполнять на следующем этапе: если true, то запускается executeMyAction(), а если falsehandleErrorMyAction(). В последнем случае проверяется существование метода handleErrorMyAction(). Если он не существует symfony ищет общий метод handleError(). Если же и он не существует, будет возвращено значение sfView::ERROR и, соответственно, будет использован шаблон myActionError.php. Рисунок 6-2 иллюстрирует вышеописанное.

Рисунок 6-2 – Процесс валидации

Процесс валидации

Итак, для того чтоб пользоваться такой системой необходимо просто помнить несколько договоренностей о наименованиях методов:

  • validateActionName это валидационный метод, возвращает true или false. Фреймворк выполнит этот метод первым при запросе к действию ActionName. Если validateActionName не существует, то будет выполняться запрашиваемое действие.
  • Метод handleErrorActionName вызывается в случае если валидационный метод вернет false. Если такого метода не существует, выведется шаблон Error.
  • executeActionName — метод действия. Должен существовать для любого действия.

Листинг 6-29 содержит пример класса действий с валидационным методом. Даже если результат валидации будет отрицательным шаблон myActionSuccess.php все равно будет обработан, однако значения параметров будут разниться.

Листинг 6-29 – Пример валидационного метода

[php]
class mymoduleActions extends sfActions
{
  public function validateMyAction()
  {
    return ($this->getRequestParameter('id') > 0);
  }

  public function handleErrorMyAction()
  {
    $this->message = "Invalid parameters";

    return sfView::SUCCESS;
  }

  public function executeMyAction()
  {
    $this->message = "The parameters are correct";
  }
}

В коде валидационных методов вы можете делать что угодно. Единственное — убедитесь, что они возвращают булевое значение. Так как это методы класса sfActions, из них у вас будет доступ к объектам sfRequest и sfUser, что очень удобно для проверки входящих данных и контекста.

Этот механизм можно использовать для валидации форм (контроль вводимых пользователем в форму данных). Но для этой часто встречающейся задачи symfony предоставляет специальные средства, описанные в главе 10.

Фильтры

Работу безопасности можно рассматривать как работу фильтра, через который пропускаются все запросы перед выполнением действия. По результатам произведенных в фильтре проверок, можно изменить выполнение запроса — например приказав выполнять другое действие (в случае фильтра безопасности — действие default/secure вместо запрашиваемого действия). Результатом обобщения этой идеи в symfony стали фильтры. Вы можете задавать количество фильтров, которые будут выполняться перед выполнением действия или перед формированием ответа (response rendering) при каждом запросе. Можно рассматривать фильтры как блоки кода, похожие на preExecute() или postExecute(). Отличие в том что фильтры относятся к более высокому уровню иерархии (не к модулю, а ко всему приложению).

Цепочка фильтров

Symfony рассматривает обработку запроса как цепочку фильтров. Когда фреймворк получает запрос выполняется первый фильтр (а это всегда sfRenderingFilter). В какой-то момент он вызывает следующий фильтр, который в свою очередь тоже вызывает следующий фильтр, и т. д. Когда последний фильтр выполнен (а это фильтр sfExecutionFilter), предыдущий фильтр продолжает свою работу до завершения, и т.д. Таким образом идет возврат к первому фильтру. Рисунок 6-3 иллюстрирует работу цепочки фильтров. Отметим, что на схеме изображена упрощенная цепь фильтров — реальная цепочка содержит больше фильтров.

TIP Я переводил словосочетание «filter class» просто как «фильтр». Следует помнить что фильтр является классом.

переводчик

Рисунок 6-3 – Пример цепочки фильтров

Пример цепочки фильтров

Структура фильтров построена исходя из проиллюстрированного процесса. Все они являются потомками класса sfFilter, и содержат один метод execute(), в который нужно передать объект $filterChain. В какой-то точке этого метода, с помощью $filterChain->execute(), будет вызван следующий фильтр цепочки. Пример можно посмотреть в листинге 6-30. Таким образом фильтр состоит из двух частей:
* Код перед вызовом $filterChain->execute() выполняется перед выполнением действия. * Код после вызова $filterChain->execute() выполняется после выполнения действия, но до создания пакета ответа (rendering).

Листинг 6-30 – Структура фильтра

[php]
class myFilter extends sfFilter
{
  public function execute ($filterChain)
  {
    // Код выполняемый перед действием
    ...

    // Перейти к выполнению следующего фильтра цепочки
    $filterChain->execute();

    // Код выполняемый после действия, но перед формированием ответа
    ...
  }
}

Стандартная цепочка фильтров задана в конфигурационном файле filters.yml, и приведена в листинге 6-31. Этот файл содержит список фильтров, которые должны быть выполнены при каждом запросе.

Листинг 6-31 – Стандартная цепочка фильтров в файле myapp/config/filters.yml

rendering: ~
web_debug: ~
security:  ~

# Как правило свои фильтры следует вставлять тут

cache:     ~
common:    ~
flash:     ~
execution: ~

Как видно, эти объявления не имеют параметров (символ ~ означает null на языке YAML), поскольку они наследуют параметры, определенные в ядре фреймворка. В ядре, symfony задает установки class и param для каждого из этих фильтров. Например, листинг 6-32 содержит стандартные параметры для фильтра rendering.

Листинг 6-32 – Стандартные параметры фильтра rendering в файле $sf_symfony_data_dir/config/filters.yml

rendering:
  class: sfRenderingFilter   # класс фильтра
  param:                     # параметры фильтра
    type: rendering

Оставляя пустое значение (~) в filters.yml, вы тем самым сообщайте фреймворку, что он должен запускать фильтр со стандартными параметрами, заданными в ядре.

Вы можете настраивать цепочку фильтров множеством способов:

  • Отключить фильтр, с помощью параметра enabled: off. К примеру, чтоб отключить фильтр web debug, нужно прописать:

    web_debug:
      enabled: off
    
  • Исключая фильтр из цепочки, не нужно удалять содержимое filters.yml. Если вы сделайте это symfony выдаст ошибку.

  • Чтоб добавить свой фильтр нужно прописать его объявление в цепочке (обычно после фильтра security). Создание своего фильтра рассмотрено в следующей секции. Помните, что фильтр rendering должен быть первым фильтром цепочки, а execution — последним.

  • Можно заменить (override) стандартные классы или изменить параметры фильтров (в особенности чтоб модифицировать систему безопасности (security system) и использовать для обеспечения безопасности ваш собственный фильтр).

TIP Параметр enabled: off отключает созданные вами фильтры. Стандартные же фильтры можно деактивировать в файле settings.yml, изменяя опции web_debug, use_security, cache и use_flash.

Создание своего фильтра

Создать фильтр очень просто. Нужно всего лишь сконструировать класс похожий на пример из листинга 6-30, и поместить его в одну из директорий lib/ проекта. Дальше сработает система автоматического подключения классов.

Действие может сделать forward или redirect на другое действие, и таким образом заново выполнить всю последовательность фильтров. Разумно если фильтры будут выполняться только для первого действия текущего запроса. Для этой цели существует метод isFirstCall() класса sfFilter возвращающий булевое значение. Пользоваться методом имеет смысл только до выполнения действия.

Лучше проиллюстрировать вышесказанное на примере. Листинг 6-33 содержит фильтр для автоматического входа (логина) пользователей с cookie MyWebSite (подразумевается, что этот cookie создается в действии login). Это простенький, но вполне рабочий способ обеспечить возможность “remember me” (“запомнить меня”), которая часто предлагается в формах логина.

Листинг 6-33 – Пример фильтра в apps/myapp/lib/rememberFilter.class.php

[php]
class rememberFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // Выполнить этот фильтр только один раз
    if ($this->isFirstCall())
    {
      // Объекты запроса и сессии не доступны из фильтра напрямую.
      // Для доступа к ним следует использовать объект контекста (context object)
      $request = $this->getContext()->getRequest();
      $user    = $this->getContext()->getUser();

      if ($request->getCookie('MyWebSite'))
      {
        // войти («залогиниться»)
        $user->setAuthenticated(true);
      }
    }

    // Выполнить следующий фильтр
    $filterChain->execute();
  }
}

В некоторых случаях нужно прервать выполнение цепочки фильтров и в конце фильтра перейти к выполнению действия. У sfFilter нету метода forward(), зато у sfController — есть, и к действию можно перейти таким образом:

[php]
return $this->getContext()->getController()->forward('mymodule', 'myAction');

NOTE При создании нового экземпляра класса sfFilter вызывается метод initialize(). Вы можете заменить (override) его в вашем фильтре, если вы хотите организовать работу с параметрами фильтра (которые, как описано ниже, заданы в filters.yml ) по-своему.

Активация фильтра и его параметры

После создания фильтра его нужно активировать, то есть добавить в цепочку фильтров. Для этого нужно объявить класс фильтра в файле filters.yml (который находится в директории config/ приложения или модуля) как показано в листинге 6-34.

Листинг 6-34 – Активация фильтра в apps/myapp/config/filters.yml

rendering: ~
web_debug: ~
security:  ~

remember:                 # У фильтра должно быть уникальное имя
  class: rememberFilter
  param:
    cookie_name: MyWebSite
    condition:   %APP_ENABLE_REMEMBER_ME%

cache:     ~
common:    ~
flash:     ~
execution: ~

Активированный фильтр будет выполняться при каждом запросе. Под ключом param: в файле filters.yml для фильтра могут быть заданы один или несколько параметров. Их можно получить из фильтра с помощью метода getParameter(). Листинг 6-35 демонстрирует как получить значение параметра.

Листинг 6-35 – Получение значения параметра в apps/myapp/lib/rememberFilter.class.php

[php]
class rememberFilter extends sfFilter
{
  public function execute ($filterChain)
  {
      ...
      if ($request->getCookie($this->getParameter('cookie_name')))
      ...
  }
}

Опция condition определяет должен выполнятся фильтр или нет. Таким образом, настройки фильтра могут опираться на настройки приложения (как в листинге 6-34). Фильтр remember выполняется только если в app.yml присутствует следующие:

all:
  enable_remember_me: on

Примеры фильтров

Фильтры полезны если нужно выполнять какой-то код для каждого действия. К примеру, если система статистики установлена на удаленном сервере (distant analytics system), необходимо поместить на каждую страницу код, вызывающий нужный скрипт этой системы (distant tracker script). Можно поместить его в главный шаблон (layout), но тогда код будет срабатывать для всего приложения. Еще один вариант — включить этот код в фильтр (как показано в листинге 6-36) и активировать его помодульно.

Листинг 6-36 – Фильтр google analytics

[php]
class sfGoogleAnalyticsFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // Перед действием ничего не происходит
    $filterChain->execute();

    // Добавляем в ответ (response) нужный код (tracker code)
    $googleCode = '
<script src="http://www.google-analytics.com/urchin.js"  type="text/javascript">
</script>
<script type="text/javascript">
  _uacct="UA-'.$this->getParameter('google_id').'";urchinTracker();
</script>';
    $response = $this->getContext()->getResponse();
    $response->setContent(str_ireplace('</body>', $googleCode.'</body>',$response->getContent()));
   }
}

Учтите, что этот фильтр не совершенен, поскольку к ответу (response), который не является HTML, код добавляться не должен.

Листинг 6-37 содержит еще один пример — фильтр для повышения безопасности передачи данных, превращающий запрос в SSL (если он таковым не является).

Листинг 6-37 – Фильтр secure communication

[php]
class sfSecureFilter extends sfFilter
{
  public function execute($filterChain)
  {
    $context = $this->getContext();
    $request = $context->getRequest();

    if (!$request->isSecure())
    {
      $secure_url = str_replace('http', 'https', $request->getUri());

      return $context->getController()->redirect($secure_url);
      // Обрываем цепочку фильтров
    }
    else
    {
      // Запрос безопасен, поэтому можно продолжить работу
      $filterChain->execute();
    }
  }
}

Фильтры интенсивно используются в плагинах (plug-in), поскольку они позволяют расширять возможности приложения глобально. Чтоб узнать больше о плагинах обратитесь к главе 17. Wiki (http://www.symfony-project.com/trac/wiki) содержит больше примеров фильтров.

Конфигурация модуля

Несколько аспектов поведения модулей зависят от настроек. Чтоб изменить их нужно создать файл module.yml в директории config/ и задать опции для каждого режима (или для всех режимов сразу под ключом all:). Листинг 6-38 содержит пример файла module.yml модуля mymodule.

Листинг 6-38 – Настройки модуля в apps/myapp/modules/mymodule/config/module.yml

all:                 # Для всех режимов
  enabled:     true
  is_internal: false
  view_name:   sfPHP

Опция enabled позволяет отключить все действия модуля. При обращении к отключенным действиям запрос будет переадресовываться на действие module_disabled_module/module_disabled_action (которое задается в settings.yml).

С помощью параметра is_internal можно разрешить только внутренние обращения к действиям. К примеру это удобно для действия которое рассылают письма. Можно вызвать его из другого действия и таким образом разослать письма, но извне этого сделать нельзя.

Параметр view_name задает класс вида (класс view). Он должен быть потомком класса sfView. Эта опция позволяет использовать другие обработчики шаблонов, например Smarty.

Итого

В symfony контроллер поделен на две части: фронт-контроллер (единственная точка входа в приложение для данного режима) и действия, каждое из которых содержат логику странички. Возвращая одну из констант sfView в действии, можно по-разному обрабатывать view. В самом действии можно работать с различными элементами контекста, такими как объект запроса (sfRequest) и объект сессии (sfUser).

Мощности объекта сессии (session object), объекта действия (action object), и настроек безопасности, в сочетании создают систему безопасности, которая может ограничивать доступ и предоставлять привилегии (credential). Для валидации запроса предусмотрены специальные методы validate() и handleError(). Методы preExecute() и postExecute() обеспечивают возможность выполнения общего для всех действий кода в рамках модуля. Для этой же цели на уровне приложения предназначены фильтры. В код содержащийся в фильтре выполнятся при каждом запросе.

Перевел Алексей Гоголев postman [at] dev [dot] co [dot] ua

Статья также доступна по адресу http://developer.co.ua/posts/view/glava_6_vnutri_kontrollera