everything you always wanted to know about forms* *but were afraid to ask
DESCRIPTION
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à.TRANSCRIPT
SYMFONYDAY 2013
Everything you always wanted to know about forms*
*but were afraid to ask
@bit_sharkAndrea Giuliano
TreeAbstract Data Structure
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
Symfony Forms are trees
Form
Form Form Form
Form
Andrea Giuliano @bit_shark
Example: a meeting form
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
Meeting
form
Symfony’s point of view
Andrea Giuliano @bit_shark
$builder->add('name', 'string')
Meeting
form
Namestring
Symfony’s point of view
Andrea Giuliano @bit_shark
$builder->add('when', 'date')
Meeting
form
Namestring
Datedate
Month
choice
Day
choice
Year
choice
Symfony’s point of view
Andrea Giuliano @bit_shark
Meeting
form
Namestring
Datedate
Month
choice
Day
choice
Year
choice
Symfony’s point of view
$builder->add('featured', 'checkbox')
Featuredcheckbox
Data format
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
Data format
Model Data
Norm Data
View Data
???
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
Datedate
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
Datedate
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
Data Transformers
Andrea Giuliano @bit_shark
Data transformersclass 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
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
Events
Andrea Giuliano @bit_shark
To change data information use form events
Events
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
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!{! 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
?
References
https://speakerdeck.com/webmozart/symfony2-form-tricks http://www.flickr.com/photos/yahya/132963781/ http://www.flickr.com/photos/lutherankorean/2694858251/ http://www.flickr.com/photos/lauroroger/8808985531/ http://www.flickr.com/photos/gifake/4643253235/ http://www.flickr.com/photos/zorin-denu/5222189908/ http://www.flickr.com/photos/aigle_dore/10014783623/ http://www.flickr.com/photos/skosoris/4985591296/ http://www.flickr.com/photos/sharynmorrow/248647126/