Set data for Embed Form in Symfony2 - forms

I'm trying to set data for contact_phone and contact_postal_address type in an embed form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('phone', 'contact_phone')
->add('postal', 'contact_postal_address');
}
But as I know, when creating the form, I can set only 1 object for the form like this
$form = $this->createForm('address', $address); //address is the embed form
How can I do to set data for each child type in address type?

Create a form-specific model that you use for this form, for example:
class ModelForForm
{
public $phone;
public $postal;
}
You can add any assertions here as well if you're using annotations, such as Valid().
Then you can populate this new model with your data:
$model = new ModelForForm();
$model->postal = $address;
// ...
$form = $this->createForm('address', $model);
Make sure you update your form type's data_class option to be the name of the class you create above.

Related

Symfony2 Entity Form Type gets data

I have 2 entities: Audio and Destination
In Audio:
/**
* #ORM\OneToOne(targetEntity="HearWeGo\HearWeGoBundle\Entity\Destination", inversedBy="audio")
* #Assert\NotBlank(message="This field must be filled")
*
*/
private $destination;
I created a Form Type name AddAudioType used to upload an audio to database
<?php
namespace HearWeGo\HearWeGoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use HearWeGo\HearWeGoBundle\Entity\Audio;
class AddAudioType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name','text')
->add('content','file')
->add('destination','entity',array('class'=>'HearWeGoHearWeGoBundle:Destination','property'=>'name'))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class'=>"HearWeGo\\HearWeGoBundle\\Entity\\Audio"));
}
public function getName()
{
return 'add_audio';
}
}
?>
In Controller
/**
* #Route("/admin/add/audio",name="add_audio")
*/
public function addAudioAction(Request $request)
{
if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')){
return new Response('Please login');
}
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');
$audio=new Audio();
$form=$this->createForm(new AddAudioType(),$audio,array(
'method'=>'POST',
'action'=>$this->generateUrl('add_audio')
));
$form->add('submit','submit');
if ($request->getMethod()=='POST')
{
$form->handleRequest($request);
if ($form->isValid())
{
$destination=$this->getDoctrine()->getRepository('HearWeGoHearWeGoBundle:Destination')
->findByName($form->get('destination')->getData()->getName());
$audio->setDestination($destination);
$name=$_FILES['add_audio']['name']['content'];
$tmp_name=$_FILES['add_audio']['tmp_name']['content'];
if (isset($name))
{
if (!empty($name))
{
$location=$_SERVER['DOCUMENT_ROOT']."/bundles/hearwegohearwego/uploads/";
move_uploaded_file($tmp_name,$location.$name);
$audio->setContent($location.$name);
$em=$this->getDoctrine()->getEntityManager();
$em->persist($audio);
$em->flush();
return new Response('Audio '.$audio->getName().' has been created!');
}
}
}
}
return $this->render('#HearWeGoHearWeGo/manage/addAudio.html.twig',array('form'=>$form->createView()));
}
In AddAudioType, I declared so that it gets all records from Destination entity table and allows user to choose one of them, then persist it to database
Now there's something another I have to handle: Because relationship between Audio and Destination is one-to-one, user is not allowed to choose a Destination which already appeared in Audio table. Now in AddAudioType, I don't want to get all records from Destination table, but only some that hasn't appeared in Audio table yet. How should I do it?
When you do in your form builder
->add('destination', 'entity', array(
'class'=>'HearWeGoHearWeGoBundle:Destination',
'property'=>'name'
));
you're saying that you want all of possible Destination entities
If you want to filter them, you have two possibilities
First one (recommended)
Write your own method to exclude already "associated" Destinations into DestionationRepository. If you don't know what is a repository or you don't know how to write one, please refer to this document. Method implementation is left to you as an exercise (No, really, I don't know all entities so I cannot make any guess)
Once you've done this, you have to pass DestinationRepository to your form, as an option (required I suppose [see setRequired() method below]), so, something like this (I'll omit uninteresting code)
//AddAudioType
<?php
[...]
public function buildForm(FormBuilderInterface $builder, array $options)
{
$destination_repo = $options['dr'];
$builder->[...]
->add('destination','entity',array(
'class'=>'HearWeGoHearWeGoBundle:Destination',
'choices'=> $destination_repo->yourCustomRepoFunctionName(),
'property'=>'name'));
}
$resolver->setRequired(array(
'dr',
));
Now that you have setted all for your form, you need to pass DestinationRepository to your form. How do you that?
It's quite simple indeed
//In controller you're instatiating your form
[...]
public function addAudioAction()
{
[...]
$destination_repo = $this->getDoctrine()
->getManager()
->getRepository('HearWeGoHearWeGoBundle:Destination');
$form=$this->createForm(new AddAudioType(), $audio, array(
'method' => 'POST',
'action' => $this->generateUrl('add_audio'),
'dr' => $destination_repo,
));
}
It's going to work like a charm as long as you write a good "filter" method (ie.: you exlude with NOT IN clause all Destinations that got the key into other table)
Second one
You simply write your method into the form
//AddAudioType
use Doctrine\ORM\EntityRepository;
<?php
[...]
public function buildForm(FormBuilderInterface $builder, array $options)
{
$destination_repo = $options['dr'];
$builder->[...]
->add('destination','entity',array(
'class'=>'HearWeGoHearWeGoBundle:Destination',
'choices'=> function(EntityRepository $repository) use ($someParametersIfNeeded) {
return $repository->createQueryBuilder('d')
->[...];},
'property'=>'name'));
}
In this second case, createQueryBuilder is also not implemented and left to you. One thing you need to remember: choices will need a query builder, so don't call ->getQuery() and ->getResult()
Why fist one?
Custom function should always stay within repos. So you are writing some code into the place that has to be (see points below to know way)
Because code, that way, is reusable (DRY principle)
Because you can test code more easily
Custom repo function
public function findDestinationWithoutAudio() {
$query= "SELECT d
FROM HearWeGoHearWeGoBundle:Destination d
WHERE d NOT IN (SELECT IDENTITY(a.destination)
FROM HearWeGoHearWeGoBundle:Audio a)"
;
return $this->getEntityManager()->createQuery($query)->getResult();
}
If you want to know why you should use IDENTITY() function and not foreign key directly: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#dql-functions

