Symfony 表单:所选选项无效

问题描述 投票:0回答:2

我的 symfony 应用程序有这个 实体

<?php

namespace App\Entity;

class Lead
{
    private ?string $zipCode;
    private ?string $city;

    public function getZipCode(): ?string
    {
        return $this->zipCode;
    }

    public function setZipCode(string $zipCode): self
    {
        $this->zipCode = $zipCode;

        return $this;
    }

    public function getCity(): ?string
    {
        return $this->city;
    }

    public function setCity(string $city): self
    {
        $this->city = $city;

        return $this;
    }
}

表格LeadType是:

$builder->add('zipCode', TextType::class, [
        'attr' => [
            'placeholder' => 'Ex : 44000',
            'onchange' => 'cityChoices(this.value)',
        ]
    ])
    ->add('city', ChoiceType::class, [
        'choices' => [],
        'attr' => ['class' => 'form-control'],
        'choice_attr' => function () {return ['style' => 'color: #010101;'];},
    ]);

当用户输入邮政编码时,javascript函数cityChoices()使用外部API添加选择选项,例如:

我的问题是控制器中的表单无效。因为用户选择了 LeadType 的选项中未提供的城市('choices' => [])。

这是错误:

0 => Symfony\Component\Form\FormError {#3703 ▼
          #messageTemplate: "The selected choice is invalid."
          #messageParameters: array:1 [▼
            "{{ value }}" => "Bar-le-Duc"
          ]

如何使选择有效?

php forms symfony validation symfony-forms
2个回答
5
投票

完全@craight

我添加到表格中:

->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent$event){
   $form = $event->getForm();
   $city = $event->getData()['city'];
   if($city){
       $form->add('city', ChoiceType::class, ['choices' => [$city => $city]]);
   }
})

预提交时,我更新选择并仅让用户选择

'choices' => ['Bar-le-Duc' => 'Bar-le-Duc'],

表单在控制器中生效


0
投票

另一个答案,当将

EntityType
与之前可用但不再可用的实体(例如,软删除或过期)一起使用时,是使用自定义数据转换器在验证之前过滤发布的结果。

请参阅如何使用数据转换器 @ Symfony 文档

在下面的(非功能性)示例中,

EntityType
(或
ChoiceType
)的自定义数据转换器根据给定自定义过滤器的数据库查询结果删除无效值:

<?php

/**
 * Transformer for collection of CustomerType objects.
 *
 * @implements DataTransformerInterface<Collection<array-key,string>,Collection<array-key,string>>
 */
class EntityCollectionTransformer implements DataTransformerInterface
{
    protected ?Builder $queryBuilder = null;

    /**
     * The valid DeliveryPackOption::id for the current context.
     *
     * @var string[]
     */
    protected ?array $validIds = null;

    /**
     * Constructor, injects the repository.
     */
    public function __construct(
        protected readonly EntityRepository $repository
    ) {
    }

    /**
     * Get the query builder, to apply custom filters.
     */
    public function getQueryBuilder(): Builder
    {
        // Reset the query builder
        $this->queryBuilder = $this->repository->getQueryBuilder();

        // Reset the valid ids
        $this->validIds = null;

        return $this->queryBuilder;
    }

    /**
     * Get the valid DeliveryPackOption Ids for the current context.
     *
     * @return string[]
     */
    public function getValidIds(): array
    {
        if (null !== $this->validIds) {
            return $this->validIds;
        }

        if (null === $this->queryBuilder) {
            $message = sprintf('You must call getQueryBuilder() before calling using %s.', self::class);
            throw new \LogicException($message);
        }

        /** @var Collection<array-key,Entity> */
        $results = $this->queryBuilder->getQuery()->execute();

        // Get the valid ids from the database
        $this->validIds = array_map(
            fn (Entity $option): string => $option->getId(),
            $results->toArray(),
        );

        return $this->validIds;
    }

    /**
     * Check that the given Entity::id are available as choices for the current context.
     *
     * Prevent "invalid" choices to be submitted.
     *
     * @param Collection<array-key,string>|null $value
     *
     * @return Collection<array-key,string>
     */
    #[\Override]
    public function transform(mixed $value): Collection
    {
        if (!$value instanceof Collection || 0 === $value->count()) {
            return new ArrayCollection();
        }

        return new ArrayCollection(array_filter(
            $value->toArray(),
            fn (string $id): bool => in_array($id, $this->getValidIds(), true)
        ));
    }

    /**
     * Transform a Entity:id array to its corresponding ArrayCollection of Document.
     *
     * @param Collection<array-key,string>|null $value
     *
     * @return Collection<array-key,string>|null
     */
    #[\Override]
    public function reverseTransform(mixed $value): ?Collection
    {
        return $value;
    }
}

并在表单中,获取变压器服务,并设置自定义查询过滤器:

<?php

class FormType extends AbstractType {
    protected ?EntityTransformer $entityTransformer = null;

    /**
     * Set the DeliveryPackOptionReferenceCollectionTransformer service, used for dependency injection.
     */
    #[Required]
    public function setEntityTransformer(
        EntityTransformer $entityTransformer
    ): void {
        $this->entityTransformer = $entityTransformer;
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        if (!$this->entityTransformer instanceof EntityTransformer) {
            throw new \RuntimeException('EntityTransformer service is not set.');
        }

        // Prepare the Entity collection filtering transformer query builder
        $qb = $this->entityTransformer->getQueryBuilder();
        $qb->andWhere('deleted = :false');
           ->setParameter('false', false);

        $builder
            ->add('city', EntityType::class, [
                'class' => Entity::class,
            ])
        ;

        $builder->get('delivery.deliveryPackOptions')
            ->addModelTransformer($this->entityTransformer);
    }

}
© www.soinside.com 2019 - 2024. All rights reserved.