Форми (частина 2)

19/04/2016 0 symfony, розробка, шаблон Twig

Задаємо форму в шаблоні

Ви вже бачили як можна за допомогою однієї лінійки коду надати цілу форму. Звичайно, зазвичай, вам буде треба набагато більше гнучкості:  

TWIG

{# app/Resources/views/default/new.html.twig #}
{{ form_start(form) }}
    {{ form_errors(form) }}

    {{ form_row(form.task) }}
    {{ form_row(form.dueDate) }}
{{ form_end(form) }}

PHP

<!-- app/Resources/views/default/newAction.html.php -->
<?php echo $view['form']->start($form) ?>
   <?php echo $view['form']->errors($form) ?>

   <?php echo $view['form']->row($form['task']) ?>
   <?php echo $view['form']->row($form['dueDate']) ?>
<?php echo $view['form']->end($form) ?>

Ви вже ознайомилися з функціями form_start() та form_end(), а за що ж відповідають інші функції?  

form_errors(form)

Відображає будь-які помилки для цілої форми (помилки у певних полях відображаються біля кожного поля).

form_row(form.dueDate)

Відображає мітку, будь-які помилки, та віджет форми HTML для даного поля (наприклад, dueDate) за замовчуванням, всередині елемента div.

Більшість роботи виконує помічник form_row, який відображає мітку, помилки та віджет форми HTML кожного поля, всередині тега div за замовчуванням. У підрозділі “Оформлення форми”, ви навчитеся налаштовувати вихідні дані form_row на різних рівнях.  

Ви можете мати доступ до поточних даних вашої форми через form.vars.value:

TWIG

{{ form.vars.value.task }}

PHP

<?php echo $form->vars['value']->getTask() ?>

Задаємо кожну форму вручну

За допомогою помічника form_row ви можете напрочуд швидко задавати кожне поле вошої форми (і розмітка використана для рядка також може бути налаштована). Проте, так як життя не таке вже й просте, ви можете спробувати задати кожне поле вручну. Кінцевий результат буде таким самим як і при використанні помічника form_row:

TWIG
{{ form_start(form) }}
    {{ form_errors(form) }}

    <div>
        {{ form_label(form.task) }}
        {{ form_errors(form.task) }}
        {{ form_widget(form.task) }}
    </div>

    <div>
        {{ form_label(form.dueDate) }}
        {{ form_errors(form.dueDate) }}
        {{ form_widget(form.dueDate) }}
    </div>

    <div>
        {{ form_widget(form.save) }}
    </div>

{{ form_end(form) }}

PHP

<?php echo $view['form']->start($form) ?>

    <?php echo $view['form']->errors($form) ?>

    <div>
        <?php echo $view['form']->label($form['task']) ?>
        <?php echo $view['form']->errors($form['task']) ?>
        <?php echo $view['form']->widget($form['task']) ?>
    </div>

    <div>
        <?php echo $view['form']->label($form['dueDate']) ?>
        <?php echo $view['form']->errors($form['dueDate']) ?>
        <?php echo $view['form']->widget($form['dueDate']) ?>
    </div>

    <div>
        <?php echo $view['form']->widget($form['save']) ?>
    </div>

<?php echo $view['form']->end($form) ?>

Якщо автоматично згенерована мітка для поля не цілком правильна, ви можете задати її чітко:  

TWIG

{{ form_label(form.task, 'Task Description') }}

PHP

<?php echo $view['form']->label($form['task'], 'Task Description') ?>

Деякі типи полів мають додаткові опції, які виконуються за допомогою веб-віджета. Ці опції містять інформацію про кожний тип, проте існує спільна опція attr, яка дозволяє зміну значень в елементі форми. За допомогою наступного коду ми додамо клас task_field до заданого поля вводу:  

TWIG

{{ form_widget(form.task, {'attr': {'class': 'task_field'}}) }}

PHP

<?php echo $view['form']->widget($form['task'], array(
    'attr' => array('class' => 'task_field'),
)) ?>

Якщо вам потрібно задати поля форми вручну, ви можете отримати доступ до індивідуальних значень для полів, таких як  id,name та label. Наприклад, щоб отримати id:

TWIG

{{ form.task.vars.id }}

PHP

<?php echo $form['task']->vars['id']?>

Щоб отримати значення, використане для властивості імені поля форми, використовуйте значення full_name:  

TWIG

{{ form.task.vars.full_name }}

PHP

<?php echo $form['task']->vars['full_name'] ?>

Довідкова інформація про функції у шаблоні Twig

Якщо ви користуєтеся шаблоном Twig, уся додадкова інформація про форму, яка задає функції, доступна у довіднику. Ви можете прочитати і дізнатися більше про помічників (helpers) та опції, які використовуються з кожним із них.

Змінюємо операцію та метод форми

Досі, помічник form_start() використовувався для того, щоб задати тег початку форми і ми припускали, що кожна форма належить одній і тій же URL-адресі та запитові POST. Деколи буде потрібно змінити ці параметри. Ви можете зробити це кількома способами. Якщо ви будуєте форму в контролері, ви можете використати setAction() і setMethod():

$form = $this->createFormBuilder($task)
    ->setAction($this->generateUrl('target_route'))
    ->setMethod('GET')
    ->add('task', TextType::class)
    ->add('dueDate', DateType::class)
    ->add('save', SubmitType::class)
    ->getForm();

Відповідно до прикладу, ми створили маршрут під назвою target_route, що вказує на контролер, який обробляє форму.

У підрозділі створення класів форм ви дізнаєтеся як розділити код створення форми на різні класи. Користуючись зовнішнім класом форми у контролері, ви можете подати операцію і метод як опції форми:

use AppBundle\Form\Type\TaskType;
// ...

$form = $this->createForm(TaskType::class, $task, array(
    'action' => $this->generateUrl('target_route'),
    'method' => 'GET',
));

Зрештою, ви можете не брати до уваги операцію і метод у шаблоні, подавши їх у form() або в помічникові form_start():

TWIG

{# app/Resources/views/default/new.html.twig #}
{{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }}

PHP

<!-- app/Resources/views/default/newAction.html.php -->
<?php echo $view['form']->start($form, array(
    // The path() method was introduced in Symfony 2.8. Prior to 2.8,
    // you had to use generate().
    'action' => $view['router']->path('target_route'),
    'method' => 'GET',
)) ?>

Якщо метод форми не є GET, або POST, а PUT, PATCH, чи DELETE, Symfony вставить приховане поле з назвою  _method, яке буде зберігати цей метод.  Форма буде подаватися зі звичайним запитом POST, проте маршрутизатор Symfony може визначити параметер поля  _method, і буде інтерпретувати його як запити PUT, PATCH or DELETE. Деталі ви зможете дізнатися у довіднику.

Створюємо класи форм

Так як ви вже побачили, форму можна створювати і використовувати напряму у контролері. Проте, найкраще буде створити форму в окремому і незалежному PHP класі, який може бути використаним знову, будь-де у вашому додатку. Створимо новий клас, у якому розмістимо код для створення форми завдання:

// src/AppBundle/Form/Type/TaskType.php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('task')
            ->add('dueDate', null, array('widget' => 'single_text'))
            ->add('save', SubmitType::class)
        ;
    }
}

