SlideShare ist ein Scribd-Unternehmen logo
1 von 51
Downloaden Sie, um offline zu lesen
Formulaires Symfony2
Cas pratiques et explications
Alexandre Salomé – sfPot mai 2013
2/51
Plan
● Pré-requis
– Avoir lu la documentation des formulaires
● 4 cas pratiques
– Un formulaire de login
– Changer de mot de passe (ancien/nouveau)
– Traductions avec Doctrine
– Masquer les champs pour certains utilisateurs
● Explications à chaque cas
3/51
Cas n°1 – Formulaire de login
« Le formulaire de connexion n'utilise pas le 
composant Form, ce qui nous oblige à dupliquer 
le HTML. Il faut utiliser le composant Form »
4/51
Requis par la couche de sécurité
● POST /login-check
_username
_password
_csrf_token (intention : authenticate)
$this->get('form.csrf_provider')->generateCsrfToken('authenticate')
_remember_me
5/51
Login – Le formulaire
class LoginType extends AbstractType
{
public function buildForm($builder, $options)
{
$builder
->add('_username', 'text')
->add('_password', 'password')
->add('_remember_me', 'checkbox')
->add('submit', 'submit'); // new!
}
public function setDefaultOptions($resolver)
{
$resolver->setDefaults(array(
'csrf_field_name' => '_csrf_token',
'intention' => 'authenticate',
));
}
public function getName() { return 'login'; }
}
6/51
Le conteneur de services
<service id="form.type.login" class="BacklogFormTypeLoginType">
<tag name="form.type" alias="login" />
</service>
7/51
Construction de formulaire
8/51
Formulaires dans les contrôleurs
$this->createForm($type, $data, $options)
// équivalent à
$this
->get('form.factory')
->create($type, $data, $options)
 ;
9/51
FormFactory
$this
->get('form.factory')
->create('login')
;
login[_username]
login[_password]
login[_remember_me]
login[_csrf_token]
$this
->get('form.factory')
->createNamed('foo', 'login')
;
foo[_username]
foo[_password]
foo[_remember_me]
foo[_csrf_token]
$this
->get('form.factory')
->createNamed('', 'login')
;
_username
_password_
_remember_me
_csrf_token
10/51
Login – Le contrôleur (1/2)
public function loginAction()
{
$form = $this->get('form.factory')
->createNamed('', 'login', array(
'action' => $this->generateUrl('session_loginCheck')
));
if ($error = $this->getErrorMessage()) {
$form->addError(new FormError($error));
}
return $this->render('...', array(
'form' => $form->createView()
));
}
11/51
Login – Le contrôleur (2/2)
protected function getErrorMessage()
{
$request = $this->getRequest();
$attrs = $request->attributes;
$session = $request->getSession();
if ($attrs->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $attrs->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
return $error instanceof Exception ?
$error->getMessage()
: $error
;
}
12/51
Login – Le template
{{ form(form) }}
13/51
Cas n°1 - Conclusion
● Construction d'un formulaire
● Paramétrage du CSRF (nom de champ + intention)
● Flexibilité grâce à la FormFactory
● Réutilisation des templates
14/51
Cas n°2 – Changement de MDP
« Je veux que l'utilisateur saisisse son ancien
mot de passe pour en mettre un nouveau »
15/51
Cycle de vie du formulaire
Construction
FormBuilder
Utilisation
Form
Soumission
$form->bind
Listeners
Modifiable
Lecture
seulement
Lecture
seulement
Attributs & Options
Modifiable
Lecture
seulement
Lecture
seulement
(Data|View)
Transformers
Modifiable
Lecture
seulement
Lecture
seulement
Enfants / Parents
Modifiable Modifiable
Lecture
seulement
16/51
ProfileType
class ProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array
$options)
{
$builder
->add('fullname', 'text')
->add('initials', 'text')
->add('change_password', 'change_password', array(
'virtual' => true
))
->add('submit', 'submit')
;
}
public function getName()
{
return 'profile';
}
}
17/51
ChangePasswordType
class ChangePasswordType extends AbstractType
{
// ...
public function buildForm(FormBuilderInterface $builder, array
$options)
{
$encoderFactory = $this->encoderFactory;
$builder
->add('old_password', 'password', array(
'mapped' => false
))
->add('new_password', 'repeated', array(
'mapped' => false,
'type' => 'password'
))
18/51
change_password
● Data = user
● Injection du service d'encodage
● À la soumission du formulaire
– Vérifie le mot de passe
– Ajoute un message d'erreur si le MDP est incorrect
– Enregistre le nouveau mot de passe
19/51
Le formulaire
class ChangePasswordType extends AbstractType
{
// ...
public function buildForm(FormBuilderInterface $builder, array
$options)
{
// ...
$builder->addEventListener(FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($encoderFactory) {
...
});
20/51
Le formulaire
function (FormEvent $event) use ($encoderFactory) {
$form = $event->getForm();
$user = $form->getData();
$encoder = $encoderFactory->getEncoder($user);
$oldPassword = $form->get('old_password')->getData();
$newPassword = $form->get('new_password')->getData();
if (!$oldPassword || !$newPassword) {
return;
}
if (!$user->isPasswordValid($oldPassword, $encoder)) {
$form->addError(new FormError('Bad credentials'));
return;
}
$user->setPassword($newPassword, $encoder);
}
21/51
Déclaration du form type
<service id="form.type.change_password" class="...">
<argument type="service" id="security.encoder_factory" />
<tag name="form.type" alias="change_password" />
</service>
22/51
Cas n°2 - Conclusion
● Le cycle de vie d'un formulaire
● Le rôle des FormType
● Les listeners pour interagir après la construction
● virtual = true
– Partage la donnée avec le sous-formulaire
● mapped = false
– Permet de « hooker » dans un FormType
23/51
Cas n°3 – Traductions & Doctrine
« Je veux gérer mes traductions
en Javascript de manière homogène »
24/51
Le contrat
● Contrôleur intact
● Modèle de données explicite (oneToMany)
● Mise en commun au niveau des formulaires
25/51
Modèle Doctrine (1/2)
AcmeEntityProduct:
type: entity
id: ~
fields:
upc: {type: string, length: 64, nullable: true }
price: {type: price, nullable: true }
oneToMany:
translations:
targetEntity: ProductTranslation
mappedBy: product
indexBy: culture
cascade: [ persist, remove ]
26/51
Modèle Doctrine (2/2)
AcmeEntityProductTranslation:
type: entity
id: ~
fields:
culture: {type: string, length: 8 }
title: {type: string, length: 255, nullable: true }
baseline: {type: text, nullable: true }
manyToOne:
product:
targetEntity: Product
inversedBy: translations
joinColumn:
name: product_id
referencedColumnName: id
27/51
Le contrat – le contrôleur
public function editAction(Request $request, $id)
{
$product = ...;
$form = $this->createForm('product', $product);
if ($request->isMethod('POST') &&
$form->bind($request)->isValid()
) {
// save and flush
}
return $this->render(...);
}
28/51
Le contrat – l'API commune
$product->getUpc();
$product->setUpc($upc);
$product->getTranslations();
$trans = $product->getTranslation('fr_FR');
$trans->getTitle();
$trans->setTitle('Product title');
$trans->getBaseline();
$trans->setBaseline('Baseline of the product');
$product->removeTranslation($trans);
$product->addTranslation(new ProductTranslation(...));
29/51
Le formulaire
class TranslationsType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false
));
}
public function getParent()
{
return 'collection';
}
public function getName()
{
return 'translations';
}
}
30/51
Le formulaire
class TranslationsType extends AbstractType
{
public function __construct($defaultCulture, array $availableCultures =
array())
{
$this->availableCultures = $availableCultures;
$this->defaultCulture = $defaultCulture;
}
public function buildView(FormView $view, FormInterface $form, array
$options)
{
$cultures = $this->availableCultures;
$existing = array_keys($form->all());
$view->vars['missing_cultures'] = array_diff($cultures,
$existing);
$view->vars['default_culture'] = $this->defaultCulture;
}
}
31/51
Templating
E-mail : alexandre@...
Prénom : Salomé
Nom : Alexandre
Cet e-mail est déjà utilisé
Il est interdit d'utiliser le domaine « @... »
32/51
Templating
E-mail : alexandre@...
Prénom : Salomé
Nom : Alexandre
Cet e-mail est déjà utilisé
Il est interdit d'utiliser le domaine « @... »
LABEL WIDGET
LABEL WIDGET
LABEL WIDGET
ERRORS
33/51
Templating
E-mail : alexandre@...
Prénom : Salomé
Nom : Alexandre
Cet e-mail est déjà utilisé
Il est interdit d'utiliser le domaine « @... »
LABEL WIDGET
LABEL WIDGET
LABEL WIDGET
ERRORS
ROW
34/51
Templating
E-mail : alexandre@...
Prénom : Salomé
Nom : Alexandre
Cet e-mail est déjà utilisé
Il est interdit d'utiliser le domaine « @... »
ROW
ROW
ROW
35/51
form_div_layout.html.twig
{% block ..._widget %}
{% block ..._label %}
{% block ..._errors %}
{% block ..._row %}
{% block birthday_row %}
{% block date_row %} 
{% block form_row %}
Au moment du rendu, le moteur cherche un bloc dans le template
correspondant au type ou au parent le plus proche. Par exemple
pour le type « birthday » :
Ce fichier comporte les blocs utilisés pour le rendu des formulaires. Les
plus fréquents sont les suivants :
36/51
La vue
{% block translations_row %}
{% spaceless %}
...
{% endspaceless %}
{% endblock %}
37/51
{% block translations_row %}
{% spaceless %}
<div class="translations-container" data-id="{{ id }}" {% if prototype is defined
%}data-prototype="{{ form_widget(prototype)|e }}"{% endif %}>
<ul class="nav nav-tabs nav-translations">
{% for key, subForm in form.children %}
<li{{ key == default_culture ? ' class="active"' : '' }}>
<a data-toggle="tab" href="#{{ id }}-{{ key }}">{{ key }}</a>
</li>
{% endfor %}
{% if prototype is defined %}
{% for culture in missing_cultures %}
<li><a data-toggle="tab" href="#{{ id }}-{{ culture }}"
data-translation-create="{{ culture }}"><i class="icon-plus"></i> {{ culture }}</a></li>
{% endfor %}
{% endif %}
</ul>
<div class="form-translations">
{% for key, subForm in form.children %}
<div class="tab-pane{{ loop.first ? ' active' : '' }}" id="{{ id }}-{{ key }}"
{{ form_widget(subForm) }}
</div>
{% endfor %}
</div>
</div>
<hr />
{% endspaceless %}
{% endblock %}
La vue
38/51
Le Javascript
$(document).on('click', '.nav-translations a[data-translation-create]',
function (e) {
e.preventDefault();
var $link = $(e.currentTarget);
var $container = $($link.parents(".translations-container")[0]);
var $translations = $container.find('.form-translations');
var culture = $link.attr('data-translation-create');
var prototype = $container.attr('data-prototype');
var id = $container.attr('data-id');
prototype = prototype.replace(/__name__/g, culture) ;
$link.find('i.icon-plus').remove();
$translations.find(".tab-pane").removeClass('active');
var newCulture = $translations.append(
'<div class="tab-pane active" id="' + id + '-' + culture + '">'
+ prototype
+ '</div>'
);
$link.removeAttr('data-translation-create');
});
39/51
Déclaration du form type
<service id="form.type.translations" class="...">
<tag name="form.type" alias="translations" />
</service>
40/51
Implémentation
class ProductType extends AbstractType
{
public function buildForm(...)
{
$builder
->add('upc', 'text')
->add('active', 'checkbox')
->add('translations', 'translations', array(
'type' => 'product_translation'
))
;
}
}
41/51
Conclusion
● Mise en commun des templates de formulaires
● Étendre un type de formulaire
● Relation entre formulaire et modèle
42/51
Cas n°4 – Champs cachés
« Je veux masquer certains champs
pour certains utilisateurs »
43/51
Extension de type
● Réutilisation horizontale de comportements
● Exemples
– CSRF
● FormTypeCsrfExtension
– Validation
● FormTypeValidatorExtension
44/51
FormType vs FormTypeExtension
FormType FormTypeExtension
buildForm
buildView
finishView
setDefaultOptions
getParent
getName
getExtendedType
45/51
Héritage de type
csrf
validation
security
46/51
SecurityTypeExtension (1/3)
class SecurityTypeExtension extends AbstractTypeExtension
{
public function __construct(SecurityContextInterface $context)
{
$this->context = $context;
}
/**
* {@inheritDoc}
*/
public function getExtendedType()
{
return 'form';
}
}
47/51
SecurityTypeExtension (2/3)
// …
public function buildForm(FormBuilderInterface $builder, array $options)
{
$g = $options['is_granted'];
if (null === $g || $this->context->isGranted($g)) {
return;
}
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) {
$form = $event->getForm();
if ($form->isRoot()) // ...
$form->getParent()->remove($form->getName());
});
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('is_granted' => null));
}
48/51
SecurityTypeExtension (3/3)
<service id="form.type_extension.security" class="...">
<argument type="service" id="security.context" />
<tag name="form.type_extension" alias="form" />
</service>
49/51
Utilisation
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array
$options)
{
$builder
->add('upc', 'text', array('label' => 'Code UPC'))
->add('translations', 'translations', array(
'type' => 'product_translation',
'is_granted' => 'ROLE_MODERATOR'
))
;
}
}
50/51
Cas n°4 - Conclusion
● Implémentation simple et utilisation rapide
● Utiliser les options pour configurer
– is_granted / is_granted_subject
– data_as_subject
Fin
Questions ?

Weitere ähnliche Inhalte

Was ist angesagt?

La référence Clear php
La référence Clear phpLa référence Clear php
La référence Clear phpDamien Seguy
 
Comment Créer Un Site De Membres vol 03
Comment Créer Un Site De Membres vol 03Comment Créer Un Site De Membres vol 03
Comment Créer Un Site De Membres vol 03Patrick Van Hoof
 
Créer une barre de progression grâce à PHP 5.4
Créer une barre de progression grâce à PHP 5.4Créer une barre de progression grâce à PHP 5.4
Créer une barre de progression grâce à PHP 5.4🏁 Pierre-Henry Soria 💡
 
ZendFramework2 - Présentation
ZendFramework2 - PrésentationZendFramework2 - Présentation
ZendFramework2 - Présentationjulien pauli
 
Formation PHP
Formation PHPFormation PHP
Formation PHPkemenaran
 
Pots de Miel, Honeypot informatique - Sécurité informatique
Pots de Miel, Honeypot informatique - Sécurité informatiquePots de Miel, Honeypot informatique - Sécurité informatique
Pots de Miel, Honeypot informatique - Sécurité informatique🏁 Pierre-Henry Soria 💡
 
SOLID : les principes à l’origine du succès de Symfony et de vos applications
SOLID : les principes à l’origine du succès de Symfony et de vos applicationsSOLID : les principes à l’origine du succès de Symfony et de vos applications
SOLID : les principes à l’origine du succès de Symfony et de vos applicationsVladyslav Riabchenko
 
Void-safe en Eiffel
Void-safe en EiffelVoid-safe en Eiffel
Void-safe en Eiffelbmarchal
 
Symfony CQRS and _event_sourcing
Symfony CQRS and _event_sourcingSymfony CQRS and _event_sourcing
Symfony CQRS and _event_sourcingSymfonyMu
 
Quoi de neuf dans Zend Framework 1.10 ?
Quoi de neuf dans Zend Framework 1.10 ?Quoi de neuf dans Zend Framework 1.10 ?
Quoi de neuf dans Zend Framework 1.10 ?Mickael Perraud
 
Introduction au langage PHP (2éme partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (2éme partie) élaborée par Marouan OMEZZINEIntroduction au langage PHP (2éme partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (2éme partie) élaborée par Marouan OMEZZINEMarouan OMEZZINE
 
php2 : formulaire-session-PDO
php2 : formulaire-session-PDOphp2 : formulaire-session-PDO
php2 : formulaire-session-PDOAbdoulaye Dieng
 
Qui a laissé son mot de passe dans le code
Qui a laissé son mot de passe dans le codeQui a laissé son mot de passe dans le code
Qui a laissé son mot de passe dans le codeDamien Seguy
 
Scrapez facilement et gratuitement
Scrapez facilement et gratuitementScrapez facilement et gratuitement
Scrapez facilement et gratuitementMadeline Pinthon
 
Php 2 - Approfondissement MySQL, PDO et MVC
Php 2 - Approfondissement MySQL, PDO et MVCPhp 2 - Approfondissement MySQL, PDO et MVC
Php 2 - Approfondissement MySQL, PDO et MVCPierre Faure
 
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINEIntroduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINEMarouan OMEZZINE
 

Was ist angesagt? (19)

La référence Clear php
La référence Clear phpLa référence Clear php
La référence Clear php
 
Comment Créer Un Site De Membres vol 03
Comment Créer Un Site De Membres vol 03Comment Créer Un Site De Membres vol 03
Comment Créer Un Site De Membres vol 03
 
Créer une barre de progression grâce à PHP 5.4
Créer une barre de progression grâce à PHP 5.4Créer une barre de progression grâce à PHP 5.4
Créer une barre de progression grâce à PHP 5.4
 
ZendFramework2 - Présentation
ZendFramework2 - PrésentationZendFramework2 - Présentation
ZendFramework2 - Présentation
 
Cours php
Cours phpCours php
Cours php
 
Formation PHP
Formation PHPFormation PHP
Formation PHP
 
Pots de Miel, Honeypot informatique - Sécurité informatique
Pots de Miel, Honeypot informatique - Sécurité informatiquePots de Miel, Honeypot informatique - Sécurité informatique
Pots de Miel, Honeypot informatique - Sécurité informatique
 
SOLID : les principes à l’origine du succès de Symfony et de vos applications
SOLID : les principes à l’origine du succès de Symfony et de vos applicationsSOLID : les principes à l’origine du succès de Symfony et de vos applications
SOLID : les principes à l’origine du succès de Symfony et de vos applications
 
Void-safe en Eiffel
Void-safe en EiffelVoid-safe en Eiffel
Void-safe en Eiffel
 
Symfony CQRS and _event_sourcing
Symfony CQRS and _event_sourcingSymfony CQRS and _event_sourcing
Symfony CQRS and _event_sourcing
 
Quoi de neuf dans Zend Framework 1.10 ?
Quoi de neuf dans Zend Framework 1.10 ?Quoi de neuf dans Zend Framework 1.10 ?
Quoi de neuf dans Zend Framework 1.10 ?
 
Introduction au langage PHP (2éme partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (2éme partie) élaborée par Marouan OMEZZINEIntroduction au langage PHP (2éme partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (2éme partie) élaborée par Marouan OMEZZINE
 
php2 : formulaire-session-PDO
php2 : formulaire-session-PDOphp2 : formulaire-session-PDO
php2 : formulaire-session-PDO
 
C libro escenarioii
C libro escenarioiiC libro escenarioii
C libro escenarioii
 
Qui a laissé son mot de passe dans le code
Qui a laissé son mot de passe dans le codeQui a laissé son mot de passe dans le code
Qui a laissé son mot de passe dans le code
 
Scrapez facilement et gratuitement
Scrapez facilement et gratuitementScrapez facilement et gratuitement
Scrapez facilement et gratuitement
 
Php 2 - Approfondissement MySQL, PDO et MVC
Php 2 - Approfondissement MySQL, PDO et MVCPhp 2 - Approfondissement MySQL, PDO et MVC
Php 2 - Approfondissement MySQL, PDO et MVC
 
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINEIntroduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
 
Introduction au Jquery
Introduction au JqueryIntroduction au Jquery
Introduction au Jquery
 

Andere mochten auch

Madagascar et son guichet unique TRADENET
Madagascar et son guichet unique TRADENETMadagascar et son guichet unique TRADENET
Madagascar et son guichet unique TRADENETAAEC_AFRICAN
 
6 Dematerialisation
6  Dematerialisation6  Dematerialisation
6 DematerialisationTim Curtis
 
Eim360 Dématérialisation et Archivage électronique
Eim360 Dématérialisation et Archivage électroniqueEim360 Dématérialisation et Archivage électronique
Eim360 Dématérialisation et Archivage électroniqueSollan France
 
Request for Proposal (RFP) management - Ask the right questions and choose wi...
Request for Proposal (RFP) management - Ask the right questions and choose wi...Request for Proposal (RFP) management - Ask the right questions and choose wi...
Request for Proposal (RFP) management - Ask the right questions and choose wi...Harold van Heeringen
 
La mise en œuvre de l’archivage numérique courant et intermédiaire au CD 34 :...
La mise en œuvre de l’archivage numérique courant et intermédiaire au CD 34 :...La mise en œuvre de l’archivage numérique courant et intermédiaire au CD 34 :...
La mise en œuvre de l’archivage numérique courant et intermédiaire au CD 34 :...AssociationAF
 
Sécurité des bases de données
Sécurité des bases de donnéesSécurité des bases de données
Sécurité des bases de donnéeslitayem bechir
 
Aplicación CRM Analytics (spanish)
Aplicación CRM Analytics (spanish)Aplicación CRM Analytics (spanish)
Aplicación CRM Analytics (spanish)Stratebi
 
Lionel Barzon III: Four Digital Skills For Your Career
Lionel Barzon III: Four Digital Skills For Your CareerLionel Barzon III: Four Digital Skills For Your Career
Lionel Barzon III: Four Digital Skills For Your CareerLionel Barzon III
 
jQuery sans jQuery
jQuery sans jQueryjQuery sans jQuery
jQuery sans jQuerygoldoraf
 
Protecting Your SsaSets 01.07.10
Protecting Your SsaSets 01.07.10Protecting Your SsaSets 01.07.10
Protecting Your SsaSets 01.07.10michael keyes
 
Boletin Septiembre - Destacan trabajo del CNE en procesos electorales
Boletin Septiembre - Destacan trabajo del CNE en procesos electorales Boletin Septiembre - Destacan trabajo del CNE en procesos electorales
Boletin Septiembre - Destacan trabajo del CNE en procesos electorales Dra. Roxana Silva Ch.
 
LNUG - A year with AWS
LNUG - A year with AWSLNUG - A year with AWS
LNUG - A year with AWSAndrew Clarke
 
La sécurité informatique expliquée aux salariés
La sécurité informatique expliquée aux salariésLa sécurité informatique expliquée aux salariés
La sécurité informatique expliquée aux salariésNRC
 

Andere mochten auch (17)

Madagascar et son guichet unique TRADENET
Madagascar et son guichet unique TRADENETMadagascar et son guichet unique TRADENET
Madagascar et son guichet unique TRADENET
 
Methode Agile
Methode Agile Methode Agile
Methode Agile
 
6 Dematerialisation
6  Dematerialisation6  Dematerialisation
6 Dematerialisation
 
Eim360 Dématérialisation et Archivage électronique
Eim360 Dématérialisation et Archivage électroniqueEim360 Dématérialisation et Archivage électronique
Eim360 Dématérialisation et Archivage électronique
 
Request for Proposal (RFP) management - Ask the right questions and choose wi...
Request for Proposal (RFP) management - Ask the right questions and choose wi...Request for Proposal (RFP) management - Ask the right questions and choose wi...
Request for Proposal (RFP) management - Ask the right questions and choose wi...
 
La mise en œuvre de l’archivage numérique courant et intermédiaire au CD 34 :...
La mise en œuvre de l’archivage numérique courant et intermédiaire au CD 34 :...La mise en œuvre de l’archivage numérique courant et intermédiaire au CD 34 :...
La mise en œuvre de l’archivage numérique courant et intermédiaire au CD 34 :...
 
Sécurité des bases de données
Sécurité des bases de donnéesSécurité des bases de données
Sécurité des bases de données
 
Aplicación CRM Analytics (spanish)
Aplicación CRM Analytics (spanish)Aplicación CRM Analytics (spanish)
Aplicación CRM Analytics (spanish)
 
Lionel Barzon III: Four Digital Skills For Your Career
Lionel Barzon III: Four Digital Skills For Your CareerLionel Barzon III: Four Digital Skills For Your Career
Lionel Barzon III: Four Digital Skills For Your Career
 
Twitter työkäytössä
Twitter työkäytössäTwitter työkäytössä
Twitter työkäytössä
 
jQuery sans jQuery
jQuery sans jQueryjQuery sans jQuery
jQuery sans jQuery
 
Git Quick Intro
Git Quick IntroGit Quick Intro
Git Quick Intro
 
Protecting Your SsaSets 01.07.10
Protecting Your SsaSets 01.07.10Protecting Your SsaSets 01.07.10
Protecting Your SsaSets 01.07.10
 
El periodico en el aula
El periodico en el aula El periodico en el aula
El periodico en el aula
 
Boletin Septiembre - Destacan trabajo del CNE en procesos electorales
Boletin Septiembre - Destacan trabajo del CNE en procesos electorales Boletin Septiembre - Destacan trabajo del CNE en procesos electorales
Boletin Septiembre - Destacan trabajo del CNE en procesos electorales
 
LNUG - A year with AWS
LNUG - A year with AWSLNUG - A year with AWS
LNUG - A year with AWS
 
La sécurité informatique expliquée aux salariés
La sécurité informatique expliquée aux salariésLa sécurité informatique expliquée aux salariés
La sécurité informatique expliquée aux salariés
 

Ähnlich wie Formulaires Symfony2 - Cas pratiques et explications

Introduction à Sinatra
Introduction à SinatraIntroduction à Sinatra
Introduction à SinatraRémi Prévost
 
Doctrine en dehors des sentiers battus
Doctrine en dehors des sentiers battusDoctrine en dehors des sentiers battus
Doctrine en dehors des sentiers battusRomaric Drigon
 
S2-02-PHP-objet.pptx
S2-02-PHP-objet.pptxS2-02-PHP-objet.pptx
S2-02-PHP-objet.pptxkohay75604
 
Atelier WordPress: Création d&rsquo;extension WordPress
Atelier WordPress: Création d&rsquo;extension WordPressAtelier WordPress: Création d&rsquo;extension WordPress
Atelier WordPress: Création d&rsquo;extension WordPressIZZA Samir
 
Présentation de DBAL en PHP
Présentation de DBAL en PHPPrésentation de DBAL en PHP
Présentation de DBAL en PHPMickael Perraud
 

Ähnlich wie Formulaires Symfony2 - Cas pratiques et explications (8)

Cours PHP avancé
Cours PHP avancéCours PHP avancé
Cours PHP avancé
 
Héritage et redéfinition de méthode
Héritage et redéfinition de méthodeHéritage et redéfinition de méthode
Héritage et redéfinition de méthode
 
Introduction à Sinatra
Introduction à SinatraIntroduction à Sinatra
Introduction à Sinatra
 
Doctrine en dehors des sentiers battus
Doctrine en dehors des sentiers battusDoctrine en dehors des sentiers battus
Doctrine en dehors des sentiers battus
 
S2-02-PHP-objet.pptx
S2-02-PHP-objet.pptxS2-02-PHP-objet.pptx
S2-02-PHP-objet.pptx
 
POO-JAVA-partie-1.pdf
POO-JAVA-partie-1.pdfPOO-JAVA-partie-1.pdf
POO-JAVA-partie-1.pdf
 
Atelier WordPress: Création d&rsquo;extension WordPress
Atelier WordPress: Création d&rsquo;extension WordPressAtelier WordPress: Création d&rsquo;extension WordPress
Atelier WordPress: Création d&rsquo;extension WordPress
 
Présentation de DBAL en PHP
Présentation de DBAL en PHPPrésentation de DBAL en PHP
Présentation de DBAL en PHP
 

Formulaires Symfony2 - Cas pratiques et explications

  • 1. Formulaires Symfony2 Cas pratiques et explications Alexandre Salomé – sfPot mai 2013
  • 2. 2/51 Plan ● Pré-requis – Avoir lu la documentation des formulaires ● 4 cas pratiques – Un formulaire de login – Changer de mot de passe (ancien/nouveau) – Traductions avec Doctrine – Masquer les champs pour certains utilisateurs ● Explications à chaque cas
  • 3. 3/51 Cas n°1 – Formulaire de login « Le formulaire de connexion n'utilise pas le  composant Form, ce qui nous oblige à dupliquer  le HTML. Il faut utiliser le composant Form »
  • 4. 4/51 Requis par la couche de sécurité ● POST /login-check _username _password _csrf_token (intention : authenticate) $this->get('form.csrf_provider')->generateCsrfToken('authenticate') _remember_me
  • 5. 5/51 Login – Le formulaire class LoginType extends AbstractType { public function buildForm($builder, $options) { $builder ->add('_username', 'text') ->add('_password', 'password') ->add('_remember_me', 'checkbox') ->add('submit', 'submit'); // new! } public function setDefaultOptions($resolver) { $resolver->setDefaults(array( 'csrf_field_name' => '_csrf_token', 'intention' => 'authenticate', )); } public function getName() { return 'login'; } }
  • 6. 6/51 Le conteneur de services <service id="form.type.login" class="BacklogFormTypeLoginType"> <tag name="form.type" alias="login" /> </service>
  • 8. 8/51 Formulaires dans les contrôleurs $this->createForm($type, $data, $options) // équivalent à $this ->get('form.factory') ->create($type, $data, $options)  ;
  • 10. 10/51 Login – Le contrôleur (1/2) public function loginAction() { $form = $this->get('form.factory') ->createNamed('', 'login', array( 'action' => $this->generateUrl('session_loginCheck') )); if ($error = $this->getErrorMessage()) { $form->addError(new FormError($error)); } return $this->render('...', array( 'form' => $form->createView() )); }
  • 11. 11/51 Login – Le contrôleur (2/2) protected function getErrorMessage() { $request = $this->getRequest(); $attrs = $request->attributes; $session = $request->getSession(); if ($attrs->has(SecurityContext::AUTHENTICATION_ERROR)) { $error = $attrs->get(SecurityContext::AUTHENTICATION_ERROR); } else { $error = $session->get(SecurityContext::AUTHENTICATION_ERROR); $session->remove(SecurityContext::AUTHENTICATION_ERROR); } return $error instanceof Exception ? $error->getMessage() : $error ; }
  • 12. 12/51 Login – Le template {{ form(form) }}
  • 13. 13/51 Cas n°1 - Conclusion ● Construction d'un formulaire ● Paramétrage du CSRF (nom de champ + intention) ● Flexibilité grâce à la FormFactory ● Réutilisation des templates
  • 14. 14/51 Cas n°2 – Changement de MDP « Je veux que l'utilisateur saisisse son ancien mot de passe pour en mettre un nouveau »
  • 15. 15/51 Cycle de vie du formulaire Construction FormBuilder Utilisation Form Soumission $form->bind Listeners Modifiable Lecture seulement Lecture seulement Attributs & Options Modifiable Lecture seulement Lecture seulement (Data|View) Transformers Modifiable Lecture seulement Lecture seulement Enfants / Parents Modifiable Modifiable Lecture seulement
  • 16. 16/51 ProfileType class ProfileType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('fullname', 'text') ->add('initials', 'text') ->add('change_password', 'change_password', array( 'virtual' => true )) ->add('submit', 'submit') ; } public function getName() { return 'profile'; } }
  • 17. 17/51 ChangePasswordType class ChangePasswordType extends AbstractType { // ... public function buildForm(FormBuilderInterface $builder, array $options) { $encoderFactory = $this->encoderFactory; $builder ->add('old_password', 'password', array( 'mapped' => false )) ->add('new_password', 'repeated', array( 'mapped' => false, 'type' => 'password' ))
  • 18. 18/51 change_password ● Data = user ● Injection du service d'encodage ● À la soumission du formulaire – Vérifie le mot de passe – Ajoute un message d'erreur si le MDP est incorrect – Enregistre le nouveau mot de passe
  • 19. 19/51 Le formulaire class ChangePasswordType extends AbstractType { // ... public function buildForm(FormBuilderInterface $builder, array $options) { // ... $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($encoderFactory) { ... });
  • 20. 20/51 Le formulaire function (FormEvent $event) use ($encoderFactory) { $form = $event->getForm(); $user = $form->getData(); $encoder = $encoderFactory->getEncoder($user); $oldPassword = $form->get('old_password')->getData(); $newPassword = $form->get('new_password')->getData(); if (!$oldPassword || !$newPassword) { return; } if (!$user->isPasswordValid($oldPassword, $encoder)) { $form->addError(new FormError('Bad credentials')); return; } $user->setPassword($newPassword, $encoder); }
  • 21. 21/51 Déclaration du form type <service id="form.type.change_password" class="..."> <argument type="service" id="security.encoder_factory" /> <tag name="form.type" alias="change_password" /> </service>
  • 22. 22/51 Cas n°2 - Conclusion ● Le cycle de vie d'un formulaire ● Le rôle des FormType ● Les listeners pour interagir après la construction ● virtual = true – Partage la donnée avec le sous-formulaire ● mapped = false – Permet de « hooker » dans un FormType
  • 23. 23/51 Cas n°3 – Traductions & Doctrine « Je veux gérer mes traductions en Javascript de manière homogène »
  • 24. 24/51 Le contrat ● Contrôleur intact ● Modèle de données explicite (oneToMany) ● Mise en commun au niveau des formulaires
  • 25. 25/51 Modèle Doctrine (1/2) AcmeEntityProduct: type: entity id: ~ fields: upc: {type: string, length: 64, nullable: true } price: {type: price, nullable: true } oneToMany: translations: targetEntity: ProductTranslation mappedBy: product indexBy: culture cascade: [ persist, remove ]
  • 26. 26/51 Modèle Doctrine (2/2) AcmeEntityProductTranslation: type: entity id: ~ fields: culture: {type: string, length: 8 } title: {type: string, length: 255, nullable: true } baseline: {type: text, nullable: true } manyToOne: product: targetEntity: Product inversedBy: translations joinColumn: name: product_id referencedColumnName: id
  • 27. 27/51 Le contrat – le contrôleur public function editAction(Request $request, $id) { $product = ...; $form = $this->createForm('product', $product); if ($request->isMethod('POST') && $form->bind($request)->isValid() ) { // save and flush } return $this->render(...); }
  • 28. 28/51 Le contrat – l'API commune $product->getUpc(); $product->setUpc($upc); $product->getTranslations(); $trans = $product->getTranslation('fr_FR'); $trans->getTitle(); $trans->setTitle('Product title'); $trans->getBaseline(); $trans->setBaseline('Baseline of the product'); $product->removeTranslation($trans); $product->addTranslation(new ProductTranslation(...));
  • 29. 29/51 Le formulaire class TranslationsType extends AbstractType { public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'allow_add' => true, 'allow_delete' => true, 'prototype' => true, 'by_reference' => false )); } public function getParent() { return 'collection'; } public function getName() { return 'translations'; } }
  • 30. 30/51 Le formulaire class TranslationsType extends AbstractType { public function __construct($defaultCulture, array $availableCultures = array()) { $this->availableCultures = $availableCultures; $this->defaultCulture = $defaultCulture; } public function buildView(FormView $view, FormInterface $form, array $options) { $cultures = $this->availableCultures; $existing = array_keys($form->all()); $view->vars['missing_cultures'] = array_diff($cultures, $existing); $view->vars['default_culture'] = $this->defaultCulture; } }
  • 31. 31/51 Templating E-mail : alexandre@... Prénom : Salomé Nom : Alexandre Cet e-mail est déjà utilisé Il est interdit d'utiliser le domaine « @... »
  • 32. 32/51 Templating E-mail : alexandre@... Prénom : Salomé Nom : Alexandre Cet e-mail est déjà utilisé Il est interdit d'utiliser le domaine « @... » LABEL WIDGET LABEL WIDGET LABEL WIDGET ERRORS
  • 33. 33/51 Templating E-mail : alexandre@... Prénom : Salomé Nom : Alexandre Cet e-mail est déjà utilisé Il est interdit d'utiliser le domaine « @... » LABEL WIDGET LABEL WIDGET LABEL WIDGET ERRORS ROW
  • 34. 34/51 Templating E-mail : alexandre@... Prénom : Salomé Nom : Alexandre Cet e-mail est déjà utilisé Il est interdit d'utiliser le domaine « @... » ROW ROW ROW
  • 35. 35/51 form_div_layout.html.twig {% block ..._widget %} {% block ..._label %} {% block ..._errors %} {% block ..._row %} {% block birthday_row %} {% block date_row %}  {% block form_row %} Au moment du rendu, le moteur cherche un bloc dans le template correspondant au type ou au parent le plus proche. Par exemple pour le type « birthday » : Ce fichier comporte les blocs utilisés pour le rendu des formulaires. Les plus fréquents sont les suivants :
  • 36. 36/51 La vue {% block translations_row %} {% spaceless %} ... {% endspaceless %} {% endblock %}
  • 37. 37/51 {% block translations_row %} {% spaceless %} <div class="translations-container" data-id="{{ id }}" {% if prototype is defined %}data-prototype="{{ form_widget(prototype)|e }}"{% endif %}> <ul class="nav nav-tabs nav-translations"> {% for key, subForm in form.children %} <li{{ key == default_culture ? ' class="active"' : '' }}> <a data-toggle="tab" href="#{{ id }}-{{ key }}">{{ key }}</a> </li> {% endfor %} {% if prototype is defined %} {% for culture in missing_cultures %} <li><a data-toggle="tab" href="#{{ id }}-{{ culture }}" data-translation-create="{{ culture }}"><i class="icon-plus"></i> {{ culture }}</a></li> {% endfor %} {% endif %} </ul> <div class="form-translations"> {% for key, subForm in form.children %} <div class="tab-pane{{ loop.first ? ' active' : '' }}" id="{{ id }}-{{ key }}" {{ form_widget(subForm) }} </div> {% endfor %} </div> </div> <hr /> {% endspaceless %} {% endblock %} La vue
  • 38. 38/51 Le Javascript $(document).on('click', '.nav-translations a[data-translation-create]', function (e) { e.preventDefault(); var $link = $(e.currentTarget); var $container = $($link.parents(".translations-container")[0]); var $translations = $container.find('.form-translations'); var culture = $link.attr('data-translation-create'); var prototype = $container.attr('data-prototype'); var id = $container.attr('data-id'); prototype = prototype.replace(/__name__/g, culture) ; $link.find('i.icon-plus').remove(); $translations.find(".tab-pane").removeClass('active'); var newCulture = $translations.append( '<div class="tab-pane active" id="' + id + '-' + culture + '">' + prototype + '</div>' ); $link.removeAttr('data-translation-create'); });
  • 39. 39/51 Déclaration du form type <service id="form.type.translations" class="..."> <tag name="form.type" alias="translations" /> </service>
  • 40. 40/51 Implémentation class ProductType extends AbstractType { public function buildForm(...) { $builder ->add('upc', 'text') ->add('active', 'checkbox') ->add('translations', 'translations', array( 'type' => 'product_translation' )) ; } }
  • 41. 41/51 Conclusion ● Mise en commun des templates de formulaires ● Étendre un type de formulaire ● Relation entre formulaire et modèle
  • 42. 42/51 Cas n°4 – Champs cachés « Je veux masquer certains champs pour certains utilisateurs »
  • 43. 43/51 Extension de type ● Réutilisation horizontale de comportements ● Exemples – CSRF ● FormTypeCsrfExtension – Validation ● FormTypeValidatorExtension
  • 44. 44/51 FormType vs FormTypeExtension FormType FormTypeExtension buildForm buildView finishView setDefaultOptions getParent getName getExtendedType
  • 46. 46/51 SecurityTypeExtension (1/3) class SecurityTypeExtension extends AbstractTypeExtension { public function __construct(SecurityContextInterface $context) { $this->context = $context; } /** * {@inheritDoc} */ public function getExtendedType() { return 'form'; } }
  • 47. 47/51 SecurityTypeExtension (2/3) // … public function buildForm(FormBuilderInterface $builder, array $options) { $g = $options['is_granted']; if (null === $g || $this->context->isGranted($g)) { return; } $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { $form = $event->getForm(); if ($form->isRoot()) // ... $form->getParent()->remove($form->getName()); }); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array('is_granted' => null)); }
  • 48. 48/51 SecurityTypeExtension (3/3) <service id="form.type_extension.security" class="..."> <argument type="service" id="security.context" /> <tag name="form.type_extension" alias="form" /> </service>
  • 49. 49/51 Utilisation class ProductType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('upc', 'text', array('label' => 'Code UPC')) ->add('translations', 'translations', array( 'type' => 'product_translation', 'is_granted' => 'ROLE_MODERATOR' )) ; } }
  • 50. 50/51 Cas n°4 - Conclusion ● Implémentation simple et utilisation rapide ● Utiliser les options pour configurer – is_granted / is_granted_subject – data_as_subject