leveraging symfony2 forms
DESCRIPTION
This session will introduce you to the new Form component in Symfony2. With the new domain-driven paradigma and its flexible design, the component opens a door to a wide range of possibilities. The brand new architecture makes creating complex forms easier and faster than ever before. This talk will teach you today what you need to know to build powerful forms tomorrow.TRANSCRIPT
Bernhard Schussek
Leveraging Symfony2 Forms
Symfony Live Conference, March 03rd 2011
About myself
Software Architect in Vienna
Student of Software Engineering
Symfony since 2006
Outdoor and music junkie
Agenda
Introductory example
The Form Config class
Form processing
Fields
Useful features
The Symfony2 Form component
The Symfony2 Form component
Evolution of symfony1 sfForm
2.5 years development
Fixes most of its problems
Reusable widgets
Embedded forms
Why 2.5 years?
"It takes a long time to make somthing complicated simple, but if you do, it will work without problems for a long time."
– F. Andy Seidl, http://faseidl.com
Service Oriented Architecture
Applications provide services
Services are exchangable
These services can be consumed by different actors
Humans
Machines
Forms don't contain business logic
Services do
Example: Online sausage shop
Example: Online sausage shop
You
Request
Response
Example: Online sausage shop
Request
Response
YouConsumer Service
Example: Online sausage shop
Request
Response
Consumer Service
Example: Online sausage shop
Request
Response
Consumer Service
Service implementation
class Order{ function setName($name);
function setAddress($address);
function setSausage($sausage);
function setAmount($amount);}
Flow of information
I am Max!
Flow of information
I am Max!
$order->setName('Max')
Flow of information
Sensio Labs,Paris
Address:
MaxName:
BratwurstSausage:
5Amount:
Order form
Flow of information
Sensio Labs,Paris
Address:
MaxName:
BratwurstSausage:
5Amount:
$order->setName('Max')
->setAddress( 'Sensio Labs, Paris')
->setSausage( 'Bratwurst')->setAmount(5);
Order form
Flow of information
$order->getName()
->getAddress()
->getSausage()
->getAmount();
Liip OfficeZurich
Address:
BobName:
CervelatSausage:
10Amount:
Existing order
The Form Config class
class OrderFormConfig extends AbstractConfig{ public function configure( FieldInterface $form, array $options) {
}}
PHP class
Form definition
class OrderFormConfig extends AbstractConfig{ public function configure( FieldInterface $form, array $options) { $form->add('text', 'name') ->add('text', 'address') ->add('text', 'sausage') ->add('integer', 'amount'); }}
PHP class
Form definition
class OrderFormConfig extends AbstractConfig{
...
public function getIdentifier() { return 'form.order'; }}
PHP class
Form definition
Why not a simple OrderForm class?
Why we need Config classes
OrderForm cannot be put into the Dependency Injection Container ...
... but OrderFormConfig can!
Dependency Injection Container
<service id="form.order" class="OrderFormConfig"> <tag name="form.config" alias="form.order" /> <argument type="service" id="form.factory" /></service>
public function getIdentifier(){ return 'form.order';}
Tag alias == form identifier
Form processing
Form processing
$factory = $this->get('form.factory');$form = $factory->getInstance('form.order');
$form->setData($order);
if ($request->getMethod() === 'POST') { $form->bindRequest($request);
if ($form->isValid()) { $order->send(); return new RedirectResponse(...); }}
Form identifier
Form processing
$factory = $this->get('form.factory');$form = $factory->getInstance('form.order');
$form->setData($order);
if ($request->getMethod() === 'POST') { $form->bindRequest($request);
if ($form->isValid()) { $order->send(); return new RedirectResponse(...); }}
Service object
Form processing
$factory = $this->get('form.factory');$form = $factory->getInstance('form.order');
$form->setData($order);
if ($request->getMethod() === 'POST') { $form->bindRequest($request);
if ($form->isValid()) { $order->send(); return new RedirectResponse(...); }}
Calls setters
Form processing
$factory = $this->get('form.factory');$form = $factory->getInstance('form.order');
$form->setData($order);
if ($request->getMethod() === 'POST') { $form->bindRequest($request);
if ($form->isValid()) { $order->send(); return new RedirectResponse(...); }} $order now contains submitted data!
Form rendering
In the action
return $this->render( 'HelloBundle:Hello:index.twig.html', array('form' => $form->getRenderer()));
Contains view variables and methods
Form rendering
In the template
<form action="#" method="post"> {{ form.widget }}</form>
"widget" is a view method
Form rendering
<form action="#" method="post"> {{ form.errors }} {% for field in form.vars.fields %} {{ field.errors }} {{ field.label }} {{ field.widget }} {% endfor %} {{ form.rest }}</form>
"fields" is a view variable
Form rendering
<form action="#" method="post"> {{ form.errors }} {{ form.name.errors }} {{ form.name.label }} {{ form.name.widget }} {{ form.rest }}</form>
Form rendering
Renders all fields that weren't rendered before
... fields that you forgot to render manually
... hidden fields
{{ form.rest }}
What about validation?
Form validation
uses the Symfony2 Validator
"Constraints" are put onto your PHP classes (services, entities etc.)
Validation constraints
class Order{ /** * @check:NotNull * @check:AssertType("string") * @check:MaxLength(50, message= * "Long name, dude...") */ private $name;}
Fields
Text input
$form->add('text', 'title');
Title:
Textarea
$form->add('textarea', 'content');
Content:
Date selector
$form->add('date', 'publishAt');
Publish at:
Mmmh localized!
Country selector
$form->add('country', 'nationality');
Nationality:
Localized too! Yummy!Localized too! Yummy!
File upload
$form->add('file', 'profilePicture');
Profile picture:
Remembers uploaded files on errors!
Repeated input
$form->add('repeated', 'email');
Email:
Email (again):
Core fieldsbirthday
checkbox
choice
collection
country
date
datetime
entity
file
hidden
integer
language
locale
money
number
password
percent
repeated
textarea
text
timezone
url
Field architecture
Filters
Value transformers
Filters
modify a value
unidirectional
Example: FixUrlProtocolFilter
symfony-project.com http://symfony-project.com
Value Transformers
convert values between two representations
bidirectional
Example: DateTimeToArrayTransformer
array( 'year' => 2011, 'month' => 5, 'day' => 1,)
object(DateTime)
Example: Entity fieldThe user sees:
Entity field
Checkbox"0"
Checkbox"1"
Checkbox"2"
Checkbox"3"
Symfony sees:
Tag IDs
Checkbox"0"
Checkbox"1"
Checkbox"2"
Checkbox"3"
array("0" => "0", "1" => "1", "2" => "1", ...)
"0" "1" "0" "0"
Tag IDs
Checkbox"0"
Checkbox"1"
Checkbox"2"
Checkbox"3"
array("0" => "0", "1" => "1", "2" => "1", ...)
"0" "1" "0" "0"
Tag IDs
Checkbox"0"
Checkbox"1"
Checkbox"2"
Checkbox"3"
false true true false
array("0" => false, "1" => true, "2" => true, ...)
ArrayCollection($securityTag, $validatorTag)
Checkbox"0"
Checkbox"1"
Checkbox"2"
Checkbox"3"
false true true false
array("0" => false, "1" => true, "2" => true, ...)
ArrayCollection($securityTag, $validatorTag)
ChoicesToArrayTransformer
Checkbox"0"
Checkbox"1"
Checkbox"2"
Checkbox"3"
false true true false
array("0" => false, "1" => true, "2" => true, ...)
ArrayCollection($securityTag, $validatorTag)
ArrayToEntitiesTransformer
CSRF protection
CrossSite Request Forgery
A form is submitted using the session of another person
All kinds of misuse
Builtin protection in Symfony2
CSRF protection
app/config/config.yml
framework: csrf_protection: enabled: true secret: 30665e19ef0010d5620553
Field creation
Manual
Automatic
Symfony2 looks at metadata of the domain class to "guess" the correct field type and settings
E.g. Validator metadata, Doctrine2 metadata
Manual field creation
public function configure( FieldInterface $form, array $options){ $form->add('entity', 'sausage', array( 'class' => 'Sausage', ));}
but Doctrine already knows, that "sausage" is a ToOne relationship to the Sausage class!
Automatic field creation
public function configure( FieldInterface $form, array $options){ $form->setDataClass('Order');
$form->add('sausage');}
Automatic with options overriding
public function configure( FieldInterface $form, array $options){ $form->setDataClass('Order') ->add('sausage', array( 'required' => false, ));}
Embedded forms
Symfony2 allows to embed forms into another very easily
Fields and forms implement FieldInterface
"A form is a field"
Embedded toone forms
public function configure( FieldInterface $form, array $options){ $form->add('form.sausage', 'sausage');}
Identifier of SausageFormConfig
Embedded tomany forms
public function configure( FieldInterface $form, array $options){ $form->add('collection', 'sausages', array( 'identifier' => 'form.sausage' ));}
Identifier of SausageFormConfig
Config options
class ConcealedFieldConfig extends Abstract..{ public function getDefaultOptions($options) { return array( 'concealed' => true, ); }}
Options for influencing the field's/form's creation
Config inheritance
class ConcealedFieldConfig extends Abstract..{ public function getParent(array $options) { return $options['concealed'] ? 'password' : 'text'; }}
Dynamic inheritance from other forms/fields
Form themes
Are normal Twig templates
Blocks for each field type
{% block textarea__widget %} <textarea {{ block('attributes') }}> {{ value }} </textarea>{% endblock textarea__widget %}
Form themes
Can specify widget, errors, label and row templates for specific field types
{% block textarea__row %} <tr><td colspan="2"> {{ this.errors }} {{ this.widget }} </td></tr>{% endblock textarea__row %}
Form themes
Core themes:
TwigBundle::div_layout.html.twig
TwigBundle::table_layout.html.twig
Configuration in the DI parameter "form.theme.template"
More flexible configuration options coming soon
Questions?
Thanks for listening!
Code can currently be found onhttps://github.com/bschussek/symfony/tree/experimental
Bernhard SchussekTwitter: @webmozart
The End
Copyright
"dog window" by Larry Wentzelhttp://www.flickr.com/photos/wentzelepsy
"Symfony Live 2011 Logo" by Sensio Labshttp://www.sensiolabs.com