Leveraging Symfony2 Forms

Post on 10-May-2015

40,014 views 0 download

Tags:

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

uni­directional

Example: FixUrlProtocolFilter

symfony-project.com http://symfony-project.com

Value Transformers

convert values between two representations

bi­directional

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

Cross­Site Request Forgery

A form is submitted using the session of another person

All kinds of misuse

Built­in 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 To­One 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 to­one forms

public function configure( FieldInterface $form, array $options){ $form->add('form.sausage', 'sausage');}

Identifier of SausageFormConfig

Embedded to­many 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