Цей новий клас містить усі необхідні вказівки для створення форми класу. Його можна використати і у “будівництві” об’єкта фоми у контролері:

// src/AppBundle/Controller/DefaultController.php
use AppBundle\Form\Type\TaskType;

public function newAction()
{
    $task = ...;
    $form = $this->createForm(TaskType::class, $task);

    // ...
}

Помістивши код форми у її ж клас, ви зможете легко використовувати її будь-де у вашому проекті. Це найкращий спосіб створення форм, але вибір залежить повністю від вас.

Налаштовуємо data_class

Кожна форма повинна “знати” ім’я класу, у якому знаходяться найважливіші дані (наприклад, AppBundle\Entity\Task). Як правило, форма їх “вгадує” на основі об’єкта, переданого другогому аргументу до createForm (тобто, $task). Пізніше, коли ви почнете вставляти форми, цього вже не буде достатньо. Отож, хоча чітко вказувати опцію data_class не завжди необхідно, але загалом, це є хорошою ідеєю. Для цього, додамо   до класу типу форми такий код:

use Symfony\Component\OptionsResolver\OptionsResolver;

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'AppBundle\Entity\Task',
    ));
}

При відображенні форм в об’єкти, відображуються усі поля. Будь-які поля на формі, які не існують у відображеному об’єкті, можуть спричинити виняток і бути відображеними на екрані. Якщо вам у формі будуть потрібні додаткові поля (наприклад, чекбокс “Чи ви згідні з цими положеннями?”), які не відображатимуться базовому об’єктові, тоді вам потрібно буде налаштувати опцію mapped на false:

use Symfony\Component\Form\FormBuilderInterface;

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('task')
        ->add('dueDate', null, array('mapped' => false))
        ->add('save', SubmitType::class)
    ;
}

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

Дані поля можна отримати у контролері за допомогою:

$form->get('dueDate')->getData();

Також, дані не відображеного поля можна змінити напряму:

$form->get('dueDate')->setData(new \DateTime());

Задаємо тип форми як сервіс

Тип форми може мати зовнішні залежності. Ви можете задати тип форми як сервіс, і впровадити усі необхідні залежності.  

Сервіси, а також сервіс контейнером ми розберемо пізніше. Дочитавши розділ до кінця, ви змрозумієте матеріал набагато краще.

Можливо, у типі форми вам потрібно буде використати сервіс під назвою app.my_service. Щоб отримати сервіс, створимо конструктор до типу форми:

// src/AppBundle/Form/Type/TaskType.php
namespace AppBundle\Form\Type;

use App\Utility\MyService;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

class TaskType extends AbstractType
{
    private $myService;

    public function __construct(MyService $myService)
    {
        $this->myService = $myService;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // You can now use myService.
        $builder
            ->add('task')
            ->add('dueDate', null, array('widget' => 'single_text'))
            ->add('save', SubmitType::class)
        ;
    }
}

Задаємо тип форми як сервіс.

YAML

# src/AppBundle/Resources/config/services.yml
services:
    app.form.type.task:
        class: AppBundle\Form\Type\TaskType
        arguments: ["@app.my_service"]
        tags:
            - { name: form.type }

XML

<!-- src/AppBundle/Resources/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="app.form.type.task" class="AppBundle\Form\Type\TaskType">
            <tag name="form.type" />
            <argument type="service" id="app.my_service"></argument>
        </service>
    </services>
</container>

PHP

// src/AppBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Reference;

$container->register('app.form.type.task', 'AppBundle\Form\Type\TaskType')
    ->addArgument(new Reference('app.my_service'))
    ->addTag('form.type')

Щоб дізнатися більше, читайте розділ “Створюємо тип поля як сервіс”.

Форми та Doctrine

Мета форми - перетворити дані об’єкта (наприклад, Task) у форму HTML, а тоді перетворити відправлені користувачем дані назад у форму першого об’єкта. Зберігання об’єкта Task не стосується теми форм. Проте, якщо ви налаштували клас Task, таким чином, щоб він зберігався у Doctrine (наприклад, для цього ви додали відображення метаданих (див. розділ Бази даних і Doctrine), тоді зберегти його після відправлення форми можна зробити лише у випадку, коли форма буде дійсною:

if ($form->isValid()) {
    $em = $this->getDoctrine()->getManager();
    $em->persist($task);
    $em->flush();

    return $this->redirectToRoute('task_success');
}

Якщо через певні обставини у вас немає доступу до початкового об’єкта $task, ви можете добути його з форми:

$task = $form->getData();

Детальніше про це ви зможете прочитати у розділі Бази даних і Doctrine.

Основне, що вам слід зрозуміти, це те, що коли форма відправлена, дані з неї відразу передаються базовому об’єктові. Якщо ви хочете зберегти ці дані, вам просто потрібно зберегти сам об’єкт (який вже містить ці дані).  

Вбудовані форми

Вам часто потрібно буде створювати форму, яка міститиме поля багатьох різних об’єктів. Наприклад, реєстраційна форма може містити дані, що належать об’єктові User, так само як і багато об’єктів Address. Нащастя, це надзвичайно легко з компонентом Form.  

Вбудовуємо єдиний об’єкт

Припустимо, що кожен Task належить до простого об’єкта Category. Розпочнемо, звичайно, зі створення об’єкта Category:

// src/AppBundle/Entity/Category.php
namespace AppBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Category
{
    /**
     * @Assert\NotBlank()
     */
    public $name;
}

Далі, додамо нову властивість category до класу Task.

// ...

class Task
{
    // ...

    /**
     * @Assert\Type(type="AppBundle\Entity\Category")
     * @Assert\Valid()
     */
    protected $category;

    // ...

    public function getCategory()
    {
        return $this->category;
    }

    public function setCategory(Category $category = null)
    {
        $this->category = $category;
    }
}

Ми додали обмеження Valid до властивості category. Тепер валідація спадатиме до відповідного класу. Якщо ви не будете вживати це обмеження, дочірній клас не буде перевірений.  

Тепер, коли ваш додаток оновлений, і відображає нові вимоги, створимо новий клас форми, щоб користувач міг змінити об’єкт Category:

// src/AppBundle/Form/Type/CategoryType.php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CategoryType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name');
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Category',
        ));
    }
}

