Symfony2.7, having problems with a form collections field and Doctrine - php

I have a Blog entity which is supposed to have one Poll entity and each Poll entity has many Choice entities associated to it. I was trying to create an "Add/Edit Poll" form for the blog. In that form, in addition to the Poll form fields I want to have a field that allows you to add Choices (a simple text field each). I'm having trouble figuring out how I should code this so that it works well. The PollType form looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', 'text');
$builder->add('choices', 'collection',
array(
'label_attr' => array(
'style' => 'vertical-align: top;'
),
'type' => 'text',
'required' => false,
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'attr' => array(
'style' => 'display: inline-block; margin: 0 0 20px 20px; width: 200px;'
)));
$builder->add('description', 'textarea', array('attr' => array('rows' => 6)));
$builder->add('save', 'submit', array('label' => 'Create Poll'));
$builder->getForm();
}
Originally I had 'type' => new ChoiceType() with ChoiceType being:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', 'text');
}
But I had to remove all this because "Variable "expanded" does not exist in bootstrap_3_layout.html.twig" error that I could not find any solution for. I changed the type to text but now I'm stumped as to how I would collect the data from each "choices" field, persist it to the Choices entity, and also associating all of them with the new Poll (which may not have been inserted yet so I don't have an ID to add to Choices->pollID). Is there a standard way for doing this kind of thing that I'm missing?

If you have Poll entity and Choice entity, so it looks like this:
$builder->add('choice', 'entity', array(
'class' => 'AcmeHelloBundle:Choice',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('choice.name', 'ASC');
},
));
Also you must have OneToMany or ManyToMany association with your entities.
More about entity Field Type

Related

Nested CollectionType Save Entity in ArrayCollection

i want to store some data
class Offer
{
/**
* #ORM\Column(type="array")
*/
private $meterPoints;
public function __construct()
{
$this->meterPoints = new ArrayCollection();
}
}
for an offer as a CollectionType.
class OfferType extends AbstractType
{
$builder
->add('meterPoints', CollectionType::class, array(
'entry_type' => OfferMeterPointType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'prototype_name' => '__mp_name__',
))
}
In the OfferMeterPointType I also have an EntityType
class OfferMeterPointType extends AbstractType
{
$builder
->add('meterPoint', EntityType::class, array(
'attr' => array(
'class' => 'chosen-select'
),
'class' => 'AppBundle:MeterPoint',
'choice_label' => 'meterPoint',
))
->add('billData', CollectionType::class, array(
'label' => false,
'entry_type' => OfferBillType::class,
'allow_add' => true,
'allow_delete' => true,
'entry_options' => array(
'label' => false
)
));
}
Now when I persist that entity the whole AppBundle:MeterPoint object get serialized and not just the id. I kind of understand why doctrine does that but can I change it such that just the id will be stored?
Also when I want to edit an Offer
$offer = $em->getRepository('AppBundle:Offer')->findOneById(2);
$form = $this->createForm(OfferType::class, $offer);
i get an Exception
Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
I guess a solution would be to create an Entity for the OfferMeterPointType but I don't really want to do that. Because I allmost never need that data.
Update
I tried like martin suggested. now the exception is gone but it still saves the complete object
$meterPoints = $this->em->getRepository('AppBundle:MeterPoint')->findAll();
//dump($meterPoints);
$builder
->add('meterPoint', ChoiceType::class, array(
'label' => 'offerMeterPoint.meterPoint',
'attr' => array(
'class' => 'chosen-selesct',
'placeholder' => 'ob.to'
),
'choices' => $meterPoints,
'choice_label' => function($meterPoint) {
return $meterPoint->getMeterPoint();
},
'choice_value' => function($meterPoint) {
if($meterPoint === null){
return null;
}
dump($meterPoint);
return $meterPoint->getId();
},
'placeholder' => 'global.plz_select'
))
Update 2
Got it working
changed the ChoiceType
$meterPoints = $this->em->getRepository('AppBundle:MeterPoint')->findAll();
$mps = array();
foreach($meterPoints as $mp){
$mps [$mp->getMeterPoint()] = $mp->getId();
}
//dump($meterPoints);
$builder
->add('meterPoint', ChoiceType::class, array(
'label' => 'offerMeterPoint.meterPoint',
'attr' => array(
'class' => 'chosen-selesct',
'placeholder' => 'ob.to'
),
'choices' => $mps,
'placeholder' => 'global.plz_select'
))
You get a object serialized because your column type is array and EntityType automatically replaces choice values with objects.
There's however choice_value that accepts also a callable so I'd try fiddling with it and maybe you can get it to return just the id (maybe force string type?).
If this doesn't help then probably use just ChoiceType and handle the logic yourself.
This happens because Doctrine automatically deserializes the objects from $meterPoints that you try to use as entities with EntityType. These objects are obviously not managed by the Doctrine Entity manager. Thus the error.
I think you'll have to convert the $meterPoints to database entities before using $this->createForm(...) yourselves if you want to avoid creating more relations. Eventually and maybe even easier approach would be writing a custom Doctrine type that could do this for you:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/custom-mapping-types.html

cant translate name UploadButton in my Form

i got a problem in my form. Im translating all my fields with message.yml and it works. But i have also a upload button from VichUploaderBundle in it. I can translate the label, but when im testing it, the label is in english, but the button is in german.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', TextType::class, array('disabled' => true))
->add('title',TextType::class, array('label' => 'edit.title'))
->add('body', TextareaType::class, array('label' => 'edit.body'))
->add('date', DateType::class, array('disabled' => true, 'label' => 'edit.date'))
->add('tags', EntityType::class, array(
'class' => 'AppBundle:Tag',
'choice_label' => 'getTitle',
'multiple'=> true,
'expanded'=> true))
->add(
'technology',
EntityType::class,
array(
'class' => 'AppBundle\Entity\Technology',
'choice_label' => 'getTitle',
'group_by' => 'parent.getTitle',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('t')
->where('t.parent IS NOT NULL')
->andWhere('SIZE(t.children) <= 0');
}
))
->add('imageFile', VichImageType::class, array(
'required' => false,
'allow_delete' => true,
'download_link' => true,
'label' => 'edit.image_file',
))
;
}
I dont know how to translate the button.
You are only creating a html file input.
This input does not have some label attribute.
<input type="file" name="imageFile"/>
-> the button label is set by your browser (which seems to be in german).
You can try to change it with CSS/JS
These text is defined by the browser and not by PHP or Symfony. You will also notice that this widget will be displayed differently in different browsers and on different operating systems. There is also no possibility to change the design of the widget. Most CSS properties won't have an effect on .
The only thing that you could do is to implement a custom JavaScript uploader, but that will probably only work in modern browsers.
I have not never done this, but a quick Google search brought up http://www.queness.com/post/11434/7-javascript-ajax-file-upload-plugins that features few JavaScript plugins to do this.

Choice field default value

I have the following form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices' => array(
'Friend' => 'friend',
'Guide' => 'guide'
)
));
}
How can I make 'Friend' checkbox to be checked by default when the form is rendered ?
I think you should try with data option, but it's just in the case where you don't even have a data saved inside your object, because it will override it else.
Important : It's good for create action, but not for edit action.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices' => array(
'Friend' => 'friend',
'Guide' => 'guide'
),
'data' => 'friend'
));
}
Official link
Extract :
When you create a form, each field initially displays the value of the
corresponding property of the form's domain object (if an object is
bound to the form). If you want to override the initial value for the
form or just an individual field, you can set it in the data option
UPDATE If YOU NEED EMPTY VALUE:
As the answer below, replace data with empty_data if you need in any case to update default value
Use the empty_data form field option. (not data because it will override any posted data unless you set it dynamically).
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices' => array(
'Friend' => 'friend',
'Guide' => 'guide'
),
'empty_data' => 'friend'
));
}
Another option for complex cases is to use Sf Dynamic Form Events.
If you don't want to override value for an edition you can do this :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$form->add(
'type',
ChoiceType::class,
[
'expanded' => true,
'multiple' => false,
'choices' => [
'Friend' => 'friend',
'Guide' => 'guide'
],
'data' => $event->getData() ?: 'friend'
]);
});
}
I think it would be better to set initial values in the Entity constructor:
public function __construct()
{
$this->exercises = new ArrayCollection();
$this->setTitle("WOCHE#") ;
$this->setYear(date('Y'));
$this->setWeekInCalendar(Week::getCurrentWeekInCalendar());
}
An other solution would be to set placeholder as false.
This would set the first value as default and minimize setup effort.
If the field needs to be nullable you could add one more choice i.e. 'empty' => null
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices' => array(
'Friend' => 'friend',
'Guide' => 'guide'
),
'placeholder' => false
));
}

Split form field of entity property into several subfields with FormBuilder

I have one superclass which gets extended by two entities. The superclass has a N:M relation with another entity. In the formbuilder of the latter, I need to separate the two subtypes into excluding separate fields.
This is its FormType::buildForm method:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('options_subtype_1', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'expanded' => true,
'multiple' => true,
'property_path'=> 'options',
'query_builder' => /* specific filter 1 */
])
->add('options_subtype_2', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'expanded' => true,
'multiple' => true,
'property_path'=> 'options',
'query_builder' => /* specific filter 2 */
])
;
}
However, no options are saved on form submission, except when I comment one of the two fields above (the one left is saved). I imagine setting property_path to the same property from two fields on the same form does not work.
How can I achieve this?
I'm still searching for the perfect solution by myself. For now I handle this kind of issues with a mapped hidden field and a PRE_SUBMIT Form Event. This could look like
FormType::buildForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('options_subtype_1', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'expanded' => true,
'multiple' => true,
'mapped'=> false,
'data' => $builder->getData()->getOptions(),
'query_builder' => /* specific filter 1 */
])
->add('options_subtype_2', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'expanded' => true,
'multiple' => true,
'mapped' => false,
'data' => $builder->getData()->getOptions(),
'query_builder' => /* specific filter 2 */
])
->add('options', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'required' => false,
'multiple' => true,
'attr' => ['style' => 'visibility: hidden;'],
'label' => false
]);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$data = $event->getData();
unset($data['options']);
$data['options'] = array_merge($data['options_subtype_1'], $data['options_subtype_2']);
$event->setData($data);
}
);
}

How to add mapping to a form

I have a form which I create using a form class. One field of this form is country_id, which gives me a country id in a text field.
However, I want create a select field instead, and not only with all ids, but rather with country names mapped by those ids.
I have a table in a database mapping country ids to names. However, the form uses only one entity, not this one.
How can I create that select using this other table here? How can I solve this?
Thanks!
EDIT:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text', ['required' => false])
->add('address', 'text', ['required' => false])
->add('zipCode', 'text', ['required' => false])
->add('city', 'text', ['required' => false]);
$builder->add('country', 'entity', [
'class' => 'AcmeBundle:Country',
'property' => 'shortName',
'required' => false,
]);
// $builder->add('country', 'text', ['required' => false]);
$builder->add('number', 'text', ['required' => false]);
}
That's the whole method inside the, let's call it, Foo entity. It generates proper HTML with ids as values, but there are still those errors I mentioned in the comment.
You need the entity Field Type. Your FormType should look like this:
$builder->add('country', 'entity', array(
'class' => 'YourBundle:Country',
'property' => 'name',
));
Checkout the docs for more info.

Categories