SlideShare a Scribd company logo
Symfony
Form component
Plan
1. Basic usage
2. Validation
3. Custom types
4. Events
5. Data Transformers
6. Form type extensions
7. Rendering overview
What's the Form
component?
Basic usages
Basic usage (Symfony way)
public function createAction(Request $request)
{
$form = $this
->createFormBuilder([])
->add('comment', 'textarea')
->getForm()
;
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
// Do what ever you want with the data...
$comment = $data['comment'];
}
return [
'form' => $form->createView(),
];
}
Basic usage (rendering)
# create.html.twig
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.comment) }}
<input type="submit" name="Let's go" />
{{ form_end(form) }}
Basic usage (Object instead of array)
public function createOrUpdateAction(Request $request, MyObject $myObject = null)
{
$myObject = $myObject :? new MyObject();
$form = $this
->createFormBuilder($myObject, [
'data_class' => MyObject::class,
])
->add('comment', 'textarea')
->getForm()
;
$form->handleRequest($request);
if ($form->isValid()) {
// Do what ever you want with the updated object...
$comment = $myObject->getComment();
}
return [
'form' => $form->createView(),
];
}
Validation
Validation
$form = $this->createFormBuilder()
->add('comment', 'textarea', [
'required' => true,
'constraints' => [
new NotBlank(),
],
])
->getForm()
;
Basic usage (Validation on the object)
use SymfonyComponentValidatorConstraints as Assert;
class MyObject
{
/**
* @AssertNotBlank
* @AssertLength(
* min=10,
* minMessage="The comment have to be useful"
* )
*/
private $comment;
// Required methods
public function getComment();
public function setComment($comment);
}
Basic usage (Get validation errors)
...that's rendered for you!
But if you want access to them...
$form->handleRequest($request);
if (!$form->isValid()) {
$errors = form->getErrors();
// `$errors` is an array of `FormError` objects.
}
Hey!
31 built-in types
46 built-in validators
Custom types
Custom type definition
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('comment', 'textarea')
;
}
}
Using the Form Type
public function createAction(Request $request)
{
$form = $this->createForm(new MyFormType());
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
// Do what ever you want with the data...
$comment = $data['comment'];
}
return [
'form' => $form->createView(),
];
}
Form Type options
class MyFormType extends AbstractType
{
//! Replace `setDefaultOptions` since Symfony 3.0 /!
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(['my-custom-option']);
$resolver->setDefaults(array(
'data_class' => 'AppModelObject',
));
}
}
Form Type as a service
class GenderType extends AbstractType
{
private $genderChoices;
public function __construct(array $genderChoices)
{
$this->genderChoices = $genderChoices;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->genderChoices,
));
}
}
Form Type as a service
Register the form type
<service id="app.form.type.gender" class="AppFormTypeGenderType">
<argument>%genders%</argument>
<tag name="form.type" />
</service>
Use the form type
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('gender', GenderType::class)
;
}
}
Events
Or how to dynamically update the FormType based on
data.
Form workflow
It dispatches different events
while handling the requests.
» PRE_SET_DATA
» POST_SET_DATA
» PRE_SUBMIT
» SUBMIT
» POST_SUBMIT
The name field only for a new product
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
// The product name is only updatable for a new `Product`
if (!$product || null === $product->getId()) {
$form->add('name', TextType::class);
}
});
}
Event subscribers
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->addEventSubscriber(new AddNameFieldSubscriber());
}
 An event subscriber
class AddNameFieldSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event)
{
$product = $event->getData();
$form = $event->getForm();
if (!$product || null === $product->getId()) {
$form->add('name', TextType::class);
}
}
}
List based on another field
1. Initial Form Type
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('sport', EntityType::class, array(
'class' => 'AppBundle:Sport',
'placeholder' => '',
))
;
}
List based on another field
2. Our form modifier
$formModifier = function (FormInterface $form, Sport $sport = null) {
$positions = null === $sport ? array() : $sport->getAvailablePositions();
$form->add('position', EntityType::class, array(
'class' => 'AppBundle:Position',
'placeholder' => '',
'choices' => $positions,
));
};
List based on another field
3. The listeners
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getSport());
});
$builder->get('sport')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$sport = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $sport);
});
Data Transformers
Normalization flow
1. Model data. Our object returned
by getData().
2. Internal representation, mostly
our model data. Almost never
used by the developer.
3. View data. The data structure
sent to submit().
Using the CallbackTransformer
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description', TextareaType::class);
$builder->get('description')->addModelTransformer(new CallbackTransformer(function ($originalDescription) {
// transform <br/> to n so the textarea reads easier
return preg_replace('#<brs*/?>#i', "n", $originalDescription);
}, function ($submittedDescription) {
// remove most HTML tags (but not br,p)
$cleaned = strip_tags($submittedDescription, '<br><br/><p>');
// transform any n to real <br/>
return str_replace("n", '<br/>', $cleaned);
}));
}
An integer to an object
1. Our task Form Type
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', TextareaType::class)
->add('issue', TextType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppModelTask'
));
}
// ...
}
An integer to an object
2. The transformer
class IssueToNumberTransformer implements DataTransformerInterface
{
private $issueRepository;
public function __construct(IssueRepository $issueRepository) {
$this->issueRepository = $issueRepository;
}
// Two methods to implement...
public function transform($issue);
public function reverseTransform($issueNumber);
}
An integer to an object
3. From model to view
public function transform($issue)
{
return null !== $issue ? $issue->getId() : '';
}
An integer to an object
3. From view to model
public function reverseTransform($issueNumber)
{
if (empty($issueNumber)) {
return;
}
if (null === ($issue = $this->issueRepository->find($issueNumber))) {
throw new TransformationFailedException(sprintf('An issue with number "%s" does not exist!', $issueNumber));
}
return $issue;
}
An integer to an object
4. Voilà!
class TaskType extends AbstractType
{
private $issueRepository;
public function __construct(IssueRepository $issueRepository) {
$this->issueRepository = $issueRepository;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->get('issue')->addModelTransformer(new IssueToNumberTransformer($this->issueRepository));
}
}
Form Type Extensions
An extension class
class IconTypeExtension extends AbstractTypeExtension
{
public function getExtendedType()
{
return ChoiceType::class;
}
// We can now declare the following methods...
public function configureOptions(OptionsResolver $resolver);
public function buildForm(FormBuilderInterface $builder, array $options);
public function buildView(FormView $view, FormInterface $form, array $options);
public function finishView(FormView $view, FormInterface $form, array $options)
}
An optional extra icon
class IconTypeExtension extends AbstractTypeExtension
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'icon' => null,
]);
}
}
An optional extra icon
class IconTypeExtension extends AbstractTypeExtension
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['icon'] = $options['icon'];
}
}
You'll need to extend the choice_widget block using a form
theme, so you can display an icon when the local icon variable is
defined.
Register the extension
<service
id="app.image_type_extension"
class="AppFormExtensionImageTypeExtension">
<tag name="form.type_extension"
extended-type="SymfonyComponentFormExtensionCoreTypeChoiceType" />
</service>
 Rendering
Using a form theme
{% form_theme form 'form/fields.html.twig' %}
{# or.. #}
{% form_theme form _self %}
{{ form(form) }}
Example theme
# form/fields.html.twig
{% block form_row %}
{% spaceless %}
<div class="form_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
Creating a form theme
Using Twig blocks.
» [type]_row
» [type]_widget
» [type]_label
» [type]_errors
If the given block of type is not found, it will use the parent's
type.
FormType's buildView
class GenderType extends AbstractType
{
private $genderChoices;
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['genders'] = $this->genderChoices;
}
}
FormType's buildView
# form/fields.html.twig
{% block gender_widget %}
{% spaceless %}
{# We can use the `gender` variable #}
{% endspaceless %}
{% endblock %}
FormType's finishView
Called when the children's view is completed.
public function finishView(FormView $view, FormInterface $form, array $options)
{
$multipart = false;
foreach ($view->children as $child) {
if ($child->vars['multipart']) {
$multipart = true;
break;
}
}
$view->vars['multipart'] = $multipart;
}
Creating a form without name
1. The difference?
Your form properties won't be namespaced
Example: comment instead of my_form[comment]
You might need/want to use it for:
- Legacy applications compatibility
- APIs (with the FOS Rest Body Listener)
Creating a form without name
2. How?
private $formFactory;
public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}
public function createAction(Request $request)
{
$form = $this->formFactory->createNamed(null, new MyFormType());
$form->handleRequest($request);
// ...
}
Wow, we're done!
Ad

More Related Content

What's hot (20)

Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
Leonardo Proietti
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
Jonathan Wage
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
Jorn Oomen
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
Leonardo Proietti
 
Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)
Samuel ROZE
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
Kris Wallsmith
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
Fabien Potencier
 
Matters of State
Matters of StateMatters of State
Matters of State
Kris Wallsmith
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
Hugo Hamon
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
Bill Chang
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
Hugo Hamon
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
Hugo Hamon
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
Hugo Hamon
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
Артём Курапов
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
Ryan Weaver
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-london
Kris Wallsmith
 
Advanced jQuery
Advanced jQueryAdvanced jQuery
Advanced jQuery
sergioafp
 
Cyclejs introduction
Cyclejs introductionCyclejs introduction
Cyclejs introduction
Arye Lukashevski
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
Nishan Subedi
 
前端MVC 豆瓣说
前端MVC 豆瓣说前端MVC 豆瓣说
前端MVC 豆瓣说
Ting Lv
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
Leonardo Proietti
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
Jonathan Wage
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
Jorn Oomen
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
Leonardo Proietti
 
Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)
Samuel ROZE
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
Kris Wallsmith
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
Fabien Potencier
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
Hugo Hamon
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
Bill Chang
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
Hugo Hamon
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
Hugo Hamon
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
Hugo Hamon
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
Ryan Weaver
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-london
Kris Wallsmith
 
Advanced jQuery
Advanced jQueryAdvanced jQuery
Advanced jQuery
sergioafp
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
Nishan Subedi
 
前端MVC 豆瓣说
前端MVC 豆瓣说前端MVC 豆瓣说
前端MVC 豆瓣说
Ting Lv
 

Viewers also liked (9)

Docker orchestration with Kubernetes
Docker orchestration with KubernetesDocker orchestration with Kubernetes
Docker orchestration with Kubernetes
Samuel ROZE
 
Docker and Kubernetes
Docker and KubernetesDocker and Kubernetes
Docker and Kubernetes
Shiju Varghese
 
Introduction to Kubernetes
Introduction to KubernetesIntroduction to Kubernetes
Introduction to Kubernetes
Paris Apostolopoulos
 
CoreOS Overview and Current Status
CoreOS Overview and Current StatusCoreOS Overview and Current Status
CoreOS Overview and Current Status
Sreenivas Makam
 
Kubernetes Basics
Kubernetes BasicsKubernetes Basics
Kubernetes Basics
Eueung Mulyana
 
Service Discovery using etcd, Consul and Kubernetes
Service Discovery using etcd, Consul and KubernetesService Discovery using etcd, Consul and Kubernetes
Service Discovery using etcd, Consul and Kubernetes
Sreenivas Makam
 
An Introduction to Kubernetes
An Introduction to KubernetesAn Introduction to Kubernetes
An Introduction to Kubernetes
Imesh Gunaratne
 
Delve into Helm - Advanced DevOps
Delve into Helm - Advanced DevOpsDelve into Helm - Advanced DevOps
Delve into Helm - Advanced DevOps
Lachlan Evenson
 
How to Become a Thought Leader in Your Niche
How to Become a Thought Leader in Your NicheHow to Become a Thought Leader in Your Niche
How to Become a Thought Leader in Your Niche
Leslie Samuel
 
Docker orchestration with Kubernetes
Docker orchestration with KubernetesDocker orchestration with Kubernetes
Docker orchestration with Kubernetes
Samuel ROZE
 
CoreOS Overview and Current Status
CoreOS Overview and Current StatusCoreOS Overview and Current Status
CoreOS Overview and Current Status
Sreenivas Makam
 
Service Discovery using etcd, Consul and Kubernetes
Service Discovery using etcd, Consul and KubernetesService Discovery using etcd, Consul and Kubernetes
Service Discovery using etcd, Consul and Kubernetes
Sreenivas Makam
 
An Introduction to Kubernetes
An Introduction to KubernetesAn Introduction to Kubernetes
An Introduction to Kubernetes
Imesh Gunaratne
 
Delve into Helm - Advanced DevOps
Delve into Helm - Advanced DevOpsDelve into Helm - Advanced DevOps
Delve into Helm - Advanced DevOps
Lachlan Evenson
 
How to Become a Thought Leader in Your Niche
How to Become a Thought Leader in Your NicheHow to Become a Thought Leader in Your Niche
How to Become a Thought Leader in Your Niche
Leslie Samuel
 
Ad

Similar to Symfony CoP: Form component (20)

Taming forms with React
Taming forms with ReactTaming forms with React
Taming forms with React
GreeceJS
 
Everything you always wanted to know about forms* *but were afraid to ask
Everything you always wanted to know about forms* *but were afraid to askEverything you always wanted to know about forms* *but were afraid to ask
Everything you always wanted to know about forms* *but were afraid to ask
Andrea Giuliano
 
Symfony tips and tricks
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricks
Mariusz Kozłowski
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
Jeroen van Dijk
 
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
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
Abbas Ali
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
Daniel Knell
 
14. CodeIgniter adaugarea inregistrarilor
14. CodeIgniter adaugarea inregistrarilor14. CodeIgniter adaugarea inregistrarilor
14. CodeIgniter adaugarea inregistrarilor
Razvan Raducanu, PhD
 
Data20161007
Data20161007Data20161007
Data20161007
capegmail
 
Symfony2. Form and Validation
Symfony2. Form and ValidationSymfony2. Form and Validation
Symfony2. Form and Validation
Vladimir Doroshenko
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
eugenio pombi
 
Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?
Julien Vinber
 
Migrare da symfony 1 a Symfony2
 Migrare da symfony 1 a Symfony2  Migrare da symfony 1 a Symfony2
Migrare da symfony 1 a Symfony2
Massimiliano Arione
 
Drupal csu-open atriumname
Drupal csu-open atriumnameDrupal csu-open atriumname
Drupal csu-open atriumname
Emanuele Quinto
 
Apostrophe
ApostropheApostrophe
Apostrophe
tompunk
 
Михаил Крайнюк - 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
DrupalSib
 
Lithium Best
Lithium Best Lithium Best
Lithium Best
Richard McIntyre
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
Lars Jankowfsky
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
Łukasz Chruściel
 
Taming forms with React
Taming forms with ReactTaming forms with React
Taming forms with React
GreeceJS
 
Everything you always wanted to know about forms* *but were afraid to ask
Everything you always wanted to know about forms* *but were afraid to askEverything you always wanted to know about forms* *but were afraid to ask
Everything you always wanted to know about forms* *but were afraid to ask
Andrea Giuliano
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
Jeroen van Dijk
 
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
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
Abbas Ali
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
Daniel Knell
 
14. CodeIgniter adaugarea inregistrarilor
14. CodeIgniter adaugarea inregistrarilor14. CodeIgniter adaugarea inregistrarilor
14. CodeIgniter adaugarea inregistrarilor
Razvan Raducanu, PhD
 
Data20161007
Data20161007Data20161007
Data20161007
capegmail
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
eugenio pombi
 
Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?
Julien Vinber
 
Drupal csu-open atriumname
Drupal csu-open atriumnameDrupal csu-open atriumname
Drupal csu-open atriumname
Emanuele Quinto
 
Apostrophe
ApostropheApostrophe
Apostrophe
tompunk
 
Михаил Крайнюк - 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
DrupalSib
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
Łukasz Chruściel
 
Ad

More from Samuel ROZE (8)

Event streaming: what will go wrong? (Symfony World 2020)
Event streaming: what will go wrong? (Symfony World 2020)Event streaming: what will go wrong? (Symfony World 2020)
Event streaming: what will go wrong? (Symfony World 2020)
Samuel ROZE
 
Living documentation
Living documentationLiving documentation
Living documentation
Samuel ROZE
 
Micro services may not be the best idea
Micro services may not be the best ideaMicro services may not be the best idea
Micro services may not be the best idea
Samuel ROZE
 
Take care of our micro services
Take care of our micro servicesTake care of our micro services
Take care of our micro services
Samuel ROZE
 
(micro)services avec Symfony et Tolerance
(micro)services avec Symfony et Tolerance(micro)services avec Symfony et Tolerance
(micro)services avec Symfony et Tolerance
Samuel ROZE
 
Using continuouspipe to speed up our workflows
Using continuouspipe to speed up our workflowsUsing continuouspipe to speed up our workflows
Using continuouspipe to speed up our workflows
Samuel ROZE
 
Behat c'est plus que ça | Behat is more than that
Behat c'est plus que ça | Behat is more than thatBehat c'est plus que ça | Behat is more than that
Behat c'est plus que ça | Behat is more than that
Samuel ROZE
 
Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer
Samuel ROZE
 
Event streaming: what will go wrong? (Symfony World 2020)
Event streaming: what will go wrong? (Symfony World 2020)Event streaming: what will go wrong? (Symfony World 2020)
Event streaming: what will go wrong? (Symfony World 2020)
Samuel ROZE
 
Living documentation
Living documentationLiving documentation
Living documentation
Samuel ROZE
 
Micro services may not be the best idea
Micro services may not be the best ideaMicro services may not be the best idea
Micro services may not be the best idea
Samuel ROZE
 
Take care of our micro services
Take care of our micro servicesTake care of our micro services
Take care of our micro services
Samuel ROZE
 
(micro)services avec Symfony et Tolerance
(micro)services avec Symfony et Tolerance(micro)services avec Symfony et Tolerance
(micro)services avec Symfony et Tolerance
Samuel ROZE
 
Using continuouspipe to speed up our workflows
Using continuouspipe to speed up our workflowsUsing continuouspipe to speed up our workflows
Using continuouspipe to speed up our workflows
Samuel ROZE
 
Behat c'est plus que ça | Behat is more than that
Behat c'est plus que ça | Behat is more than thatBehat c'est plus que ça | Behat is more than that
Behat c'est plus que ça | Behat is more than that
Samuel ROZE
 
Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer
Samuel ROZE
 

Recently uploaded (20)

Troubleshooting JVM Outages – 3 Fortune 500 case studies
Troubleshooting JVM Outages – 3 Fortune 500 case studiesTroubleshooting JVM Outages – 3 Fortune 500 case studies
Troubleshooting JVM Outages – 3 Fortune 500 case studies
Tier1 app
 
Top Magento Hyvä Theme Features That Make It Ideal for E-commerce.pdf
Top Magento Hyvä Theme Features That Make It Ideal for E-commerce.pdfTop Magento Hyvä Theme Features That Make It Ideal for E-commerce.pdf
Top Magento Hyvä Theme Features That Make It Ideal for E-commerce.pdf
evrigsolution
 
Exchange Migration Tool- Shoviv Software
Exchange Migration Tool- Shoviv SoftwareExchange Migration Tool- Shoviv Software
Exchange Migration Tool- Shoviv Software
Shoviv Software
 
What Do Candidates Really Think About AI-Powered Recruitment Tools?
What Do Candidates Really Think About AI-Powered Recruitment Tools?What Do Candidates Really Think About AI-Powered Recruitment Tools?
What Do Candidates Really Think About AI-Powered Recruitment Tools?
HireME
 
AEM User Group DACH - 2025 Inaugural Meeting
AEM User Group DACH - 2025 Inaugural MeetingAEM User Group DACH - 2025 Inaugural Meeting
AEM User Group DACH - 2025 Inaugural Meeting
jennaf3
 
[gbgcpp] Let's get comfortable with concepts
[gbgcpp] Let's get comfortable with concepts[gbgcpp] Let's get comfortable with concepts
[gbgcpp] Let's get comfortable with concepts
Dimitrios Platis
 
Autodesk Inventor Crack (2025) Latest
Autodesk Inventor    Crack (2025) LatestAutodesk Inventor    Crack (2025) Latest
Autodesk Inventor Crack (2025) Latest
Google
 
Do not let staffing shortages and limited fiscal view hamper your cause
Do not let staffing shortages and limited fiscal view hamper your causeDo not let staffing shortages and limited fiscal view hamper your cause
Do not let staffing shortages and limited fiscal view hamper your cause
Fexle Services Pvt. Ltd.
 
Reinventing Microservices Efficiency and Innovation with Single-Runtime
Reinventing Microservices Efficiency and Innovation with Single-RuntimeReinventing Microservices Efficiency and Innovation with Single-Runtime
Reinventing Microservices Efficiency and Innovation with Single-Runtime
Natan Silnitsky
 
NYC ACE 08-May-2025-Combined Presentation.pdf
NYC ACE 08-May-2025-Combined Presentation.pdfNYC ACE 08-May-2025-Combined Presentation.pdf
NYC ACE 08-May-2025-Combined Presentation.pdf
AUGNYC
 
sequencediagrams.pptx software Engineering
sequencediagrams.pptx software Engineeringsequencediagrams.pptx software Engineering
sequencediagrams.pptx software Engineering
aashrithakondapalli8
 
Mobile Application Developer Dubai | Custom App Solutions by Ajath
Mobile Application Developer Dubai | Custom App Solutions by AjathMobile Application Developer Dubai | Custom App Solutions by Ajath
Mobile Application Developer Dubai | Custom App Solutions by Ajath
Ajath Infotech Technologies LLC
 
Medical Device Cybersecurity Threat & Risk Scoring
Medical Device Cybersecurity Threat & Risk ScoringMedical Device Cybersecurity Threat & Risk Scoring
Medical Device Cybersecurity Threat & Risk Scoring
ICS
 
Sequence Diagrams With Pictures (1).pptx
Sequence Diagrams With Pictures (1).pptxSequence Diagrams With Pictures (1).pptx
Sequence Diagrams With Pictures (1).pptx
aashrithakondapalli8
 
Wilcom Embroidery Studio Crack Free Latest 2025
Wilcom Embroidery Studio Crack Free Latest 2025Wilcom Embroidery Studio Crack Free Latest 2025
Wilcom Embroidery Studio Crack Free Latest 2025
Web Designer
 
Solar-wind hybrid engery a system sustainable power
Solar-wind  hybrid engery a system sustainable powerSolar-wind  hybrid engery a system sustainable power
Solar-wind hybrid engery a system sustainable power
bhoomigowda12345
 
Digital Twins Software Service in Belfast
Digital Twins Software Service in BelfastDigital Twins Software Service in Belfast
Digital Twins Software Service in Belfast
julia smits
 
A Comprehensive Guide to CRM Software Benefits for Every Business Stage
A Comprehensive Guide to CRM Software Benefits for Every Business StageA Comprehensive Guide to CRM Software Benefits for Every Business Stage
A Comprehensive Guide to CRM Software Benefits for Every Business Stage
SynapseIndia
 
Download MathType Crack Version 2025???
Download MathType Crack  Version 2025???Download MathType Crack  Version 2025???
Download MathType Crack Version 2025???
Google
 
Orion Context Broker introduction 20250509
Orion Context Broker introduction 20250509Orion Context Broker introduction 20250509
Orion Context Broker introduction 20250509
Fermin Galan
 
Troubleshooting JVM Outages – 3 Fortune 500 case studies
Troubleshooting JVM Outages – 3 Fortune 500 case studiesTroubleshooting JVM Outages – 3 Fortune 500 case studies
Troubleshooting JVM Outages – 3 Fortune 500 case studies
Tier1 app
 
