Cleaner Symfony forms
Cleaner Symfony forms
Forms are essential component for development of Symfony web application. But if I look at typical Symfony form then I see several violations of Four Rules of Simple design.
Problems in symfony forms
-
Duplicated form alias -
getName
and alias defined in services must match -
Unnecessary coupling to Symfony - do I really need to know about
OptionsResolverInterface
? -
Optional
$options
-
Typical Symfony form
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Task'
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('task')
->add('dueDate', null, array('widget' => 'single_text'))
->add('save', 'submit');
if (is_null($options['data']->getId())) {
$builder->add('assignee');
}
}
public function getName()
{
return 'task';
}
}
services:
acme_demo.form.type.task:
class: AppBundle\Form\Type\TaskType
tags:
- { name: form.type, alias: task }
Cleaner solution
I follow simple design rules, rather than Symfony Best Practices, to keep form as readable as possible and as simple as possible. This approach has worked very well for me especially in apps with many forms. Don't inherit Symfony's AbstractType
, but CleanFormType
(excuse dummy name) which solves previous problems:
- Alias is defined in one place (in
services.yml
) - Form is coupled only to
FormBuilderInterface
- Options are truly optional
namespace AppBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
class TaskType extends CleanFormType
{
private $isNewTaskCreated;
public function getDefaults()
{
return array(
'data_class' => 'AppBundle\Entity\Task'
);
}
protected function loadOptions(array $options)
{
$this->isNewTaskCreated = is_null($options['data']->getId());
}
protected function build(FormBuilderInterface $builder)
{
$builder
->add('task')
->add('dueDate', null, array('widget' => 'single_text'))
->add('save', 'submit');
if ($this->isNewTaskCreated) {
$builder->add('assignee');
}
}
}
services:
acme_demo.form.type.task:
class: AppBundle\Form\Type\TaskType
arguments:
- "task"
tags:
- { name: form.type, alias: task }
Form definition is even simpler without defaults and options:
class TaskType extends CleanFormType
{
protected function build(FormBuilderInterface $builder)
{
$builder
->add('task')
->add('dueDate', null, array('widget' => 'single_text'))
->add('save', 'submit');
}
}
Source code
The CleanFormType is available in the gist. You can use it and modify it as you please. Here is the class:
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
abstract class CleanFormType extends AbstractType
{
private $formName;
public function __construct($formName)
{
$this->formName = $formName;
}
public function getName()
{
return $this->formName;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
$this->getDefaults()
);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->loadOptions($options);
$this->build($builder);
}
protected function getDefaults()
{
return array();
}
/** @SuppressWarnings("unused") */
protected function loadOptions(array $options)
{
}
abstract protected function build(FormBuilderInterface $builder);
}
Written by Zdeněk Drahoš
Related protips
Have a fresh tip? Share with Coderwall community!
Post
Post a tip
Best
#Php
Authors
Sponsored by #native_company# — Learn More
#native_title#
#native_desc#