You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Symfony的EasyAdmin中为Mission表单添加个性化验证

解决EasyAdmin中Mission与Contact的国家一致性验证及动态筛选问题

一、实体级验证:确保Contact国籍与Mission国家一致

如果之前自定义Validator没生效,大概率是约束应用位置不对或者验证逻辑有问题,按以下步骤来:

  1. 创建自定义约束类
// src/Validator/Constraints/ContactMatchesMissionCountry.php
namespace App\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

#[Attribute]
class ContactMatchesMissionCountry extends Constraint
{
    public string $message = "关联联系人的国籍必须与任务国家一致";

    public function getTargets(): string
    {
        return self::CLASS_CONSTRAINT;
    }
}
  1. 创建验证器类
// src/Validator/Constraints/ContactMatchesMissionCountryValidator.php
namespace App\Validator\Constraints;

use App\Entity\Mission;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class ContactMatchesMissionCountryValidator extends ConstraintValidator
{
    public function validate(mixed $value, Constraint $constraint): void
    {
        if (!$value instanceof Mission) {
            return;
        }

        $missionCountry = $value->getCountry();
        $contact = $value->getContact();

        // 任务无国家或无关联联系人时跳过验证
        if (!$missionCountry || !$contact) {
            return;
        }

        $contactNationality = $contact->getNationality();
        if ($contactNationality !== $missionCountry) {
            $this->context
                ->buildViolation($constraint->message)
                ->atPath('contact') // 指定错误显示在Contact字段上
                ->addViolation();
        }
    }
}
  1. 在Mission实体上应用约束
// src/Entity/Mission.php
use App\Validator\Constraints as AppAssert;

#[AppAssert\ContactMatchesMissionCountry]
class Mission
{
    // ... 其他属性和方法

    private ?Country $country = null; // 假设你的国家是Country实体或枚举
    private ?Contact $contact = null;

    // 对应的getter/setter
}

配置完成后,EasyAdmin在保存/更新Mission时会自动触发验证,不符合条件会在Contact字段下方显示错误提示。

二、动态筛选Contact选项:从根源避免错误

如果想在用户选择Mission国家后,只显示对应国籍的Contact,分两种场景处理:

编辑页面(已有Mission国家)

在MissionCrudController的configureFields方法中,给Contact字段设置动态QueryBuilder:

// src/Controller/Admin/MissionCrudController.php
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use Doctrine\ORM\QueryBuilder;

class MissionCrudController extends AbstractCrudController
{
    // ... 其他方法

    public function configureFields(string $pageName): iterable
    {
        // ... 其他字段
        $country = $this->getContext()->getEntity()->getInstance()->getCountry();

        yield AssociationField::new('contact')
            ->setQueryBuilder(function (QueryBuilder $qb) use ($country) {
                if ($country) {
                    $qb->andWhere('entity.nationality = :country')
                        ->setParameter('country', $country);
                }
            });
    }
}

编辑页面时,会自动过滤出与当前Mission国家匹配的Contact。

创建页面(需联动选择)

创建页面需要前端联动,监听Country字段的变化,动态更新Contact选项:

  1. 在configureFields中给Country和Contact字段添加自定义ID:
yield ChoiceField::new('country')
    ->setId('mission-country'); // 假设Country是枚举或下拉选择

yield AssociationField::new('contact')
    ->setId('mission-contact');
  1. 添加自定义JavaScript(需编译后引入)
// assets/js/easyadmin/mission-form.js
document.addEventListener('DOMContentLoaded', function() {
    const countrySelect = document.getElementById('mission-country');
    const contactSelect = document.getElementById('mission-contact');

    if (!countrySelect || !contactSelect) return;

    countrySelect.addEventListener('change', async function() {
        const countryId = this.value;
        // 清空现有选项
        contactSelect.innerHTML = '<option value="">请选择联系人</option>';

        if (!countryId) return;

        // 调用内部接口获取对应国家的联系人
        const response = await fetch(`/api/contacts-by-country/${countryId}`);
        const contacts = await response.json();

        contacts.forEach(contact => {
            const option = document.createElement('option');
            option.value = contact.id;
            option.textContent = `${contact.firstName} ${contact.lastName}`;
            contactSelect.appendChild(option);
        });
    });
});
  1. 在CrudController中引入该JS:
public function configureAssets(Assets $assets): Assets
{
    return parent::configureAssets($assets)
        ->addJsFile('build/easyadmin/mission-form.js');
}
  1. 创建对应的内部接口控制器:
// src/Controller/Api/ContactApiController.php
use App\Entity\Contact;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

class ContactApiController extends AbstractController
{
    #[Route('/api/contacts-by-country/{countryId}', name: 'api_contacts_by_country')]
    public function getContactsByCountry(int $countryId): JsonResponse
    {
        $contacts = $this->getDoctrine()
            ->getRepository(Contact::class)
            ->findBy(['nationality' => $countryId]);

        $data = array_map(function(Contact $contact) {
            return [
                'id' => $contact->getId(),
                'firstName' => $contact->getFirstName(),
                'lastName' => $contact->getLastName()
            ];
        }, $contacts);

        return new JsonResponse($data);
    }
}

这样创建页面时,选择国家后,Contact下拉框会自动加载对应国籍的联系人,从根源避免错误选择。

内容的提问来源于stack exchange,提问作者Rhapsody

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 05:06:25