Symfony/Form

02/2015 / Milan "perún" Herda / @moriquend / hrad.perunhq.org

O čom budeme hovoriť

  • základné použitie
  • vlastný FormType
  • handler

O čom nebudeme hovoriť

  • vykreslenie formuláru
  • ukladanie do DB
  • validácie a ich prepojenie na objekty

Symfony/Form

  • samostatne použiteľný Symfony komponent pre prácu s formulármi
  • uľahčuje vytváranie formuláru
  • umožňuje kombinovať formuláre
  • zjednodušuje prístup k odoslaným formulárovým dátam

Inštalácia

Composer


{
    "require": {
        "symfony/form": "~2.6.3"
    }
}
                    

Základné použitie

Vytvorenie formuláru

Vo vnútri Symfony/Silex controlleru:

// symfony controller
$formBuilder = $this->get('form.factory')->createBuilder()
// $formBuilder = $this->createFormBuilder();

// silex controller
$formBuilder = $app['form.factory']->createBuilder()

$formBuilder
    ->add('name')
    ->add('email')
    ->add('gender', 'choice', [
        'choices' => [
            'male'   => 1,
            'female' => 2,
        ]
    ])
    ->add('submit', 'submit');

$form = $formBuilder->getForm();
                    

Zistenie a spracovanie odoslania


// zistenie odoslania a naplnenie dátami z requestu
$form->handleRequest($request);

// formulár bol odoslaný a je validný
if ($form->isValid()) {
    $data = $form->getData();

    // spracovať dáta
    // nastaviť flash message
    // urobiť redirect
}
                    

Nastavenie predvolených hodnôt


$defaultData = [
    'name'   => 'Ferko',
    'email'  => 'ferko.mrkvicka@example.com',
    'gender' => 1,
];

// symfony controller
$formBuilder = $this->get('form.factory')->createBuilder('form', $defaultData)
// $formBuilder = $this->createFormBuilder($defaultData);

// silex controller
$formBuilder = $app['form.factory']->createBuilder('form', $defaultData)
                    

Validácie


use Symfony\Component\Validator\Constraints as Assert;

// pre silex potrebujeme aj zaregistrovať validátor
$app->register(new Silex\Provider\ValidatorServiceProvider());

$formBuilder
    ->add('name', 'text', [
        'constraints' => [
            new Assert\NotBlank(),
            new Assert\Length(['min' => 3]),
        ]
    ])
    ->add('email', 'text', [
        'constraints' => [
            new Assert\NotBlank(),
            new Assert\Email(),
        ]
    ])
    ->add('gender', 'choice', [
        'choices' => [
            'male'   => 1,
            'female' => 2,
        ],
        'constraints' => [
            new Assert\Choice([1, 2]),
        ]
    ])
    ->add('submit', 'submit');
                    

Kompletný príklad

Všetok tento kód sa teraz nachádza v controlleri


use Symfony\Component\Validator\Constraints as Assert;

$defaultData = [
    'name'   => 'Ferko',
    'email'  => 'ferko.mrkvicka@example.com',
    'gender' => 1,
];

// symfony controller
$formBuilder = $this->get('form.factory')->createBuilder('form', $defaultData)

// silex controller
$app->register(new Silex\Provider\ValidatorServiceProvider());
$formBuilder = $app['form.factory']->createBuilder('form', $defaultData)

$formBuilder
    ->add('name', 'text', [
        'constraints' => [
            new Assert\NotBlank(),
            new Assert\Length(['min' => 3]),
        ]
    ])
    ->add('email', 'text', [
        'constraints' => [
            new Assert\NotBlank(),
            new Assert\Email(),
        ]
    ])
    ->add('gender', 'choice', [
        'choices' => [
            'male'   => 1,
            'female' => 2,
        ],
        'constraints' => [
            new Assert\Choice([1, 2]),
        ]
    ])
    ->add('submit', 'submit');

$form = $formBuilder->getForm();

$form->handleRequest($request);

if ($form->isValid()) {
    $data = $form->getData();

    // spracovať dáta
    // nastaviť flash message
    // urobiť redirect
}
                    

Zoštíhľujeme controller

Vyhodíme submit button

  • submit button nebudeme vôbec vo formulári uvádzať
  • vykreslíme ho (ručne) v html šablóne
  • neskôr nám to umožní kombinovať viaceré formuláre

Pre formulár vytvoríme samostatnú triedu, tzv. FormType

  • presunieme tam celú logiku vytvárania formuláru (tj. aké fieldy obsahuje)
  • budeme tak mať formulár znovupoužiteľný (napr. vo viacerých controlleroch)
  • uložíme do adresáru Form/Type/

namespace Profesia\UserBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints as Assert;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', 'text', [
                'constraints' => [
                    new Assert\NotBlank(),
                    new Assert\Length(['min' => 3]),
                ]
            ])
            ->add('email', 'text', [
                'constraints' => [
                    new Assert\NotBlank(),
                    new Assert\Email(),
                ]
            ])
            ->add('gender', 'choice', [
                'choices' => [
                    'male'   => 1,
                    'female' => 2,
                ],
                'constraints' => [
                    new Assert\Choice([1, 2]),
                ]
            ]);
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults([
        ]);
    }

    public function getName()
    {
        return 'UserType';
    }
}
                    

Upravíme vytváranie formu v controlleri


use Profesia\UserBundle\Form\Type\UserType;

// symfony controller
$form = $this->get('form.factory')->create(new UserType(), $defaultData)

// silex controller
$form = $app['form.factory']->create(new UserType(), $defaultData)

$form->handleRequest($request);

if ($form->isValid()) {
    $data = $form->getData();

    // spracovať dáta
    // nastaviť flash message
    // urobiť redirect
}
                    

Spracovanie formuláru presunieme do samostatnej triedy

  • vytvoríme triedu, do ktorej presunieme kontrolu a spracovanie odoslaného formuláru
  • spracovaním rozumieme len prípadné uloženie do db a odpálenie eventov
  • presmerovanie by malo byť zodpovednosťou controlleru
  • flash message sú taktiež súčasťou controlleru
  • FormHandler bude k svojej úlohe potrebovať nejaké závislosti (prístup k db, event dispatcher), takže ho zaregistrujeme do DI containeru
  • uložíme do adresáru Form/Handler

use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;

class UserHandler
{
    public function handle(FormInterface $form, Request $request)
    {
        $form->handleRequest($request);

        if (!$form->isSubmitted()) {
            return false;
        }

        if (!$form->isValid()) {
            return false;
        }

        $data = $form->getData();

        // uložíme do db
        // odpálime prípadné eventy cez event dispatcher

        return true;
    }
}
                    

Závislosti FormHandleru

Pokiaľ má FormHandler nejaké závislosti (db, repozitár, event dispatcher, domain manager) ,tak ich odovzdáme cez jeho konštruktor (dependency injection).

Nie je dobré konštruovať formHandler priamo v controlleri, pretože jeho závislosti sa môžu meniť a zťažuje to možnosť testovať controller.

Takže si na FormHandler buď vytvoríme továrničku (factory) alebo ho zaregistrujeme do DI kontajneru.

Registrácia FormHandleru do DI kontajneru v Silexe


$app['form_handler.user'] = $app->share(function ($app) {
    return new UserHandler(
        //prípadné závislosti sa injectujú ako parametre, napr.:
        /*
        $app['doctrine.orm.entity_manager'],
        $app['event.dispatcher']
        */
    );
});
                    

Controller po vytiahnutí form handleru

Symfony

use Profesia\UserBundle\Form\Type\UserType;

$form        = $this->get('form.factory')->create(new UserType(), $defaultData)
$formHandler = $this->get('form_handler.user');

if ($formHandler->handle($form, $request)) {
    // nastaviť flash message
    // urobiť redirect
}
                    
Silex

use Profesia\UserModule\Form\Type\UserType;

$form        = $app['form.factory']->create(new UserType(), $defaultData)
$formHandler = $app['form_handler.user'];

if ($formHandler->handle($form, $request)) {
    // nastaviť flash message
    // urobiť redirect
}
                    

Čo sme týmito úpravami získali?

  • ztenčili sme controller, ktorý teraz robí minimum práce
  • formulár a formHandler sú ľahko testovateľné cez unit testy
  • formulár a jeho formHandler vieme použiť na veľa miestach
  • zmena formulárového prvku sa robí na jednom mieste
  • formulár môže existovať ako subformulár vo väčšom formulári
  • rôzne spracovanie toho istého formuláru vieme riešiť zámenou FormHandleru

Dá sa ísť aj ďalej?

  • formulár vieme zaregistrovať ako service do DI kontajneru
  • interface pre FormHandler
  • nová trieda združujúca formulár a jeho formHandler
  • ako dátový typ formuláru použiť triedu namiesto poľa
  • vytiahnuť validácie z formuláru do validovaného objektu cez anotácie

Viac informácií

Otázky?

Ďakujem za pozornosť