Наша мета - дозволити внесення змін до об’єкта Category класу Task, всередині форми завдання. Для цього, додамо поле category до об’єкта TaskType чий тип є екземпляром нового класу CategoryType:

use Symfony\Component\Form\FormBuilderInterface;
use AppBundle\Form\Type\CategoryType;

public function buildForm(FormBuilderInterface $builder, array $options)
{
    // ...

    $builder->add('category', CategoryType::class);
}

Поля класу CategoryType можуть тепер відображатися поряд з полями класу TaskType.

Задавайте поля Category так само, як і поля першого класу Task.

TWIG

{# ... #}

<h3>Category</h3>
<div class="category">
    {{ form_row(form.category.name) }}
</div>

{# ... #}

PHP

<!-- ... -->

<h3>Category</h3>
<div class="category">
    <?php echo $view['form']->row($form['category']['name']) ?>
</div>

<!-- ... -->

Коли користувач відправляє форму, дані у полях Category використовуються для створення  екземпляра Category, який потім налаштовується у полі category екземпляра Task.

Природньо, ми маємо доступ до екземпляра Category через $task->getCategory() і можемо зберегти його до бази даних, або користуватись ним коли завгодно.  

Вбудовуємо набір форм

Ми також можемо помістити набір форм в одну форму (уявимо форму Category з багатьма під-формами Product). Це можна зробити, користуючись тип поля  collection.  

Детальніше про вбудування наборів форм і CollectionType ви можете дізнатися у довіднику.

Темізація форм

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

Symfony використовує шаблони, які задають кожну частину форми, такі як теги label, input, повідомлення про помилки та інші.   

У шаблоні Twig, кожен фрагмент форми представлений Twig-блоком. Щоб оформити всі частини відображеної форми, просто позбутьтеся відповідного блока.  

У PHP, кожен фрагмент відображається через індивідуальний файл шаблону. Щоб оформити всі частини відображеної форми, просто створіть новий шаблон замість старого.  

Для того аби зрозуміти як це працює оформіть фрагмент form_row, та додайте вдастивість класу до елемента div біля кожного рядка. Для цього створіть новий файл шаблону, який буде зберігати нову верстку:

TWIG

{# app/Resources/views/form/fields.html.twig #}
{% block form_row %}
{% spaceless %}
    <div class="form_row">
        {{ form_label(form) }}
        {{ form_errors(form) }}
        {{ form_widget(form) }}
    </div>
{% endspaceless %}
{% endblock form_row %}

PHP

<!-- app/Resources/views/form/form_row.html.php -->
<div class="form_row">
    <?php echo $view['form']->label($form, $label) ?>
    <?php echo $view['form']->errors($form) ?>
    <?php echo $view['form']->widget($form, $parameters) ?>
</div>

Фрагмент форми form_row буде використовуватися коли ви задаватимете більшість полів через функцію form_row. Щоб вказати компонентові Form використовувати новий, вказаний зверху, фрагмент, додайте наступний код, зверху в шаблоні, який відображає форму:

TWIG

{# app/Resources/views/default/new.html.twig #}
{% form_theme form 'form/fields.html.twig' %}

{# or if you want to use multiple themes #}
{% form_theme form 'form/fields.html.twig' 'form/fields2.html.twig' %}

{# ... render the form #}

PHP

<!-- app/Resources/views/default/new.html.php -->
<?php $view['form']->setTheme($form, array('form')) ?>

<!-- or if you want to use multiple themes -->
<?php $view['form']->setTheme($form, array('form', 'form2')) ?>

<!-- ... render the form -->

Тег form_theme (у Twig) "імпортує" визначені у даному шаблоні фрагменти, і використовує їх для відображення форми. Іншими словами, якщо функція form_row пізніше знову використовується, вона буде застосовувати блок з вашої користувацької теми (замість блоку за замовчуванням form_row, який поставляється з Symfony).

Ваша користувацька тема не повинна видаляти усі блоки. При відображенні блоку якого ви не видалили з користувацької теми, двигун тематизації повернеться до загальної теми (визначається на рівні пакетів).  

Якщо у вас буде декілька користувацьких тем, програма шукатиме їх у зазначеному порядку, перед тим як повернутися до загальної теми.   

Для налаштування форми, просто видаліть відповідний фрагмент. Щоб дізнатися який саме блок чи файл замінювати, читайте наступний підрозділ.  

Більше інформації стосовно цієї теми ви дізнаєтесь у довіднику.  

Називаємо фрагменти форм

У Symfony, відображається кожна частина форми - елементи HTML форми, помилки, мітки, і т. д. - все це визначається у базовій темі, яка являє собою набір блоків у шаблоні Twig і набір файлів шаблону в PHP.

У Twig, кожен потрібний блок вихначається в одному файлі шаблону (наприклад, form_div_layout.html.twig), який знаходиться у Twig Bridge. Всередині цього файлу, ви зможете побачити кожен потрібний для відображення форми блок, а також кожен тип поля за замовчуванням.  

У PHP, фрагменти - це окремі файли фаблону. За замовчуванням вони знаходяться у директорії Resources/views/Form фреймворка пакетів (GitHub).  

Кожне ім’я фрагменту наслідує такий самий базовий шаблон, і складається з двох частин, з’єднаних знаком (_). Ось декілька прикладів:  

  • form_row - використовується form_row для відображення більшості полів;  

  • textarea_widget - використовується  form_widget для відображення типу поля textareа;

  • form_errors - використовується form_errors для відображення помилок у полях;

Кожен фрагмент є один і той самий базовий шаблон type_part. Частинка type відповідає відображеному полю type (наприклад, textarea, checkbox, date, і т. д.), в той час як частинка part відповідає тому, що відображається (наприклад, label, widget, errors, і т. д). За замовчуванням, існує 4 можливі частини форми, які можуть відображатися:  

 

label

(наприклад, form_label)

відображає мітку поля

widget

(наприклад, form_widget)

відображає репрезентацію HTML поля  

errors

(наприклад,  form_errors)

відображає помилки поля  

row

(наприклад,  form_row)

відображає цілий рядок поля (мітку, веб-віджет і помилки)

 

Існує дві інші частини -   rows і rest - проте, вам не слід турбуватися про їх видалення.  

Знаючи тип поля (наприклад, textarea) і яке поле ви б хотіли налаштувати (наприклад, widget), ви можете створити ім’я фрагменту, якого потрібно буде позбутися (наприклад, textarea_widget).  

Наслідування фрагмента форми

У деяких випадках може виявитися, що  фрагмента, якого ви захочете оформити, небуде. Наприклад, фрагмента textarea_errors у темах Symfony за замовчуванням не існує. Отже, як ми можемо відобразити помилки для текстового поля?  

А ось і відповідь: через фрагмент form_errors. Коли Symfony виводить помилки для поля текстового типу, програма шукає спочатку фрагмент textarea_errors, перш як повертатися до фрагмента form_errors. Кожен тип поля має батьківський тип (батьківський тип для textarea -  text, а батьківський елемент - form), якщо основного фрагмента не існує, тоді Symfony використовує фрагмент для батьківського типу.

Отож, щоб позбутися помилок лише у полях textarea скопіюйте фрагмент form_errors, назвіть його textarea_errors, і налаштуйте. Щоб позбутися помилок за замовчуванням, які відображаються для усіх полів, напряму скопіюйте та налаштуйте фрагмент  form_errors.  

“Батьківські” типи кожного типу поля, для кожного типу поля, доступні тут.  

Загальна тематизація форм

У вище наведеному прикладі, для того щоб “імпортувати” користувацькі фрагменти форми у ту ж саму форму. Ви також можете задати Symfony команду імпортувати налаштування форми у всьому вашому проекті.

Twig

Щоб автоматично включити оформлені блоки з раніше створеного у всіх шаблонах шаблону fields.html.twig, змініть файл конфігурації у додатку:  

YAML

# app/config/config.yml
twig:
    form_themes:
        - 'form/fields.html.twig'
    # ...

XML

<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:twig="http://symfony.com/schema/dic/twig"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/twig http://symfony.com/schema/dic/twig/twig-1.0.xsd">

    <twig:config>
        <twig:theme>form/fields.html.twig</twig:theme>
        <!-- ... -->
    </twig:config>
</container>

PHP

// app/config/config.php
$container->loadFromExtension('twig', array(
    'form_themes' => array(
        'form/fields.html.twig',
    ),
    // ...
));

Any blocks inside the fields.html.twig template are now used globally to define form output.

Налаштування вихідних даних форми в одному файлі у Twig


У Twig, ви також можете налаштувати блок форми просто всередині шаблону, саме там, де це потрібно.

{% extends 'base.html.twig' %}

{# import "_self" as the form theme #}
{% form_theme form _self %}

{# make the form fragment customization #}
{% block form_row %}
    {# custom field row output #}
{% endblock form_row %}

{% block content %}
    {# ... #}

    {{ form_row(form.task) }}
{% endblock %}

Тег {% form_theme form _self %} дає можливісь налаштувати блоки форми прямо всередині шаблону, який буде використовувати всі ці налаштування. Використовуйте цей метод, щоб швидко налаштувати вихідні дані форми, які будуть потлібні лише в одному шаблоні.  

Функціонувати цей тег ({% form_theme form _self %}) буде лише у тому випадку, коли ваш шаблон розширює другий. Якщо ні, вам потрібно буде вказати form_theme для окремого шаблону.  

PHP

Для того щоб автоматично включити налаштовані шаблони з створеної раніше в усіх шаблонах директорії app/Resources/views/Form, змініть конфігураційний файл у додатку наступним чином:  


YAML

# app/config/config.yml
framework:
    templating:
        form:
            resources:
                - 'Form'
# ...

XML

<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:framework="http://symfony.com/schema/dic/symfony"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

    <framework:config>
        <framework:templating>
            <framework:form>
                <framework:resource>Form</framework:resource>
            </framework:form>
        </framework:templating>
        <!-- ... -->
    </framework:config>
</container>

PHP

// app/config/config.php
$container->loadFromExtension('framework', array(
    'templating' => array(
        'form' => array(
            'resources' => array(
                'Form',
            ),
        ),
    ),
    // ...
));

Усі фрагменти всередині директорії app/Resources/views/Form використовуються загально, щоб визначити вихідні дані форми.  

Захист від міжсайтової підробки запиту (CSRF)

Міжсайтова підробка запиту (Cross-site request forgery) - це метод, за допомогою якого зловмисник старається змусити ваших користувачів несвідомо відправити дані, які вони не хотіли відправляти. Нащастя, ці атаки можна знешкодити, використавши знак CSRF, всередині ваших форм.

Проте у нас є хороші новини! За замовчуванням Symfony автоматично вставить і перевірить усі знаки CSRF. Це означає, що ви маєте перевагу захисту CSRF, нічого не робивши. Взагалі, кожна форма, про яку йдеться у даному розділі, має багато переваг з таким захистом!

Захист CSRF працюватиме коли ми додамо приховане поле до форми з назвою _token за замовчуванням - це обмежить значення, яке знаєте ви та ваш користувач. Це переконує, що користувач - і ніхто інший - заповнює і відпраліє дані. Symfony автоматично перевіряє наявність і точність знака CSRF.  

Поле  _token є схованим полем і відображатиметься автоматично, якщо ви добавите у ваш шаблон функцію form_end(), яка перевірятиме чи всі невідображені поля є вихідними даними.  

Оскільки цей знак зберігається у сеансі, то сеанс розпочинається автоматично, як тільки ви задасте форму з захистом CSRF.  

Знак CSRF може налаштовуватися на основі форма за формою. Наведемо приклад:

use Symfony\Component\OptionsResolver\OptionsResolver;

class TaskType extends AbstractType
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class'      => 'AppBundle\Entity\Task',
            'csrf_protection' => true,
            'csrf_field_name' => '_token',
            // a unique key to help generate the secret token
            'csrf_token_id'   => 'task_item',
        ));
    }

    // ...
}

Щоб вимкнути захист CSRF, налаштуйте опцію csrf_protection на false. Налаштування можна зробити загальним для усього вашого проекту. Детальніше читайте у довіднику.  

Опція csrf_token_id є необов’язковою, проте вона сильно збільшує безпеку згенерованого знаку CSRF, роблячи його для кожної форми різним.  

Позначки CSRF повинні бути різними для кожного користувача. Ось чому вам слід бути обережними, при кешуванні сторінок з формами, які містять такий тип захисту. Детальніше дізнавайтесь у довіднику.  

Використовуємо форму без класу

У більшості випадків, форма прив’язана до об’єкта, і поля форми отримують та зберігають дані у властивостях цього об’єкта. Це те, про що ви прочитали у цьому розділі з класом Task.  

Проте деколи, вам буде просто потрібо використовувати форму без класу, і повернути масив відправлених даних. Насправді, це дуже легко:

// make sure you've imported the Request namespace above the class
use Symfony\Component\HttpFoundation\Request;
// ...

public function contactAction(Request $request)
{
    $defaultData = array('message' => 'Type your message here');
    $form = $this->createFormBuilder($defaultData)
        ->add('name', TextType::class)
        ->add('email', EmailType::class)
        ->add('message', TextareaType::class)
        ->add('send', SubmitType::class)
        ->getForm();

    $form->handleRequest($request);

    if ($form->isValid()) {
        // data is an array with "name", "email", and "message" keys
        $data = $form->getData();
    }

    // ... render the form
}

За замовчуванням, форма “припускає” що ви хочете працювати з масивами даних, а не з об’єктом. Існує два шляхи, якими можна змінити дії форми і прикріпити її до об’єктів.  

  1. Подайте об’єкт при створенні форми (у формі першого арумента, щоб зробити FormBuilder чи другого аргумента, щоб створити createForm);

  2. Створіть опцію data_class у вашій формі.  

Якщо ви не скористаєтесь ніяким з цих шляхів, тоді форма вмведе вам дані у формі масиву. У цьому прикладі, оскільки $defaultData не є об’єктом (і опція data_class не налаштована),  $form->getData() зрештою повертає масив.  

Також, напряму, через об’єкт запиту, ви матимете доступ до значень POST (у нашому випадку це “name”), in this case "name"):

$request->request->get('name');

Проте, ми радимо вам використовувати метод  getData(), адже це кращий варіант, бо він повертає дані (зазвичай об’єкт) після того, як він вже змінений компонентом Form.

Додаємо превірку (валідацію)

Річ, про яку ми поки що не згадали - це валідація. Зазвичай, коли ви даєте запит $form->isValid(), об’єкт перевіряється зчитуючи обмеження, які ви використали для класу. Якщо ваша форма відображена об’єктом (тобто, ви використовуєте опцію data_class, або використовуєте об’єкт у формі), це типовий підхід, який ви будете застосовувати. Детальніше ви можете дізнатися у розділі “Валідація”. (лінк на попередній блог).  

Якщо форма не відображається об’єктові, і натомість ви хочете добути простий масив з відправлених даних, як ви зможете додати обмеження до даних вашої форми?

А ось відповіть: налаштуйте обмеження самостійно, і прикріпіть їх до окремих файлів. Цілий процес детальніше описаний у розділі “Валідація”, проте ось короткий приклад для вас:

use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\Extension\Core\Type\TextType;

$builder
   ->add('firstName', TextType::class, array(
       'constraints' => new Length(array('min' => 3)),
   ))
   ->add('lastName', TextType::class, array(
       'constraints' => array(
           new NotBlank(),
           new Length(array('min' => 3)),
       ),
   ))
;

Ящо ви використовуєте групи обмежень, тоді при створенні форми вам потрібно або посилатися на групу Default, або налаштовувати правильну групу в обмеженні яке ви додаєте.

new NotBlank(array('groups' => array('create', 'update'))

Висновки

Тепер ви знаєте всі необхідні “будівничі” блоки для створення складних, функціональних форм у вашому додатку. При “будуванні” форм, пам’ятайте що основною метою форми є перетворити дані з об’єкта (Task) у форму HTML, щоб користувач міг змінювати дані. Наступна мета - взяти дані відправлені користувачем і повторно передати їх об’єкту.   

Звичайно є ще дуже багато інформації про “світ форм”, яку ви могли б вивчити, наприклад, процес завантаженнями файлів у Doctrine, або як створити форму, де можна додати динамічну кількість під-форм (наприклад, список завдань, де ви зможете зберігати файли JavaScript перед відправленням). За цією інформацією звертайтеся до довідника. Також почитайте документацію, яка включає приклади, як використовувати кожен тип поля і його опції.

Збагачуйте свої знання, читаючи довідник:

Поділитися