Monday, March 7, 2022

[FIXED] Symfony 5.4 Form: Issue with EntityType custom query_builder orderBy('rand') and setMaxResult

Issue

I'm struggling with a 'strange' behavior. When I use setMaxResult() + Rand() on my query_builder. I got randomly the message that my value is not valid.

Symfony\Component\Validator\ConstraintViolation {#1320 ▼ -message: "Cette valeur n'est pas valide." -messageTemplate: "This value is not valid." -parameters: [▶] -plural: null -root: Symfony\Component\Form\Form {#911 ▶} -propertyPath: "children[press]" -invalidValue: "8" -constraint: Symfony\Component\Form\Extension\Validator\Constraints\Form {#987 …} -code: "1dafa156-89e1-4736-b832-419c2e501fca" -cause: Symfony\Component\Form\Exception\TransformationFailedException {#916 …} }

If I remove setMaxResult(10) it works fine, if I remove Rand() It works too but not both

Could you please help me... I don't get it and I don't know what I can do

Here is my code:

GridType:


namespace App\Form;

use App\Entity\Press;
use App\Model\GridModel;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class GridType extends AbstractType
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    public function __construct(
        EntityManagerInterface $entityManager
    )
    {
        $this->entityManager = $entityManager;
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('press', EntityType::class, [
                'class' => Press::class,
                'query_builder' => $this->entityManager->getRepository(Press::class)->getIncluded($options['grid']),
                'choice_label' => 'number',
                'placeholder' => 'Sélectionner une presse',
            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'grid' => null,
            'data_class' => GridModel::class,
            'csrf_protection' => false,
        ]);
    }
}

PressRepository:

<?php

namespace App\Repository;

use App\Constant\GlobalConstant;
use App\Entity\Grid;
use App\Entity\Press;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @method Press|null find($id, $lockMode = null, $lockVersion = null)
 * @method Press|null findOneBy(array $criteria, array $orderBy = null)
 * @method Press[]    findAll()
 * @method Press[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class PressRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Press::class);
    }

    /**
     * @param Grid|null $grid
     * @return QueryBuilder|null
     */
    public function getIncluded(Grid $grid = null): ?QueryBuilder
    {
        $result = $this->createQueryBuilder('i')
            ->andWhere('i.status = :status')
            ->andWhere('i.include = :include')
            ->setParameters([
                'status' => GlobalConstant::STATUS_VALID,
                'include' => true,
            ]);

        if ($grid) {
            $result->andWhere('i NOT IN (:grid)')
                ->setParameter(
                    'grid', $grid->getPress()
                );
        }

        return $result->orderBy('i.number', 'ASC')
            ->setMaxResults(5)
            ->orderBy('RAND()');
    }

Solution

The way Forms work, the query is executed every time the type is instanced, that means that the result of the initial query when loading the empty form is different than the one when submitting it (because of the random ordering). Since the submitted value is not anymore in the valid 'choices', you see that TransformationFailedException.

One way to solve this would be to use ChoiceType instead of EntityType, and persist the query result in the session, for example.

public function __construct(
    PressRepository $repository,
    // This is deprecated in 5.3
    // see https://symfony.com/blog/new-in-symfony-5-3-session-service-deprecation
    SessionInterface $session
)
{
    $this->repository = $repository;
    $this->session = $session;
}

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    if (!$session->has('press_form_choices')) {
        $session->set(
            'press_form_choices',
            $this->repository->getIncluded($options['grid'])
        );
    }
    $choices = $session->get('press_form_choices');
    $builder
    ->add('press', ChoiceType::class, [
        'choices' => $choices,
        'choice_label' => 'number',
        'choice_value' => 'id',
        'placeholder' => 'Sélectionner une presse',
    ]);
}   

Upon sucessfull submission, clean up your session in your controller: $session->remove('press_form_choices');.



Answered By - msg

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.