SlideShare a Scribd company logo
1 of 45
Download to read offline
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 
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
Symfony Forms are trees 
Andrea Giuliano @bit_shark 
Form 
Form Form Form 
Form
Example: a meeting form 
Andrea Giuliano @bit_shark
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
Symfony’s point of view 
Andrea Giuliano @bit_shark 
Meeting 
form
Symfony’s point of view 
Andrea Giuliano @bit_shark 
Meeting 
form 
$builder->add('name', 'string') 
Name 
string
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
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
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 
Data 
Andrea Giuliano @bit_shark 
Norm 
Data 
How the information 
View 
Data 
is represented in the application model
Data format 
Model 
Data 
Andrea Giuliano @bit_shark 
Norm 
Data 
View 
Data 
How the information 
is represented in the view domain
Data format 
Model 
Data 
Andrea Giuliano @bit_shark 
Norm 
Data 
View 
Data 
???
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
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?
Normalized Data 
Which format would you like to play with 
Andrea Giuliano @bit_shark 
in your application logic? 
$form->getNormData()
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 
Model 
Data 
Andrea Giuliano @bit_shark 
Norm 
Data 
View 
Data 
transform() transform() 
Model Transformer View Transformer 
reverseTransform() reverseTransform()
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';! 
}! 
}! 
Andrea Giuliano @bit_shark 
in case of dependencies?
Andrea Giuliano @bit_shark
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>
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';! 
}! 
}!
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
Andrea Giuliano @bit_shark 
Remember 
Data Transformers transforms 
data representation 
Don’t use them to change 
data information
Events
Andrea Giuliano @bit_shark 
To change data information use 
form events 
Events
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');! 
}! 
}! 
}
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
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';! 
}! 
}
Events 
$form->setData($symfonyDay); 
PRE_SET_DATA 
Model Norm View 
Andrea Giuliano @bit_shark 
meeting [Form] 
POST_SET_DATA 
child
Events 
POST_BIND 
$form->handleRequest($request); 
Model Norm View 
Andrea Giuliano @bit_shark 
meeting [Form] 
child 
PRE_BIND 
BIND
Test
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
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
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
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
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 
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
?
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/

More Related Content

What's hot

sfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin BundlesfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin Bundleth0masr
 
Disregard Inputs, Acquire Zend_Form
Disregard Inputs, Acquire Zend_FormDisregard Inputs, Acquire Zend_Form
Disregard Inputs, Acquire Zend_FormDaniel Cousineau
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにYuya Takeyama
 
Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)Arnaud Langlade
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web servicesMichelangelo van Dam
 
Custom Signals for Uncoupled Design
Custom Signals for Uncoupled DesignCustom Signals for Uncoupled Design
Custom Signals for Uncoupled Designecomsmith
 
Web весна 2013 лекция 6
Web весна 2013 лекция 6Web весна 2013 лекция 6
Web весна 2013 лекция 6Technopark
 
Taming forms with React
Taming forms with ReactTaming forms with React
Taming forms with ReactGreeceJS
 
Form demoinplaywithmysql
Form demoinplaywithmysqlForm demoinplaywithmysql
Form demoinplaywithmysqlKnoldus Inc.
 
Aplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneAplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneRafael Felix da Silva
 
Fields in Core: How to create a custom field
Fields in Core: How to create a custom fieldFields in Core: How to create a custom field
Fields in Core: How to create a custom fieldIvan Zugec
 
Javascript & jQuery: A pragmatic introduction
Javascript & jQuery: A pragmatic introductionJavascript & jQuery: A pragmatic introduction
Javascript & jQuery: A pragmatic introductionIban Martinez
 
Web осень 2012 лекция 6
Web осень 2012 лекция 6Web осень 2012 лекция 6
Web осень 2012 лекция 6Technopark
 
Data20161007
Data20161007Data20161007
Data20161007capegmail
 
Dealing with Legacy PHP Applications
Dealing with Legacy PHP ApplicationsDealing with Legacy PHP Applications
Dealing with Legacy PHP ApplicationsClinton Dreisbach
 

What's hot (20)

D8 Form api
D8 Form apiD8 Form api
D8 Form api
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
sfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin BundlesfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin Bundle
 
Disregard Inputs, Acquire Zend_Form
Disregard Inputs, Acquire Zend_FormDisregard Inputs, Acquire Zend_Form
Disregard Inputs, Acquire Zend_Form
 
Cyclejs introduction
Cyclejs introductionCyclejs introduction
Cyclejs introduction
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
 
Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)
 
Backbone - TDC 2011 Floripa
Backbone - TDC 2011 FloripaBackbone - TDC 2011 Floripa
Backbone - TDC 2011 Floripa
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 
Custom Signals for Uncoupled Design
Custom Signals for Uncoupled DesignCustom Signals for Uncoupled Design
Custom Signals for Uncoupled Design
 
Ruby - Design patterns tdc2011
Ruby - Design patterns tdc2011Ruby - Design patterns tdc2011
Ruby - Design patterns tdc2011
 
Web весна 2013 лекция 6
Web весна 2013 лекция 6Web весна 2013 лекция 6
Web весна 2013 лекция 6
 
Taming forms with React
Taming forms with ReactTaming forms with React
Taming forms with React
 
Form demoinplaywithmysql
Form demoinplaywithmysqlForm demoinplaywithmysql
Form demoinplaywithmysql
 
Aplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneAplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com Backbone
 
Fields in Core: How to create a custom field
Fields in Core: How to create a custom fieldFields in Core: How to create a custom field
Fields in Core: How to create a custom field
 
Javascript & jQuery: A pragmatic introduction
Javascript & jQuery: A pragmatic introductionJavascript & jQuery: A pragmatic introduction
Javascript & jQuery: A pragmatic introduction
 
Web осень 2012 лекция 6
Web осень 2012 лекция 6Web осень 2012 лекция 6
Web осень 2012 лекция 6
 
Data20161007
Data20161007Data20161007
Data20161007
 
Dealing with Legacy PHP Applications
Dealing with Legacy PHP ApplicationsDealing with Legacy PHP Applications
Dealing with Legacy PHP Applications
 

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

Mikhail Kraynuk. Form api. Drupal 8
Mikhail Kraynuk. Form api. Drupal 8Mikhail Kraynuk. Form api. Drupal 8
Mikhail Kraynuk. Form api. Drupal 8i20 Group
 
Mikhail Kraynuk. Form api. Drupal 8
Mikhail Kraynuk. Form api. Drupal 8Mikhail Kraynuk. Form api. Drupal 8
Mikhail Kraynuk. Form api. Drupal 8DrupalSib
 
DrupalJam 2018 - Maintaining a Drupal Module: Keep It Small and Simple
DrupalJam 2018 - Maintaining a Drupal Module: Keep It Small and SimpleDrupalJam 2018 - Maintaining a Drupal Module: Keep It Small and Simple
DrupalJam 2018 - Maintaining a Drupal Module: Keep It Small and SimpleAlexander Varwijk
 
Михаил Крайнюк - Form API + Drupal 8: Form and AJAX
Михаил Крайнюк - Form API + Drupal 8: Form and AJAXМихаил Крайнюк - Form API + Drupal 8: Form and AJAX
Михаил Крайнюк - Form API + Drupal 8: Form and AJAXDrupalSib
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
 
Getting the Most Out of jQuery Widgets
Getting the Most Out of jQuery WidgetsGetting the Most Out of jQuery Widgets
Getting the Most Out of jQuery Widgetsvelveeta_512
 
Crowdsourcing with Django
Crowdsourcing with DjangoCrowdsourcing with Django
Crowdsourcing with DjangoSimon Willison
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologyDaniel Knell
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using CodeceptionJeroen van Dijk
 
Firebase & SwiftUI Workshop
Firebase & SwiftUI WorkshopFirebase & SwiftUI Workshop
Firebase & SwiftUI WorkshopPeter Friese
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful softwareJorn Oomen
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Fabien Potencier
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015Fernando Daciuk
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonKris Wallsmith
 

Similar to Everything you always wanted to know about forms* *but were afraid to ask (20)

Mikhail Kraynuk. Form api. Drupal 8
Mikhail Kraynuk. Form api. Drupal 8Mikhail Kraynuk. Form api. Drupal 8
Mikhail Kraynuk. Form api. Drupal 8
 
Mikhail Kraynuk. Form api. Drupal 8
Mikhail Kraynuk. Form api. Drupal 8Mikhail Kraynuk. Form api. Drupal 8
Mikhail Kraynuk. Form api. Drupal 8
 
DrupalJam 2018 - Maintaining a Drupal Module: Keep It Small and Simple
DrupalJam 2018 - Maintaining a Drupal Module: Keep It Small and SimpleDrupalJam 2018 - Maintaining a Drupal Module: Keep It Small and Simple
DrupalJam 2018 - Maintaining a Drupal Module: Keep It Small and Simple
 
Михаил Крайнюк - Form API + Drupal 8: Form and AJAX
Михаил Крайнюк - Form API + Drupal 8: Form and AJAXМихаил Крайнюк - Form API + Drupal 8: Form and AJAX
Михаил Крайнюк - Form API + Drupal 8: Form and AJAX
 
Migrare da symfony 1 a Symfony2
 Migrare da symfony 1 a Symfony2  Migrare da symfony 1 a Symfony2
Migrare da symfony 1 a Symfony2
 
Symfony2. Form and Validation
Symfony2. Form and ValidationSymfony2. Form and Validation
Symfony2. Form and Validation
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Getting the Most Out of jQuery Widgets
Getting the Most Out of jQuery WidgetsGetting the Most Out of jQuery Widgets
Getting the Most Out of jQuery Widgets
 
Lithium Best
Lithium Best Lithium Best
Lithium Best
 
Crowdsourcing with Django
Crowdsourcing with DjangoCrowdsourcing with Django
Crowdsourcing with Django
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using Codeception
 
Firebase & SwiftUI Workshop
Firebase & SwiftUI WorkshopFirebase & SwiftUI Workshop
Firebase & SwiftUI Workshop
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Unit testing UIView
Unit testing UIViewUnit testing UIView
Unit testing UIView
 
Separation of concerns - DPC12
Separation of concerns - DPC12Separation of concerns - DPC12
Separation of concerns - DPC12
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-london
 

More from Andrea Giuliano

CQRS, ReactJS, Docker in a nutshell
CQRS, ReactJS, Docker in a nutshellCQRS, ReactJS, Docker in a nutshell
CQRS, ReactJS, Docker in a nutshellAndrea Giuliano
 
Go fast in a graph world
Go fast in a graph worldGo fast in a graph world
Go fast in a graph worldAndrea Giuliano
 
Concurrent test frameworks
Concurrent test frameworksConcurrent test frameworks
Concurrent test frameworksAndrea Giuliano
 
Index management in depth
Index management in depthIndex management in depth
Index management in depthAndrea Giuliano
 
Consistency, Availability, Partition: Make Your Choice
Consistency, Availability, Partition: Make Your ChoiceConsistency, Availability, Partition: Make Your Choice
Consistency, Availability, Partition: Make Your ChoiceAndrea Giuliano
 
Asynchronous data processing
Asynchronous data processingAsynchronous data processing
Asynchronous data processingAndrea Giuliano
 
Think horizontally @Codemotion
Think horizontally @CodemotionThink horizontally @Codemotion
Think horizontally @CodemotionAndrea Giuliano
 
Index management in shallow depth
Index management in shallow depthIndex management in shallow depth
Index management in shallow depthAndrea Giuliano
 

More from Andrea Giuliano (10)

CQRS, ReactJS, Docker in a nutshell
CQRS, ReactJS, Docker in a nutshellCQRS, ReactJS, Docker in a nutshell
CQRS, ReactJS, Docker in a nutshell
 
Go fast in a graph world
Go fast in a graph worldGo fast in a graph world
Go fast in a graph world
 
Concurrent test frameworks
Concurrent test frameworksConcurrent test frameworks
Concurrent test frameworks
 
Index management in depth
Index management in depthIndex management in depth
Index management in depth
 
Consistency, Availability, Partition: Make Your Choice
Consistency, Availability, Partition: Make Your ChoiceConsistency, Availability, Partition: Make Your Choice
Consistency, Availability, Partition: Make Your Choice
 
Asynchronous data processing
Asynchronous data processingAsynchronous data processing
Asynchronous data processing
 
Think horizontally @Codemotion
Think horizontally @CodemotionThink horizontally @Codemotion
Think horizontally @Codemotion
 
Index management in shallow depth
Index management in shallow depthIndex management in shallow depth
Index management in shallow depth
 
Stub you!
Stub you!Stub you!
Stub you!
 
Let's test!
Let's test!Let's test!
Let's test!
 

Recently uploaded

How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxLoriGlavin3
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfLoriGlavin3
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionDilum Bandara
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxBkGupta21
 

Recently uploaded (20)

How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptx
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdf
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An Introduction
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptx
 

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
  • 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
  • 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. ?
  • 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/