Symfony 2 : bind request to form object containing objects

I am trying to do something that I am not sure it is possible to do.
Here is my form object :
class DeclarationForm {
private $string1;
private $paramObject;
}
Here is the Param Object :
class Param {
private $id;
private $name;
}
I wanted the form to display a select for the 'ParamObject' field in the creation phase
public function buildForm(FormBuilderInterface $builder, array $options) {
$phase = intval($this->options['phase']);
if($phase === 0) {
$params_qualities = $this->options['params_qualities'] // this is an array of Param Objects;
$qualities = new ObjectChoiceList($params_qualities, 'name', array(), null, 'id');
$builder->add('paramObject', 'choice', array(
'required' => true,
'choice_list' => $qualities
));
}
...
}
it works fine and of course, when I submit the form only the id of the selected option is put in the request.
The problem is when I use $form->handleRequest($request); in the controller, it tries to put a string (the id value) in a Param Object of my DeclarationForm.
Is it even possible to get the label of the selected option in the request to populate the Param Object when handleRequest tries to bind the request to the object ?
How to do that ?
Thank you
It is possible, but not with just one Form. In Symfony, each individual Form has a single backing data object (if at all), and each HTML field corresponds to a single member of that object (apart from special cases like dates and entities). However, one of the Field Types you can use is Form, representing a child Form with its own data object.
In your case, that means doing something like this:
class DecorationFormType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$phase = intval($this->options['phase']);
if($phase === 0) {
//Add child form for param; pass options (with dropdown info) in
//By default fieldname needs to match object member
$builder->add('paramObject', new ParamType(), ['options' => $options] );
}
...
}
}
class ParamType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$params_qualities = $this->options['params_qualities'] // this is an array of Param Objects;
$qualities = new ObjectChoiceList($params_qualities, 'name', array(), null, 'id');
//By default fieldname needs to match object member
$builder->add('id', 'choice', array(
'required' => true,
'choice_list' => $qualities
));
...
}
}
Then in the Controller
public parameterAction() {
//Get param options somehow and stick in $options
$paramForm = $this->createForm(new DecorationFormType(), $decorationForm, $options);
}
So basically you create your main Form as normal, and in that Form one of the Fields it adds is the subform. The name of that Field needs to match a public property or getter on the DeclarationForm class so that it can find the data object (you can override this in the options). You set data_class on each Form appropriately.
Apologies if that's not quite right, I've not tested it and I'm more used to using Collections (which is where you have potentially several of a given subform, depending on the data).
Generally the advice is to use Form Events to manipulate what Fields get added to Forms (your Param one only gets added if phase==0 for example), but I don't think it matters if you're only going to use the Form once.
I wouldn't normally have used the Form Options to pass dropdown info in to the Form, but that might just be me, not sure what best practice is - on the one hand you'd be mixing your own stuff up with a whole load of fixed Symfony keys, but on the other it's a handy place to put it! I've used members of my data objects for that kind of thing in the past.
As for your question about getting the label back from the HTML form - you can't do that as is, because as you've seen the only thing the Request contains is the ID. I can imagine solving this several ways:
Use the labels as the dropdown keys (if they're unique)
Remap IDs to labels in the Param object somehow, give it the list of options so when you go getName() or whatever it can magically give you what you want
Just accept that you get an ID back and look up what it means when you use it!

Symfony2: how to get the Type that a form is constructed from

I'm trying to dynamically generate my forms based on the user's permissions for that I have created a extension which adds listeners to forms and filters their fields accordingly.this works just fine However I'm having trouble with getting the typeName (returned from the getName method for classes which implement the FormTypeInterface) of each field (which is FormInterface).I've tried FormInterface::getName but that returns the field name that is given to the builder e.g: $builder->add('fieldName',new FooType()) when I call getName on a FormInterface that is constructed like this I get "fieldName".What I want is the returned value from FooType::getName.How can I do that?I have also checked FormInterface->getConfig->getName() but that also gave the same same Result.the code for the listener:
class FooListener implements EventSubscriberInterface{
public static function getSubscribedEvents()
{
//set low priority so it's run late
return array(
FormEvents::PRE_SET_DATA => array('removeForbiddenFields', -50),
);
}
public function removeForbiddenFields(FormEvent $event){
$form = $event->getForm();
$formName = $form->getName();/*what I want for this is to return the name of the
type of the form.e.g: for the field that is construced with the code below
it must return FooType::getName*/
$fields = $form->all();
if($fields){
$this->removeForbiddenFormFields($form, $formName, $fields);
}
}
}
class barType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('fieldName',new FooType());
}
....
}
For those of you using Symfony 3 this can now be done with:
$formClass = $form->getConfig()->getType()->getInnerType();
$formClass will be a stdClass representation of the class itself (not the instance of it though), and you can use get_class($formClass) to get a string for its FQCN, e.g. "App\Form\Type\SillyFormType".
I've not tested this on Symfony 4, though it'll likely be the same.
I found the answer.
$form->getConfig()->getType()->getName();
this will return a name returned from the FooType::getName through a ResolvedTypeDataCollectorProxy class.
From Symfony > 2.8 getName() is deprecated and removed.
You can now use:
$form->get('fieldName')->getConfig()->getInnerType()
To get specific FieldName Type.
not sure this is what you are looking for, this goes in the form FormName
public function __construct($permissions = null) {
$this->permissions = $permissions;
}
and this is how you create the form, while in buildForm you can use an if or some other logic
$myForm = $this->createForm(new FormName($user->getPermissions()));

