Symfony4.3 Sorting preferred choices provided by query builder in EntityType - forms

I have a selection of events provided by query builder. All entities have to be shown in the selection field, using the future events as preferred choices. Both parts should be sorted by date (asc).
Till now it worked fine the following way:
->add('event', EntityType::class, array(
'class' => 'App:Event',
'choice_label' => 'name',
'expanded' => false,
'multiple' => false,
'required' => false,
'placeholder' => '-',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('e')
->orderBy('e.date', 'ASC');
},
'preferred_choices' => function ($event) {
if($event->getDate() > new \DateTime()){
return true;
}
return false;
}
))
Since the update to Symfony 4.3 the sort order of the preferred choices is no longer kept, as described here:
https://github.com/symfony/symfony/pull/30985
I've no array of the preferred choices, as I use a callable to decide which event is in the future. How can I sort the preferred choices now? I couldn't find any clue for that in the changing description.
Is there really no longer any other way than creating an array of events outside the formbuilder?

Related

Symfony - Combine two properties in a single entity form field

I have an entity for publications with many different fields as booktitle, conference etc. I want to build a search form and one of the feature requests is to combine two search parameters in a single choice field. So far I have something like this in the form builder:
$builder->add('booktitle', 'entity', array(
'required' => false,
'label' => 'Conference/Booktitle',
'property' => 'booktitle',
'class' => 'indPubBundle:Publication',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('p')
->groupBy('p.booktitle')
->orderBy('p.booktitle', 'ASC');
}
));
Basically I am displaying all booktitles as a choice field. What I want now is to have the conferences as well in the same choice field. Is there a way to achieve that?
The Entity field type is a child of the Choice field type. So you can provide Data via the "choices" parameter. Combine that with a (e.g. repository) method that returns an array with the data you need might be a solution that works for you.
$builder->add('booktitle', 'entity', array(
'required' => false,
'label' => 'Conference/Booktitle',
'class' => 'indPubBundle:Publication',
'choices' => $this->getDoctrine()->getRepository('indPubBundle:Publication')->getData(),
));

Remove null values coming from empty collection form item

I'm trying to implement a ManyToMany relation in a form between 2 entities (say, Product and Category to make simpe) and use the method described in the docs with prototype and javascript (http://symfony.com/doc/current/cookbook/form/form_collections.html).
Here is the line from ProductType that create the category collection :
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category',
'required' => false),
'allow_add' => true,
'allow_delete' => true,
));
When I had a new item, a new select appear set to the empty value 'Select a category'. The problem is that if I don't change the empty value, it is sent to the server and after a $form->bind() my Product object get some null values in the $category ArrayCollection.
I first though to test the value in the setter in Product entity, and add 'by_reference'=>false in the ProductType, but in this case I get an exception stating that null is not an instance of Category.
How can I make sure the empty values are ignored ?
Citing the documentation on 'delete_empty':
If you want to explicitly remove entirely empty collection entries from your form you have to set this option to true
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category'),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true
));
Since you use embedded forms, you could run in some issues such as Warning: spl_object_hash() expects parameter 1 to be object, null given when passing empty collections.
Removing required=>false as explained on this answer did not work for me.
A similar issue is referenced here on github and resolved by the PR 9773
I finally found a way to handle that with Event listeners.
This discussion give the meaning of all FormEvents.
In this case, PRE_BIND (replaced by PRE_SUBMIT in 2.1 and later) will allow us to modify the data before it is bind to the Entity.
Looking at the implementation of Form in Symfony source is the only source of information I found on how to use those Events. For PRE_BIND, we see that the form data will be updated by the event data, so we can alter it with $event->setData(...). The following snippet will loop through the data, unset all null values and set it back.
$builder->addEventListener(FormEvents::PRE_BIND, function(FormEvent $event){
$data = $event->getData();
if(isset($data["categories"])) {
foreach($data as $key=>$value) {
if(!isset($value) || $value == "")
unset($data[$key]);
}
$event->setData($data);
});
Hope this can help others !
Since Symfony 3.4 you can pass a closure to delete_empty:
$builder
->add('authors', CollectionType::class, [
'delete_empty' => function ($author) {
return empty($author['firstName']);
},
]);
https://github.com/symfony/symfony/commit/c0d99d13c023f9a5c87338581c2a4a674b78f85f

Disable some checkboxes in form

I was wondering if there is an easy way of how to disable one checkbox from modifying it by user (Symfony 2.1). I was trying something like this:
$builder->add('adminRoles', 'entity', array(
'property' => 'roleName',
'class' => 'MyBundle:Role',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('r')
->orderBy('r.roleName', 'ASC');
},
'disabled' => $this->disabledRoles,
'expanded' => true,
'multiple' => true
));
By $this->disabledRoles I meant an array of IDs of Role entities or entities themselves, but it seems that it just accepts boolean value which is applied for all entities (checkboxes). Thanks for your advice :-)
You will need to add a form listener in order to customize individual elements.
http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html
It might seem like a lot of work but it's easy enough once you work through the examples. You will end up passing disabledRoles to the listener and then setting the disabled flag accordingly.

at least one checkbox selected Symfony2

I am adding the following field to my form:
->add('interessi_profilo', 'entity', array(
'label' => 'Interessi (Tr)',
'class' => 'MyProfiloBundle:TipoInteresse',
'required' => true,
'multiple' => true,
'expanded' => true,
'property' => 'tipo',
'query_builder' => function(\My\ProfiloBundle\Entity\TipoInteresseRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.id', 'ASC');
},
I would like for the form to be sent only if at least one checkbox is selected and, if possible, have a tooltip that tells the user: at least one option must be selected
Try putting the Count constraint on the field holding the collection:
use Symfony\Component\Validator\Constraints\Count;
class Entity
{
/**
* #Count(min = 1, minMessage = "At least one item must be selected")
*/
private $collection;
// ...
}

Symfony2 form populating entitiy based on other entity

I'm using Symfony2 form component to create and modify forms. I'm currently loading all model entities as choices, but I need to get only the Models related to the selected (posted) value from manufacturer.
How can I achieve this?
class VehicleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('manufacturer', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false
));
$builder->add('model', 'entity', array(
'label' => 'Model',
'class' => 'BalslevCarBundle:Models',
'property' => 'model',
'empty_value' => 'All',
'required' => false
));
}
public function getName()
{
return 'search';
}
}
First of all, you have to create "dynamically" your choices based onto user selection.
There are two ways, strictly coupled with your application behaviour, that leads (in both cases) at the same "core" code; the difference is only the way you call it.
So, let's explain the two possibilities:
Select manufacturer "statically" and don't change it over the time of request: use data retrieved from your business logic
Select manufacturer "dinamically" and can change them over the time of request: replace your form at every request; new form will be provided by business logic
So, the HEART of your question is: how can I retrieve only associated entities?
Answer
Into your VehicleType.php (where entity, in this case, is VehicleType ?)
private $manufacturerId;
public function __construct($manufacturerId)
{
$this->manufacturerId = $manufacturerId;
}
public function BuildForm(FormBuilderInterface $builder)
{
$manufacturerId = $this->manufacturerId;
$manufacturerQuery = function(EntityRepository $repo) use ($manufacturerId ){
return $repo->createQueryBuilder('m')
->where('m.id = :id')
->setParameter('id',$manufacturerId );};
$builder->add('manufacturer', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false,
'query_builder' => $manufacturerQuery;
));
$builder->add('model', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false,
'query_builder' => $modelQuery;
));
}
As you can see, in this way we use a custom repository to get only entities that are suitable for our needs. Obviously I am writing my DQL query without knowing your DB structure: it's your task and responsibility to adapt it to your real situation.
Moreover, you have to create another query for model (called $modelQuery in my answer) to retrieve correct models based on selected $id