【发布时间】: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和什么都没有填写
- test 字段为空,test 字段已正确验证并使用 NotBlank() vioaliton
- 即使字段为空,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中定义了更多组吗?以下是关于Defaultvalidation group 如何应用以及它如何影响嵌套对象验证的复习。 -
不,我不在任何地方使用群组。仅在
CalculationType中。我还更新了我的问题,以便您可以查看整个对象。 -
你找到解决方案了吗?我面临着完全相同的问题!
-
很遗憾没有。我遇到此问题的项目已暂停,因此我停止寻找解决方案。
标签: php forms symfony validation constraints