Symfony2 FOSUserBundle: accessing DB inside profile build form - symfony

I'm using FOSUserBundle on Symfony2.
I've extended the profile form type to include my fields.
I'd like to pre-populate one of those fields with one value found in DB (not in the user entity).
Basically I need to access DB from inside the buildForm.
I don't want (if possible) to override the original controller.
EDIT: I probably cannot use the "entity" field type as that (as far as I understand) creates the equivalent of a choice (with values loaded from DB). I need to have the field editable. I need to have access to the current user entity so that I have access to its ID. With that ID I can perform a query and get a text value from my DB (it's a license associated to the user) and use that value to populate one of the text fields of my form. Could I possibly override the getLicense() method of the user class to perform my queries there? How can I have access to DB inside an entity?
Hints?
Thank you!

You need the Entity field:
http://symfony.com/doc/current/reference/forms/types/entity.html
Here's an example:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('foobar', 'entity', array(
'class' => 'DummyBundle:Test',
'property' => 'name',
'multiple' => false,
'expanded' => false,
'query_builder' => function(\Doctrine\ORM\EntityRepository $er) {
return $er->createQueryBuilder('i')
->orderBy('i.name', 'ASC');
}
));
}

Related

How to use KNP Translatable in a form type

I'm using KNP Translatable and I have the following data structure:
User (id, name, email, password...)
Role (id, name #translatable)
User Role is a many to many relation.
I have the form type defined as this:
->add('roles', 'entity', [
'class' => 'SocialCarBackendBundle:Role',
'property' => 'name',
'multiple' => true,
'expanded' => true
])
And I implemented the __call method in the role entity:
public function __call($method, $arguments)
{
try {
return $this->proxyCurrentLocaleTranslation($method, $arguments);
} catch (\Symfony\Component\Debug\Exception\ContextErrorException $e) {
return $this->proxyCurrentLocaleTranslation('get' . ucfirst($method), $arguments);
}
}
Now, in the twig template I can call the name property of the roles without problems and it renders it correctly.
But when trying to render the form I get this error:
Neither the property "name" nor one of the methods "getName()",
"name()", "isName()", "hasName()", "__get()" exist and have public
access in class "SocialCar\BackendBundle\Entity\Role".
Is there any workaround for this? Thanks a lot
symfony's propertyaccessor component has not magic calls enabled for EntityType property
you can see vendor/symfony/symfony/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php to prove that.
so you have three ways(in order of complexity):
do getter and setters that call proxyCurrentLocaleTranslation, imho there are nothing bad using less magic things:)
use a more complex property like this
'property' => 'translations[' . $options['locale'] . '].name',
where $options['locale'] is the locale injected inside the form as an option
you can create a different EntityType class that extends your custom DoctrineType class that initializes PropertyAccessor to support magic calls
for more info about property accessor:
http://symfony.com/doc/current/components/property_access/introduction.html
and about the second way:
https://github.com/KnpLabs/DoctrineBehaviors/issues/67

Add custom validator on unmapped field, but with context of whole form submission?

tl;dr:
I need a custom validation constraint to run on an unmapped form field
I need the ID of the object the form is manipulating to eliminate it from consideration doing my validation constraint
Attaching the validation to the form itself or the unmapped field doesn't give me enough context to run my validation query
I have an unmapped field on my Person entity form that I need to run a validation on. I've followed this great article on how to do this, but my use case is slightly different and not entirely covered by the article.
I am making my own Unique Constraint that needs to run a custom query to determine the uniqueness. To run the query, I need access to the field value that was submitted, as well as the original Person object (so I can get it's ID if it's an update operation). Without also having the that Person object I won't be able to eliminate it from consideration during the uniqueness query.
If I apply the validator on the PersonType class like so:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver
->setDefaults(array(
'data_class' => 'CS\AcmeBundle\Entity\Person',
'constraints' => array(
new MyUniqueValidator(),
)
))
;
}
Then the validator gets passed the entire Person object to perform the validation on. This doesn't help me, because the submitted form data is not persisted to the Person object (it's an unmapped field that I handle after $form->isValid() is called in the controller).
If I apply the validator to the unmapped field directly instead:
$builder
->add('myUnmappedField', 'text', array(
'mapped' => false,
'constraints' => array(
new MyUniqueValidator(),
)
),
))
Then the object I get passed to the validator is just the standalone form text, and nothing else. I don't have the ID Person object (if it was an update operation) to perform by uniqueness query.
Hopefully I've explained this properly. Do I have any options to do this sort of validation gracefully?
You say you have unmapped field. Would it help, if you make it mapped to the Person entity? Just make a new property in the Person class with getter and setter methods, but not to the ORM, since you don't want it persisted.
If you do not want to polute your Person class, you can also make another composite class, which will hold your currently unmapped field and a Person object (you will then make it mapped). Ofcourse you will then set data_class to match the new object's namespace.
Both above solutions should work with the upper code you have there. Please let me know it it helped.
You can achieve this by using a callback constraint with a closure.
To access your Person entity, you will need to add the field via an event listener.
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$person = $event->getData();
$form->add('myUnmappedField', TextType::class, [
'mapped' => false,
'constraints' => [
new Symfony\Component\Validator\Constraints\Callback([
'callback' => function ($value, ExecutionContextInterface $context) use ($person) {
// Here you can use $person->getId()
// $value is the value of the unmapped field
}
])
],
]);
});

symfony2 Entity select tag display

I have this Symfony form with a ManyToMany relation working fine, it displays all the parties with the property name on the entity Party.
When submitted, it queries the database according to the parties that are selected and retrieves the persons that are invited to those parties.
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('parties', 'entity', array(
'class' => 'ProtoBundle:Party',
'multiple' => true,
'expanded' => false,
'property' => 'name',
'required' => false,));
}
with the parameter
'multiple' => 'true,
all the parties are displayed at the same time in a select drop down box (not what i want).
What I want is just to have one select tag with parameter
'empty_value' => 'choose a party'
, then when the user clicks on it, the values are displayed. Actually I can do this with the parameter
'multiple'=> false,
but the problem is that I get this error message:
Neither the property "parties" nor one of the methods "setParties()", "__set()" or "__call()" exist and have public access in class "Acme\ProtoBundle\Entity\Person".
Does anyone knows how to make this select tag working and bring me a detailed solution?
In first of all you should get in consideration that if you really need many to many relation when you want simple select box without multiple select.
But...
in entity you will have to check if comming value is array, and that's it:
public function setParties($parties)
{
if (!is_array($parties)) {
$parties = array($parties);
}
$this->parties = $parties;
}

Setting a default entity value

I'm trying to set an entity form type to have a default value, i.e the user that is currently logged in.
My code is as follows:
EventType.php
->add('forUser', 'entity', array(
'label' => 'Staff',
'class' => 'BMUserBundle:User'
'property' => 'fullName',
'empty_value' => 'Select Staff'.
'query_builder' => function(EntityRepository $er) {
return $er->findAllStaff();
}
))
This works fine and returns all th staff members in the drop down.
I have also tried to do the following:
$form = $this->createForm(new EventType());
$form->get('forUser')->setData("USER_ENTITY") -> entity of logged in user
This doesn't seem to affect the form and it just lists the users with no default selected.
Am I approaching this correctly?
Thanks
When you instanciate your entity type in the controller, you first have to create a Event object with the user set for the "forUser" relation. Then you just have to pass as 2nd argument your Event object and it will automatically populate the type with.
Here is a small code example to show you how to do :
/src/Your/AppBundle/Controller/YourController.php
// get the authenticated user
$user = $this->get('security.context')->getToken()->getUser();
$event = new Event();
$event->setForUser($user);
$form = $this->createForm(new EventType(), $event);
But as forgottenbas said, you have to verify that your EventType has well the data_class option with your Event Classname as value. You'll get more information about it here.
If you let me tell you an off topic advice, you shouldn't use Event as ClassName because the event concept is well too present in Symfony2 development so it'll only obfuscate the code.
Good luck for your project, I hope it'll help you.

How do I add an unbound field to a form in Symfony which is otherwise bound to an entity?

Maybe I'm missing the obvious but how do I (or can I) add an extra "unbound" field to a Symfony form that is otherwise bound to an entity?
Let's say I have an entity with fields first_name and last_name. I do the typical thing in my form class buildForm method.
$builder
->add('first_name')
->add('last_name')
;
and this in my controller:
$editForm = $this->createForm(new MyType(), $entity);
That works nicely but I'd like to add another text box, let's call it "extra", and receive the value in the POST action. If I do $builder->add('extra')‍, it complains that
NoSuchPropertyException in PropertyAccessor.php line 479:
Neither the property "extra" nor one of the methods "getExtra()", "extra()", "isExtra()", "hasExtra()", "__get()" exist and have public access in class...
Which is correct. I just want to use it to collect some extra info from the user and do something with it other than storing it with the entity.
I know how to make a completely standalone form but not one that's "mixed".
Is this possible?
In your form add a text field with a false property_path:
$builder->add('extra', 'text', array('property_path' => false));
You can then access the data in your controller:
$extra = $form->get('extra')->getData();
UPDATE
The new way since Symfony 2.1 is to use the mapped option and set that to false.
->add('extra', null, array('mapped' => false))
Credits for the update info to Henrik Bjørnskov ( comment below )
Since Symfony 2.1, use the mapped option:
$builder->add('extra', 'text', [
'mapped' => false,
]);
According to the Documentation:
allow_extra_fields
Usually, if you submit extra fields that aren't configured in your form, you'll get a "This form should not contain extra fields." validation error.
You can silence this validation error by enabling the allow_extra_fields option on the form.
mapped
If you wish the field to be ignored when reading or writing to the object, you can set the mapped option to false.
class YourOwnFormType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'allow_extra_fields' => true
)
);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$form = $builder
->add('extra', TextType::class, array(
'label' => 'Extra field'
'mapped' => false
))
;
return $form;
}
}

Resources