Symfony 4 forms CollectionType: make FileType element required for new rows only - forms

I have an array of images that I want to be able to add to/update/delete from in a Symfony 4 form.
To create a form for these images, I'm using a custom form with a FileType in it:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('image', FileType::class, array(
'data_class' => null
))
;
}
I am then using a CollectionType filled with instances of the form described above to render a form for each of the images in the array, with 'allow_add' and 'allow_delete' so I can add/remove rows via JavaScript.
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('imagesets', CollectionType::class, array(
'entry_type' => ImageType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
'allow_delete' => true
));
}
This works fine for adding new images, but when updating existing images, the FileType element shouldn't be required, it should only be required for the new rows.
Question: How can I make the FileType NOT required for existing images, yet required for all the new rows?
(Note, I will be passing plain arrays to these form objects, not Doctrine entities.)

You should add an EventListener to your ImageType form and modify the required attribute if the object is not new (or not null). Have in mind that adding the second element with the same name as the previous to the form, replaces it.
$builder
->add('image', FileType::class, array(
'data_class' => null,
'required' => true,
))
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
// get the form object
$form = $event->getForm();
// get the entity/object data
$image = $event->getData();
// if it is new, it will be null
if(null !== $image) {
// modify the input
$form->add('image', FileType::class, array(
'data_class' => null,
'required' => false,
))
;
});
}

Related

Symfony change form collection dropdown with propel model

Is it possible to change the contents of a form dropdown that is part of a form collection that is populated using propel but the data is not mapped. Example of code to get the data below:
AddressType:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->
->add("addressOne", new addressOneType()),
->add("addressTwo", new addressTwoType(), array(
"required" => false,
)),
}
addressOneType:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->
->setMethod('POST')
->add('Country', 'model', array(
'mapped' => 'bundle\nameBundle\Model\Countries',
'required' => true,
'multiple' => false,
'expanded' => false,
'property' => 'label',
'query' => CountryQuery::create()->find(),
))
->getForm();
}
This collection is used for a particular part of an application however in this part in need to call a service from the form itself. Would this be possible as I've tried to extend the ContainerInterface and declare this inside of the construct method however this just throws an error.
However, I beleive this to be due to the fact that the form builder is not declared as a service.
Is there an easier way of changing the data of the drop down menu by injecting a new model to override the original. For example:
$form = $this->createForm(new AddressType());
$newData = CountriesQuery::create()
->orderBy("different_field");
$form['collectionName']['fieldname']->setData($newData);
Doing the above doesn't change or override the original model that is changing the data. With or without the ->find() at the end of the $newData field.
Does anyone know of a way to overwrite the data set by the model?
A very simple way for pass specific options to form is in constructor ...
class addressOneType
{
protected $countryQuery;
public function __constructor( $countryQuery = null )
{
$this->countryQuery = $countryQuery;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$query = $this->countryQuery ? $this->countryQuery :
CountryQuery::create();
$builder
->setMethod('POST')
->add('Country', 'model', array(
'mapped' => 'bundle\nameBundle\Model\Countries',
'required' => true,
'multiple' => false,
'expanded' => false,
'property' => 'label',
'query' => $query->find(),
))
->getForm();
}
}
... and you can call to form in this way ...
$cQuery = CountriesQuery::create()->orderBy("different_field");
$form = $this->createForm(new AddressType($cQuery));

Save value instead of key when using SimpleChoiceList

This is my form main function:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('naam', 'text', array(
'label' => "Naam",
'attr' => array(
'placeholder' => "Vul hier een functionele omschrijving in van de deltaload",
)
))
->add('tabel', 'choice', array(
'empty_value' => 'Kies een tabel',
'choices' => $this->loadChoiceList()->getChoices()
))
;
}
This is the loadChoiceList() function:
protected function loadChoiceList()
{
$array = array(
'Preview' => 'Preview',
'Hidden' => 'Hidden',
'Live' => 'Live'
);
$choices = new SimpleChoiceList($array);
return $choices;
}
When viewing this form, I can make a selection of one of the three defined choices. But when saving it to the database, it saves a 0, 1 or a 2 depending on the choice.
How can I make it so that it saves the value instead of the key?
I also tried the ChoiceList() class, but the outcome made no difference.
For the record, I realize that this isn't normalized DB design, but this is a simplified way of explaining my question with a simplified example.
The answer is simple.
Change this:
'choices' => $this->loadChoiceList()->getChoices()
To:
'choice_list' => $this->loadChoiceList()
Or, if you want to use the 'choices' option, you can return the regular array with your function, not the SimpleChoiceList object.

bind error to embedded form field in symfony2's controller

Using Symfony2.3.4
As the title quite explains it I need to bind an error message to an embedded form field and preferably in the controller. I'm thinking maybe similar to how I do it with single forms:
use Symfony\Component\Form\FormError;
//....
public function createAction(){
//.....
$postData = current($request->request->all());
if ($postData['field_name'] == '') {
$error = new FormError("Write some stuff in here");
$form->get('field_name')->addError($error);
}
//.....
}
or maybe it's got to be done differently, either way I need help,
thank$
I see, that you are trying to display a message when a form field does not contain any value. You can do this easily in your form class, like this:
buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('field_name', 'text', array(
'label' => 'Field label',
'required' => true,
'constraints' => array(
new NotBlank(array('message' => 'Write some stuff in here.'))
),
));
}
If you need to inject some other kind of constraint to your form which is not part of Symfony2 framework, you can create your own validation constraint.
If you want to add some options to your form in controller, it can be done in the method where you create the form by setting your own options:
class YourController {
public function createForm(YourEntity $yourEntity){
$form = $this->createForm(new YourFormType(), $yourFormType, array(
'action' => $this->generateUrl('your_action_name',
array('your_custom_option_key' => 'Your custom option value')),
'method' => 'POST',
));
return $form;
}
// Rest of code omitted.
}
After that you need to add an option in setDefaultOptions(OptionsResolverInterface $resolver) method, in your form class, like this:
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults(array(
'your_custom_option_key' => '',
));
}
And then access it in buildForm(FormBuilderInterface $builder, array $options) method, like this:
buildForm(FormBuilderInterface $builder, array $options) {
$options['your_custom_option_key']; // Access content of your option
// The rest of code omitted.
}

Symfony form creates new object and create first one-to-many object

I have an entity for support tickets: SupportTicket(). I also have an entry for replies to each ticket: SupportEntry(). I setup a one-to-many relationship between SupportTicket() and SupportEntry().
Now what I'm trying to do is build my form so that it creates the initial SupportTicket and then inserts the first SupportEntry, all in the same form. I've been messing around with my code for a while, only half-understanding what I'm doing, but this is where I'm at right now:
// My controller, creating the form
$supportTicket = new SupportTicket();
$form = $this->createFormBuilder($supportTicket)
->add('subject', 'text', array(
'label' => 'Subject'
))
->add('jobNumber', 'text', array(
'label' => 'Job Number'
))
->add('supportGroup', 'entity', array(
'label' => 'Group',
'class' => 'ShawmutClientBundle:SupportGroup',
'property' => 'name',
'multiple' => true,
'expanded' => true
))
// ->add('supportEntries', new SupportEntryType())
->add('supportEntries', new SupportEntryType())
->add('Save', 'submit')
->getForm();
My attempt at the custom form type
<?php
namespace Shawmut\ClientBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SupportEntryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('comment', 'textarea');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Shawmut\ClientBundle\Entity\SupportEntry',
));
}
public function getName()
{
return 'SupportEntryType';
}
}
The form does have the comment box that I've pulled in from the form type, but when I try to submit the form, I get this error:
Neither the property "supportEntries" nor one of the methods "setSupportEntries()", "_set()" or "_call()" exist and have public access in class "Me\MyBundle\Entity\SupportTicket".
And yeah, that makes sense. It should be the addSupportEntries() method which is there. So how do I tell the form builder to use addSupportEntries instead of setSupportEntries?
Thanks in advance
Give the collection form type a go.
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'cascade_validation' => true,
)
)
If you are using the collection form type, and the textarea is not showing, add:
'allow_add' => true
to the properties array().
The code would look something like this:
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'allow_add' => true
'cascade_validation' => true,
)
)
To show the widget, assuming you are using twig:
{{ form_widget(form.supportEntries.vars.prototype.comment) }}
For saving the support entry, depending on how you built your entities, you might need to make some extra modifications.
The documentation should help you get it right:
How to Embed a Collection of Forms

Doctrine 2 result caching in Symfony with form type entity

I use APC result caching in docrine, and have filter form with type entity in all website pages and want cache this, but when I add useResultCache() to method I get exception
Entities passed to the choice field must be managed
example
...->getQuery()->useResultCache(true, null, 'someindex')->getResult()
but all action without form with entity type work normally.
Any ideas?
Don't know if You've figured out how to do it, but here's how I've done it (spent half a day figuring this out).
/* in FormType.php */
public function buildForm(FormBuilderInterface $builder, array $options)
{
$items = $options['entity_repository']
->findItems()
->useResultCache(true, 3600, 'my_cache')
->getResult();
$choice_list = new ObjectChoiceList($items, 'name', array(), null, 'id');
$builder->add('item', 'entity', array(
'class' => 'MyBundle:Items',
'multiple' => true,
'expanded' => true,
'choice_list' => $choice_list,
));
}