Всё намного проще и само собой подходов куча.
Классический подход НЕ подразумевает js и добавляет новые поля после каждого сабмита формы, решается крайне просто:
<?php
interface ObjectInterface {
public function getCountry(): ?Country;
public function getRegion(): ?Region;
}
class AppType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', EntityType::class, [
'class' => \App\Entity\Country::class,
])
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
/** @var ObjectInterface $object */
if (null === $object = $event->getData()) {
return;
}
$form = $event->getForm();
if ($object->getCountry()) {
$form->add('region', EntityType::class, [
'class' => \App\Entity\Region::class,
'query_builder' => function (EntityRepository $repository) use ($object) {
return $repository->createQueryBuilder('e')->where('e.country = :country')->setParameter('country', $object->getCountry());
}
]);
}
if ($object->getRegion()) {
$form->add('city', EntityType::class, [
'class' => \App\Entity\City::class,
'query_builder' => function (EntityRepository $repository) use ($object) {
return $repository->createQueryBuilder('e')->where('e.region = :region')->setParameter('region', $object->getRegion());
}
]);
}
});
}
}
Цена вопроса 3 POST запроса
Но такой подход не удобен и только путает.
Как решить?
Одним из способов будет являться следующее:
- Создаём контроллер с методами получения регионов, городов.
- На фронте любыми способами рисуем инпут
- Динамически (на фронте) делаем с этими формами что угодно
- Отправляем форму
В этом случае symfony форма будет выглядить примитивно
$builder->add('country')->add('region')->add('city');
Надо будет только задать группу валидации, где вы спокойненько провалидуриете всё дерево, начиная со страны.
Цена вопроса - 2 ajax запроса, 1 POST
Или...
Рисуем форму
$builder->add('country')->add('region')->add('city');
Где region и city - ChoiceType пустые.
Аяксом забираем с сервера регионы, а затем города.
В событии формы (SUBMIT и POST_SUBMIT с приоритетом нижк валидатора) ручками ссетим эти данные.
Лично мне предпочтителен первый подход (классика без js), на которую потом уже навешиваются плюшки.
Запросов больше, но объект собирается, как на фронте, так и на бэке последовательно и нет никаких проблем ни с редактированием, ни с созданием.
Для понимания. PRE_SET_DATA - это GET запрос, прим перед резолвом данных (имеено по этому в коде проверка на null). PRE_SUBMIT - это POST запрос. В обоих случаях у вас объект в событии.
Соответственно за последовательность отвечает data_class, объект DTO (или сущнсоть) в котором будет страна, регион и город.
Бонусом (разобравшись с этой ересью) - валидаторы, всякие ресты и сериализаторы прикручиваются к таким формам на раз-два по необходимости.
> Пробовал - не работает. В него не попадают значения динамических полей
Отвечу на ваш коммент тут.
Во-первых - событие PRE_SET_DATA с проверкой данных на null
Во-вторых - событие PRE_SUBMIT (пожеланию) на биндинг данных (если у вас в форме не используется сущности)
symfony.com/doc/current/form/dynamic_form_modifica...