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à.
3. Tree: Abstract data Type
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
4. Symfony Forms are trees
Andrea Giuliano @bit_shark
Form
Form Form Form
Form
6. Let’s create it with Symfony
namespace MyAppMyBundleForm;!
!
use SymfonyComponentFormAbstractType;!
use SymfonyComponentFormFormBuilderInterface;!
use SymfonyComponentOptionsResolverOptionsResolverInterface;!
!
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
8. Symfony’s point of view
Andrea Giuliano @bit_shark
Meeting
form
$builder->add('name', 'string')
Name
string
9. 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
10. 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
12. 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
13. Data format
Model
Data
Andrea Giuliano @bit_shark
Norm
Data
How the information
View
Data
is represented in the application model
14. Data format
Model
Data
Andrea Giuliano @bit_shark
Norm
Data
View
Data
How the information
is represented in the view domain
15. Data format
Model
Data
Andrea Giuliano @bit_shark
Norm
Data
View
Data
???
16. Model Data and View Data
$builder->add('when', 'date')
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
17. 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
What $form->getData() will return?
18. Normalized Data
Which format would you like to play with
Andrea Giuliano @bit_shark
in your application logic?
$form->getNormData()
20. 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
21. Data transformers
Model
Data
Andrea Giuliano @bit_shark
Norm
Data
View
Data
transform() transform()
Model Transformer View Transformer
reverseTransform() reverseTransform()
22. 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
23. 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';!
}!
}!
Andrea Giuliano @bit_shark
in case of dependencies?
25. Data transformers
<service id="dnsee.type.my_text" !
class="DnseeMyBundleFormTypeMyTextType">!
<argument type="service" id="dnsee.my_awesome_manager"/>!
<tag name="form.type" alias="my_text" />!
Andrea Giuliano @bit_shark
Use it by creating your own type
</service>
26. Data transformers
class MyTextType extends AbstractType!
{!
private $myManager;!
Andrea Giuliano @bit_shark
Use it by creating your own type
!
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';!
}!
}!
27. 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';!
}!
}!
Andrea Giuliano @bit_shark
use my_text as a standard type
28. Andrea Giuliano @bit_shark
Remember
Data Transformers transforms
data representation
Don’t use them to change
data information
31. 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) {!
Andrea Giuliano @bit_shark
return;!
}!
!
if (!$data->getFacebookId()) {!
$form->add('username');!
$form->add('password');!
}!
}!
}
32. 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';!
}!
!
...!
!
}
Andrea Giuliano @bit_shark
add it to your type
33. Events
class RegistrationForm extends AbstractType!
{!
public function buildForm(FormBuilderInterface $builder, array $options)!
{!
$builder->add('name');!
$builder->add('surname');!
!
$builder->get('surname')->addEventListener(!
Andrea Giuliano @bit_shark
FormEvents::BIND,!
function(FormEvent $event){!
$event->setData(ucwords($event->getData()))!
}!
);!
}!
!
public function getName()!
{!
return 'registration';!
}!
}
37. namespace AcmeTestBundleTestsFormType;!
!
use DnseeEventBundleFormTypeEventType;!
use DnseeEventBundleModelEventObject;!
use SymfonyComponentFormTestTypeTestCase;!
!
class MeetingTypeTest extends TypeTestCase!
{!
Andrea Giuliano @bit_shark
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
38. namespace AcmeTestBundleTestsFormType;!
!
use DnseeEventBundleFormTypeEventType;!
use DnseeEventBundleModelEvent;!
use SymfonyComponentFormTestTypeTestCase;!
!
class MeetingTypeTest extends TypeTestCase!
{!
public function testSubmitValidData()!
{!
$formData = array(!
Andrea Giuliano @bit_shark
'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
39. Test
namespace AcmeTestBundleTestsFormType;!
!
use DnseeEventBundleFormTypeEventType;!
use DnseeEventBundleModelEventObject;!
use SymfonyComponentFormTestTypeTestCase;!
!
class MeetingTypeTest extends TypeTestCase!
{!
public function testSubmitValidData()!
{!
!
[...]!
!
$form->submit($formData);!
!
$this->assertTrue($form->isSynchronized());!
$this->assertEquals($object, $form->getData());!
!
[...]!
!
Andrea Giuliano @bit_shark
test data transformers
40. Test
namespace AcmeTestBundleTestsFormType;!
!
use AcmeTestBundleFormTypeTestedType;!
use AcmeTestBundleModelTestObject;!
use SymfonyComponentFormTestTypeTestCase;!
!
class MeetingTypeTest extends TypeTestCase!
{!
public function testSubmitValidData()!
{!
!
[...]!
!
$view = $form->createView();!
$children = $view->children;!
!
foreach (array_keys($formData) as $key) {!
Andrea Giuliano @bit_shark
$this->assertArrayHasKey($key, $children);!
}!
}!
}!
test form view creation
41. Test
namespace AcmeTestBundleTestsFormType;!
!
use DnseeEventBundleFormTypeEventType;!
use DnseeEventBundleModelEventObject;!
use SymfonyComponentFormTestTypeTestCase;!
!
class MeetingTypeTest extends TypeTestCase!
{!
protected function getExtensions()!
{!
$myCustomType = new MyCustomType();!
return array(new PreloadedExtension(array(!
Andrea Giuliano @bit_shark
$myCustomType->getName() => $customType,!
), array()));!
}!
!
public function testSubmitValidData()!
{!
[...]!
}!
}
42. Test
namespace AcmeTestBundleTestsFormType;!
!
use DnseeEventBundleFormTypeEventType;!
use DnseeEventBundleModelEventObject;!
use SymfonyComponentFormTestTypeTestCase;!
!
class MeetingTypeTest extends TypeTestCase!
{!
protected function getExtensions()!
{!
$myCustomType = new MyCustomType();!
return array(new PreloadedExtension(array(!
Andrea Giuliano @bit_shark
$myCustomType->getName() => $customType,!
), array()));!
}!
!
public function testSubmitValidData()!
{!
[...]!
}!
}
Test your custom type
FIRST