Development

Documentation/ru_RU/book/1.0/07-Inside-the-View-Layer

You must first sign up to be able to contribute.

Глава 7 - Внутри слоя представления

Этот перевод 7-ой главы выполнил Сергей Васильев. Вы можете связаться со мной по e-mail symfony2007 at devoffice dot com .

Список переведеных мной терминов, с объяснением их значения:

  • helper - помощник. PHP-функция симфони, которая используется в представлениях;
  • layout - обрамление. HTML-код верстки страницы, общий для многих представлений;
  • partial - обособленный фрагмент шаблона. Сохраняется в каталоге templates/, имя файла задаётся в форме _mypartialname.php;
  • placeholder - заполнитель (текстовый или графический элемент электронного шаблона страницы, замещаемый реальным элементом);
  • slot - заглушка. Одна из разновидностей заполнителя в симфони;

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

  • Веб-дизайнеры обычно работают над шаблонами представления данных текущего действия и обрамлением, которое содержит код, общий для всех страниц.
  • Для повторного использования разработчики обычно пакуют фрагменты кода шаблона в компоненты. Они используют слоты и слоты компонентов, что бы покрыть более чем одну зону обрамления. Веб-дизайнеры так же могут работать над этими фрагментами шаблона.
  • Разработчики концентрируются на YAML-конфигурации представления (устанавливая свойства ответа и другие элементы интерфейса) и на объекте ответа. Работая с переменными в шаблонах, не следует забывать о риске cross-site scripting, хорошее понимание техник экранирования необходимо для безопасной записи данных, введенных пользователем.

Какой бы ни была ваша роль, вы найдёте полезные инструменты для ускорения рутинной работы создания представлений. Эта глава описывает все такие инструменты.

Шаблоны

Пример 7-1 показывает типичный шаблон symfony. Он содержит HTML-код и немного базового PHP-кода, обычно это обращения к переменным, определенным в action (действии) (через $this->name = 'foo';) и к помощникам.

Пример 7-1 - Шаблон indexSuccess.php

[php]
<h1>Welcome</h1>
<p>Welcome back, <?php echo $name ?>!</p>
<ul>What would you like to do?
  <li><?php echo link_to('Read the last articles', 'article/read') ?></li>
  <li><?php echo link_to('Start writing a new one', 'article/write') ?></li>
</ul>

Как объяснено в Главе 4, альтернативный PHP-синтаксис более предпочтителен для шаблонов, что бы сделать их более читабельными для не-PHP-разработчиков. Вы должны свести к минимуму PHP-код в шаблонах, потому что эти файлы используются для создания графического интерфейса приложения, и они иногда создаются и сопровождаются другой командой, специализирующейся на представлении, а не на логике приложения. Храня логику внутри действия, так же легче иметь несколько шаблонов для одного действия, без дублирования кода.

Помощники

Помощники - это PHP-функции, которые возвращают HTML-код и могут быть использованы в шаблонах. В Примере 7-1, функция link_to() - это помощник. Иногда помощники всего лишь помогают сберечь время, упаковывая в себя куски кода, часто используемые в шаблонах. Например, вы можете легко представить определение функции для этого помощника:

[php]
<?php echo input_tag('nickname') ?>
 => <input type="text" name="nickname" id="nickname" value="" />

Определение функции должно выглядеть примерно так, как в Примере 7-2.

Пример 7-2 - Определение помощника

[php]
function input_tag($name, $value = null)
{
  return '<input type="text" name="'.$name.'" id="'.$name.'"value="'.$value.'" />';
}

