Skip to content
Advertisement

Symfony 5.4 Form: Issue with EntityType custom query_builder orderBy(‘rand’) and setMaxResult

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.

SymfonyComponentValidatorConstraintViolation {#1320 ▼ -message: “Cette valeur n’est pas valide.” -messageTemplate: “This value is not valid.” -parameters: [▶] -plural: null -root: SymfonyComponentFormForm {#911 ▶} -propertyPath: “children[press]” -invalidValue: “8” -constraint: SymfonyComponentFormExtensionValidatorConstraintsForm {#987 …} -code: “1dafa156-89e1-4736-b832-419c2e501fca” -cause: SymfonyComponentFormExceptionTransformationFailedException {#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 AppForm;

use AppEntityPress;
use AppModelGridModel;
use DoctrineORMEntityManagerInterface;
use SymfonyBridgeDoctrineFormTypeEntityType;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;

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 AppRepository;

use AppConstantGlobalConstant;
use AppEntityGrid;
use AppEntityPress;
use DoctrineBundleDoctrineBundleRepositoryServiceEntityRepository;
use DoctrineORMQueryBuilder;
use DoctrinePersistenceManagerRegistry;

/**
 * @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()');
    }

Advertisement

Answer

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');.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement