You are on page 1of 45

Everything you always wanted

to know about forms*


*but were afraid to ask

Andrea Giuliano
@bit_shark

SYMFONYDAY 2013
Tree
Abstract Data Structure
Tree: Abstract data Type

collection of nodes each of which has an associated


value and a list of children connected to their parents by
means of an edge
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


Lets 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


Symfonys point of view

Meeting
form

Andrea Giuliano @bit_shark


Symfonys point of view

Meeting
form

Name
string

$builder->add('name', 'string')

Andrea Giuliano @bit_shark


Symfonys point of view

Meeting
form

$builder->add('when', 'date')

Name Date
string date

Month Day Year


choice choice choice

Andrea Giuliano @bit_shark


Symfonys point of view

$builder->add('featured', 'checkbox')

Meeting
form

Name Date Featured


string date checkbox

Month Day Year


choice choice choice

Andrea Giuliano @bit_shark


Data format
Data format
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;!
}

Andrea Giuliano @bit_shark


Data format

Model Norm View


Data Data Data

How the information



is represented in the application model

Andrea Giuliano @bit_shark


Data format

Model Norm View


Data Data Data

How the information



is represented in the view domain

Andrea Giuliano @bit_shark


Data format

Model Norm View


Data Data Data

???

Andrea Giuliano @bit_shark


Model Data and View Data

Meeting
widget View Data input Model Data
form

choice array string string


Date single_text string datetime DateTime
date

array array

timestamp integer
$builder->add('when', 'date')

Andrea Giuliano @bit_shark


Model Data and View Data

Meeting
widget View Data input Model Data
form

choice array string string


Date single_text string datetime DateTime
date

array array

timestamp integer

What $form->getData() will return?


Andrea Giuliano @bit_shark
Normalized Data

Which format would you like to play with


in your application logic?

$form->getNormData()

Andrea Giuliano @bit_shark


Data Transformers
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


Data transformers

transform() transform()

Model Norm View


Data Data Data
Model Transformer View Transformer

reverseTransform() reverseTransform()

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

<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>

Use it by creating your own type


Andrea Giuliano @bit_shark
Data transformers
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';!
}!
}!

Use it by creating your own type


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

Dont use them to change


data information

Andrea Giuliano @bit_shark


Events
Events

To change data information use


form events

Andrea Giuliano @bit_shark


Events
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');!
}!
}!
}

Andrea Giuliano @bit_shark


Events

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';! add it to your type
}!
!
...!
!
}

Andrea Giuliano @bit_shark


Events
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';!
}!
}

Andrea Giuliano @bit_shark


Events

$form->setData($symfonyDay);

PRE_SET_DATA

meeting [Form]

Model Norm View POST_SET_DATA

child

Andrea Giuliano @bit_shark


Events
$form->handleRequest($request);

POST_BIND
PRE_BIND

child

meeting [Form]

BIND

Model Norm View

Andrea Giuliano @bit_shark


Test
Test
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);!
}!
}!
}

Andrea Giuliano @bit_shark


Test
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()!
{! decide the data
$formData = array(! to be submitted
'name' => 'SymfonyDay',!
'when' => '2013-10-18',!
'featured' => true,!
);!
!
$type = new EventType();!
$form = $this->factory->create($type);!
!
$event = new Event();!
$event->fromArray($formData);!
!
[...]!

Andrea Giuliano @bit_shark


Test
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()!
{!
! test data transformers
[...]!
!
$form->submit($formData);!
!
$this->assertTrue($form->isSynchronized());!
$this->assertEquals($object, $form->getData());!
!
[...]!
!

Andrea Giuliano @bit_shark


Test

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()!
{!
!
[...]!
test form view creation
!
$view = $form->createView();!
$children = $view->children;!
!
foreach (array_keys($formData) as $key) {!
$this->assertArrayHasKey($key, $children);!
}!
}!
}!

Andrea Giuliano @bit_shark


Test
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()!
{!
[...]!
}!
}

Andrea Giuliano @bit_shark


Test
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 your custom type
[...]! FIRST
}!
}

Andrea Giuliano @bit_shark


?
http://joind.in/9784

Andrea Giuliano
@bit_shark
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/

You might also like