#php #ajax #symfony
Задача: форма регистрации, кроме всего прочего есть три выпадающих списка. В первом выбирается страна, после этого во второй Ajax'ом загружаются регионы и, при выборе региона, в третий грузятся города. Все три поля грузят данные из трёх моделей: Country, Region, City. Код buildForm: $builder ->add('country', EntityType::class, ['class' => 'AppBundle:Country', 'choice_label' => 'name', 'placeholder' => '--- Выберите страну ---']) ->add('region', RegionSelectorType::class, [ 'required' => false]) ->add('city', EntityType::class, ['class' => 'AppBundle:City', 'choice_label' => 'name', 'placeholder' => '--- Выберите город ---', 'required' => false]) ->add('post_code', null, ['required' => false]) ->add('address', null, ['required' => false]) Страна грузится сразу из сущности, их там немного. А вот для регионов и городов я хотел сделать динамическую загрузку, чтобы не грузить зря кучу данных. Пока застопорился на регионе, город будет по тому же принципу. Проблема в том, что при отправке формы не происходит трансформация поля, по ID, в сущность. Пробовал и Using Transformer, Creating a Reusable issue_selector Field, в профилере пишется Unable to reverse value for property path "region": The choice "15" does not exist or is not unique Код моего типа RegionSelectorType: namespace AppBundle\Form; use AppBundle\Form\DataTransformer\RegionToNumberTransformer; use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class RegionSelectorType extends AbstractType { private $manager; public function __construct(ObjectManager $manager) { $this->manager = $manager; } public function buildForm(FormBuilderInterface $builder, array $options) { $transformer = new RegionToNumberTransformer($this->manager); $builder->addModelTransformer($transformer); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'invalid_message' => 'Выбранный регион не существует', )); } public function getParent() { return ChoiceType::class; } } Код трансформера: namespace AppBundle\Form\DataTransformer; use AppBundle\Entity\Region; use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; class RegionToNumberTransformer implements DataTransformerInterface { private $manager; public function __construct(ObjectManager $manager) { $this->manager = $manager; } /** * Transforms an object (region) to a string (number). * * @param Region|null $region * @return string */ public function transform($region) { if (null === $region) { return ''; } return $region->getId(); } /** * Transforms a string (number) to an object (region). * * @param string $regionNumber * @return Region|null * @throws TransformationFailedException if object (region) is not found. */ public function reverseTransform($regionNumber) { // no region number? It's optional, so that's ok if (!$regionNumber) { return; } $region = $this->manager ->getRepository('AppBundle:Region') // query for the issue with this id ->find($regionNumber) ; if (null === $region) { // causes a validation error // this message is not shown to the user // see the invalid_message option throw new TransformationFailedException(sprintf( 'An region with number "%s" does not exist!', $regionNumber )); } return $region; } } Сервис регистрируется так: services: app.form.type.region_selector: class: AppBundle\Form\RegionSelectorType arguments: ['@doctrine.orm.entity_manager'] tags: - { name: form.type, alias: region_selector } В общем, все по мануалу. Функция reverseTransform() трансформера вообще не срабатывает. Стоит только заменить в getParent() ChoiceType::class на TextType::class, как в примере все работает: reverseTransform() срабатывает, сущность создается. С EntityType вместо моего типа тоже все работает. Создаю регион типа EntityType, в choices подсовываю пустой массив, - ошибка. Т.е., в поле формы должен присутствовать EntityType тот, что потом будет возвращен сабмитом. Первое что приходит в голову, создавать EventListеner, отлавливать передаваемое ChoiceType'ом значение и создавать на лету сущность, но зачем тогда DataTransformer'ы? Symfony использую 3, но, думаю, на второй будут примерно те же проблемы и решение.
Ответы
Ответ 1
но зачем тогда DataTransformer'ы? DataTransformerы тут ни при чём. Первое что приходит в голову, создавать EventListеner Как вы сами верно заметили, можно создавать EventListеner. Но не обязательно использовать ChoiceType, можно использовать также EntityType. Ниже примеры кода: Форма регистрации: namespace AppBundle\Form; use AppBundle\Entity\City; use AppBundle\Entity\Country; use AppBundle\Entity\Region; use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; class RegistrationForm extends AbstractType { public function buildForm( FormBuilderInterface $builder, array $options ) { $builder ->add('country', EntityType::class, [ 'class' => Country::class, ]) ->add('region', EntityType::class, [ 'class' => Region::class, 'query_builder' => function(EntityRepository $er) { $qb = $er->createQueryBuilder('r') ->where('r.country IS NULL') ; return $qb; }, ]) ->add('city', EntityType::class, [ 'class' => City::class, 'query_builder' => function(EntityRepository $er) { $qb = $er->createQueryBuilder('c') ->where('c.region IS NULL') ; return $qb; }, ]) ; $builder->addEventListener(FormEvents::POST_SET_DATA, function(FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); /** @var $country Country */ $country = $data['country']; $country = $country ? $country->getId() : false; /** @var $region Region */ $region = $data['region']; $region = $region ? $region->getId() : false; $this->modifyForm($form, $country, $region); }); $builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); /** @var $country integer */ $country = (int)$data['country']; $region = (int)$data['region']; $this->modifyForm($form, $country, $region); }); } protected function modifyForm(FormInterface $form, $country, $region = null) { if ( $country ) { $form->add('region', EntityType::class, [ 'class' => Region::class, 'query_builder' => function(EntityRepository $er) use ( $country ) { $qb = $er->createQueryBuilder('r') ->where('r.country = :country') ->setParameter('country', $country) ->orderBy('r.name') ; return $qb; } ]); if ( $region ) { $form->add('city', EntityType::class, [ 'class' => City::class, 'query_builder' => function(EntityRepository $er) use ( $region ) { $qb = $er->createQueryBuilder('c') ->where('c.region = :region') ->setParameter('region', $region) ->orderBy('r.name') ; return $qb; } ]); } } } } По умолчанию, регионы и города будут пустые, добились этого посредством параметра quiery_builder. Если указана страна, то мы ловим это в eventListener, и меняем query_builder, чтобы получить регионы этой страны. Для городов тоже самое. Думаю, с остальными справитесь.
Комментариев нет:
Отправить комментарий