Symfony5.4实体字段关联校验:如何用约束注解实现双向非空绑定?
Symfony 5.4实体类字段关联验证解决方案
问题描述
我在Symfony 5.4中有如下实体类:
use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; class Assignments { public const SALARY_RANGES = [ 'Red', 'Green', null, ]; /** * @ORM\Column(length=255, nullable=true) * @Assert\Choice(choices=Assignments::SALARY_RANGES, strict=true) */ private ?string $salaryRange; /** * @ORM\ManyToOne(targetEntity="Employee", inversedBy="assignments") * @ORM\JoinColumn(name="employee_id", referencedColumnName="id", onDelete="CASCADE") */ private ?Employee $employee; }
需要实现规则:当且仅当employee不为null时,salaryRange必须为非null值。尝试使用@Assert\Callback但无法获取另一个字段的值:
/** * @Assert\Callback({"ExampleValidator", "assertEmployeeOnlyCallback"}) */
public static function assertEmployeeOnlyCallback(mixed $data, ExecutionContextInterface $context): void { // `$data` 是 `salaryRange` 的值,但 `employee` 的值在哪里? }
解决方案
方法一:正确使用类级别@Assert\Callback
你之前的错误在于把@Assert\Callback注解放在了字段上,导致仅传入单个字段值。要获取整个实体对象,需将注解放在类级别:
- 在实体类顶部添加类级别的Callback注解:
/** * @ORM\Entity() * @Assert\Callback({"App\Validator\AssignmentsValidator", "validateSalaryRangeDependency"}) */ class Assignments { // ... 原有字段定义 // 确保存在对应的getter方法 public function getEmployee(): ?Employee { return $this->employee; } public function getSalaryRange(): ?string { return $this->salaryRange; } }
- 编写验证器类,接收完整实体对象:
namespace App\Validator; use App\Entity\Assignments; use Symfony\Component\Validator\Context\ExecutionContextInterface; class AssignmentsValidator { public static function validateSalaryRangeDependency(Assignments $assignment, ExecutionContextInterface $context): void { // 验证正向规则:关联员工时,薪资范围不能为空 if ($assignment->getEmployee() !== null && $assignment->getSalaryRange() === null) { $context->buildViolation('关联员工时,必须设置薪资范围') ->atPath('salaryRange') // 将错误绑定到salaryRange字段 ->addViolation(); } // 验证反向规则:设置薪资范围时,必须关联员工 if ($assignment->getSalaryRange() !== null && $assignment->getEmployee() === null) { $context->buildViolation('仅当关联员工时可设置薪资范围') ->atPath('employee') // 将错误绑定到employee字段 ->addViolation(); } } }
方法二:使用@Assert\Expression(更简洁)
无需单独编写验证器,直接通过表达式实现字段间的关联验证:
对salaryRange字段添加约束:
/** * @ORM\Column(length=255, nullable=true) * @Assert\Choice(choices=Assignments::SALARY_RANGES, strict=true) * @Assert\Expression( * "this.getEmployee() === null or value !== null", * message="关联员工时,薪资范围不能为空" * ) */ private ?string $salaryRange;
对employee字段添加反向约束:
/** * @ORM\ManyToOne(targetEntity="Employee", inversedBy="assignments") * @ORM\JoinColumn(name="employee_id", referencedColumnName="id", onDelete="CASCADE") * @Assert\Expression( * "this.getSalaryRange() === null or value !== null", * message="仅当关联员工时可设置薪资范围" * ) */ private ?Employee $employee;
内容的提问来源于stack exchange,提问作者Álvaro González
相关产品推荐
相关产品推荐

