【问题标题】:Symfony conditional validation for Valid() constraint with groupsValid() 约束的 Symfony 条件验证与组
【发布时间】:2020-09-04 10:30:12
【问题描述】:

至少对我来说,我遇到了非常奇怪的 Symfony 行为。 我有嵌套表单的表单类型。我只需要在某些情况下验证这个嵌套表单。

这就是我将验证组添加到 Valid() 约束的原因。 如果我使用相同的组,例如。 TextType,它按预期工作。如果我从 Valid() 中删除组,它也可以工作。所以这意味着 Valid() 约束本身有效,只是在使用验证组时不起作用。

      $builder
               ->add('test', TextType::class, [
                    'mapped' => false,
                    'constraints' => [new NotBlank(['groups' => 'SenderAddress'])],    
                ])
                ->add('senderAddress', AddressType::class, [
                    'label' => false,
                    'constraints' => [new Valid(['groups' => 'SenderAddress'])],
                ])
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'validation_groups' => function (FormInterface $form) {
                if (!$form->get('senderAddresses')->getData()) {
                    return ['Default', 'SenderAddress'];
                }
                return ['Default'];
            },
        ]);
    }

所以如果senderAddresses和什么都没有填写

  1. test 字段为空,test 字段已正确验证并使用 NotBlank() vioaliton
  2. 即使字段为空,senderAddress 也不显示任何 vioaliton ?!

但是当我尝试删除 senderAddress 的组验证时,它按预期工作。 我做错了什么?

->add('senderAddress', AddressType::class, [
   'label' => false,
   'constraints' => [new Valid()],  //but this is not how I want it!
])

编辑: 这些是我使用的对象

CalculationType 的实体顺序

<?php

namespace App\Entity;

use App\Repository\OrderRepository;
use App\Traits\IdentifierTrait;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=OrderRepository::class)
 * @ORM\Table(name="`order`")
 */
class Order
{
    use IdentifierTrait;

    public const STATUSES = [
        'order.statuses.calculation' => self::STATUS_CALCULATION,
        'order.statuses.created' => self::STATUS_CREATED,
    ];

    public const STATUS_CALCULATION = 1;
    public const STATUS_CREATED = 2;

    /**
     * @ORM\ManyToOne(targetEntity="Address", cascade={"persist"})
     *
     * @var Address
     */
    private Address $senderAddress;

    /**
     * @ORM\ManyToOne(targetEntity="Address", cascade={"persist"})
     *
     * @var Address
     */
    private Address $recipientAddress;

    /**
     * @ORM\ManyToOne(targetEntity="Package", cascade={"persist"})
     * @Assert\Valid()
     *
     * @var Package
     */
    private Package $package;

    /**
     * @ORM\Column(type="datetime")
     *
     * @var DateTime
     */
    private DateTime $createdAt;

    /**
     * @ORM\Column(type="integer", length=1, options={"unsigned": true})
     *
     * @var int
     */
    protected int $status = self::STATUS_CALCULATION;

    /**
     * @ORM\Column(type="text", length=255, nullable=TRUE)
     *
     * @var null|string
     */
    private ?string $serviceType = null;

    /**
     * @ORM\ManyToOne(targetEntity=User::class)
     *
     * @var User
     */
    private User $user;

    /**
     * Order constructor.
     */
    public function __construct()
    {
        $this->createdAt = new DateTime('now');
    }

    /**
     * @return Address
     */
    public function getSenderAddress(): Address
    {
        return $this->senderAddress;
    }

    /**
     * @param Address $senderAddress
     */
    public function setSenderAddress(Address $senderAddress): void
    {
        $this->senderAddress = $senderAddress;
    }

    /**
     * @return Address
     */
    public function getRecipientAddress(): Address
    {
        return $this->recipientAddress;
    }

    /**
     * @param Address $recipientAddress
     */
    public function setRecipientAddress(Address $recipientAddress): void
    {
        $this->recipientAddress = $recipientAddress;
    }

    /**
     * @return Package
     */
    public function getPackage(): Package
    {
        return $this->package;
    }