Top Magento Hyvä Theme Features That Make It Ideal for E-commerce.pdf
Top Magento Hyvä Theme Features That Make It Ideal for E-commerce.pdfTop Magento Hyvä Theme Features That Make It Ideal for E-commerce.pdf
Top Magento Hyvä Theme Features That Make It Ideal for E-commerce.pdf
evrigsolution
 
Exchange Migration Tool- Shoviv Software
Exchange Migration Tool- Shoviv SoftwareExchange Migration Tool- Shoviv Software
Exchange Migration Tool- Shoviv Software
Shoviv Software
 
What Do Candidates Really Think About AI-Powered Recruitment Tools?
What Do Candidates Really Think About AI-Powered Recruitment Tools?What Do Candidates Really Think About AI-Powered Recruitment Tools?
What Do Candidates Really Think About AI-Powered Recruitment Tools?
HireME
 
AEM User Group DACH - 2025 Inaugural Meeting
AEM User Group DACH - 2025 Inaugural MeetingAEM User Group DACH - 2025 Inaugural Meeting
AEM User Group DACH - 2025 Inaugural Meeting
jennaf3
 
[gbgcpp] Let's get comfortable with concepts
[gbgcpp] Let's get comfortable with concepts[gbgcpp] Let's get comfortable with concepts
[gbgcpp] Let's get comfortable with concepts
Dimitrios Platis
 
Autodesk Inventor Crack (2025) Latest
Autodesk Inventor    Crack (2025) LatestAutodesk Inventor    Crack (2025) Latest
Autodesk Inventor Crack (2025) Latest
Google
 
Do not let staffing shortages and limited fiscal view hamper your cause
Do not let staffing shortages and limited fiscal view hamper your causeDo not let staffing shortages and limited fiscal view hamper your cause
Do not let staffing shortages and limited fiscal view hamper your cause
Fexle Services Pvt. Ltd.
 
Reinventing Microservices Efficiency and Innovation with Single-Runtime
Reinventing Microservices Efficiency and Innovation with Single-RuntimeReinventing Microservices Efficiency and Innovation with Single-Runtime
Reinventing Microservices Efficiency and Innovation with Single-Runtime
Natan Silnitsky
 
NYC ACE 08-May-2025-Combined Presentation.pdf
NYC ACE 08-May-2025-Combined Presentation.pdfNYC ACE 08-May-2025-Combined Presentation.pdf
NYC ACE 08-May-2025-Combined Presentation.pdf
AUGNYC
 
sequencediagrams.pptx software Engineering
sequencediagrams.pptx software Engineeringsequencediagrams.pptx software Engineering
sequencediagrams.pptx software Engineering
aashrithakondapalli8
 
Mobile Application Developer Dubai | Custom App Solutions by Ajath
Mobile Application Developer Dubai | Custom App Solutions by AjathMobile Application Developer Dubai | Custom App Solutions by Ajath
Mobile Application Developer Dubai | Custom App Solutions by Ajath
Ajath Infotech Technologies LLC
 
Medical Device Cybersecurity Threat & Risk Scoring
Medical Device Cybersecurity Threat & Risk ScoringMedical Device Cybersecurity Threat & Risk Scoring
Medical Device Cybersecurity Threat & Risk Scoring
ICS
 
Sequence Diagrams With Pictures (1).pptx
Sequence Diagrams With Pictures (1).pptxSequence Diagrams With Pictures (1).pptx
Sequence Diagrams With Pictures (1).pptx
aashrithakondapalli8
 
Wilcom Embroidery Studio Crack Free Latest 2025
Wilcom Embroidery Studio Crack Free Latest 2025Wilcom Embroidery Studio Crack Free Latest 2025
Wilcom Embroidery Studio Crack Free Latest 2025
Web Designer
 
Solar-wind hybrid engery a system sustainable power
Solar-wind  hybrid engery a system sustainable powerSolar-wind  hybrid engery a system sustainable power
Solar-wind hybrid engery a system sustainable power
bhoomigowda12345
 
Digital Twins Software Service in Belfast
Digital Twins Software Service in BelfastDigital Twins Software Service in Belfast
Digital Twins Software Service in Belfast
julia smits
 
A Comprehensive Guide to CRM Software Benefits for Every Business Stage
A Comprehensive Guide to CRM Software Benefits for Every Business StageA Comprehensive Guide to CRM Software Benefits for Every Business Stage
A Comprehensive Guide to CRM Software Benefits for Every Business Stage
SynapseIndia
 
Download MathType Crack Version 2025???
Download MathType Crack  Version 2025???Download MathType Crack  Version 2025???
Download MathType Crack Version 2025???
Google
 
Orion Context Broker introduction 20250509
Orion Context Broker introduction 20250509Orion Context Broker introduction 20250509
Orion Context Broker introduction 20250509
Fermin Galan
 

Symfony CoP: Form component

  • 2. Plan 1. Basic usage 2. Validation 3. Custom types 4. Events 5. Data Transformers 6. Form type extensions 7. Rendering overview
  • 5. Basic usage (Symfony way) public function createAction(Request $request) { $form = $this ->createFormBuilder([]) ->add('comment', 'textarea') ->getForm() ; $form->handleRequest($request); if ($form->isValid()) { $data = $form->getData(); // Do what ever you want with the data... $comment = $data['comment']; } return [ 'form' => $form->createView(), ]; }
  • 6. Basic usage (rendering) # create.html.twig {{ form_start(form) }} {{ form_errors(form) }} {{ form_row(form.comment) }} <input type="submit" name="Let's go" /> {{ form_end(form) }}
  • 7. Basic usage (Object instead of array) public function createOrUpdateAction(Request $request, MyObject $myObject = null) { $myObject = $myObject :? new MyObject(); $form = $this ->createFormBuilder($myObject, [ 'data_class' => MyObject::class, ]) ->add('comment', 'textarea') ->getForm() ; $form->handleRequest($request); if ($form->isValid()) { // Do what ever you want with the updated object... $comment = $myObject->getComment(); } return [ 'form' => $form->createView(), ]; }
  • 9. Validation $form = $this->createFormBuilder() ->add('comment', 'textarea', [ 'required' => true, 'constraints' => [ new NotBlank(), ], ]) ->getForm() ;
  • 10. Basic usage (Validation on the object) use SymfonyComponentValidatorConstraints as Assert; class MyObject { /** * @AssertNotBlank * @AssertLength( * min=10, * minMessage="The comment have to be useful" * ) */ private $comment; // Required methods public function getComment(); public function setComment($comment); }
  • 11. Basic usage (Get validation errors) ...that's rendered for you! But if you want access to them... $form->handleRequest($request); if (!$form->isValid()) { $errors = form->getErrors(); // `$errors` is an array of `FormError` objects. }
  • 12. Hey! 31 built-in types 46 built-in validators
  • 14. Custom type definition class MyFormType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('comment', 'textarea') ; } }
  • 15. Using the Form Type public function createAction(Request $request) { $form = $this->createForm(new MyFormType()); $form->handleRequest($request); if ($form->isValid()) { $data = $form->getData(); // Do what ever you want with the data... $comment = $data['comment']; } return [ 'form' => $form->createView(), ]; }
  • 16. Form Type options class MyFormType extends AbstractType { //! Replace `setDefaultOptions` since Symfony 3.0 /! public function configureOptions(OptionsResolver $resolver) { $resolver->setRequired(['my-custom-option']); $resolver->setDefaults(array( 'data_class' => 'AppModelObject', )); } }
  • 17. Form Type as a service class GenderType extends AbstractType { private $genderChoices; public function __construct(array $genderChoices) { $this->genderChoices = $genderChoices; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'choices' => $this->genderChoices, )); } }
  • 18. Form Type as a service Register the form type <service id="app.form.type.gender" class="AppFormTypeGenderType"> <argument>%genders%</argument> <tag name="form.type" /> </service> Use the form type class MyFormType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('gender', GenderType::class) ; } }
  • 19. Events Or how to dynamically update the FormType based on data.
  • 20. Form workflow It dispatches different events while handling the requests. » PRE_SET_DATA » POST_SET_DATA » PRE_SUBMIT » SUBMIT » POST_SUBMIT
  • 21. The name field only for a new product public function buildForm(FormBuilderInterface $builder, array $options) { // ... $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { $product = $event->getData(); $form = $event->getForm(); // The product name is only updatable for a new `Product` if (!$product || null === $product->getId()) { $form->add('name', TextType::class); } }); }
  • 22. Event subscribers public function buildForm(FormBuilderInterface $builder, array $options) { // ... $builder->addEventSubscriber(new AddNameFieldSubscriber()); }
  • 23.  An event subscriber class AddNameFieldSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents() { return array(FormEvents::PRE_SET_DATA => 'preSetData'); } public function preSetData(FormEvent $event) { $product = $event->getData(); $form = $event->getForm(); if (!$product || null === $product->getId()) { $form->add('name', TextType::class); } } }
  • 24. List based on another field 1. Initial Form Type public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('sport', EntityType::class, array( 'class' => 'AppBundle:Sport', 'placeholder' => '', )) ; }
  • 25. List based on another field 2. Our form modifier $formModifier = function (FormInterface $form, Sport $sport = null) { $positions = null === $sport ? array() : $sport->getAvailablePositions(); $form->add('position', EntityType::class, array( 'class' => 'AppBundle:Position', 'placeholder' => '', 'choices' => $positions, )); };
  • 26. List based on another field 3. The listeners $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formModifier) { $data = $event->getData(); $formModifier($event->getForm(), $data->getSport()); }); $builder->get('sport')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($formModifier) { // It's important here to fetch $event->getForm()->getData(), as // $event->getData() will get you the client data (that is, the ID) $sport = $event->getForm()->getData(); // since we've added the listener to the child, we'll have to pass on // the parent to the callback functions! $formModifier($event->getForm()->getParent(), $sport); });
  • 28. Normalization flow 1. Model data. Our object returned by getData(). 2. Internal representation, mostly our model data. Almost never used by the developer. 3. View data. The data structure sent to submit().
  • 29. Using the CallbackTransformer public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('description', TextareaType::class); $builder->get('description')->addModelTransformer(new CallbackTransformer(function ($originalDescription) { // transform <br/> to n so the textarea reads easier return preg_replace('#<brs*/?>#i', "n", $originalDescription); }, function ($submittedDescription) { // remove most HTML tags (but not br,p) $cleaned = strip_tags($submittedDescription, '<br><br/><p>'); // transform any n to real <br/> return str_replace("n", '<br/>', $cleaned); })); }
  • 30. An integer to an object 1. Our task Form Type class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('description', TextareaType::class) ->add('issue', TextType::class) ; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => 'AppModelTask' )); } // ... }
  • 31. An integer to an object 2. The transformer class IssueToNumberTransformer implements DataTransformerInterface { private $issueRepository; public function __construct(IssueRepository $issueRepository) { $this->issueRepository = $issueRepository; } // Two methods to implement... public function transform($issue); public function reverseTransform($issueNumber); }
  • 32. An integer to an object 3. From model to view public function transform($issue) { return null !== $issue ? $issue->getId() : ''; }
  • 33. An integer to an object 3. From view to model public function reverseTransform($issueNumber) { if (empty($issueNumber)) { return; } if (null === ($issue = $this->issueRepository->find($issueNumber))) { throw new TransformationFailedException(sprintf('An issue with number "%s" does not exist!', $issueNumber)); } return $issue; }
  • 34. An integer to an object 4. Voilà! class TaskType extends AbstractType { private $issueRepository; public function __construct(IssueRepository $issueRepository) { $this->issueRepository = $issueRepository; } public function buildForm(FormBuilderInterface $builder, array $options) { // ... $builder->get('issue')->addModelTransformer(new IssueToNumberTransformer($this->issueRepository)); } }
  • 36. An extension class class IconTypeExtension extends AbstractTypeExtension { public function getExtendedType() { return ChoiceType::class; } // We can now declare the following methods... public function configureOptions(OptionsResolver $resolver); public function buildForm(FormBuilderInterface $builder, array $options); public function buildView(FormView $view, FormInterface $form, array $options); public function finishView(FormView $view, FormInterface $form, array $options) }
  • 37. An optional extra icon class IconTypeExtension extends AbstractTypeExtension { public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'icon' => null, ]); } }
  • 38. An optional extra icon class IconTypeExtension extends AbstractTypeExtension { public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['icon'] = $options['icon']; } } You'll need to extend the choice_widget block using a form theme, so you can display an icon when the local icon variable is defined.
  • 39. Register the extension <service id="app.image_type_extension" class="AppFormExtensionImageTypeExtension"> <tag name="form.type_extension" extended-type="SymfonyComponentFormExtensionCoreTypeChoiceType" /> </service>
  • 41. Using a form theme {% form_theme form 'form/fields.html.twig' %} {# or.. #} {% form_theme form _self %} {{ form(form) }}
  • 42. Example theme # form/fields.html.twig {% block form_row %} {% spaceless %} <div class="form_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </div> {% endspaceless %} {% endblock form_row %}
  • 43. Creating a form theme Using Twig blocks. » [type]_row » [type]_widget » [type]_label » [type]_errors If the given block of type is not found, it will use the parent's type.
  • 44. FormType's buildView class GenderType extends AbstractType { private $genderChoices; public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['genders'] = $this->genderChoices; } }
  • 45. FormType's buildView # form/fields.html.twig {% block gender_widget %} {% spaceless %} {# We can use the `gender` variable #} {% endspaceless %} {% endblock %}
  • 46. FormType's finishView Called when the children's view is completed. public function finishView(FormView $view, FormInterface $form, array $options) { $multipart = false; foreach ($view->children as $child) { if ($child->vars['multipart']) { $multipart = true; break; } } $view->vars['multipart'] = $multipart; }
  • 47. Creating a form without name 1. The difference? Your form properties won't be namespaced Example: comment instead of my_form[comment] You might need/want to use it for: - Legacy applications compatibility - APIs (with the FOS Rest Body Listener)
  • 48. Creating a form without name 2. How? private $formFactory; public function __construct(FormFactoryInterface $formFactory) { $this->formFactory = $formFactory; } public function createAction(Request $request) { $form = $this->formFactory->createNamed(null, new MyFormType()); $form->handleRequest($request); // ... }
  翻译: