FOSUserBundle show group roles in edit view - symfony

I'm trying by overriding the controller and the formtype, to show the roles from the selected group in my view, but I don't find the right way. I've followed the steps to override everything, that works, but problem comes when I try to say to the service that I'm passing to the constructor an entity object.
As the formtype has to be overridden, how to pass through the service that you need to implement, my Group entity?
Does anyone have an idea of how to achieve that?
Here's what I've done:
Override the controller, and when creating the Form, pass the $group entity
$formFactory = $this->container->get('fos_user.group.form.factory');
$form = $formFactory->createForm($group); //Here
Override the form, and use a custom __construct method where I can pass my entity (maybe here is my error and that should be done in a better or other way)
public function __construct(Container $container, Groups $group)
{
$this->container = $container;
$this->roles = array_keys($this->container->getParameter('security.role_hierarchy.roles'));
$this->group = $group; #How?
}
The container to get the keys for the roles is passed without errors, that works.
Create the service as the documentation says (here comes the real problem and the exceptions)
x_s_cosmos.group.form.type:
class: X\S\CosmosBundle\Form\Type\GroupFormType
arguments: [#service_container, here_should_be_my_entity?]
tags:
- { name: form.type, alias: kosmos_group_form }
I'm really stacked with that and don't have any idea of how to go on.

Finally, after overriding the GroupController.php and adding a choice field type to my form, I could achieve my goal.
$form->add('roles', 'choice', array(
'choices' => $this->getExistingRoles(),
'data' => $group->getRoles(),
'label' => 'Roles',
'expanded' => true,
'multiple' => true,
'mapped' => true,
));
Where getExistingRoles() is:
$roleHierarchy = $this->container->getParameter('security.role_hierarchy.roles');
$roles = array_keys($roleHierarchy);
foreach ($roles as $role) {
$theRoles[$role] = $role;
}
return $theRoles;
I was just going in the wrong way, it was not so difficult to get the roles of a group and show them in an admin interface so that you can choose one of your system roles and give it to the group. No need to override the FormType, just the controller to add your own fields to the form.
Hope it helps, as it has given lot of headache to me.

You should not pass an entity to the constructor. If you need to access entity inside the form, you have to add an event listener to the form builder, like this:
public function buildForm(FormBuilder $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$entity = $event->getData();
// This is your Group entity, now do something with it...
if ($entity instanceof Group) {
// $form->add ...
}
});
}

Related

Pre-populate form value of unmapped field

I have a form that is bound to an entity, but it also has an extra unmapped field:
(from the FormType class)
$builder
->add('name')
->add('qoh')
->add('serialNumber', 'text', array('mapped' => false, 'required' => false))
I want to pre-populate the serialNumber field from the controller with information taken from the request URL. The closest method I have found would be:
$form->setData(mixed $modelData)
but the API does not specify what form '$modelData' takes and nothing I've tried has had any effect.
Someone on Symfony's IRC channel gave me this answer, and they declined to post it here:
$form->get('serialNumber')->setData($serial_number);
You can use Form Events. For example if you want to set the data from the database to a non mapped field you can use POST_SET_DATA:
class AddNonMappedDataSubscriber implements EventSubscriberInterface
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::POST_SET_DATA => 'postSetData'
);
}
public function postSetData(FormEvent $event){
$form = $event->getForm();
$myEntity = $event->getData();
if($myEntity){
$serialNumber = $myEntity->getNumber();
$form->get('serialNumber')->setData($serialNumber);
}
}
}
You can pre-populate the field in twig (Set default value of Symfony 2 form field in Twig).
...
{{ form_widget(form.serialNumber, { value : serialNumber }) }}
...

Symfony2 Forms - How to use parametrized constructors in form builders

I am learning to use Symfony2 and in the documentation I have read, all entities being used with Symfony forms have empty constructors, or none at all. (examples)
http://symfony.com/doc/current/book/index.html Chapter 12
http://symfony.com/doc/current/cookbook/doctrine/registration_form.html
I have parametrized constructors in order to require certain information at time of creation. It seems that Symfony's approach is to leave that enforcement to the validation process, essentially relying on metadata assertions and database constraints to ensure that the object is properly initialized, forgoing constructor constraints to ensure state.
Consider:
Class Employee {
private $id;
private $first;
private $last;
public function __construct($first, $last)
{ .... }
}
...
class DefaultController extends Controller
{
public function newAction(Request $request)
{
$employee = new Employee(); // Obviously not going to work, KABOOM!
$form = $this->createFormBuilder($employee)
->add('last', 'text')
->add('first', 'text')
->add('save', 'submit')
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
}
Should I not be using constructor arguments to do this?
Thanks
EDIT : Answered Below
Found a solution:
Looking into the API for the Controllers "createForm()" method I found something that is not obvious from the examples. It seems that the second argument is not necessarily an object:
**Parameters**
string|FormTypeInterface $type The built type of the form
mixed $data The initial data for the form
array $options Options for the form
So rather than pass in an instance of the Entity, you can simply pass in an Array with the appropriate field values:
$data = array(
'first' => 'John',
'last' => 'Doe',
);
$form = $this->createFormBuilder($data)
->add('first','text')
->add('last', 'text')
->getForm();
Another option (which may be better), is to create an empty data set as a default option in your Form Class.
Explanations here and here
class EmployeeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('first');
$builder->add('last');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'empty_data' => new Employee('John', 'Doe'),
));
}
//......
}
class EmployeeFormController extends Controller
{
public function newAction(Request $request)
{
$form = $this->createForm(new EmployeeType());
}
//.........
}
Hope this saves others the head scratching.