    /**
     * @param Package $package
     */
    public function setPackage(Package $package): void
    {
        $this->package = $package;
    }

    /**
     * @return string|null
     */
    public function getServiceType(): ?string
    {
        return $this->serviceType;
    }

    /**
     * @param string|null $serviceType
     */
    public function setServiceType(?string $serviceType): void
    {
        $this->serviceType = $serviceType;
    }

    /**
     * @return DateTime
     */
    public function getCreatedAt(): DateTime
    {
        return $this->createdAt;
    }

    /**
     * @param DateTime $createdAt
     */
    public function setCreatedAt(DateTime $createdAt): void
    {
        $this->createdAt = $createdAt;
    }

    /**
     * @return int
     */
    public function getStatus(): int
    {
        return $this->status;
    }

    /**
     * @param int $status
     */
    public function setStatus(int $status): void
    {
        $this->status = $status;
    }

    /**
     * @return User
     */
    public function getUser(): User
    {
        return $this->user;
    }

    /**
     * @param User $user
     */
    public function setUser(User $user): void
    {
        $this->user = $user;
    }
}

AddressType 的实体地址

<?php

namespace App\Entity;

use App\Repository\AddressRepository;
use App\Traits\IdentifierTrait;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Intl\Countries;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=AddressRepository::class)
 */
class Address
{
    use IdentifierTrait;

    public const TYPES = [
        'address.types.sender' => self::TYPE_SENDER,
        'address.types.recipient' => self::TYPE_RECIPIENT,
    ];

    public const TYPE_SENDER = 1;
    public const TYPE_RECIPIENT = 2;

    /**
     * @ORM\ManyToOne(targetEntity=User::class, inversedBy="addresses")
     *
     * @var User|null
     */
    private ?User $user;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     * @Assert\Length(
     *     max="100"
     * )
     *
     * @var string|null
     */
    private ?string $name = null;

    /**
     * @ORM\Column(type="string", length=150)
     * @Assert\NotBlank()
     * @Assert\Length(
     *     max="150"
     * )
     *
     * @var string|null
     */
    private ?string $street = null;

    /**
     * @ORM\Column(type="string", length=100)
     * @Assert\NotBlank()
     * @Assert\Length(
     *     max="100"
     * )
     *
     * @var string|null
     */
    private ?string $city = null;

    /**
     * @ORM\Column(type="string", length=15)
     * @Assert\NotBlank()
     * @Assert\Length(
     *     max="15"
     * )
     *
     * @var string|null
     */
    private ?string $postalCode = null;

    /**
     * @ORM\Column(type="string", length=3)
     * @Assert\NotBlank()
     * @Assert\Length(
     *     max="3"
     * )
     *
     * @var string|null
     */
    private ?string $country = null;

    /**
     * @ORM\Column(type="string", length=3, nullable=true)
     *
     * @var string|null
     */
    private ?string $province = null;

    /**
     * @ORM\Column(type="integer", length=1, options={"unsigned": true})
     *
     * @var int
     */
    protected int $type = self::TYPE_SENDER;

    /**
     * Address constructor.
     *
     * @param int $type
     * @param User|null $user
     */
    public function __construct(int $type = self::TYPE_SENDER, User $user = null)
    {
        $this->type = $type;
        $this->user = $user;
    }

    /**
     * @return User|null
     */
    public function getUser(): ?User
    {
        return $this->user;
    }

    /**
     * @param User|null $user
     */
    public function setUser(?User $user): void
    {
        $this->user = $user;
    }

    /**
     * @return string|null
     */
    public function getName(): ?string
    {
        return $this->name;
    }

    /**
     * @param string|null $name
     */
    public function setName(?string $name): void
    {
        $this->name = $name;
    }

    /**
     * @return string|null
     */
    public function getStreet(): ?string
    {
        return $this->street;
    }

    /**
     * @param string|null $street
     */
    public function setStreet(?string $street): void
    {
        $this->street = $street;
    }

    /**
     * @return string|null
     */
    public function getCity(): ?string
    {
        return $this->city;
    }

    /**
     * @param string|null $city
     */
    public function setCity(?string $city): void
    {
        $this->city = $city;
    }

    /**
     * @return string|null
     */
    public function getPostalCode(): ?string
    {
        return $this->postalCode;
    }

    /**
     * @param string|null $postalCode
     */
    public function setPostalCode(?string $postalCode): void
    {
        $this->postalCode = $postalCode;
    }

    /**
     * @return string|null
     */
    public function getCountry(): ?string
    {
        return $this->country;
    }

    /**
     * @param string|null $country
     */
    public function setCountry(?string $country): void
    {
        $this->country = $country;
    }

    /**
     * Get country name
     */
    public function getCountryName(): string
    {
        return Countries::getName($this->country);
    }

    /**
     * @return string|null
     */
    public function getProvince(): ?string
    {
        return $this->province;
    }

    /**
     * @param string|null $province
     */
    public function setProvince(?string $province): void
    {
        $this->province = $province;
    }

    /**
     * Get full description
     *
     * @return string
     */
    public function getFullDescription(): string
    {
        return ($this->name ? ($this->name . ' - ') : '') . $this->street . ', ' . $this->postalCode . ' ' . $this->city . ', ' . $this->getCountryName();
    }

    /**
     * @return int
     */
    public function getType(): int
    {
        return $this->type;
    }

    /**
     * @param int $type
     */
    public function setType(int $type): void
    {
        $this->type = $type;
    }

}

计算类型

<?php

namespace App\Form;

use App\Entity\Address;
use App\Entity\User;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints\Valid;

class CalculationType extends AbstractType
{

    /**
     * @var UserInterface|User|null
     */
    private ?UserInterface $user;

    /**
     * CalculationFormType constructor.
     *
     * @param Security $security
     */
    public function __construct(Security $security)
    {
        $this->user = $security->getUser();
    }

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     *
     */
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('senderAddress', AddressType::class, [
                'label' => false,
                'constraints' => [new Valid(['groups' => 'NotNullAddress'])],  //todo doesnt work
            ])
            ->add('recipientAddress', AddressType::class, [
                'label' => false,
                'constraints' => [new Valid(['groups' => 'NotNullAddress'])],  //todo doesnt work
                'show_name' => false,
            ])
            ->add('package', PackageType::class, ['label' => false])
            ->add('calculate', SubmitType::class, [
                'label' => 'order.calculate',
            ]);

        $this->addressesField($builder, 'senderAddresses', Address::TYPE_SENDER);
        $this->addressesField($builder, 'recipientAddresses', Address::TYPE_RECIPIENT);

    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'validation_groups' => static function (FormInterface $form) {
                if (!$form->get('senderAddresses')->getData()) {
                    return ['Default', 'NotNullAddress'];
                }
                return ['Default'];
            },
        ]);
    }

    /**
     * Addresses field
     *
     * @param FormBuilderInterface $builder
     * @param string $childName
     * @param int $type
     */
    private function addressesField(FormBuilderInterface $builder, string $childName, int $type): void
    {
        $builder->add($childName, EntityType::class, [
            'class' => Address::class,
            'mapped' => false,
            'required' => false,
            'placeholder' => 'order.newAddress',
            'choice_label' => 'fullDescription',
            'query_builder' => function (EntityRepository $er) use ($type) {
                return $er->createQueryBuilder('address')
                    ->andWhere('address.user = :user')
                    ->andWhere('address.type = ' . $type)
                    ->setParameter('user', $this->user);
            },
            'attr' => [
                'class' => 'select-existing-address',
            ],
        ]);
    }
}

地址类型

<?php

namespace App\Form;

use App\Entity\Address;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class AddressType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     *
     * @throws \JsonException
     */
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        if ($options['show_name']) {
            $builder->add('name');
        }

        $builder->add('street')
            ->add('city')
            ->add('postalCode')
            ->add('country', CountryType::class, [
                'preferred_choices' => ['CZ', 'DE', 'IT', 'SK'],
            ])
            ->add('province');
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Address::class,
            'show_name' => true,
        ]);
    }
}

【问题讨论】:

  • 您在AddressType 中定义了更多组吗?以下是关于 Default validation group 如何应用以及它如何影响嵌套对象验证的复习。
  • 不,我不在任何地方使用群组。仅在CalculationType 中。我还更新了我的问题,以便您可以查看整个对象。
  • 你找到解决方案了吗?我面临着完全相同的问题!
  • 很遗憾没有。我遇到此问题的项目已暂停,因此我停止寻找解决方案。

标签: php forms symfony validation constraints


【解决方案1】:

我刚刚为我正在构建的表单解决了这个问题。我在这里为可能需要解决此问题的未来用户添加此答案。 验证嵌入对象时,您必须将验证组添加到该对象的属性中。我为此使用了注释,因此 Address 实体应该如下所示(注意添加到每个需要验证的属性的 groups 选项):

<?php

namespace App\Entity;

use App\Repository\AddressRepository;
use App\Traits\IdentifierTrait;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Intl\Countries;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=AddressRepository::class)
 */
class Address
{
    use IdentifierTrait;

    public const TYPES = [
        'address.types.sender' => self::TYPE_SENDER,
        'address.types.recipient' => self::TYPE_RECIPIENT,
    ];

    public const TYPE_SENDER = 1;
    public const TYPE_RECIPIENT = 2;

    /**
     * @ORM\ManyToOne(targetEntity=User::class, inversedBy="addresses")
     *
     * @var User|null
     */
    private ?User $user;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     * @Assert\Length(
     *     max="100",
     *     groups={"SenderAddress"}
     * )
     *
     * @var string|null
     */
    private ?string $name = null;

    /**
     * @ORM\Column(type="string", length=150)
     * @Assert\NotBlank(groups={"SenderAddress"})
     * @Assert\Length(
     *     max="150",
     *     groups={"SenderAddress"},
     * )
     *
     * @var string|null
     */
    private ?string $street = null;

    /**
     * @ORM\Column(type="string", length=100)
     * @Assert\NotBlank(groups={"SenderAddress"})
     * @Assert\Length(
     *     max="100",
     *     groups={"SenderAddress"}
     * )
     *
     * @var string|null
     */
    private ?string $city = null;

    /**
     * @ORM\Column(type="string", length=15)
     * @Assert\NotBlank(groups={"SenderAddress"})
     * @Assert\Length(
     *     max="15"
     *     groups={"SenderAddress"}
     * )
     *
     * @var string|null
     */
    private ?string $postalCode = null;

    /**
     * @ORM\Column(type="string", length=3)
     * @Assert\NotBlank(groups={"SenderAddress"})
     * @Assert\Length(
     *     max="3"
     *     groups={"SenderAddress"}
     * )
     *
     * @var string|null
     */
    private ?string $country = null;

    /**
     * @ORM\Column(type="string", length=3, nullable=true)
     *
     * @var string|null
     */
    private ?string $province = null;

    /**
     * @ORM\Column(type="integer", length=1, options={"unsigned": true})
     *
     * @var int
     */
    protected int $type = self::TYPE_SENDER;
    

    // the rest of the entity....

}

Order 实体应该只有发件人地址的 Valid 注解:

/**
 * @ORM\Entity(repositoryClass=OrderRepository::class)
 * @ORM\Table(name="`order`")
 */
class Order
{
    use IdentifierTrait;

    public const STATUSES = [
        'order.statuses.calculation' => self::STATUS_CALCULATION,
        'order.statuses.created' => self::STATUS_CREATED,
    ];

    public const STATUS_CALCULATION = 1;
    public const STATUS_CREATED = 2;

    /**
     * @Assert\Valid()
     * @ORM\ManyToOne(targetEntity="Address", cascade={"persist"})
     * @var Address
     */
    private Address $senderAddress;
    
    // the rest of the entity

表单应该与修改一起使用。 更多详情here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-13
    • 2012-08-17
    • 2010-09-22
    • 2014-03-20
    • 2018-02-07
    • 1970-01-01
    • 2017-03-10
    相关资源
    最近更新 更多