Symfony forms in action. Глава 1.

Приветствуем!

Хотите что-то написать?

Нужно назвать себя.

Если вы пришли в первый раз,
то нужно зарегистрироваться.

Читайте нас в:

Блог на ya.ru
Блог на Деловом квартале
Блог на Twitter.om
Блог на Livejournal.com
29 декабря

ПереводыSymfony forms in action. Глава 1.

Формы состоят из полей, такие как: скрытые поля, текстовые поля, селектбоксы и чекбоксы. Данная глава представит вам пути создания и изменения форм, используя фреймворк форм symfony.

Symfony 1.1 требует прочтения глав данной книги. Вам так же придётся создать проект и frontend приложение. Используйте вступление книги для получения информации по созданию проекта.

Прежде чем начнём

А начнём мы с добавления формы контактов в приложение symfony На изображении 1-1 мы видим форму такой, какой её видят пользователи, которые хотят отправить нам сообщение.

Изображение 1-1 - Контактная форма

Контактная форма

Мы создадим 3 поля для данной формы: имя пользователя, электропочта пользователя и сообщение, которое он хочет нам отослать. В первом упражнении мы просто выведем отосланную информацию как показано на изображении 1-2

Изображение 1-2 - Страница благодарности

Страница благодарности

Изображение 1-3 - Взаимодействие приложения и пользователя

Взаимодействие приложения и пользователя

Виджеты (Widgets)

Классы sfForm и sfWidget

Пользователи вводят информацию в поля, которые образуют форму. В symfony форма это производный объкт от класса sfForm. В нашем примере мы создадим класс ContactForm, расширяющий класс sfForm.

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

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

Виджет представляет собой поле формы. Для нашего примера формы нам нужно 3 виджета, представляющих 3 поля: name, email и message. Список 1-1 показываем нам первое применение класса ContactForm

Список 1-1 - класс ContactForm с 3мя полями

 

// lib/form/ContactForm.class.phpclass ContactForm extends sfForm{  public function configure()  {    $this->setWidgets(array(      'name'    => new sfWidgetFormInput(),      'email'   => new sfWidgetFormInput(),      'message' => new sfWidgetFormTextarea(),    ));  }}

 

Виджеты определяются в методе configure(). Этот метод автоматически вызывается конструктором класса sfForm. Метод setWidgets() используется для определения самих виджетов, используемых в форме. Метод setWidgets() может принимать ассоциативный массив в котором ключи - это имена полей, а значения - объекты виджетов. Каждый виджет - это объект, наследующий класс sfWidget(). В данном примере мы использовали 2 типа виджетов -

  • sfWidgetFormInput : виджет, представляющий поле вводе (input)
  • sfWidgetFormTextarea: виджет, представляющий поле ввода текста (textarea)

 

Как правило, мы храним классы форм в папке lib/form. Вы можете хранить их в любой директории, находящейся в механизме автозагрузки symfony. И как мы увидим далее - symfony использует lib/form для генерации форм из моделей объектов

Отображение форм

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

 

 

Давайте изменим действие index в модуле contact и передадим копию объекта формы шаблону, как это показано в списке 1-2.

Список 1-2 - Класс действий модуля contact

 

// apps/frontend/modules/contact/actions/actions.class.phpclass contactActions extends sfActions{  public function executeIndex()  {    $this->form = new ContactForm();  }}

 

Во время создания формы метод configure(), определённый ранее, будет вызван автоматически.

нам лишь нужно создать шаблон для отображения формы как показано в списке 1-3

Список 1-3 - Шаблон, отображающий форму

 

// apps/frontend/modules/contact/templates/indexSuccess.php<form action="" method="POST">  <table>    <?php echo $form ?>    <tr>      <td colspan="2">        <input type="submit"  />      </td>    </tr>  </table></form>

 

Формы на symfony лишь отображают информацию для пользователя. В шаблоне indexSuccess строка <?php echo $form ?> лишь показывает 3 поля. Другие элементы, такие как тэг form и кнопка отсылки (submit) должны быть добавлены разработчиком. На первый взгляд это не кажется очевидным, но в дальнейшем мы увидим насколько полезно и легко встраивать формы.

Использовании конструкции <?php echo $form ?> очень полезно при прототипировании и определении форм. Она позволяет разработчику сконцетрироваться на бизнес-логике без беспокойств о аспектах отображения. Третья глава разъяснит о персонализации шаблона и обрамления формы.

При отображении формы с использованием <?php echo $form ?> PHP фактически выводит текстовое представление объекта $form. Для преобразования объекта в строку PHP пытается вызвать волшебный метод __toString(). Каждый виджет применяет эту фолшебную функцию для конвертации объекта в HTML код. Вызов <?php echo $form ?> равносилен вызову <?php echo $form->__toString() ?>

Теперь мы можем увидеть форму в браузере (Изображение 1-4) по адресу /contact/index

Изображение 1-4 - Сгенерированная форма контакта

Сгенерированная форма контакта

Список 1-4 - Сгенерированный шаблоном код

 

<form action="/frontend_dev.php/contact/submit" method="POST">  <table>     <!-- Beginning of generated code by <?php echo $form ?> -->    <tr>      <th><label for="name">Name</label></th>      <td><input type="text" name="name" id="name"  /></td>    </tr>    <tr>      <th><label for="email">Email</label></th>      <td><input type="text" name="email" id="email"  /></td>    </tr>    <tr>      <th><label for="message">Message</label></th>      <td><textarea rows="4" cols="30" name="message" id="message"></textarea></td>    </tr>    <!-- End of generated code by <?php echo $form ?> -->     <tr>      <td colspan="2">        <input type="submit"  />      </td>    </tr>  </table></form>

 

Мы видим, что форма отображается при помощи трёх строк <tr> таблицы на HTML. Именно поэтому мы должны вставить форму в тэг <table>. Каждая строка включает тэг <label> и тэг формы (<input> или <textarea>)

Лэйблы

Лэйблы автоматически генерируются для каждого из полей. По дефолту, лейблы это представление имён полей за исключением 2х правил: первая буква - заглавная, а знак подчёркивания заменяется пробелом. Пример:

 

$this->setWidgets(array(  'first_name' => new sfWidgetFormInput(), // Созданный лейбл: "First name"  'last_name'  => new sfWidgetFormInput(), // Созданный лейбл: "Last name"));

 

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

 

$this->widgetSchema->setLabels(array(  'name'    => 'Your name',  'email'   => 'Your email address',  'message' => 'Your message',));

 

Вы так же можете изменить один лейбл при помощи setLabel()

 

$this->widgetSchema->setLabel('email', 'Your email address');

 

В третье главе мы узнаем как расширять лейблы из шаблона для дальнейших изменений формы

Схема виджетов

При использовании функции setWidgets() symfony создаёт объекта класса sfWidgetFormSchema. Данный объект это виджет, являющийся набором виджетов. В нашей форме ContactForm мы вызывали виджеты при помощи setWidgets(), что равносильно следующему коду:

 

$this->setWidgetSchema(new sfWidgetFormSchema(array(  'name'    => new sfWidgetFormInput(),  'email'   => new sfWidgetFormInput(),  'message' => new sfWidgetFormTextarea(),))); // что так же равнозначно следующему: $this->widgetSchema = new sfWidgetFormSchema(array(  'name'    => new sfWidgetFormInput(),  'email'   => new sfWidgetFormInput(),  'message' => new sfWidgetFormTextarea(),));

 

Функция setLabels() применяется к коллекции виджетов, находящихся в объекте класса widgetSchema.

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

Через сгенерированные таблицы - к звёздам

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

 

class ContactForm extends sfForm{  public function configure()  {    $this->setWidgets(array(      'name'    => new sfWidgetFormInput(),      'email'   => new sfWidgetFormInput(),      'message' => new sfWidgetFormTextarea(),    ));     $this->widgetSchema->setFormFormatterName('list');  }}

 

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

Отправка формы

Когда мы создавали шаблон для отображения формы мы использовали внутренний адрес contact/submit в теге формы. Теперь нам нужно добавить действией отправки(submit) в модуль contact. Список 1-5 показывает нам как действие может получать информацию, пришедшую от пользователя и отправлять на страницу благодарности, где мы вновь отображаем эти данные пользователю.

Список 1-5 - Использование действия submit в модуле contact.

 

public function executeSubmit($request){  $this->forward404Unless($request->isMethod('post'));   $params = array(    'name'    => $request->getParameter('name'),    'email'   => $request->getParameter('email'),    'message' => $request->getParameter('message'),  );   $this->redirect('contact/thankyou?'.http_build_query($params));} public function executeThankyou(){} // apps/frontend/modules/contact/templates/thankyouSuccess.php<ul>  <li>Name:    <?php echo $sf_params->get('name') ?></li>  <li>Email:   <?php echo $sf_params->get('email') ?></li>  <li>Message: <?php echo $sf_params->get('message') ?></li></ul>

 

http_build_query это встроенная функция PHP, которая генерирует URL-закодированную строку запроса из массива параметров

метод executeSubmit() производит 3 действия:

  • Для большей безопасности проверяется, что страница была отправлена методом POST. Если же это не метод POST, тогда пользователя отправляют на страницу 404. В шаблоне indexSuccess мы определили метод отсылки POST (<form ... method="POST">):

     

    $this->forward404Unless($request->isMethod('post'));		

     

  • Далее мы получаем значения, присланные пользователем и сохраняем их в массиве $params

     

    $params = array(  'name'    => $request->getParameter('name'),  'email'   => $request->getParameter('email'),  'message' => $request->getParameter('message'),);		

     

  • В конце концов мы редиректим пользователя на страницу благодарности

     

    $this->redirect('contact/thankyou?'.http_build_query($params));		

     

 

Вместо редиректа пользователя на другую страницу мы бы могли создать шаблон submitSuccess.php. Пока это возможно, лучше редиректить пользователя после отсылки через POST:

  • Это предотвратит повторную отсылку формы, если пользователь обновил страницу благодарности
  • Пользователь может всегда вернуться без запроса о повторной отсылке формы

 

Вы наверное заметили, что executeSubmit() и executeIndex() отличаются. При вызове данных методов symfony передаёт текущий объекта класса sfRequest как первый аргумент методов вида executeXXX(). С PHP вам не нужно собирать все параметры, поэтому мы не определили переменную $request в executeIndex(), так как она просто нам не нужна

На изображении 1-5 показана схема работы при взаимодействии с пользователем

Схема работы

Схема работы

При повторном отображении данных, введённых пользователем, мы рискуем быть подвереженными XSS (Cross-Site Scripting) атаке. Дальнейшую информацию по предотвращению XSS атак, внедрив политики исключений, вы можете найти в главе из книги по symfony.

После отсылки формы вы должны увидеть страницу как на изображении 1-6

Изображение 1-6 - Страница, отображаемая после отсылки формы

Страница, отображаемая после отсылки формы

Вместо создания массива params, будет гораздо легче получить информацию от пользователя прямо в массив. Список 1-6 показываем изменение HTML-атрибута виджетов name, для того чтобы сохранить значения полей в массиве $contacs

Список 1-6 - Изменение атрибута name для виджетов формы.

 

class ContactForm extends sfForm{  public function configure()  {    $this->setWidgets(array(      'name'    => new sfWidgetFormInput(),      'email'   => new sfWidgetFormInput(),      'message' => new sfWidgetFormTextarea(),    ));     $this->widgetSchema->setNameFormat('contact[%s]');  }}

 

Вызов setNameFormat() позволяет изменять атрибут name для всех виджетов. %s будет автоматически заменено на имя поля во время генерации формы. Например, атрибут name для поля email будет contact[email]. PHP автоматически создаёт массив со значениями запроса, включающего формат contact[email]. таким образом значения будут доступны через массив contact.

Теперь мы можем напрямую получить массив contact из запроса, как это показано в списке 1-7

Список 1-7 - Новый формат атрибута name

 

public function executeSubmit($request){  $this->forward404Unless($request->isMethod('post'));   $this->redirect('contact/thankyou?'.http_build_query($request->getParameter('contact')));}

 

При отображении html-кода формы, как вы можете видеть, symfony создал не только атрибут name, но так же и атрибут id. id создаётся автоматически из name, заменив запрещённые символы символом подчёркивания (_):

Название Атрибут name Атрибут id
name contact[name] contact_name
email contact[email] contact_email
message contact[message] contact_message

Другое решение

В данном примере мы использовали 2 действия для работы с формой: index для отображения и submit для отправки. Раз форма показывается методом GET и отправляется методом POST - мы можем слить 2 данных метода в один метод index, как это сделано в списке 1-8.

Список 1-8 - объединение двух действий для формы

 

class contactActions extends sfActions{  public function executeIndex($request)  {    $this->form = new ContactForm();     if ($request->isMethod('post'))    {      $this->redirect('contact/thankyou?'.http_build_query($request->getParameter('contact')));    }  }}

 

Вы можете измеить метод отсылки изменив шаблон indexSuccess.php:

<form method="post" action="<?php echo url_for('contact/index') ?>"> </form>

В дальнейшем мы будет использовать данный синтаксис. Он короче и делает код более удобным и понятным.

Конфигурируем виджеты

Опции виджетов

Если вебсайт управляется несколькими администраторами, мы конечно-же захотим добавить выпадающий список
с темами, чтобы отправить запрос в сооветсвии с тематикой (изображение 1-7). Список 1-9 добавляет
поле тематики(subject) с выпадающим списком, используя виджет sfWidgetFormSelect.

Изображение 1-7 - Добавление поля subject в форму

Добавление поля subject в форму

Список 1-8 - Добавление поля subject в форму

 

class ContactForm extends sfForm{  protected static $subjects = array('Subject A', 'Subject B', 'Subject C');   public function configure()  {    $this->setWidgets(array(      'name'    => new sfWidgetFormInput(),      'email'   => new sfWidgetFormInput(),      'subject' => new sfWidgetFormSelect(array('choices' => self::$subjects)),      'message' => new sfWidgetFormTextarea(),    ));     $this->widgetSchema->setNameFormat('contact[%s]');  }}

 

PHP не различает обычный и ассоциативный массивы, поэтому массив, который мы используем,
идентичен следующему коду:

 

$subjects = array(0 => 'Subject A', 1 => 'Subject B', 2 => 'Subject C');

 

При генерации виджет использует ключи массива как значения (value) тэга ,
а значения массива как текст внутри тэга.

 

<select name="contact[subject]" id="contact_subject">  <option value="0">Subject A</option>  <option value="1">Subject B</option>  <option value="2">Subject C</option></select>

 

Для замены атрибута value нужно просто установить ключи:

 

$subjects = array('A' => 'Subject A', 'B' => 'Subject B', 'C' => 'Subject C');

 

И получим такой код:

 

<select name="contact[subject]" id="contact_subject">  <option value="A">Subject A</option>  <option value="B">Subject B</option>  <option value="C">Subject C</option></select>

 

Виджет sfWidgetFormSelect, как и все другие виджеты, принимает список опций как первые аргумент. Опции могут быть как обязательными так и нет. У виджета sfWidgetFormSelectwidget обязательной является опция choices. А вот доступные опции для виджетов, которые мы уже использовали:

Виджет Обязательные опции Дополнительные опции
sfWidgetFormInput - type (default to text)
    is_hidden (default to false)
sfWidgetFormSelect choices multiple (default to false)
sfWidgetFormTextarea - -

Если уж так не терпится знать все опции виджетов, вы можете заглянуть в документацию по API, доступную по адресу http://www.symfony-project.org/api/1_1/.
Там вы увидите разъяснения, а так стандартные значения доп. опций. Например, все опции для виджета
sfWidgetFormSelect можно найти здесь:
http://www.symfony-project.org/api/1_1/sfWidgetFormSelect

HTML атрибуты виджетов

Какждый виджет так же принимает список HTML атрибутов как второй необязательный аргумент.
Это может быть очень полезным при определении параметров для сгенерированных тегов.
Список 1-10 показывает как добавить атрибут class к полю email

Список 1-10 - Определение аттрибутов для виджета

 

$emailWidget = new sfWidgetFormInput(array(), array('class' => 'email')); // Получемый HTML

 

Мы так же имеем возможность заменить параметр id, как показано в списке 1-11

Список 1-11 Замена атрибута ID

 

$emailWidget = new sfWidgetFormInput(array(), array('class' => 'email', 'id' => 'email')); // Полученный HTML

 

Можно даже заменить параметр value:

Список 1-12 - Стандартный атрибут value

 

$emailWidget = new sfWidgetFormInput(array(), array('value' => 'Your Email Here')); // полученный HTML

 

Данный параметр работае с полями input, но ничего не может поделать с чекбоксами и радиокнопками, тем более уж с textarea. Класс sfForm предлагает особые методы для определения стандартных значений для каждого из полей, по общей схеме, для любого типа виджетов.

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

Определение стандартных значений для полей

Частенько бывает полезным установить определённой стандартное значение для каждого из полей.
Например, когда мы отображаем сообщение-помошник в поле, которое пропадает после наведения фокуса на поле.
Список 1-13 показывает как установить стандартные значения через методы setDefault() и setDefaults()

Список 1-13 - Стандартные значения виджетов через setDefault() и setDefaults()

 

class ContactForm extends sfForm{  public function configure()  {    // ...     $this->setDefault('email', 'Your Email Here');     $this->setDefaults(array('email' => 'Your Email Here', 'name' => 'Your Name Here'));  }}

 

Методы setDefault() и setDefaults() весьма полезны для определения одинаковых стандартных
значений для каждого объекта класса формы. Если мы хотим изменить существующий объект используя формы,
стандартные значения будут зависеть от копии, тем не менее они должны быть динамичными. Список 1-14
показываем нам, что конструктор sfForm принимает первый аргумент для установки стандартных значений.

Список 1-14 - Стандартные значения для виджетов через конструктор класса sfForm

 

public function executeIndex($request){  $this->form = new ContactForm(array('email' => 'Your Email Here', 'name' => 'Your Name Here'));   // ...}

 

Защита от XSS (Cross-Site Scripting)

При установке атрибутов для виджетов или определения дефолтных значений класс sfForm самостоятельно защищает значения против XSS атак во время создания html-кода. Данная защита не зависит от параметра escaping_strategy конфигурационного файла settings.yml. Если данные уже были защищены, то повторого применения защиты не будет. Так же оно защитит от символов ' и ", который могут побить html-код. Вот пример защиты:

 

$emailWidget = new sfWidgetFormInput(array(), array(  'value' => 'Hello "World!"',  'class' => '<script>alert("foo")</script>',)); // Получаеммый HTML<input  value="Hello "World!""  class="&lt;script&gt;alert(&quot;foo&quot;)&lt;/script&gt;"  type="text" name="contact[email]" id="contact_email" />

 

 

$ cd ~/ПУТЬ/К/ПРОЕКТУ$ php symfony generate:module frontend contact

Коментарии:

ezh 2010-09-19 21:34:04

Код не переносится (горизонтальный скрол), в коменте получений HTML действительно выводится (htmlentities(); ?)

html > body > div#site > div#content > div.blog > div.message > div.message-in > pre { overflow: ??? }

ответить
bvn 2011-02-14 22:15:55

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

ответить

Свой комментарий: