In my symfony2 application, I have an attribute of type json_array :
/**
* @ORM\Column(name="rights", type="json_array", nullable=true)
*/
protected $rights = array();
The data for this attributeis an associative array as follows :
$allRights = array(
Associate::READ_PROFILE => array('all' => false),
Associate::UPDATE_PROFILE => array('all' => false),
Associate::READ_CONTACT => array('all', 'created' => false),
);
I want to be able to edit this attribute with a collection of checkboxes collections ie. I want one line per key of the first level and then one checkbox per key of the second level.
I have starter a form type which calls a custom type :
<?php
namespace AppBundle\Form\User;
use AppBundle\Entity\User\Associate;
use AppBundle\Form\DataTransformer\RightsToArrayTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints as Assert;
class AssociateRightsType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('rights', 'fmu_rights', array(
'label' => false,
));
}
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\User\Associate',
'validation_groups' => array('Default', 'rights'),
'cascade_validation' => true,
));
}
/**
* @return string
*/
public function getName()
{
return 'appbundle_user_associate_rights';
}
}
In this custom type I have added a dataTransformer :
<?php
namespace AppBundle\Form\Type;
use AppBundle\Application\User\AssociateManager;
use AppBundle\Entity\User\Associate;
use AppBundle\Form\DataTransformer\RightsToArrayTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class RightsType extends AbstractType
{
/**
* @var AssociateManager
*/
private $associateManager;
public function __construct(AssociateManager $associateManager)
{
$this->associateManager = $associateManager;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new RightsToArrayTransformer();
$builder->addModelTransformer($transformer);
}
/**
* @return string
*/
public function getName()
{
return 'fmu_rights';
}
}
In this data transformer I make sure the data is ouputed in the right format :
<?php
namespace AppBundle\Form\DataTransformer;
use AppBundle\Entity\User\Associate;
use Symfony\Component\Form\DataTransformerInterface;
class RightsToArrayTransformer implements DataTransformerInterface
{
/**
* @param mixed $data
* @return array|mixed
*/
public function transform($data)
{
return is_array($data) ? $data + $this->getRights() : $this->getRights();
}
/**
* @param mixed $data
* @return array
*/
public function reverseTransform($data)
{
return $data;
}
private function getRights()
{
$allRights = array(
Associate::READ_PROFILE => array('all'),
Associate::UPDATE_PROFILE => array('all'),
Associate::READ_CONTACT => array('all', 'created'),
Associate::UPDATE_CONTACT => array('all', 'created'),
Associate::DELETE_CONTACT => array('all', 'created'),
Associate::IS_BOSS => array('all'),
);
foreach ($allRights as $right => $parameters) {
$allRights[$right] = array();
foreach ($parameters as $parameter) {
$allRights[$right][$parameter] = false;
}
}
return $allRights;
}
}
and I have a custom view that makes the right visual ouput :
{% block fmu_rights_widget %}
{% if attr.class is defined %}
{% set attr = attr|merge({'class': attr.class ~ ' input-sm form-control fmu_rights'}) %}
{% endif %}
{% set rights = form.vars.data %}
{% for right, parameters in rights %}
<div class="row padding-v">
<div class="col-md-2">
{{ right }}
</div>
<div class="col-md-10">
{% for parameter, value in parameters %}
{% set name = id ~ '[' ~ loop.index ~ ']' ~ right ~ '[' ~ parameter ~ ']' %}
<label for="{{ name }}">{{ parameter }}</label>
<input type="checkbox" id="{{ name }}" name="{{ name }}" {% if value %}checked{% endif %}>
{% endfor %}
</div>
</div>
{% endfor %}
{% endblock %}
However, the data returned when I send my form is the original data output, not the modified data. I guess I've done it wrong in the view. How can I edit this data correctly and be able to manipulate it the reverse transform function of my data transformer ?
Your problem is that you are creating a number of input fields (the checkboxes) of which symfony knows nothing about. In fact when you submit the form, it is probably going to throw an exception saying that it received some fields that it did not expect. You need to transform these fields into something that symfony can understand. You can do it in two ways: