Advertisement
Advertisement

More Related Content

Advertisement
Advertisement

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

  1. Everything you always wanted to know about forms* *but were afraid to ask Andrea Giuliano @bit_shark SYMFONYDAY 2013
  2. Tree Abstract Data Structure
  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
  5. Example: a meeting form Andrea Giuliano @bit_shark
  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
  7. Symfony’s point of view Andrea Giuliano @bit_shark Meeting form
  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
  11. Data format
  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()
  19. Data Transformers
  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?
  24. Andrea Giuliano @bit_shark
  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
  29. Events
  30. Andrea Giuliano @bit_shark To change data information use form events Events
  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';! }! }
  34. Events $form->setData($symfonyDay); PRE_SET_DATA Model Norm View Andrea Giuliano @bit_shark meeting [Form] POST_SET_DATA child
  35. Events POST_BIND $form->handleRequest($request); Model Norm View Andrea Giuliano @bit_shark meeting [Form] child PRE_BIND BIND
  36. Test
  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
  43. ?
  44. http://joind.in/9784 Andrea Giuliano @bit_shark
  45. 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/
Advertisement