На самом деле, функция input_tag() встроена в symfony и она несколько сложнее, так как она принимает третий параметр для добавления других атрибутов к тегу <input>. Вы можете просмотреть её полный синтаксис и опции в онлайновой документации API (http://www.symfony-project.com/api/symfony.html).

Большую часть времени помощники избавляют вас от долгого и сложного кодирования.

[php]
<?php echo auto_link_text('Please visit our website www.example.com') ?>
 => Please visit our website <a href="http://www.example.com">www.example.com</a>

Помощники облегчают процесс написания шаблонов и продуцируют лучший HTML-код, с точки зрения производительности и accessibility. Вы всегда можете использовать обычный HTML, но помощники позволяют ускорить кодирование.

TIP Вас может удивить, что имена функций-помощников используют синтаксис с символом подчеркивания, а не camelCase-соглашение, которое повсеместно используется в symfony. Это связано с тем, что, помощники - это функции, а все функции ядра PHP используют в именах синтаксис со знаками подчеркивания.

Определение помощников

Файлы symfony содержат определения помощников, которые не подгружаются автоматически (так как они содержат функции, а не классы). Помощники сгруппированы по назначению. Например, если функции помощника работают с текстом, они определены в файле с названием TextHelper.php и называются группой помощников Text. Если вам необходимо использовать помощник в шаблоне, вы должны подгрузить в шаблон связанную с помощником группу, объявив её при помощи функции use_helper(). Пример 7-3 показывает шаблон, использующий помощник auto_link_text(), который является частью группы помощников Text.

Пример 7-3 - Объявление использования помощника

[php]
// Будем использовать в этом шаблоне необходимую нам группу помощников
<?php echo use_helper('Text') ?>
...
<h1>Description</h1>
<p><?php echo auto_link_text($description) ?></p>

TIP Если вам нужно использовать более одной группы помощников, добавьте больше аргументов к вызову `use_helper(). Например, что бы загрузить в шаблон группы помощников Text и Javascript , вызовите<?php echo use_helper('Text', 'Javascript') ?>.

Несколько помощников доступны по умолчанию в каждом шаблоне, и их не нужно объявлять. Это помощники следующих групп:

  • Helper: Необходима для включения других помощников (функция use_helper() сама является помощником)
  • Tag: Базовый набор тегов, используемый чуть ли не каждым помощником.
  • Url: Управление ссылками URL
  • Asset: Помощники этой группы помогают наполнить HTML-секцию <head> и задать ссылки на изображения, файлы JavaScript и таблицы стилей
  • Partial: Помогают подгрузить фрагменты шаблонов
  • Cache: Управляют кешированием фрагментов кода
  • Form: Для ввода данных через формы

Список стандартных помощников, загружаемых по умолчанию в каждый шаблон, настраивается в файле settings.yml. Если вы знаете, что не будете использовать помощники группы Cache, или вы всегда будете использовать помощники из группы Text, измените соответствующим образом параметр standard_helpers. Это немного ускорит работу вашего приложения. Вы не можете удалить первые четыре группы помощников (Helper, Tag, Url, и Asset), так как они необходимы для работы движка шаблонов. Фактически, они отсутствуют в этом списке стандартных помощников.

TIP Если вам когда-либо понадобится использовать помощники вне шаблона, вы можете загрузить группу помощников, вызвав sfLoader::loadHelpers($helpers), где $helpers - название группы помощников или массив имён таких групп. Например, если вам необходимо использовать auto_link_text() в коде действия, сначала вызовите sfLoader::loadHelpers('Text').

Часто используемые помощники

Вы изучите некоторые помощники в следующих главах, осваивая возможности, в которых они могут помочь. Пример 7-4 содержит краткий список основных помощников вместе с HTML-кодом, который они возвращают.

Пример 7-4 - Основные помощники

[php]
// Helper group
<?php echo use_helper('HelperName') ?>
<?php echo use_helper('HelperName1', 'HelperName2', 'HelperName3') ?>

// Tag group
<?php echo tag('input', array('name' => 'foo', 'type' => 'text')) ?>
<?php echo tag('input', 'name=foo type=text') ?>  // Alternative options syntax
 => <input name="foo" type="text" />
<?php echo content_tag('textarea', 'dummy content', 'name=foo') ?>
 => <textarea name="foo">dummy content</textarea>

// Url group
<?php echo link_to('click me', 'mymodule/myaction') ?>
=> <a href="/route/to/myaction">click me</a>  // Depends on the routing settings

// Asset group
<?php echo image_tag('myimage', 'alt=foo size=200x100') ?>
 => <img src="/images/myimage.png" alt="foo" width="200" height="100"/>
<?php echo javascript_include_tag('myscript') ?>
 => <script language="JavaScript" type="text/javascript" src="/js/myscript.js"></script>
<?php echo stylesheet_tag('style') ?>
 => <link href="/stylesheets/style.css" media="screen" rel="stylesheet"type="text/css" />

Symfony содержит много других помощников, потребовалась бы целая книга, что бы описать их. Лучший справочник по помощникам - онлайн-документация API (http:// www.symfony-project.com/api/symfony.html), в которой хорошо документированы все помощники, их синтаксис, опции, приведены примеры использования.

Добавление своих собственных помощников

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

Функции-помощники (обычные PHP-функции, возвращающие HTML-код) должны быть сохранены в файле с именем FooBarHelper.php, где FooBar - имя группы помощников. Сохраните файл в каталоге app/myapp/lib/helper (или в каталоге helper/, созданном в каталоге lib/ вашего проекта), и он сможет быть найден и загружен при выполнении команды use_helper('FooBar').

TIP Такая система позволяет вам даже переопределять существующие помощники symfony. Например, чтобы переопределить все помощники группы 'Text', создайте файл TextHelper.php в вашем каталоге apps/myapp/lib/helper/. Каждый раз при обращении к функции use_helper('Text') symfony будет использовать вашу группу помощников, а не свою собственную. Будьте осторожны: так как оригинальный файл группы помощников даже не загружается, вы должны переопределить все функции группы помощников. В противном случае, некоторые помощники не будут доступны вовсе.

Разметка страницы

Шаблон, показанный в Примере 7-1, не является валидным документом XHTML. Определение DOCTYPE и теги <html> и <body> отсутствуют. Это вызвано тем, что они определены в другом месте, а именно в файле layout.php, который содержит разметку страницы. Этот файл, так же называемый глобальным шаблоном, или обрамлением, содержит HTML-код, общий для всех страниц в приложении, что бы избежать повторения во всех шаблонах.

Содержимое шаблона интегрировано в обрамление, или, если посмотреть с другой стороны, обрамление "декорирует" шаблон. Рисунок 7.1 иллюстрирует этот пример применения паттерна проектирования "Декоратор".

TIP Дополнительную информацию о паттернах проектирования вы можете найти в книге Patterns of Enterprise Application Architecture, автор Martin Fowler (Addison-Wesley, ISBN: 0-32112-742-0).

Рисунок 7-1 - Декорирование шаблона при помощи обрамления

Декорирование шаблона при помощи обрамления

Пример 7-5 показывает типичное обрамление, находящееся в каталоге templates/ приложения.

Пример 7-5 - Типичное обрамление myproject/apps/myapp/templates/layout.php

[php]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <?php echo include_http_metas() ?>
  <?php echo include_metas() ?>
  <?php echo include_title() ?>
  <link rel="shortcut icon" href="/favicon.ico" />
</head>
<body>

<?php echo $sf_data->getRaw('sf_content') ?>

</body>
</html>

Помощники, вызываемые в секции <head> получают информацию из объекта ответа и конфигурации представления. Тег <body> выводит результат шаблона. С этой разметкой при конфигурации по умолчанию и шаблоном из Примера 7-1, представление получится таким, как показано в Примере 7-6.

Пример 7-6 - Результат сборки Обрамления, Конфигурации представления и Шаблона

[php]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta name="title" content="symfony project" />
  <meta name="robots" content="index, follow" />
  <meta name="description" content="symfony project" />
  <meta name="keywords" content="symfony, project" />
  <title>symfony project</title>
  <link rel="stylesheet" type="text/css" href="/css/main.css" />
  <link rel="shortcut icon" href="/favicon.ico">
</head>
<body>

<h1>Welcome</h1>
<p>Welcome back, <?php echo $name ?>!</p>
<ul>What would you like to do?
  <li><?php echo link_to('Read the last articles', 'article/read') ?></li>
  <li><?php echo link_to('Start writing a new one', 'article/write') ?></li>
</ul>

Глобальный шаблон (обрамление) может быть полностью перенастроен для каждого приложения. Добавьте любой HTML-код, который вам необходим. Этот шаблон часто используется для поддержки навигации по сайту, размещения логотипа и т.п. У вас может быть даже более чем одно обрамление, и вы можете принимать решение, какое обрамление использовать для каждого действия. Пока что не волнуйтесь об JavaScript и таблицах стилей, ниже в этой главе в разделе "Конфигурация представления" мы покажем, как ими управлять.

Сокращения в шаблонах (shortcuts)

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

  • $sf_context: Весь объект контекста (экземпляр sfContext)
  • $sf_request: Объект запроса (экземпляр sfRequest)
  • $sf_params: Параметры запроса
  • $sf_user: Объект текущей сессии пользователя (экземпляр sfUser)

Предыдущая глава подробно описывает полезные методы объектов sfRequest и sfUser. На самом деле, вы можете вызывать эти методы в шаблонах через переменные $sf_request и $sf_user. Например, если запрос содержит параметр total, его значение доступно в шаблоне при помощи вызова одного из следующих методов:

[php]
// Long version
<?php echo $sf_request->getParameter('total'); ?>

// Shorter version
<?php echo $sf_params->get('total'); ?>

// Equivalent to the following action code
echo $this->getRequestParameter('total');

Фрагменты кода

Часто бывает необходимо включить какой-либо HTML или PHP-код в несколько страниц. Что бы избежать повторений, в PHP обычно используется директива include(). Например, если многие шаблоны вашего приложения нуждаются в одном и том же фрагменте кода, вы могли бы сохранить его в файл с именем myFragment.php в каталоге для шаблонов уровня приложения (myproject/apps/myapp/templates/) и включить его в ваши шаблоны следующим образом:

[php]
<?php include(sfConfig::get('sf_app_template_dir').'/myFragment.php') ?>

Но это не слишком удобный способ для упаковки фрагмента, в основном из-за того, что у вас могут быть различные имена переменных в фрагменте и в шаблоне, который его включает в себя. Кроме этого, система кеша symfony не имеет возможности обнаружить такое включение, так что фрагмент не может быть кеширован. Symfony предоставляет три альтернативных способа поддержки фрагментов вместо использования include:

  • Если логика проста, и вы всего лишь хотите включить файл шаблона, получающий доступ к данным, которые вы передадите ему, используйте partial – обособленный фрагмент шаблона.
  • Если логика несколько сложнее (например, вам необходим доступ к модели данных и/или изменение содержимого в зависимости от сессии), вы предпочтёте разделить представление и логику. Для этого вы будете использовать компонент.
  • Если фрагмент должен заменить какую-то часть разметки, которая уже может существовать, вы можете использовать слот.

Note Другой тип фрагмента, называемый слотом компонента(компонентным слотом), используется, когда природа фрагмента зависит от контекста (например, если фрагмент должен быть различным для различных действий внутри модуля). Слоты компонента описаны ниже в этой главе.

Включение этих фрагментов может быть достигнуто использованием помощников из группы 'Partial'. Эти помощники доступны из любого шаблона symfony без предварительного объявления.

Обособленные фрагменты шаблонов (Partials)

Partial - это обособленный в отдельный файл, повторно используемый кусок кода шаблона. Например, в приложении для опубликования статей, код шаблона показывает статью на странице подробностей о статье,и так же в списке лучших статей, и в списке последних статей. Такой код - хороший кандидат в partial, это иллюстрирует Рисунок 7-2.

Рисунок 7-2 - Повторное использование фрагментов кода в шаблонах

Повторное использование фрагментов кода в шаблонах

Так же, как и шаблоны, partial - это файлы, расположенные в каталоге templates/, и они содержат HTML-код с внедренным кодом PHP. Имя файла обособленного фрагмента шаблона всегда начинается с подчёркивания (_), и это помогает различать partial-ы и шаблоны, так как они находятся в одной папке. Шаблон может включать partial-ы, расположенные в том же самом модуле, в другом модуле, или в глобальном каталоге templates/. Включайте partial с помощью include_partial(), и укажите модуль и имя partial-а как параметр, (но исключите начальный символ подчёркивания и суффикс .php), как показано в Примере 7-7.

Пример 7-7 - Включение фрагмента кода в шаблон модуля mymodule

[php]
// Include the myapp/modules/mymodule/templates/_mypartial1.php partial
// As the template and the partial are in the same module,
// you can omit the module name
<?php include_partial('mypartial1') ?>

// Include the myapp/modules/foobar/templates/_mypartial2.php partial
// The module name is compulsory in that case
<?php include_partial('foobar/mypartial2') ?>

// Include the myapp/templates/_mypartial3.php partial
// It is considered as part of the 'global' module
<?php include_partial('global/mypartial3') ?>

Partial-ы имеют доступ к обычным помощникам symfony и к сокращениям шаблонов. Но поскольку partial-ы могут быть вызваны из любой части приложения, они не получают доступ к переменным, определённым в действии, вызывающем шаблоны,которые включают их, если они не переданы явно как аргумент. Например, если вы хотите, что бы partial получил доступ к переменной $total, действие должно передать её в шаблон, а шаблон - помощнику, как второй аргумент вызова include_partial(), как показано в Примерах 7-8, 7-9 и 7-10.

Listing 7-8 - Действие определяет переменную $total, файл mymodule/actions/actions.class.php

[php]
class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    $this->total = 100;
  }
}

Пример 7-9 - Шаблон передает переменную в обособленный фрагмент шаблона, файл mymodule/templates/indexSuccess.php

[php]
<p>Hello, world!</p>
<?php include_partial('mypartial', array('mytotal' => $total)
) ?>

Пример 7-10 - Теперь обособленный фрагмент шаблона может использовать переменную $total, mymodule/templates/_mypartial.php

[php]
<p>Total: <?php echo $mytotal ?></p>

TIP Все помощники до этого вызывались при помощи <?php echo functionName() ?>. Помощник из группы partial, однако, вызывается при помощи просто <?php include_partial() ?> , без echo, что делает его поведение более похожим на include(). Если вам нужна функция, которая возвращает контент, продуцируемый обособленным фрагментом шаблона, используйте get_partial(). Все помощники include_, описанные в этой главе, имеют соответствия get_, которые могут быть вызваны вместе с функцией echo.

Компоненты

В Главе 2, первый простой скрипт был разбит на две части, что бы отделить логику от представления. Так как паттерн MVC применим к действиям и шаблонам, возможно, вам необходимо разделить фрагмент шаблона на часть логики и часть представления. В таком случае, вам следует использовать компонент.

Компонент подобен действию, только он значительно быстрее. Логика компонента содержится в классе, наследуемом от sfComponents, расположенном в файле action/components.class.php. Его представление содержится в обособленном фрагменте шаблона. Названия классов, порожденных от sfComponents, начинаются со слова execute, как в действиях, и они могут передавать переменные своим представлениям так же, как и действия. Обособленные фрагменты шаблона служат представлениями компонентов, и называются так же, как и компонент (без слова execute в начале названия, но с подчёркиванием вместо него). Таблица 7-1 показывает сравнение соглашений о наименованиях для действий и компонентов.

Таблица 7-1 - Соглашения именования файлов для действий и компонентов

Convention               |  Actions              |  Components
------------------------ | --------------------- | ----------------------
Logic file               | `actions.class.php`   | `components.class.php`
Logic class extends      | `sfActions`           | `sfComponents`
Method naming            | `executeMyAction()`   | `executeMyComponent()`
Presentation file naming | `myActionSuccess.php` | `_myComponent.php`

TIP Так же, как вы можете распределить действия по различным файлам, класс sfComponents имеет аналогичное соответствие sfComponent, позволяющее создавать файлы с отдельными компонентами с похожим синтаксисом.

Например, у вас есть боковое меню, показывающее заголовки последних новостей на заданную тему, которая зависит от настроек пользователя, и это меню задействовано в нескольких страницах. Запросы, необходимые для получения списка новостей, слишком сложны для фрагмента шаблона, поэтому их следует поместить в action-подобный файл - компонент. Рисунок 7-3 иллюстрирует этот пример.

В коде, показанном в Примерах 7-11 и 7-12, компонент будет храниться в собственном модуле (называемом news), но вы можете хранить компоненты и действия в одном модуле, если это имеет смысл с точки зрения функциональности.

Рисунок 7-3 - Использование компонентов в шаблонах

Using components in templates

Пример 7-11 - Класс компонента modules/news/actions/components.class.php

[php]
<?php

class newsComponents extends sfComponents
{
  public function executeHeadlines()
  {
    $c = new Criteria();
    $c->addDescendingOrderByColumn(NewsPeer::PUBLISHED_AT);
    $c->setLimit(5);
    $this->news = NewsPeer::doSelect($c);
  }
}

Пример 7-12 - Обособленный фрагмент шаблона modules/news/templates/_headlines.php

[php]
<div>
  <h1>Latest news</h1>
  <ul>
  <?php foreach($news as $headline): ?>
    <li>
      <?php echo $headline->getPublishedAt() ?>
      <?php echo link_to($headline->getTitle(),'news/show?id='.$headline->getId()) ?>
    </li>
  <?php endforeach ?>
  </ul>
</div>

Теперь, всякий раз, когда вам нужно использовать компонент в шаблоне, вызывайте его так:

[php]
<?php include_component('news', 'headlines') ?>

Так же, как и обособленные фрагменты шаблона, компоненты могут получать данные через дополнительные параметры в форме ассоциативного массива. Эти параметры доступны внутри фрагмента шаблона по своему имени, а в компоненте через объект $this. Посмотрите на пример в Примере 7-13.

Пример 7-13 - Передача параметров в компонент из шаблона [php] // Вызов компонента <?php include_component('news', 'headlines', array('foo' => 'bar')) ?>

// В самом компоненте
echo $this->foo;
 => 'bar'

// В соотвествующем ему  обособленном фрагменте шаблона _headlines.php 
echo $foo;
 => 'bar'

Вы можете вызывать компоненты из других компонентов, или из обрамления, или из обычного шаблона. Как и у действий, методы execute компонента могут передавать переменные в соответствующий им обособленный фрагмент шаблона, и иметь доступ к тем же сокращениям. Но на этом сходство заканчивается. Компоненты не поддерживают опции безопастности, валидацию, не могут быть вызваны непосредственно из Интернет (их может вызвать только само приложение), и у них нет различных return возможностей. Вот почему компонент исполняется быстрее, чем действие.

Заглушки (Slots)

Обособленные фрагменты шаблона и компоненты хороши для повторного использования. Но во многих случаях от фрагментов шаблона требуется возможность заполнить более чем одну динамическую зону в разметке. Например, представьте, что вы хотите добавить свои теги в секции <head> обрамления, в зависимости от данных выполняемого действия. Или, представьте, что разметка состоит из одной основной динамической зоны, которая заполнена результатами действия, плюс большое количество маленьких зон, у которых есть контент по умолчанию, определяемый в обрамлении, но который может быть переопределен из шаблонов.

Для этих ситуаций решением является слот. Слот - это заглушка, которую вы можете вставить в любой элемент представления (в глобальный шаблон, в шаблон или в обособленный фрагмент шаблона).

Заполняющий HTML-код слота предварительно сохраняется в объекте ответа и поэтому доступен глобально, так что вы можете определить его где угодно (в обрамлении, в шаблоне, в обособленном фрагменте шаблона). Лишь убедитесь в том, что вы определили слот до того, как включить его, и помните, что глобальный шаблон исполняется после шаблонов (это процесс декорации), а partial-ы выполняются тогда, когда они вызываются в шаблоне.

Слишком абстрактно? Давайте рассмотрим пример. Представьте разметку с одной зоной для шаблона и двумя слотами - одна для бокового меню и другая для футера. Наполнение слотов определено в шаблонах. В процессе декорации, код главного шаблона оборачивает код шаблона, а слоты наполняются предварительно определёнными значениями, как показано на Рисунке 7-4. Боковое меню и футер, таким образом, могут быть контекстными по отношению к основному действию. Это похоже на разметку с более чем одной "дырой".

Рисунок 7-4 – Заглушки обрамления, содержимое которых формируется в шаблоне

Layout slots defined in a template

Если посмотреть на код, всё становится более понятным. Что бы включить слот, используйте помощник include_slot(). Функция has_slot() возвращает истину, если слот был определен ранее, предоставляя в качестве бонуса возможность фолл-бэка. Например, определите место для слота 'sidebar' (боковое меню) в главном шаблоне, и здесь же задайте его наполнение по умолчанию, как показано в Примере 7-14.

Пример 7-14 - Включение слота 'sidebar' в разметку

[php]
<div id="sidebar">
<?php if (has_slot('sidebar')): ?>
  <?php include_slot('sidebar') ?>
<?php else: ?>
  <!-- заполнение бокового меню "по умолчанию" -->
  <h1>Contextual zone</h1>
  <p>Это пространство будет содержать информацию,
  зависящую от основного содержимого страницы.</p>
<?php endif; ?>
</div>

Каждый шаблон может определить содержимое слота (вообще-то, даже partial может сделать это). Так как предполагается, что слоты содержат HTML-код, symfony предлагает удобный способ определять их: просто напишите код слота между вызовом помощников slot() и end_slot(), как в Примере 7-15.

Пример 7-15 - Переопределение содержимого слота 'sidebar' в шаблоне

[php]
...
<?php slot('sidebar') ?>
  <!-- Наполнение бокового меню, соответствующее этому шаблону -->
  <h1>User details</h1>
  <p>name:  <?php echo $user->getName() ?></p>
  <p>email: <?php echo $user->getEmail() ?></p>
<?php end_slot() ?>

Код между помощниками слота будет исполнен в контексте шаблона, так что он будет иметь доступ ко всем переменным, определённым в действии. Symfony сохранит результат этого кода как свойство объекта ответа. Он не будет показан в шаблоне, но будет доступен для последующих вызовов include_slot(), как в Примере 7-14.

Слоты очень полезны для определения зон, предназначенных для показа контекстного содержимого. Они могут также использоваться для добавления HTML-кода к главному шаблону при вызове определенных действий. Например, шаблон, показывающий список последних новостей, может добавить ссылку к RSS-фиду в блоке <head> основного шаблона. Это достигается добавлением слота 'feed' в основном шаблоне и переопределением его в шаблоне списка новостей.

SIDEBAR Где найти фрагменты шаблона

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

Во первых, хотя проект symfony содержит много каталогов, все обрамления, шаблоны и фрагменты шаблонов находятся в каталогах, называемых templates/. Так что если дизайнер в курсе, структура проекта может быть сокращена до такого вида:

myproject/
  apps/
    application1/
      templates/       # Обрамления application 1
      modules/
        module1/
          templates/   # Шаблоны и обособленные фрагменты шаблонов для модуля module1
        module2/
          templates/   # Шаблоны и обособленные фрагменты шаблонов для модуля module2
        module3/
          templates/   # Шаблоны и обособленные фрагменты шаблонов для модуля module3

Все другие каталоги в данном случае игнорируем.

Встретившись с include_partial(), дизайнеру нужно знать, что для него важен только первый аргумент. Паттерн этого аргумента module_name/partial_name, и это значит, что код представления может быть найден в файле modules/module_name/templates/_partial_name.php

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

Конфигурация представления

В symfony, представление состоит из двух различных частей:

  • HTML-верстка результата действия (хранится в шаблоне, в главном шаблоне и в фрагментах шаблонов)

  • Все остальное, включая следующее:

    • Декларации Meta: ключевые слова, описание, или длительность хранения в кеше.
    • Заголавие страницы: Не только помогает пользователям, которые открыли несколько окон броузера, найти вашу страницу, но так же будет использован поисковыми системами при индексировании сайта.
    • Подключение файлов JavaScript и таблиц стилей.
    • Обрамление: Некоторым действиям необходимы нестандартное оформление в показе: поп-апы, реклама, и прочее, результаты некоторых действий вообще не имеют обрамления, например, действия Ajax.

В представлении, всё, что не является HTML, называется конфигурацией представления, и symfony предоставляет два способа управления нею. Обычный способ - редактирование конфигурационного файла view.yml. Он может быть использован всякий раз, когда значения не зависят от контекста и от запросов к базе данных. Когда вам нужно динамически изменять переменные, альтернативным способом является установка конфигурационных параметров представления через атрибуты объекта sfResponse, непосредственно в действии.

NOTE Определение конфигурационных параметров при помощи объекта sfResponse имеет более высокий приоритет, чем настройки в конфигурационных файлах view.yml.

Конфигурационный файл view.yml

В каждом модуле может быть один файл view.yml, определяющий настройки его представлений. Он позволяет определять настройки для всего модуля и для каждого отдельного представления. Ключи первого уровня этого файла - это названия представлений. Пример 7-16 показывает пример такой конфигурации.

Пример 7-16 - Пример view.yml уровня модуля

editSuccess:
  metas:
    title: Отредактируйте ваши данные

editError:
  metas:
    title: Ошибка в редактировании данных

all:
  stylesheets: [my_style]
  metas:
    title: My website

CAUTION Обратите внимание, что названия секций в файле view.yml - это названия представлений, а не действий. Название представления состоит из названия действия и предполагаемого результата завершения действия. Например, действие edit может возвратить sfView::SUCCESS (или не вернуть ничего, так как это результат по умолчанию), в таком случае название представления будет editSuccess.

Настройки по умолчанию определяются в секции all: файла view.yml соответствующего модуля. Настройки, общие для всего приложения, определяются в файле view.yml приложения. Ещё раз, различайте каскадный принцип конфигурирования:

  • Определения из файла apps/myapp/modules/mymodule/config/view.yml воздействуют только на одно представление и переопределяют настройки уровня модуля.
  • В apps/myapp/modules/mymodule/config/view.yml, определения секции all: применяются ко всем действиям в модуле и переопределяют определения уровня приложения.
  • В apps/myapp/config/view.yml, определения секции default: применяются ко всем модулям и всем действиям приложения.

TIP Файлы view.yml на уровне модуля изначально отсутствуют. Вам придется создать такой файл в каталоге config/, когда впервые понадобится настроить конфигурационный параметр для модуля.

Посмотрев код типичного шаблона в Примере 7-5 и пример окончательного ответа в Примере 7-6, вы можете заинтересоваться, откуда берутся значения переменных в секции <head> страницы. На самом деле, это типичные настройки представления, взятые из файла view.yml уровня приложения, который показан в Примере 7-17.

Пример 7-17 - Исходная конфигурация на уровне приложения, файл apps/myapp/config/view.yml

default:
  http_metas:
    content-type: text/html

  metas:
    title:        symfony project
    robots:       index, follow
    description:  symfony project
    keywords:     symfony, project
    language:     en

  stylesheets:    [main]

  javascripts:    [ ]

  has_layout:     on
  layout:         layout

Каждая из этих настроек будет подробно описана в разделе "Настройки конфигурации представления"

Объект ответа

Будучи частью слоя представления, объект ответа часто модифицируется действием. Действия могут обращаться к объекту ответа sfResponse, вызывая метод getResponse(). Пример 7-18 перечисляет некоторые методы sfResponse, часто используемые внутри действия.

Пример 7-18 - Действие получает доступ к методам объекта sfResponse

[php]
class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    $response = $this->getResponse();

    // HTTP headers
    $response->setContentType('text/xml');
    $response->setHttpHeader('Content-Language', 'en');
    $response->setStatusCode(403);
    $response->addVaryHttpHeader('Accept-Language');
    $response->addCacheControlHttpHeader('no-cache');

    // Cookies
    $response->setCookie($name, $content, $expire, $path, $domain);

    // Metas and page headers
    $response->addMeta('robots', 'NONE');
    $response->addMeta('keywords', 'foo bar');
    $response->setTitle('My FooBar Page');
    $response->addStyleSheet('custom_style');
    $response->addJavaScript('custom_behavior');
  }
}

В дополнение к методам-сеттерам (setter methods), указанным здесь, класс sfResponse имеет геттеры (getters), которые возвращают текущее значение атрибутов ответа.

Сеттеры заголовка - мощное средство symfony. Заголовки отправляются как можно позже (в sfRenderingFilter), таким образом, вы можете менять их настолько часто, насколько захотите, и настолько поздно, насколько захотите. Они так же предоставляют полезные сокращения (shortcuts). Например, если вы не указали кодовую страницу (charset), когда вызывали setContentType(), symfony автоматически добавит кодовую страницу по умолчанию, определённую в конфигурационном файле settings.yml.

[php]
$response->setContentType('text/xml');
echo $response->getContentType();
 => 'text/xml; charset=utf-8'

Код статуса ответа в symfony соответствует спецификации HTTP. Исключительные ситуации возвращают статус 500, ненайденные страницы - статус 404, обычные страницы - 200, страницы, которые не изменялись, могут быть сокращены до простого заголовка со статусом 304 (смотрите главу 12), и так далее. Но вы можете переопределить эти типовые значения, установив свой собственный код статуса ответа при помощи метода объекта ответа setStatusCode(). Вы можете указать свой собственный код статуса и своё сообщение, или просто свой собственный код - в этом случае, symfony сама добавит наиболее употребительное сообщение к этому коду статуса.

[php]
$response->setStatusCode(404, 'This page no longer exists');

TIP Перед отправкой заголовков, symfony нормализует их имена. Таким образом, вам не нужно беспокоиться если, вы пишете content-language вместо Content-Language в вызове setHttpHeader(), так как symfony автоматически преобразует их позже.

Настройки конфигурации представления

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

  • Те, которые имеют уникальные значения (значение в файле view.yml задается строкой и ответ для них использует метод 'set')
  • Те, которые имеют множественные значения (для задания таких в файле view.yml используются массивы и ответ использует метод add)

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

Конфигурация мета-тегов

Информация, записываемая в теги <meta> ответа, не показывается в броузере, но полезна для поисковых систем. Она так же управляет кешированием каждой страницы. Определите эти теги под ключами http_metas: и metas: в файле view.yml, как в Примере 7-19, или при помощи методов ответа addHttpMeta() и addMeta() в действии, как в Примере 7-20.

Пример 7-19 Определение мета-тегов как ключей: пары значений в файле view.uml

http_metas:
  cache-control: public

metas:
  description:   Finance in France
  keywords:      finance, France

Пример 7-20 - Определение мета-тегов сеттерами объекта ответа в коде действия

[php]
$this->getResponse()->addHttpMeta('cache-control', 'public');
$this->getResponse()->addMeta('description', 'Finance in France');
$this->getResponse()->addMeta('keywords', 'finance, France');

Добавление существующего ключа заменит его текущее типовое значение (значение по умолчанию). Для мета-тегов HTTP вы можете добавить третий параметр и установить его в значение false, что бы метод addHttpMeta() (или setHttpHeader()) добавил значение к существующему значению, а не заменил его.

[php]
$this->getResponse()->addHttpMeta('accept-language', 'en');
$this->getResponse()->addHttpMeta('accept-language', 'fr', false);
echo $this->getResponse()->getHttpHeader('accept-language');
 => 'en, fr'

Для того, что бы эти мета-теги появились в итоговом документе, в секции <head> должны быть вызваны помощники include_http_metas() и include_metas() (так и происходит при ипользовании типового обрамления - см. Пример 7-15). Symfony автоматически агрегирует настройки из всех файлов view.yml включая такой, как в Примере 7-17, и атрибуты ответа, что бы сформировать правильные мета-теги. Пример из Примера 7-19 в итоге превратится код, показанный в Примере 7-21.

Пример 7-21 - Мета-теги в итоговом документе

[php]
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="cache-control" content="public" />
<meta name="robots" content="index, follow" />
<meta name="description" content="Finance in France" />
<meta name="keywords" content="finance, France" />

В качестве бонуса, HTTP-заголовок ответа будет так же дополнен определением http-metas:, даже если у вас нет вызовов помощника include_http_metas() в обрамлении, или если у вас вовсе нет обрамления. Например, если вам необходимо отправить страницу как простой текст без разметки, задайте в view.yml:

http_metas:
  content-type: text/plain

has_layout: false

Конфигурация заглавия страницы

Заглавие (title) существенно для индексирования страницы поисковыми машинами. Оно так же полезно при использовании современных броузеров, которые позволяют показывать страницы во вкладках-табах (tabbed browsing). В HTML, заглавие является одновременно и тегом, и мета-информацией страницы, поэтому в файле view.yml ключ title: является дочерним по отношению к ключу metas:. Пример 7-22 показывает определение заголовка в view.yml, а Пример 7-23 показывает использование определения заголовка в файле действия.

Пример 7-22 - Определение заголовка в view.yml

indexSuccess:
  metas:
    title: Десять негритят

Пример 7-23 - Пример определения в файле действия для динамического изменения заглавия

[php]
$this->getResponse()->setTitle(sprintf('%d негритят', $number));

В секции <head> итогового документа определение заглавия устанавливает тег <meta name="title"> в случае, если присутствует вызов помощника include_metas(), и устанавливает тег <title>, если производится вызов помощника include title(). Если вызываются оба помощника, как в типовом обрамлении (Пример 7-5), заглавие появляется дважды в тегах документа, что вполне безобидно.

Конфигурация подключаемых файлов

Добавить к представлению таблицу стилей или файл JavaScript очень просто, что и демонстрируют Примеры 7-24 и 7-25.

Пример 7-24 - подключение файлов в view.yml

indexSuccess:
  stylesheets: [mystyle1, mystyle2]
  javascripts: [myscript]

Пример 7-25 - подключение файлов в коде действия

[php]
$this->getResponse()->addStylesheet('mystyle1');
$this->getResponse()->addStylesheet('mystyle2');
$this->getResponse()->addJavascript('myscript');

В обоих случаях аргументом служит имя файла. Если имя файла содержит общепринятое расширение (.css для таблицы стилей и .js для JavaScript), вы можете опустить его. Если полное имя файла содержит путь /css/ для таблицы стилей или /js/ для JavaSctipt, так же нет необходимости его указывать. Symfony достаточно умна, что бы определить типичные расширение и размещение файла.

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

Пример 7-26 - чтобы ссылка на файл была добавлена в итоговый документ, нет необходимости вызывать в обрамлении функцию-помощник

[php]
<head>
...
<link rel="stylesheet" type="text/css" media="screen" href="/css/mystyle1.css" />
<link rel="stylesheet" type="text/css" media="screen" href="/css/mystyle2.css" />
<script language="javascript" type="text/javascript" src="/js/myscript.js">
</script>
</head>

NOTE Включение ссылок на файлы таблиц стилей и JavaScript производится фильтром sfCommonFilter. Он ищет тег <head> в ответе и добавляет теги <link> и <script> сразу перед закрывающим </head>. Это значит, что подключение файлов не может быть произведено в том случае, если в вашем обрамлении или шаблонах нет тега .

Помните, что каскадный принцип конфигурации по-прежнему применим, и всякое определение подключения файлов в файле view.yml уровня приложения приведет к их подключению к каждой HTML-странице этого приложения. Примери 7-27, 7-28, и 7-29 иллюстрируют работу этого принципа.

Пример 7-27 Пример view.yml уровня приложения

default:
  stylesheets: [main]

Пример 7-28 Пример view.yml уровня модуля

indexSuccess:
  stylesheets: [special]

all:
  stylesheets: [additional]

Пример 7-29 - Результирующий вывод indexSuccess

[php]
<link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" />
<link rel="stylesheet" type="text/css" media="screen" href="/css/additional.css" />
<link rel="stylesheet" type="text/css" media="screen" href="/css/special.css" />

Если вам нужно отменить подключение файла, определенное на более высоком уровне конфигурации, добавьте минус (-) перед именем файла, как показано в Примере 7-30.

Пример 7-30 - view.yml уровня модуля - как удалять ссылки на файлы, определенные на уровне приложения

indexSuccess:
  stylesheets: [-main, special]

all:
  stylesheets: [additional]

Что бы убрать ссылки на все таблицы стилей или файлы JavaScript, используйте следующий синтаксис:

indexSuccess:
  stylesheets: [-*]
  javascripts: [-*]

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

// In the view.yml
indexSuccess:
  stylesheets: [special: { position: first }]

[php]
// In the action
$this->getResponse()->addStylesheet('special', 'first');

Что бы указать атрибут media для подключаемой таблицы стилей, вы можете изменить значения опций по умолчанию, как показано в Примерах 7-31, 7-32, и 7-33.

Пример 7-31 – Подключение таблицы стилей с указанием media в view.yml

indexSuccess:
  stylesheets: [main, paper: { media: print }]

Пример 7-32 – То же самое в коде действия

[php]
$this->getResponse()->addStylesheet('paper', '', array('media' => 'print'));

Пример 7-33 – Окончательный результат

[php]
<link rel="stylesheet" type="text/css" media="print" href="/css/paper.css" />

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

Ваш сайт может иметь несколько вариантов обрамления. Обычно веб-сайты имеют хотя бы два варианта - основной и для всплывающих окон.

Как вы видели раньше, типичное обрамление описывается в файле myproject/apps/myapp/templates/layout.php. Дополнительные обрамления должны располагаться в том же каталоге templates/ уровня приложения. Если вы хотите, что бы представление использовало в качестве обрамления файл myapp/templates/my_layout.php, используйте конструкцию, приведенную в Примере 7-34, для файла конфигурации view.yml, или в Примере 7-35, для кода действия.

Пример 7-34 - Назначение обрамления view.yml

indexSuccess:
  layout: my_layout

Пример 7-35 - Назначение обрамления в коде действия

[php]
$this->setLayout('my_layout');

Некоторым представлениям вовсе не нужно обрамление: например, страницы простого текста или RSS-фиды. В этом случае используется установка параметраhas_layout в значение false, как показано в Примерах 7-36 и 7-37.

Пример 7-36 - Страница без обрамления в view.yml

indexSuccess:
  has_layout: false

Пример 7-37 - Как задать отсутствие обрамления страницы в коде действия

[php]
$this->setLayout(false);

NOTE представления действий Ajax по умолчанию не имеют обрамления.

Компонентные слоты

Комбинирование мощи компонентов и конфигурации представления открывает нам новые горизонты в разработке представления: систему компонентных слотов. Это альтернатива ранее описанным слотам-заглушкам, сфокусированная на повторном использовании и отделении логики от представления. Компонентные слоты более структурированы, чем заглушки, но несколько медленнее в скорости исполнения.

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

Что бы установить заполнитель для компонентного слота, используйте помощник include_component_slot(). Аргументом этой функции будет метка слота. Например, предположим, что файл уровня приложения layout.php содержит контекстное боковое меню. Пример 7-38 показывает, как будет включен компонентный слот.

Пример 7-38 - Включение компонентного слота с меткой 'sidebar'

[php]
...
<div id="sidebar">
  <?php include_component_slot('sidebar') ?>
</div>

Определите соответствие между меткой компонентного слота и именем компонента в конфигурации представления. Например, установите типичный компонент для компонентного слота с меткой 'sidebar' в файле view.yml уровня приложения под заголовком components:. Ключом является метка компонентного слота; значение должно быть массивом, содержащим имя модуля и имя компонента. Пример 7-39 покажет вам пример.

Пример 7-39 - Типичный пример настройки слота компонента с меткой 'sidebar' в файле myapp/config/view.yml

default:
  components:
    sidebar:  [bar, default]

Теперь, при выполнении кода обрамления, компонентный слот с меткой sidebar будет заполнен результатом выполнения метода executeDefault() класса barComponents, расположенного в модуле bar, и выходные данные этого метода будут представлены при помощи обособленного фрагмента шаблона _default.php, расположенного в каталоге modules/bar/templates/.

Каскад конфигурации предоставляет вам возможность переопределять эту настройку для каждого модуля. Например, в модуле user, вам, возможно, захочется, что бы контекстный компонент показывал имя пользователя и количество опубликованных ним статей. В этом случае, укажите настройку слота в файле view.yuml уровня модуля, как показано в Примере 7-40.

The configuration cascade gives you the ability to override this setting for a given module. For instance, in a user module, you may want the contextual component to display the user name and the number of articles that the user published. In that case, specialize the sidebar slot setting in the module view.yml, as shown in Пример 7-40.

Пример 7-40 - Настройка компонента слота 'sidebar' на уровне модуля в файлеmyapp/modules/user/config/view.yml

all:
  components:
    sidebar:  [bar, user]

Определение компонента для обслуживания этого слота должно выглядеть так, как в Примере 7-41.

Пример 7-41 - Компоненты, используемые слотом 'sidebar', файл modules/bar/actions/components.class.php

[php]
class barComponents extends sfComponents
{
  public function executeDefault()
  {
  }

  public function executeUser()
  {
    $current_user = $this->getUser()->getCurrentUser();
    $c = new Criteria();
    $c->add(ArticlePeer::AUTHOR_ID, $current_user->getId());
    $this->nb_articles = ArticlePeer::doCount($c);
    $this->current_user = $current_user;
  }
}

Пример 7-42 показывает представления для этих двух компонентов.

Пример 7-42 - Обособленные фрагменты шаблона, используемые компонентами слота 'sidebar', modules/bar/templates/

[php]
// _default.php
<p>Эта зона будет содержать контекстную информацию</p>

// _user.php
<p>Пользователь <?php echo $current_user->getName() ?></p>
<p>Опубликовал <?php echo $nb_articles ?> статей. </p>

Компонентные слоты могут быть использованы для верхних меню навигации, контекстной навигации, и любых динамических вставок. Как и компоненты, они могут быть использованы в глобальном шаблоне и в обычных шаблонах, и даже в других компонентах. Настройка компонента для слота всегда берётся из конфигурации последнего вызванного действия.

Если вам нужно отключить действие компонентного слота для какого-то модуля, объявите для него в конфигурации пустой массив вместо связки модуль-компонент, как это показано в Примере 7-43.

Пример 7-43 - Отключение компонентного слота в конфигурации view.yml

all:
  components:
    sidebar:  []

Пост-обработка выходных данных (Output Escaping)

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

В качестве примера - представьте, что в форме ввели следующее значение:

[php]
<script>alert(document.cookie)</script>

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

[php]
&lt;script&gt;alert(document.cookie)&lt;/script&gt;

Конечно, вы можете обрабатывать все данные самостоятельно, вызывая для каждой подозрительной переменной htmlentities(), но этот подход очень нерационален. Вместо этого, symfony предоставляет специальную подсистему, которая называется "Output Escaping", которая автоматически обработает все выводимые данные. Она активируется простым параметром в конфигурации приложения (settings.yml)

Активация Постобработки

Постобработка конфигурируется для всего приложения глобально. Два параметра контролируют то, как работает механизм постобработки: 'strategy' определяет, как переменные становятся доступными для файлов представления, а метод определяет функцию по умолчанию, которая применяется для постобработки.

Следующая секция описывает эти настройки детально, но, в основном, все, что вам необходимо, чтобы включить постобработку - установить "escaping_strategy" в "both" вместо его значения "bc", которое установлено по умолчанию так, как показано в листинге 7-14:

Listing 7-44 - Активируем Output Escaping в myapp/config/settings.yml

all:
  .settings:
    escaping_strategy: both
    escaping_method:   ESC_ENTITIES

При такой конфигурации ко всем переменным представления применяется "htmlentities()":

[php]
$this->test = '<script>alert(document.cookie)</script>';

Если постобработка включена, то в самом файле представления эта переменная будет выглядеть так:

[php]
echo $test;
 => &gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt;

При включеннной постобработке вы получите доступ к "$sf_data" во всех своих шаблонах. Это контейнер для всех обработанных данных, таким образом вы можете получить доступ к целевой переменной и так:

[php]
echo $sf_data->get('test');
=> &gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt;

TIP Объект $sf_data обладает интерфейсом массива, таким образом, вместо того, чтобы использовать "$sf_data->get('myvariable')", вы можете получить обработанный объект и так: "$sf_data['myvariable']". Но в действительности sf_data массивом не является и применение к нему таких функций, как print_r, или var_dump не даст желаемого результата.

Этот объект так же дает доступ и к необработанным данным! Это полезно, если переменная хранит HTML код, которому вы доверяете как переменной. Вызов метода "getRaw()" возвращает необработанные данные.

[php]
echo $sf_data->getRaw('test');
 => <script>alert(document.cookie)</script>

Вам придется работать с RAW данными каждый раз, когда вам необходим доступ к HTML, который действительно интерпретируется как HTML на стороне пользователя. Теперь вы можете понять, почему обрамления по умолчанию используют метод "$sf_data->getRaw('sf_content')" для включения шаблона.

Escaping Strategy

Настройка escaping_strategy определяет, как именно переменные обрабатываются. Вот возможные значения:

  • bc: Переменные не обрабатываются. Точнее, по умолчанию будет использоваться RAW версия данных, а преобразованная версия будет доступна через объект $sf_data. Этот подход применяется по умолчанию. Остерегайтесь этого подхода, при нем ваше приложение может стать легкой добычей XSS атак.
  • both: Все переменные обрабатываются. Значения доступны через объект $sf_data. Это как раз рекоменжлваный подход, так как могут возникнуть случаи, в которых вам необходимо использовать непреобразованные данные. Однако, если вы перейдете на эту опцию на среднем этапе разработки вашего приложения, некоторые функции, использовавшие непреобразованные данные по умолчанию могут перестать работать. Лучший вариант - использовать эту опцию с самого начала.
  • on: При таком подходе все переменные доступны ТОЛЬКО через объект $sf_data. Это обеспечивает явный выбор между методом get() и getRaw() (обработанные и необработанные данные соответственно), что обеспечивает лучшую безопастность и надежность.
  • off: При этой опции контейнер $sf_data не будет доступен в шаблонах. Применяйте эту опцию, если вы уверены, что точно не будете работать с преобразованием. Это даст вам определенный выйгрыш в производительности.

Преобразовывающие методы-помощники.

Преобразовывающие методы-помощники - это функции, которые возвращают преобразованную версию входных данных. За выбор функции-преобразователя отвечает настройка escaping_method в settings.yml. Вот ее доступные значения:

  • ESC_RAW: Не преобразовывает данные.
  • ESC_ENTITIES: Выполняет для входных данных htmlentities() с ENT_QUOTES в качестве стиля кавычек.
  • ESC_JS: Преобразовывает html в JS строку, которая будет выведена как html. Это полезно, когда вы работаете с динамическим html контентом, который изменяется посредствам JS.
  • ESC_JS_NO_ENTITIES: Преобразовывает данные в JS строчку, но не включает в нее html сущностей. Это полезно, когда данные должны быть выведены в диалоговом окне.

Преобразование массивов и объектов.

Преобразование вывода работает не только для строк, но и для массивов и объектов. Все значения, являющиеся объектами или массивами, так же будут преобраованы вместе со всеми их членами. Листинг 7-45 демонстрируед каскадное преобразование, с учетом параметра strategy установленного в "both":

Listing 7-45 - Преобразование массивов и объектов

[php]
// Class definition
class myClass
{
  public function testSpecialChars($value = '')
  {
    return '<'.$value.'>';
  }
}

// In the action
$this->test_array = array('&', '<', '>');
$this->test_array_of_arrays = array(array('&'));
$this->test_object = new myClass();

// In the template
<?php foreach($test_array as $value): ?>
  <?php echo $value ?>
<?php endforeach; ?>
 => &amp; &lt; &gt;
<?php echo $test_array_of_arrays[0][0] ?>
 => &amp;
<?php echo $test_object->testSpecialChars('&') ?>
 => &lt;&amp;&gt;

На самом деле перменные, которые вы использутее в шаблоне, уже не относятся к классу массивов или вашему классу объекта. Преобразователь приводит их к внутреннему типу преобразователя для наведения "декораций".

[php]
<?php echo get_class($test_array) ?>
 => sfOutputEscaperArrayDecorator
<?php echo get_class($test_object) ?>
 => sfOutputEscaperObjectDecorator

Это объявняет тот факт, что некоторые php функции (array_shift(), print_r()) не будут работать с преобразованными массивами. Но доступ к их элементам посредствам "[]" по-прежнему работает. Функция count() возвращает корректное число элементов в массиве (начиная с версии php 5.2). В шаблонах данные предоставляются в режиме read-only, так что, большинство методов не будут работать.

У вас попрежнему есть возможность использовать непреобразовенный объект через $sf_data. В качестве дополнения, методы объекта изменяются таким образом, что принимают один лишний параметр, описывающий метод преобразования. Иначе говоря, вы можете выбрать метод преобразования при каждом вызове функции у преобразуемого объекта. Указав ESC_RAW преобразование в контексте этого вызова будет отключено.

Listing 7-46 - Методы преобразования в дополнительном параметре.

[php]
<?php echo $test_object->testSpecialChars('&') ?>
=> &lt;&amp;&gt;
// The three following lines return the same value
<?php echo $test_object->testSpecialChars('&', ESC_RAW) ?>
<?php echo $sf_data->getRaw('test_object')->testSpecialChars('&') ?>
<?php echo $sf_data->get('test_object', ESC_RAW)->testSpecialChars('&') ?>
 => <&>

Если вы работаете с объектами в ваших шаблонах, вы будете использовать дополнительный параметр достаточно часто, так как это самый быстрый и простой способ получить непреобразованные данные при вызове метода.

CAUTION Все стандартные внутренние переменные symfony тоже будут преобразованы, если вы используете преобразование. Будьте аккуратны используя $sf_user, $sf_request, $sf_param.. Сами объекты будут работать, но их методы будут возвращать преобразованные данные, если вы не укажете последним параметром ESC_RAW.

Итоги

Доступно множество разных инструментов для управления слоем представления. Разработка шаблонов занимает считанные секундны, благодаря методам-помощникам. Обрамления, части шаблонов, компоненты, комонентные слоты - все эти средства приносят в разработку модульность и возможность использовать один и тот же код в нескольких местах, DRY ;).Конфигурация вида позволяет настроить хидеры страниц используя все преимущества YAML. Каскадные конфиграции позволяют настраивать вид глобально, не повтотряя настроек для каждого модуля отдельно. Для всех изменений вида, зависящих от динамических данных используйте объект sfResponse. Данные вида защищены от XSS атак благодаря системе постпреобразования.