I'm using an eventSubscriber to dynamically load a field (Paciente) into a form, in its preSubmit function I need to get in addition to the ID of the paciente, the dni of paciente. The id I can get directly, but the dni need to bring the entity, and I do not know how I can do it from here.
My event in question is as follows:
class AddHistoriaClinicaFieldSubscriber implements EventSubscriberInterface
{
private $propertyPathToHistoriaClinica;
public function __construct($propertyPathToHistoriaClinica)
{
$this->propertyPathToHistoriaClinica = $propertyPathToHistoriaClinica;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::POST_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addHistoriaClinicaForm($form, $paciente_id)
{
$formOptions = array(
'class' => 'BiobancoBundle:HistoriaClinica',
'empty_value' => '-- SELECCIONAR HISTORIA CLINICA --',
'label' => 'Historia Clínica',
'attr' => array(
'class' => 'historia_clinica_selector',
),
'query_builder' => function (EntityRepository $repository) use ($paciente_id) {
$qb = $repository->createQueryBuilder('h')
->innerJoin('h.paciente', 'p')
->where('p.id = :p')
->setParameter('p', $paciente_id)
;
return $qb;
},
);
$form->add($this->propertyPathToHistoriaClinica, 'entity', $formOptions);
}
public function preSetData(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$accessor = PropertyAccess::createPropertyAccessor();
$h = $accessor->getValue($data, $this->propertyPathToHistoriaClinica);
$paciente_id = ($h) ? $h->getPaciente()->getNumeroIdentificacion() : null;
$this->addHistoriaClinicaForm($form, $paciente_id);
}
public function preSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$paciente_id = array_key_exists('paciente', $data) ? $data['paciente'] : null;
//HERE IS WHERE I NEED TO OBTAIN THE DNI, TO PASS IT TO THE FORM
//dump($data);die();
$this->addHistoriaClinicaForm($form, $paciente_id);
}
}
EDIT 1 OF METHOD preSubmit with LifecycleEventArgs :
use Doctrine\ORM\Event\LifecycleEventArgs;
...
public function preSubmit(FormEvent $event, LifecycleEventArgs $args)
{
$data = $event->getData();
$form = $event->getForm();
$paciente_id = array_key_exists('paciente', $data) ? $data['paciente'] : null;
dump($args->getEntityManager()->getRepository("BiobancoBundle:Paciente")->find($paciente_id));die();
$this->addHistoriaClinicaForm($form, $paciente_id);
}
ERROR in line declaration of method:
Catchable Fatal Error: Argument 2 passed to BiobancoBundle\Form\EventListener\AddHistoriaClinicaFieldSubscriber::preSubmit() must be an instance of Doctrine\ORM\Event\LifecycleEventArgs, string given.
create a factory to inject the EntityManager or Repository into your subscriber.
class AddHistoriaClinicaFieldSubscriberFactory
{
public static function create($entityManager)// typehint this
{
// You could retrieve the repo here, so you don't pass the whole em to the instance
$instance = new AddHistoriaClinicaFieldSubscriber($entityManager);
// ...
return $instance;
}
}
Register it
# app/config/services.yml
services:
# ...
app.add_historia_clinica_field_subscriber_factory:
class: YOURNAMESPACE\AddHistoriaClinicaFieldSubscriberFactory
app.add_historia_clinica_field_subscriber:
class: YOURNAMESPACE\AddHistoriaClinicaFieldSubscriber
factory: 'add_historia_clinica_field_subscriber_factory:create'
arguments: ['#doctrine.orm.default_entity_manager']
tags:
- { name: WHATEVERYOUHAVEHERE }
And add a constructor to you Subscriber
class AddHistoriaClinicaFieldSubscriber implements EventSubscriberInterface
{
// ...
protected $entityManager;
public function __construct($entityManager) {// typehint this
$this->entityManager = $entityManager;
}
// ...
}
Let me know if this is clear enough
For more info check: http://symfony2-document.readthedocs.io/en/latest/cookbook/service_container/factories.html
Related
I have entity1 and entity2.
In the entity1's form, I am displaying a choice list where the options are comming from entity2.
I want to save the selected choice as string inside a column in entity1's table, but I dont want to create any relations between the tables.
How Should I do that?
class Entity1 {
/**
* #ORM\Column(type="string")
*/
private $historico;
}
class Entity2 {
/**
* #ORM\Column(type="string")
*/
private $description;
}
Entity1FormType.php
$builder->add('historico', EntityType::class, [
'class' => Entity2::class,
'choice_label' => 'description',
'choice_value' => 'description',
'placeholder' => ''
]);
The choices display fine, but when I submit I get the following error:
Expected argument of type "string", "App\Entity\Entity2" given.
If I use 'mapped' => false, the input submit as null.
How do I convert the entity object to string?
Help a symfony noob :)
If you use mapped => false you have to fetch the data manually in your controller after the form is submitted.
so you will have something like this:
public function postYourFormAction(Request $request)
{
$entity1 = new Entity1();
$form = $this->createForm(Entity1Type::class $entity1);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$entity1 = $form->getData;
$historico = $form->get('historico')->getData();
$entity1->setHistorico($historico);
$em->persist($entity1);
$em->flush();
}
}
This can be done with data transformers so you would not have to unmap fields.
Your form could be as below. Note the choice value getting the string
class OrderType extends AbstractType
{
public function __construct(private ItemToStringTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('itemCode', EntityType::class, [
'class' => Item::class,
'autocomplete' => true,
'required' => true,
'choice_value' => function (?Item $entity) {
return $entity ? $entity->getCode() : '';
},
// validation message if the data transformer fails
'invalid_message' => 'That is not a valid Item Code',
]);
$builder->get('accountCode')->addModelTransformer($this->transformer);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}
And then the data transformer can be as below
class ItemToStringTransformer implements DataTransformerInterface
{
public function __construct(private EntityManagerInterface $entityManager)
{
}
//transforming item object to string
public function reverseTransform($item): ?string
{
if (null === $item) {
return null;
}
return $item->getCode();
}
// transforming string to item object
public function transform($itemCode): ?Item
{
if (!$itemCode) {
return null;
}
$item = $this->entityManager
->getRepository(Item::class)
// query for the glCode with this id
->findOneBy(['code' => $itemCode])
;
if (null === $item) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf('Item code "%s" does not exist!', $itemCode));
}
return $item;
}
}
You can read further in the symfony documentation https://symfony.com/doc/current/form/data_transformers.html#example-2-transforming-an-issue-number-into-an-issue-entity
I'm building an API using FOSRestBundle for adding products to a basket. For the sake of keeping this example simple we have a range of products which come in different sizes.
I'd like to be able to specify the size code in the JSON request. For example:
{
"product": 3,
"size": "S"
}
(I'd also like to use the product code instead of the database ID, but that's for another day!)
Other parts of the project I have done similar tasks using data transformers, but these were simpler forms where the values didn't change based on other fields selected values.
So my current basket form...
class BasketAddType extends AbstractType
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', 'entity', [
'class' => 'CatalogueBundle:Product',
]);
$builder->get('product')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
}
public function onPostSubmit(FormEvent $event)
{
// this will be Product entity
$form = $event->getForm();
$this->addElements($form->getParent(), $form->getData());
}
protected function addElements(FormInterface $form, Product $product = null)
{
if (is_null($product)) {
$sizes = [];
} else {
$sizes = $product->getSizes();
}
$form
->add('size', 'size', [
'choices' => $sizes
]);
}
public function getName()
{
return '';
}
}
The custom size form type I'm using above is so I can add the model transformer. As found in this answer https://stackoverflow.com/a/19590707/3861815
class SizeType extends AbstractType
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new SizeTransformer($this->em));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'class' => 'CatalogueBundle\Entity\Size'
]);
}
public function getParent()
{
return 'entity';
}
public function getName()
{
return 'size';
}
}
And finally the transformer.
class SizeTransformer implements DataTransformerInterface
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function transform($size)
{
if (null === $size) {
return '';
}
return $size->getCode();
}
public function reverseTransform($code)
{
// WE NEVER GET HERE?
$size = $this->em->getRepository('CatalogueBundle:Size')
->findOneByCode($code);
if (null === $size) {
throw new TransformationFailedException('No such size exists');
}
return $size;
}
}
So I did a quick exit; in reverseTransform and it's never fired so I will always get an error on the size element about it being invalid.
What would be the best way to getting a data transformer onto the size field here?
So the problem was that I was using an entity type instead of a text type when using the model data transformer.
Here is my working code albeit probably not perfect, the primary form
class BasketAddType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', 'entity', [
'class' => 'CatalogueBundle:Product'
]);
$builder->get('product')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
}
public function onPostSubmit(FormEvent $event)
{
$form = $event->getForm()->getParent();
$product = $event->getForm()->getData();
$form
->add('size', 'size', [
'sizes' => $product->getSizes()->toArray() // getSizes() is an ArrayCollection
);
}
public function getName()
{
return '';
}
}
My custom form size type which applies the model transformer with the provided size options.
class SizeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new SizeTransformer($options['sizes']));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired([
'sizes'
]);
}
public function getParent()
{
return 'text';
}
public function getName()
{
return 'size';
}
}
And finally the size transformer.
class SizeTransformer implements DataTransformerInterface
{
protected $sizes;
public function __construct(array $sizes)
{
$this->sizes = $sizes;
}
public function transform($size)
{
if (null === $size) {
return '';
}
return $size->getCode();
}
public function reverseTransform($code)
{
foreach ($this->sizes as $size) {
if ($size->getCode() == $code) {
return $size;
}
}
throw new TransformationFailedException('No such size exists');
}
}
This solution wouldn't work too well if there were a high number of sizes available for each product. Guess if that was the case I'd need to pass both the EntityManager and the product into the transformer and query the DB accordingly.
This is a dependent fields
I have a Product entity wich in relation with a Category entity which in relation with a Collection entity
here is my code for the add product form
class ProductsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$propertyPathToCategory = 'category';
$builder
->add('title')
->add('description','textarea')
->add('collection','entity', array(
'class' => 'FMFmBundle:Collections',
'empty_value' => 'Collection',
'choice_label' => 'title'
))
->addEventSubscriber(new AddCategoryFieldSubscriber($propertyPathToCategory));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FM\FmBundle\Entity\Products'
));
}
public function getName()
{
return 'fm_fmbundle_products';
}
}
the AddCategoryFieldSubscriber
//Form/EventListener
class AddCategoryFieldSubscriber implements EventSubscriberInterface
{
private $propertyPathToCategory;
public function __construct($propertyPathToCategory)
{
$this->propertyPathToCategory = $propertyPathToCategory;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addCategoryForm($form, $collection_id)
{
$formOptions = array(
'class' => 'FMFmBundle:Categories',
'empty_value' => 'Category',
'label' => 'Category',
'attr' => array(
'class' => 'Category_selector',
),
'query_builder' => function (EntityRepository $repository) use ($collection_id) {
$qb = $repository->createQueryBuilder('category')
->innerJoin('category.collection', 'collection')
->where('collection.id = :collection')
->setParameter('collection', $collection_id)
;
return $qb;
}
);
$form->add($this->propertyPathToCategory, 'entity', $formOptions);
}
public function preSetData(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$accessor = PropertyAccess::createPropertyAccessor();
$category = $accessor->getValue($data, $this->propertyPathToCategory);
$collection_id = ($category) ? $category->getCollection()->getId() : null;
$this->addCategoryForm($form, $collection_id);
}
public function preSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$collection_id = array_key_exists('collection', $data) ? $data['collection'] : null;
$this->addCategoryForm($form, $collection_id);
}
}
add new action in the controller
public function SelectCategoryAction(Request $request)
{
$collection_id = $request->get('collecton_id');
$em = $this->getDoctrine()->getManager();
$categories = $em->getRepository('FMFmBundle:Categories')->findByCollection($collection_id);
$Jcategories=array();
foreach($categories as $category){
$Jcategories[]=array(
'id' => $category->getId(),
'title' => $category->getTitle()
);
}
return new JsonResponse($Jcategories);
}
add new route for the action
select_category:
path: /selectcategory
defaults: { _controller: FMFmBundle:Product:SelectCategory }
and some ajax
$("#collectionSelect").change(function(){
var data = {
collecton_id: $(this).val()
};
$.ajax({
type: 'post',
url: 'selectcategory',
data: data,
success: function(data) {
var $category_selector = $('#categorySelect');
$category_selector.empty();
for (var i=0, total = data.length; i < total; i++)
$category_selector.append('<option value="' + data[i].id + '">' + data[i].title + '</option>');
}
});
});
References:
Dependent Forms
I'm trying to create a new form type in Symfony 2. It is based on entity type, it uses select2 on frontend and I need the user to be able to select existing entity or create the new one.
My idea was to send entity's id and let it to be converted by the default entity type if user select existing entity or send something like "_new:entered text" if user enter new value. Then this string should be converted to the new form entity by my own model transformer, which should look something like this:
<?php
namespace Acme\MainBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
class EmptyEntityTransformer
implements DataTransformerInterface
{
private $entityName;
public function __construct($entityName)
{
$this->entityName = $entityName;
}
public function transform($val)
{
return $val;
}
public function reverseTransform($val)
{
$ret = $val;
if (substr($val, 0, 5) == '_new:') {
$param = substr($val, 5);
$ret = new $this->entityName($param);
}
return $ret;
}
}
Unfortunately, the transformer is only called when existing entity is selected. When I enter a new value, the string is sent in the request but transformer's reverseTransform method is not called at all.
I'm new to Symfony so I don't even know if this approach is correct. Do you have any Idea how to solve this?
edit:
My form type code is:
<?php
namespace Acme\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Acme\MainBundle\Form\DataTransformer\EmptyEntityTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class Select2EntityType
extends AbstractType
{
protected $router;
public function __construct(Router $router)
{
$this->router = $router;
}
/**
* {#inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
$resolver->setDefaults(array(
'placeholder' => null,
'path' => false,
'pathParams' => null,
'allowNew' => false,
'newClass' => false,
));
}
public function getParent()
{
return 'entity';
}
public function getName()
{
return 's2_entity';
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['newClass']) {
$transformer = new EmptyEntityTransformer($options['newClass']);
$builder->addModelTransformer($transformer);
}
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
$field = $view->vars['name'];
$parentData = $form->getParent()->getData();
$opts = array();
if (null !== $parentData) {
$accessor = PropertyAccess::createPropertyAccessor();
$val = $accessor->getValue($parentData, $field);
if (is_object($val)) {
$getter = 'get' . ucfirst($options['property']);
$opts['selectedLabel'] = $val->$getter();
}
elseif ($choices = $options['choices']) {
if (is_array($choices) && array_key_exists($val, $choices)) {
$opts['selectedLabel'] = $choices[$val];
}
}
}
$jsOpts = array('placeholder');
foreach ($jsOpts as $jsOpt) {
if (!empty($options[$jsOpt])) {
$opts[$jsOpt] = $options[$jsOpt];
}
}
$view->vars['allowNew'] = !empty($options['allowNew']);
$opts['allowClear'] = !$options['required'];
if ($options['path']) {
$ajax = array();
if (!$options['path']) {
throw new \RuntimeException('You must define path option to use ajax');
}
$ajax['url'] = $this->router->generate($options['path'], array_merge($options['pathParams'], array(
'fieldName' => $options['property'],
)));
$ajax['quietMillis'] = 250;
$opts['ajax'] = $ajax;
}
$view->vars['options'] = $opts;
}
}
and then I create this form type:
class EditType
extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('masterProject', 's2_entity', array(
'label' => 'Label',
'class' => 'MyBundle:MyEntity',
'property' => 'name',
'path' => 'my_route',
'pathParams' => array('entityName' => 'name'),
'allowNew' => true,
'newClass' => '\\...\\MyEntity',
))
...
Thanks for your suggestions
I think I found an answer however I'm not really sure if this is the correct solution. When I tried to understand how EntityType works I noticed that it uses EntityChoiceList to retrive list of available options and in this class there is a getChoicesForValues method which is called when ids are transformed to entities. So I implemented my own ChoiceList which adds my own class to the end of the returned array:
<?php
namespace Acme\MainBundle\Form\ChoiceList;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
class EmptyEntityChoiceList
extends EntityChoiceList
{
private $newClassName = null;
public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, array $preferredEntities = array(), $groupPath = null, PropertyAccessorInterface $propertyAccessor = null, $newClassName = null)
{
parent::__construct($manager, $class, $labelPath, $entityLoader, $entities, $preferredEntities, $groupPath, $propertyAccessor);
$this->newClassName = $newClassName;
}
public function getChoicesForValues(array $values)
{
$ret = parent::getChoicesForValues($values);
foreach ($values as $value) {
if (is_string($value) && substr($value, 0, 5) == '_new:') {
$val = substr($value, 5);
if ($this->newClassName) {
$val = new $this->newClassName($val);
}
$ret[] = $val;
}
}
return $ret;
}
}
Registering this ChoiceList to the form type is a bit complicated because the class name of original choice list is hardcoded in the DoctrineType which EntityType extends but it is not difficult to understand how to do it if you have a look into this class.
The reason why DataTransformer is not called probably is that EntityType is capable to return array of results and transform is applied to every item of this collection. If the result array is empty, there is obviously no item to call transformer on.
I had exactly the same question as yours, I chose to use an FormEvent with still a DataTransformer
The idea is to switch the field type (the entity one) just before the submit.
public function preSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (substr($data['project'], 0, 5) == '_new:') {
$form->add('project', ProjectCreateByNameType::class, $options);
}
}
This will replace the project field with a new custom one before the submit if needed.
ProjectCreateByNameType can extend a TextField and have to add the DataTransformer.
I would like to make a request to a database from a subscriber
From a repository: it 's easy I use the following (for exemple entity user where I want to retrieve user with id=1 .
$repository = $this->getDoctrine()->getManager->getRepository('NameBundle:User');
$user = $repository->find(1);
But how can I do the it from a suscriber.
Here what I tried... (without sucess as I got the following from Symfony2: "FatalErrorException: Error: Call to undefined method Sdz\BlogBundle\Form\EventListener\Subscriber::getEntityManager()"
namespace Sdz\BlogBundle\Form\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\ORM\EntityRepository;
class TraductionSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$datas = $event->getData();
$userexist == 'no'
foreach ($datas as $data){
if ($data->getUser()->getId() == 1) { $userexist = 'yes'; $user= $data->getUser(); }
}
if ($userexist == 'no') {
$repository = $this->getEntityManager()->getRepository('SdzBlogBundle:User');
$user = $repository->find(1);
}
$form
->add('notes', 'collection', array(
'type' => new NoteType,
'label' => $user->getName(),
'required' => false,
));
}
}
Just pass it to constructor (if it's not a service):
class TraductionSubscriber implements EventSubscriberInterface
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
...
and use:
new TraductionSubscriber($em);
You can get Doctrine object via container.
use Symfony\Component\DependencyInjection\ContainerInterface;
[...]
$this->container->get('doctrine')->getManager();
I have a user edit form where I would like to administer the roles assigned to a user.
Currently I have a multi-select list, but I have no way of populating it with the role hierarchy defined in security.yml.
Is there some way that I get this information to the form builder in the FormType class?
$builder->add('roles', 'choice', array(
'required' => true,
'multiple' => true,
'choices' => array(),
));
Looking around I found that I can get the roles from the container in a controller with:
$roles = $this->container->getParameter('security.role_hierarchy.roles');
I have also discovered that I could potentially set this as a dependency to be injected on the FormType class in services.xml:
<parameters>
<parameter key="security.role_heirarchy.roles">ROLE_GUEST</parameter>
</parameters>
<services>
<service id="base.user.form.type.user_form" class="Base\UserBundle\Form\UserType" public="false">
<tag name="form.type" />
<call method="setRoles">
<argument>%security.role_heirarchy.roles%</argument>
</call>
</service>
</services>
This however does not work and does not seem to ever call the setRoles method.
So how can I get this to work?
In your controller
$editForm = $this->createForm(new UserType(), $entity, array('roles' => $this->container->getParameter('security.role_hierarchy.roles')));
In UserType :
$builder->add('roles', 'choice', array(
'required' => true,
'multiple' => true,
'choices' => $this->refactorRoles($options['roles'])
))
[...]
public function getDefaultOptions()
{
return array(
'roles' => null
);
}
private function refactorRoles($originRoles)
{
$roles = array();
$rolesAdded = array();
// Add herited roles
foreach ($originRoles as $roleParent => $rolesHerit) {
$tmpRoles = array_values($rolesHerit);
$rolesAdded = array_merge($rolesAdded, $tmpRoles);
$roles[$roleParent] = array_combine($tmpRoles, $tmpRoles);
}
// Add missing superparent roles
$rolesParent = array_keys($originRoles);
foreach ($rolesParent as $roleParent) {
if (!in_array($roleParent, $rolesAdded)) {
$roles['-----'][$roleParent] = $roleParent;
}
}
return $roles;
}
You can make your own type and then pass service container, from which you can then retrieve role hierarchy.
First, create your own type:
class PermissionType extends AbstractType
{
private $roles;
public function __construct(ContainerInterface $container)
{
$this->roles = $container->getParameter('security.role_hierarchy.roles');
}
public function getDefaultOptions(array $options)
{
return array(
'choices' => $this->roles,
);
);
public function getParent(array $options)
{
return 'choice';
}
public function getName()
{
return 'permission_choice';
}
}
Then you need to register your type as service and set parameters:
services:
form.type.permission:
class: MyNamespace\MyBundle\Form\Type\PermissionType
arguments:
- "#service_container"
tags:
- { name: form.type, alias: permission_choice }
Then during creation of form, simply add *permission_choice* field:
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('roles', 'permission_choice');
}
If you want to get only single list of roles without hierarchy, then you need to flat hierarchy somehow. One of possible solution is this:
class PermissionType extends AbstractType
{
private $roles;
public function __construct(ContainerInterface $container)
{
$roles = $container->getParameter('security.role_hierarchy.roles');
$this->roles = $this->flatArray($roles);
}
private function flatArray(array $data)
{
$result = array();
foreach ($data as $key => $value) {
if (substr($key, 0, 4) === 'ROLE') {
$result[$key] = $key;
}
if (is_array($value)) {
$tmpresult = $this->flatArray($value);
if (count($tmpresult) > 0) {
$result = array_merge($result, $tmpresult);
}
} else {
$result[$value] = $value;
}
}
return array_unique($result);
}
...
}
The solution with $options array provided by webda2l does not work with Symfony 2.3. It gives me an error:
The option "roles" do not exist.
I found that we can pass parameters to the form type constructor.
In your controller :
$roles_choices = array();
$roles = $this->container->getParameter('security.role_hierarchy.roles');
# set roles array, displaying inherited roles between parentheses
foreach ($roles as $role => $inherited_roles)
{
foreach ($inherited_roles as $id => $inherited_role)
{
if (! array_key_exists($inherited_role, $roles_choices))
{
$roles_choices[$inherited_role] = $inherited_role;
}
}
if (! array_key_exists($role, $roles_choices))
{
$roles_choices[$role] = $role.' ('.
implode(', ', $inherited_roles).')';
}
}
# todo: set $role as the current role of the user
$form = $this->createForm(
new UserType(array(
# pass $roles to the constructor
'roles' => $roles_choices,
'role' => $role
)), $user);
In UserType.php :
class UserType extends AbstractType
{
private $roles;
private $role;
public function __construct($options = array())
{
# store roles
$this->roles = $options['roles'];
$this->role = $options['role'];
}
public function buildForm(FormBuilderInterface $builder,
array $options)
{
// ...
# use roles
$builder->add('roles', 'choice', array(
'choices' => $this->roles,
'data' => $this->role,
));
// ...
}
}
I took the idea from Marcello Voc, thanks to him!
For to symfony 2.3:
<?php
namespace Labone\Bundle\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class PermissionType extends AbstractType
{
private $roles;
public function __construct(Container $container)
{
$roles = $container->getParameter('security.role_hierarchy.roles');
$this->roles = $this->flatArray($roles);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->roles
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'permission_choice';
}
private function flatArray(array $data)
{
$result = array();
foreach ($data as $key => $value) {
if (substr($key, 0, 4) === 'ROLE') {
$result[$key] = $key;
}
if (is_array($value)) {
$tmpresult = $this->flatArray($value);
if (count($tmpresult) > 0) {
$result = array_merge($result, $tmpresult);
}
} else {
$result[$value] = $value;
}
}
return array_unique($result);
}
}