Share
Embed
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
Transcript
45 slides
-
SYMFONYDAY 2013 Everything you always wanted to know about forms*
*but were afraid to ask @bit_shark Andrea Giuliano -
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 -
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';! }! } -
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 -
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 -
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 -
Andrea Giuliano @bit_shark How the information is represented in the
application model Data format Model Data Norm Data View Data -
Andrea Giuliano @bit_shark Data format Model Data Norm Data View
Data How the information is represented in the view domain -
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 -
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? -
Andrea Giuliano @bit_shark Which format would you like to play
with in your application logic? $form->getNormData() Normalized Data -
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;! }! } -
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 -
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 -
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? -
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> -
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';! }! }! -
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 -
Andrea Giuliano @bit_shark Remember Data Transformers transforms data representation Don’t
use them to change data information -
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 -
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 -
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 -
Andrea Giuliano @bit_shark PRE_SET_DATA Events Model Norm View $form->setData($symfonyDay); meeting
[Form] POST_SET_DATA child -
Andrea Giuliano @bit_shark POST_BIND Events Model Norm View meeting [Form]
child $form->handleRequest($request); PRE_BIND BIND -
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 -
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 -
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 -
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 -
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 -
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