- PVSM.RU - https://www.pvsm.ru -

Формы в Symfony2, чуть сложнее чем Hello World

Привет читатель, сегодня я расскажу тебе о фомах в Symfony2.

Lukas

Картинка с Лукасом [1] на затравочку.

Дисклеймер

Из-за того, что Symfony2 довольно verbose фреймворк, я не буду приводить в статье полные исходники чего либо. Потому что, такие статьи превращиются в wall-of-code и потом набигают [2] рубияны и начинается holy war. Вместо этого я приготовил рабочее Symfony2 приложение, ссылка на которое будет в конце статьи. Однако несколько строк кода будут присутствовать в статье.

По запросу трудящих сегодня поговорим о «сложных» формах.

То, что просили описать:

Расскажите о динамических формах, когда могут на клиенте добавляться-убираться поля. Как это все грамотно обрабатывать на клиенте и т.п.

И что бы поля у моделей были виртуальными. Т.е. у модели этих полей нет, а они предположим хранились в другой модели FieldEntity которая ссылалась бы на нашу Entity

Модель

В качестве хранилища буду использовать MongoDB, со всеми вытекающими последствиями.

Модель взята из жизни и сильно упрощена, для улучшения понимания.
Есть Предмет (Item), который может продаваться. Цена (Price) отделена от Предмета, так как один и тот же Предмет может продаваться в одно и тоже время по разным ценам, в зависимости от каких-либо условий.
Назовем Продукт с установленной Ценой — Пакет (Bundle). Пакет сам по себе не может продаваться. Пакет может продаваться в рамках Набора (BundleSet). Набор — это по сути коллекция Пакетов, а значит может содержать ноль и более Пакетов.

Предмет

/**
 * @MongoDBDocument(
 *   collection="item",
 *   repositoryClass="AcmeDemoBundleDocumentItemRepository"
 * )
 */
class Item
{
    /**
     * @AssertNotBlank (message="Did you forget the title?")
     * @MongoDBString
     */
    protected $title;

Цена

/**
 * @MongoDBEmbeddedDocument
 */
class Price
{
    /**
     * @MongoDBFloat
     */
    protected $real;
    /**
     * @MongoDBString
     */
    protected $display;

Пакет

/**
 * @MongoDBEmbeddedDocument
 */
class Bundle
{
    /**
     * @MongoDBReferenceOne(targetDocument="AcmeDemoBundleDocumentItem")
     */
    protected $item;
    /**
     * @MongoDBEmbedOne(targetDocument="AcmeDemoBundleDocumentPrice")
     */
    protected $price;

Набор

/**
 * @MongoDBDocument(
 *   collection="bundle_set",
 *   repositoryClass="AcmeDemoBundleDocumentBundleSetRepository"
 * )
 */
class BundleSet
{
    /**
     * @AssertNotBlank (message="Did you forget the title?")
     * @MongoDBString
     */
    protected $title;
    /**
     * @MongoDBEmbedMany(targetDocument="AcmeDemoBundleDocumentBundle")
     */
    protected $bundles;

Все модели можно посмотреть в директории Document [3]

Формы

Формы простроены через стандартный Симфонийский FormTypes.

Для всех ТиповФорм я всегда явно указываю опцию data_class для того, что бы точно знать какой объект получится в результате $form->getData()

PriceFormType.php — Ничем не примечателен.

BundleFormType.php — содержит поле item с типом document

->add('item', 'document', array(
    'class' => 'AcmeDemoBundleDocumentItem',
    'query_builder' => function(ItemRepository $repository) {
        return $repository->createQueryBuilder();
    }
))

Опция class содержит полный неймспейс целевого документа.
Опция query_builder содержит кложур замыкание которое вернет QueryBuilder для получения всех требуемых документов.

В данном случае поле item отрендерится как выпадающий список со всеми требуемыми документами.

И самое интересное это BundleSetFormType.php — содержит поле bundles которое есть коллекция/набор BundleFormType

->add('bundles', 'collection', array(
    'allow_add'     => true,
    'allow_delete'  => true,
    'prototype'     => true,
    'type'          => new BundleFormType(),
))

Тип поля коллекция, разрешаем добавлять/удалять элементы, тип BundleFormType.

Все FormType`ы можно посмотреть в директории Form [4]

Как всем этим управлять.

К сожалению чуда не произошло и все добавления/удаления Input`ов в браузере придется делать руками на client-side`е — в темплейтах и JavaScript`ом.

1) При помощи Form Theming [5] (дока [6]) добавим кнопки Add и Remove в форму. Кнопка Add будет одна на всю форму и будет добавлять новый элемент в набор. Кнопка Remove будет возле каждого элемента набора и соответственно будет это удалять.

2) Вешаем JavaScript обработчики событий нажатия на эти кнопки. Исходники [7]

Смысл всех client-side плясок с бубном в том, что бы при сабмите формы на сервер приходили структурированные данные и Симфа могла их взять из глобального $_POST.

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

$form->bindRequest($request);
$bundleSet = $form->getData();

который можно использовать в своих целях далее. Писать в базу или что-то ещё.

Ссылка на рабочее демо-приложение HabrAcmeDemo [8]

PS
Мопед JavaScript код не мой.

PSS
Все орфо замечания прошу присылать в личку.

Автор: cystbear


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/php-2/12150

Ссылки в тексте:

[1] Лукасом: http://habrahabr.ru/post/147590/

[2] набигают: http://lurkmore.to/%D0%9D%D0%B0%D0%B1%D0%B5%D0%B3

[3] Document: https://github.com/cystbear/HabrAcmeDemo/tree/master/src/Acme/DemoBundle/Document

[4] Form: https://github.com/cystbear/HabrAcmeDemo/tree/master/src/Acme/DemoBundle/Form

[5] Form Theming: https://github.com/cystbear/HabrAcmeDemo/blob/master/src/Acme/DemoBundle/Resources/views/FormsDemo/formTheme.html.twig

[6] дока: http://symfony.com/doc/master/book/forms.html#form-theming

[7] Исходники: https://github.com/cystbear/HabrAcmeDemo/blob/master/src/Acme/DemoBundle/Resources/public/js/form.js

[8] HabrAcmeDemo: https://github.com/cystbear/HabrAcmeDemo