Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Everything you always wanted to know about forms* *but were afraid to ask

Everything you always wanted to know about forms* *but were afraid to ask

La componente dei Form di Symfony2 rende possibile la costruzione di diverse tipologie di form in modo del tutto semplice. La sua architettura flessibile e altamente scalabile permette di poter gestire strutture adatte ad ogni tipo di esigenza. Tuttavia, conoscere come utilizzare appieno tutta la sua potenza non è banale. In questo talk verrà trattato in profondità la componente Form di Symfony2, mostrando i suoi meccanismi di base e come utilizzarli per estenderli ed introdurre la propria logica di business, così da costruire form cuciti a misura delle tue necessità.

Andrea Giuliano

October 19, 2013
Tweet

More Decks by Andrea Giuliano

Other Decks in Programming

Transcript

  1. SYMFONYDAY 2013 Everything you always wanted to know about forms*

    *but were afraid to ask @bit_shark Andrea Giuliano
  2. Andrea Giuliano @bit_shark … collection of nodes each of which

    has an associated value and a list of children connected to their parents by means of an edge Tree: Abstract data Type
  3. Andrea Giuliano @bit_shark Let’s create it with Symfony namespace MyApp\MyBundle\Form;!

    ! use Symfony\Component\Form\AbstractType;! use Symfony\Component\Form\FormBuilderInterface;! use Symfony\Component\OptionsResolver\OptionsResolverInterface;! ! class MeetingType extends AbstractType! {! public function buildForm(FormBuilderInterface $builder, array $option)! {! $builder->add('name', 'string');! $builder->add('when', 'date');! $builder->add('featured', 'checkbox');! }! ! public function getName()! {! return 'meeting';! }! }
  4. Andrea Giuliano @bit_shark $builder->add('when', 'date') Meeting form Name string Date

    date Month choice Day choice Year choice Symfony’s point of view
  5. Andrea Giuliano @bit_shark Meeting form Name string Date date Month

    choice Day choice Year choice Symfony’s point of view $builder->add('featured', 'checkbox') Featured checkbox
  6. Andrea Giuliano @bit_shark class Form implements \IteratorAggregate, FormInterface! {! [...]!

    ! /**! * The form data in model format! */! private $modelData;! ! /**! * The form data in normalized format! */! private $normData;! ! /**! * The form data in view format! */! private $viewData;! } Data format
  7. Andrea Giuliano @bit_shark How the information is represented in the

    application model Data format Model Data Norm Data View Data
  8. Andrea Giuliano @bit_shark Data format Model Data Norm Data View

    Data How the information is represented in the view domain
  9. Andrea Giuliano @bit_shark $builder->add('when', 'date') widget View Data choice array

    single_text string input Model Data string string datetime DateTime array array timestamp integer Meeting form Date date Model Data and View Data
  10. Andrea Giuliano @bit_shark widget View Data choice array single_text string

    input Model Data string string datetime DateTime array array timestamp integer Meeting form Date date Model Data and View Data What $form->getData() will return?
  11. Andrea Giuliano @bit_shark Which format would you like to play

    with in your application logic? $form->getNormData() Normalized Data
  12. Andrea Giuliano @bit_shark Data transformers class BooleanToStringTransformer implements DataTransformerInterface! {!

    private $trueValue;! ! public function __construct($trueValue)! {! $this->trueValue = $trueValue;! }! ! public function transform($value)! {! if (null === $value) {! return null;! }! ! if (!is_bool($value)) {! throw new TransformationFailedException('Expected a Boolean.');! }! ! return $value ? $this->trueValue : null;! }! ! public function reverseTransform($value)! {! if (null === $value) {! return false;! }! ! if (!is_string($value)) {! throw new TransformationFailedException('Expected a string.');! }! ! return true;! }! }
  13. Andrea Giuliano @bit_shark Model Data Norm Data View Data Model

    Data Norm Data View Data transform() transform() reverseTransform() reverseTransform() Model Transformer View Transformer Data transformers
  14. Andrea Giuliano @bit_shark Data transformers class MeetingType extends AbstractType! {!

    public function buildForm(FormBuilderInterface $builder, array $option)! {! $transformer = new MyDataTransformer();! ! $builder->add('name', 'string');! $builder->add('when', 'date')->addModelTransformer($transformer);! $builder->add('featured', 'checkbox');! }! ! public function getName()! {! return 'meeting';! }! }! Add a ModelTransformer or a ViewTransformer
  15. Andrea Giuliano @bit_shark Data transformers class MeetingType extends AbstractType! {!

    public function buildForm(FormBuilderInterface $builder, array $option)! {! $transformer = new MyDataTransformer(/*AnAwesomeDependence*/);! ! $builder->add('name', 'string');! $builder->add('when', 'date')->addModelTransformer($transformer);! $builder->add('featured', 'checkbox');! }! ! public function getName()! {! return 'meeting';! }! }! in case of dependencies?
  16. Andrea Giuliano @bit_shark Data transformers Use it by creating your

    own type <service id="dnsee.type.my_text" ! class="Dnsee\MyBundle\Form\Type\MyTextType">! <argument type="service" id="dnsee.my_awesome_manager"/>! <tag name="form.type" alias="my_text" />! </service>
  17. Andrea Giuliano @bit_shark Data transformers Use it by creating your

    own type class MyTextType extends AbstractType! {! private $myManager;! ! public function __construct(MyManager $manager)! {! $this->myManager = $manager;! }! ! public function buildForm(FormBuilderInterface $builder, array $options)! {! $transformer = new MyTransformer($this->manager);! $builder->addModelTransformer($transformer);! }! ! public function getParent()! {! return 'text';! }! ! public function getName()! {! return 'my_text';! }! }!
  18. Andrea Giuliano @bit_shark Data transformers class MeetingType extends AbstractType! {!

    public function buildForm(FormBuilderInterface $builder, array $option)! {! $builder->add('name', 'my_text');! $builder->add('when', 'date');! $builder->add('featured', 'checkbox');! }! ! public function getName()! {! return 'meeting';! }! }! use my_text as a standard type
  19. Andrea Giuliano @bit_shark class FacebookSubscriber implements EventSubscriberInterface! {! public static

    function getSubscribedEvents()! {! return array(FormEvents::PRE_SET_DATA => 'preSetData');! }! ! public function preSetData(FormEvent $event)! {! $data = $event->getData();! $form = $event->getForm();! ! if (null === $data) {! return;! }! ! if (!$data->getFacebookId()) {! $form->add('username');! $form->add('password');! }! }! } Events
  20. Andrea Giuliano @bit_shark class RegistrationType extends AbstractType! {! public function

    buildForm(FormBuilderInterface $builder, array $options)! {! $builder->add('name');! $builder->add('surname');! ! $builder->addEventSubscriber(new FacebookSubscriber());! }! ! public function getName()! {! return 'registration';! }! ! ...! ! } Events add it to your type
  21. Andrea Giuliano @bit_shark class RegistrationForm extends AbstractType! {! public function

    buildForm(FormBuilderInterface $builder, array $options)! {! $builder->add('name');! $builder->add('surname');! ! $builder->get('surname')->addEventListener(! FormEvents::BIND,! function(FormEvent $event){! $event->setData(ucwords($event->getData()))! }! );! }! ! public function getName()! {! return 'registration';! }! } Events
  22. Andrea Giuliano @bit_shark POST_BIND Events Model Norm View meeting [Form]

    child $form->handleRequest($request); PRE_BIND BIND
  23. Andrea Giuliano @bit_shark namespace Acme\TestBundle\Tests\Form\Type;! ! use Dnsee\EventBundle\Form\Type\EventType;! use Dnsee\EventBundle\Model\EventObject;!

    use Symfony\Component\Form\Test\TypeTestCase;! ! class MeetingTypeTest extends TypeTestCase! {! public function testSubmitValidData()! {! $formData = array(! 'name' => 'SymfonyDay',! 'date' => '2013-10-18',! 'featured' => true,! );! ! $type = new TestedType();! $form = $this->factory->create($type);! ! $object = new TestObject();! $object->fromArray($formData);! ! // submit the data to the form directly! $form->submit($formData);! ! $this->assertTrue($form->isSynchronized());! $this->assertEquals($object, $form->getData());! ! $view = $form->createView();! $children = $view->children;! ! foreach (array_keys($formData) as $key) {! $this->assertArrayHasKey($key, $children);! }! }! } Test
  24. Andrea Giuliano @bit_shark namespace Acme\TestBundle\Tests\Form\Type;! ! use Dnsee\EventBundle\Form\Type\EventType;! use Dnsee\EventBundle\Model\Event;!

    use Symfony\Component\Form\Test\TypeTestCase;! ! class MeetingTypeTest extends TypeTestCase! {! public function testSubmitValidData()! {! $formData = array(! 'name' => 'SymfonyDay',! 'when' => '2013-10-18',! 'featured' => true,! );! ! $type = new EventType();! $form = $this->factory->create($type);! ! $event = new Event();! $event->fromArray($formData);! ! [...]! Test decide the data to be submitted
  25. Andrea Giuliano @bit_shark namespace Acme\TestBundle\Tests\Form\Type;! ! use Dnsee\EventBundle\Form\Type\EventType;! use Dnsee\EventBundle\Model\EventObject;!

    use Symfony\Component\Form\Test\TypeTestCase;! ! class MeetingTypeTest extends TypeTestCase! {! public function testSubmitValidData()! {! ! [...]! ! $form->submit($formData);! ! $this->assertTrue($form->isSynchronized());! $this->assertEquals($object, $form->getData());! ! [...]! ! Test test data transformers
  26. Andrea Giuliano @bit_shark namespace Acme\TestBundle\Tests\Form\Type;! ! use Acme\TestBundle\Form\Type\TestedType;! use Acme\TestBundle\Model\TestObject;!

    use Symfony\Component\Form\Test\TypeTestCase;! ! class MeetingTypeTest extends TypeTestCase! {! public function testSubmitValidData()! {! ! [...]! ! $view = $form->createView();! $children = $view->children;! ! foreach (array_keys($formData) as $key) {! $this->assertArrayHasKey($key, $children);! }! }! }! Test test form view creation
  27. Andrea Giuliano @bit_shark namespace Acme\TestBundle\Tests\Form\Type;! ! use Dnsee\EventBundle\Form\Type\EventType;! use Dnsee\EventBundle\Model\EventObject;!

    use Symfony\Component\Form\Test\TypeTestCase;! ! class MeetingTypeTest extends TypeTestCase! {! protected function getExtensions()! {! $myCustomType = new MyCustomType();! return array(new PreloadedExtension(array(! $myCustomType->getName() => $customType,! ), array()));! }! ! public function testSubmitValidData()! {! [...]! }! } Test
  28. Andrea Giuliano @bit_shark namespace Acme\TestBundle\Tests\Form\Type;! ! use Dnsee\EventBundle\Form\Type\EventType;! use Dnsee\EventBundle\Model\EventObject;!

    use Symfony\Component\Form\Test\TypeTestCase;! ! class MeetingTypeTest extends TypeTestCase! {! protected function getExtensions()! {! $myCustomType = new MyCustomType();! return array(new PreloadedExtension(array(! $myCustomType->getName() => $customType,! ), array()));! }! ! public function testSubmitValidData()! {! [...]! }! } Test Test your custom type FIRST
  29. ?