access underlying entity in symfony2 form builder

I'm looking for a way to access the data class entity in a symfony2 form builder class.
The reason I need this is because the text on the submit button should change depending on a value of this entity (a value the user cannot change in the form).
so basically I want to do:
if ($this->entity->getVariable() == xxx) {
// do something
} else {
// do something else
}
inside the form builder class
praxmatig pointed me in the right direction, the solution is even easier:
the underlying entity is automatically available as an option named "data", so you can do:
public function buildForm(FormBuilderInterface $builder, array $options) {
// whatever
if (isset($options['data'])) {
switch ($options['data']->getSomeVariable()) {
// whatever
}
}
// whatever
}
If you create the form from controller, you can pass anything you want as the options
// AcmeType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$entity = $options['entity'];
}
// AcmeController.php
$form = $this->createForm(new AcmeType(), $entity, array('entity' => $entity));
Or a better but harder way to do this is using the form event

Symfony2 Form - Event listener on Embed Form

Starting from the example in the symfony cookbook about dynamic form generation (link to doc)
class SportMeetupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('sport', 'entity', array(...))
;
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) {
$form = $event->getForm();
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$positions = $data->getSport()->getAvailablePositions();
$form->add('position', 'entity', array('choices' => $positions));
}
);
}
}
I reproduced this code with a difference, I embed this form in a parent form (called for the exemple SportPlayerType).
SportPlayerType is mapped on entity SportPlayer which contains a couple of attributes : $name and $meetup, a string and a SportMeetup.
My problem is in the lambda function, parameter of the addEventListener :
$event->getForm() returns the child form SportMeetupType
$event->getData() returns the parent mapped entity SportPlayer
As a result, $form->add('position') throw an error because the FormBuilder cannot match position field on entity SportPlayer.
How could I force matching between SportMeetupType and entity SportMeetup when SportMeetupType in an embed form?
We'll there is a reason the event is called PRE_SET_DATA ...
Namely the event gets fired before the submitted data is set.
Therefore you can't access ...
$event->getData()->getSport();
... in the listener as long as you don't provide a new Sport() object or an existing entity when creating the form i.e. in a controller like this:
$entity = new Sport(); // ... or get the entity from db
$form = $this->createForm(new SportMeetupType(), $entity);
Just use the POST_SET_DATA event and the data will be available.