Symfony2: adding form fields dynamically using information from request

Scenario:
step 1: fetch information from some archaic web service
step 2: adding x radio buttons to my form, while x depends on the information from the web service
I understand that I should add an Event Subscriber Class as shown in the documentation
$event has a setData method. Probably I have to use it. How can I access this method from the controller?
Additional information:
I am using Doctrine. At the moment I am creating a Doctrine entity and pass this to the form like this:
$product = new Product(); $form = $this->createForm(new ProductType(), $product);
This is my solution I ended up with. Not sure if this is the best way, so this answer is not marked as correct to encourage other, more experience Symfony user to come up with a better solution.
What I don't like about this solution, is that I cannot pass my whole product entity to the form any more, but have to add each attribute individually to the options array:
Anyway, that's how it is implemented now:
The dynamic data is passed using the options array as second parameter to createForm():
// ExampleController.php
use Acme\ExampleBundle\Entity\Product;
public function addAction()
{
// choices contains the dynamic data I am fetching from the webservice in my actual code
$choices = array("key1" => "value1", "key2" => "value2");
// now create a new Entity
$product = new Product();
// and some attributes already have values for some reason
$product->setPrice(42);
$form = $this->createForm(new AddproductType(),
array(
"choices" => $choices,
"price" => $product->getPrice()
)
);
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
// ... standard symfony stuff here
}
}
return array(
'form' => $form->createView()
);
}
And now for the form class. Note the event listener part and the setDefaultOptions method.
// ProductType.php
namespace Acme\ExampleBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$formFactory = $builder->getFormFactory();
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (\Symfony\Component\Form\FormEvent $event) use ($formFactory) {
// get the option array passed from the controller
$options = $event->getData();
$choices = $options["choices"];
$event->getForm()->add(
// add a new element with radio buttons to the form
$formFactory->createNamed("my_dynamic_field_name", "choice", null, array(
"empty_value" => "Choose an option",
"label" => "My dynamic field: ",
"choices" => $choices
)
)
);
}
);
// ... add other form elements
$builder->add("price", "text");
// ..
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
// register choices as a potential option
$resolver->setDefaults(array(
'choices' => null
));
}
}
You don't have to setData() on the $event object. You've to implement an on POST_SET_DATA logic that builds your form the way you want according to you data.
You've then to initialize your form (inside your controller) using your webservice's reply.
(Note: SET_DATA form event is deprecated since version 2.1. It'll be definitely removed in version 2.3)
Update:
You can set an array as form data and use a DataTransformer to structure your form's data the way you want. Take a look at the Data Transformation part of Symfony 2 Form Tricks during the last San Francisco Symfony Live.

Access currently logged in user in EntityRepository

I want to create a simple blog example where users have a favourite category attached to there account. This means the can only write articles for this category. (Some of them - mostly admins -will get the opportunity to switch categories, but that's not the problem... for now ^^)
So I first created a relation between the User- and the Category entity. Everything works fine. Each user now has a main category selected.
Only thing which bothers me, is that I cant get hold of the current logged in user in the EntityType (formbuilder) and EntityRepository classes.
In my "New Post" form there are relations to other entities (e.g. Tags). I use the 'entity' formtype in the EntityType class to generate these form elements. Now i wan't to filter the tags, to only allow tags which have the same category relation as the currently logged in users category to be selectable.
I tried to use the query_builder option from the entity formtype. But as i can't get the current user object, I don't know which category he has selected. Same problem with the EntityRepository.
Now I could filter the tags already in the PostController but the problem is, that I will need this over and over again. And therefore I don't wan't to code this everytime I add something new.
I thought it would be the best to place this filter in the EntityRepository. So I can always access the findAllByCategory. But I need the user-object in there.
What is the best way to accomplish this? Have searched a lot, but either I searched for the wrong terms or no one has this problem :)
You can get user object from Security Context
$user = $serviceContainer->get('security.context')->getToken()->getUser();
Small tip: In case if user is not logged in - you'll have string in $user, otherwise - User object.
Cheers ;)
You can inject security context in your form type defined as a service. Then in your tags field use the query builder with $user (current logged user) to filter tags which have the same category relation as the currently logged:
/** #DI\Service("form.type.post") */
class PostType extends \Symfony\Component\Form\AbstractType
{
/**
* #var \Symfony\Component\Security\Core\SecurityContext
*/
protected $securityContext;
/**
* #DI\InjectParams({"securityContext" = #DI\Inject("security.context")})
*
* #param \Symfony\Component\Security\Core\SecurityContext $context
*/
public function __construct(SecurityContext $securityContext)
{
$this->securityContext = $securityContext;
}
public function buildForm(FormBuilder $builder, array $options)
{
$user = $this->securityContext()->getToken()->getUser();
$builder->add('tags', 'entity', array(
'label' => 'Tags',
'class' => 'Acme\HelloBundle\Entity\Tag',
'property' => 'name',
'query_builder' => function(EntityRepository $er) use($user) {
return $er->getAllSuitableForUserChoiceQueryBuilder($user);
},
'multiple' => true,
'expanded' => true,
}
}
Filter tags into your repository:
class TagRepository extends EntityRepository
{
public function getAllSuitableForUserChoiceQueryBuilder(User $user)
{
// Filter tags that can be selected by a given user
}
}
tried to use the query_builder option from the entity formtype. But as i can't get the current user object, i don't know which category he has selected. Same problem with the EntityRepository.
As long the Category has a relation to the user, you should be able to get the User there.
For Example:
Controller
$someThing = new SomeThing();
$someThing->setUser($user);
$form = $this->createForm(new someThingType(), $someThing);
Form
$someThing = $options['data'];
$user = $someThing->getUser();
$builder->add('category', null, array(
'class' => 'MyBundle:Cateogry',
'query_builder' =>
function(EntityRepository $er) use ($user) {
return $er->getCategoriesForUser($user);
}
));
Repository
public function getCategoriesForUser($user)
{
$qb = $this->createQueryBuilder('c');
$qb->leftJoin('c.user', 'u', 'with', 'u.user = :user');
$qb->setParameter('user', $user)
;
return $qb;
}
this is not exactly your use-case but pretty similar to it. maybe it helps you.

Symfony2 + Doctrine2 / building a form from 2 joined entity objects

Is it possible to build a form from 2 joined entity objects?
I have two entities property & propertylanguage which are joined on onetomany relation.
(One property can have many languages)
Language has a title and description colomns.
So one property can have a english, french, german title.
I am trying to build a form out of that.
See below.
Controller: addProperty.php
class AddPropertyController extends Controller
{
// ....
public function indexAction(Request $request)
{
$property = new property;
$language = new propertyLanguage;
$property ->addpropertylanguage($language);
$form = $this->createForm(new propertyType($this->getDoctrine()),$property);
// .....
}
Form type: propertType.php
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('title', 'text');
// other ->add() below.
}
It returns the following error:
Neither property "title" nor method "getTitle()" nor method
"isTitle()" exists in class "\defaultBundle\Entity\property"
Of course there is no property Title in property, but there is one in propertylanguage..
Even if I try:
->add('title', 'entity', array('class'=>defaultBundle:propertylanguage));
it doesn't work.
Thanks if you have time to help me.
Best,
Pierre.
What you will want to do is make a PropertyLanguageType class as well as a PropertyType.
Then, in your PropertyType you will embed the PropertyLanguageType:
public function buildForm(FormBuilder $builder, array $options)
{
// $builder->add('propertyLanguage', new PropertyLanguageType());
// Since we have a 1 to many relation, then a collection is needed
$builder->add('propertyLanguage', 'collection', array('type' => new PropertyLanguageType()));
The PropertyLanguageType is where you add the title.
It's all in the forms section in the manual but it might take several readings.
A second approach is to add a getTitle to your Property entity which would return the title from the PropertyLanguage entity. By doing this, your original form will work. But it can be a bit of a mess when you start to have multiple associations with multiple attributes. Best to just define a type for each entity.
You could use a query_builder when defining the form. Here's how your form class might look like. Of course it will certainly not be exactly as so but that will give you a good start ;)
public function __construct($id)
{
$this->propertylanguageId = $id;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('propertylanguage', 'entity', array(
'label' => 'Property Language',
'class' => 'YourAdressToBundle:Propertylanguage',
'query_builder' => function(EntityRepository $er) use ($propertylanguageId) {
return $er->createQueryBuilder('p')
->join('p.property', 'prop', Expr\Join::WITH, 'prop.id = :propertylanguageId')
->setParameter('propertylanguageId', $propertylanguageId);
},
));
}
Hope that'll be helpful

Resources