至少对我来说,我遇到过非常奇怪的Symfony行为。我有嵌套表单的表单类型。只有在某些情况下,我才需要验证此嵌套表单。
这就是我将验证组添加到Validat((约束的原因。若我对例如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和中没有填写任何内容
- test字段为空,test域已正确验证,并使用NotBlank((vioaliton
- 即使字段为空,senderAddress也不显示任何vioaliton
但当我尝试删除senderAddress的组验证时,它可以正常工作。我做错了什么?
->add('senderAddress', AddressType::class, [
'label' => false,
'constraints' => [new Valid()], //but this is not how I want it!
])
编辑:这些是我使用的对象
计算类型的实体订单
<?php
namespace AppEntity;
use AppRepositoryOrderRepository;
use AppTraitsIdentifierTrait;
use DateTime;
use DoctrineORMMapping as ORM;
use SymfonyComponentValidatorConstraints as Assert;
/**
* @ORMEntity(repositoryClass=OrderRepository::class)
* @ORMTable(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;
/**
* @ORMManyToOne(targetEntity="Address", cascade={"persist"})
*
* @var Address
*/
private Address $senderAddress;
/**
* @ORMManyToOne(targetEntity="Address", cascade={"persist"})
*
* @var Address
*/
private Address $recipientAddress;
/**
* @ORMManyToOne(targetEntity="Package", cascade={"persist"})
* @AssertValid()
*
* @var Package
*/
private Package $package;
/**
* @ORMColumn(type="datetime")
*
* @var DateTime
*/
private DateTime $createdAt;
/**
* @ORMColumn(type="integer", length=1, options={"unsigned": true})
*
* @var int
*/
protected int $status = self::STATUS_CALCULATION;
/**
* @ORMColumn(type="text", length=255, nullable=TRUE)
*
* @var null|string
*/
private ?string $serviceType = null;
/**
* @ORMManyToOne(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 AppEntity;
use AppRepositoryAddressRepository;
use AppTraitsIdentifierTrait;
use DoctrineORMMapping as ORM;
use SymfonyComponentIntlCountries;
use SymfonyComponentValidatorConstraints as Assert;
/**
* @ORMEntity(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;
/**
* @ORMManyToOne(targetEntity=User::class, inversedBy="addresses")
*
* @var User|null
*/
private ?User $user;
/**
* @ORMColumn(type="string", length=100, nullable=true)
* @AssertLength(
* max="100"
* )
*
* @var string|null
*/
private ?string $name = null;
/**
* @ORMColumn(type="string", length=150)
* @AssertNotBlank()
* @AssertLength(
* max="150"
* )
*
* @var string|null
*/
private ?string $street = null;
/**
* @ORMColumn(type="string", length=100)
* @AssertNotBlank()
* @AssertLength(
* max="100"
* )
*
* @var string|null
*/
private ?string $city = null;
/**
* @ORMColumn(type="string", length=15)
* @AssertNotBlank()
* @AssertLength(
* max="15"
* )
*
* @var string|null
*/
private ?string $postalCode = null;
/**
* @ORMColumn(type="string", length=3)
* @AssertNotBlank()
* @AssertLength(
* max="3"
* )
*
* @var string|null
*/
private ?string $country = null;
/**
* @ORMColumn(type="string", length=3, nullable=true)
*
* @var string|null
*/
private ?string $province = null;
/**
* @ORMColumn(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 AppForm;
use AppEntityAddress;
use AppEntityUser;
use DoctrineORMEntityRepository;
use SymfonyBridgeDoctrineFormTypeEntityType;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreTypeHiddenType;
use SymfonyComponentFormExtensionCoreTypeSubmitType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentFormFormInterface;
use SymfonyComponentOptionsResolverOptionsResolver;
use SymfonyComponentSecurityCoreSecurity;
use SymfonyComponentSecurityCoreUserUserInterface;
use SymfonyComponentValidatorConstraintsValid;
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 AppForm;
use AppEntityAddress;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreTypeCountryType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;
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,
]);
}
}
我刚刚为正在构建的表单解决了这个问题。我在这里添加这个答案是为了将来可能需要解决这个问题的用户。验证嵌入对象时,必须将验证组添加到该对象的属性中。我使用了注释,所以Address实体应该是这样的(注意,groups选项添加到每个需要验证的属性中(:
<?php
namespace AppEntity;
use AppRepositoryAddressRepository;
use AppTraitsIdentifierTrait;
use DoctrineORMMapping as ORM;
use SymfonyComponentIntlCountries;
use SymfonyComponentValidatorConstraints as Assert;
/**
* @ORMEntity(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;
/**
* @ORMManyToOne(targetEntity=User::class, inversedBy="addresses")
*
* @var User|null
*/
private ?User $user;
/**
* @ORMColumn(type="string", length=100, nullable=true)
* @AssertLength(
* max="100",
* groups={"SenderAddress"}
* )
*
* @var string|null
*/
private ?string $name = null;
/**
* @ORMColumn(type="string", length=150)
* @AssertNotBlank(groups={"SenderAddress"})
* @AssertLength(
* max="150",
* groups={"SenderAddress"},
* )
*
* @var string|null
*/
private ?string $street = null;
/**
* @ORMColumn(type="string", length=100)
* @AssertNotBlank(groups={"SenderAddress"})
* @AssertLength(
* max="100",
* groups={"SenderAddress"}
* )
*
* @var string|null
*/
private ?string $city = null;
/**
* @ORMColumn(type="string", length=15)
* @AssertNotBlank(groups={"SenderAddress"})
* @AssertLength(
* max="15"
* groups={"SenderAddress"}
* )
*
* @var string|null
*/
private ?string $postalCode = null;
/**
* @ORMColumn(type="string", length=3)
* @AssertNotBlank(groups={"SenderAddress"})
* @AssertLength(
* max="3"
* groups={"SenderAddress"}
* )
*
* @var string|null
*/
private ?string $country = null;
/**
* @ORMColumn(type="string", length=3, nullable=true)
*
* @var string|null
*/
private ?string $province = null;
/**
* @ORMColumn(type="integer", length=1, options={"unsigned": true})
*
* @var int
*/
protected int $type = self::TYPE_SENDER;
// the rest of the entity....
}
订单实体应仅具有发件人地址的有效注释:
/**
* @ORMEntity(repositoryClass=OrderRepository::class)
* @ORMTable(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;
/**
* @AssertValid()
* @ORMManyToOne(targetEntity="Address", cascade={"persist"})
* @var Address
*/
private Address $senderAddress;
// the rest of the entity
该表单应能进行修改。更多详情点击此处