Symfony 形式:Country State City 三动态下拉

Symfony form: Country State City three dynamic dropdown

您好,我正在使用 Symfony 5。我需要三个动态 select 下拉菜单。这是我的实体之间的关系:国家 -> 州 -> 城市。这些链接到这样的用户实体

当我添加一个新用户时,我应该能够 select 一个国家并根据国家 selection 更新国家下拉列表;在我 select 编辑了一个州之后,城市下拉菜单也是如此。我已经按照这里的官方 Symfony 指南为国家和州工作 https://symfony.com/doc/current/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms 我应该如何管理添加第三个下拉列表?

    <?php

namespace App\Entity;

use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use App\Entity\EmployeeDetails;

/**
 * @ORM\Entity(repositoryClass=UserRepository::class)
 * @UniqueEntity(fields={"email"}, message="There is already an account with this email")
 */
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    

    /**
     * @ORM\OneToOne(targetEntity=EmployeeDetails::class, mappedBy="user_id", cascade={"persist", "remove"})
     */
    private $employeeDetails;

    /**
     * @ORM\Column(type="string", length=11)
     */
    private $delete_data;

    /**
     * @ORM\ManyToOne(targetEntity=Country::class, inversedBy="users")
     */
    private $country;

    /**
     * @ORM\ManyToOne(targetEntity=State::class, inversedBy="users")
     */
    private $state;

    /**
     * @ORM\ManyToOne(targetEntity=City::class, inversedBy="users")
     */
    private $city;


    public function getId(): ?int
    {
        return $this->id;
    }

    
    public function getEmployeeDetails(): ?EmployeeDetails
    {
        return $this->employeeDetails;
    }

    public function setEmployeeDetails(?EmployeeDetails $employeeDetails): self
    {
        // unset the owning side of the relation if necessary
        if ($employeeDetails === null && $this->employeeDetails !== null) {
            $this->employeeDetails->setUserId(null);
        }

        // set the owning side of the relation if necessary
        if ($employeeDetails !== null && $employeeDetails->getUserId() !== $this) {
            $employeeDetails->setUserId($this);
        }

        $this->employeeDetails = $employeeDetails;

        return $this;
    }

    public function getDeleteData(): ?string
    {
        return $this->delete_data;
    }

    public function setDeleteData(string $delete_data): self
    {
        $this->delete_data = $delete_data;

        return $this;
    }

    public function getCountry(): ?Country
    {
        return $this->country;
    }

    public function setCountry(?Country $country): self
    {
        $this->country = $country;

        return $this;
    }

    public function getState(): ?State
    {
        return $this->state;
    }

    public function setState(?State $state): self
    {
        $this->state = $state;

        return $this;
    }

    public function getCity(): ?City
    {
        return $this->city;
    }

    public function setCity(?City $city): self
    {
        $this->city = $city;

        return $this;
    }

}

国家

<?php

namespace App\Entity;

use App\Repository\CountryRepository;
use App\Entity\State;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass=CountryRepository::class)
 */
class Country
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\OneToMany(targetEntity=State::class, mappedBy="country")
     */
    private $states;

    /**
     * @ORM\OneToMany(targetEntity=City::class, mappedBy="country")
     */
    private $cities;

    /**
     * @ORM\OneToMany(targetEntity=User::class, mappedBy="country")
     */
    private $users;

    public function __construct()
    {
        $this->states = new ArrayCollection();
        $this->cities = new ArrayCollection();
        $this->users = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    /**
     * @return Collection|State[]
     */
    public function getStates(): Collection
    {
        return $this->states;
    }

    public function addState(State $state): self
    {
        if (!$this->states->contains($state)) {
            $this->states[] = $state;
            $state->setCountry($this);
        }

        return $this;
    }

    public function removeState(State $state): self
    {
        if ($this->states->removeElement($state)) {
            // set the owning side to null (unless already changed)
            if ($state->getCountry() === $this) {
                $state->setCountry(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|City[]
     */
    public function getCities(): Collection
    {
        return $this->cities;
    }

    public function addCity(City $city): self
    {
        if (!$this->cities->contains($city)) {
            $this->cities[] = $city;
            $city->setCountry($this);
        }

        return $this;
    }

    public function removeCity(City $city): self
    {
        if ($this->cities->removeElement($city)) {
            // set the owning side to null (unless already changed)
            if ($city->getCountry() === $this) {
                $city->setCountry(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|User[]
     */
    public function getUsers(): Collection
    {
        return $this->users;
    }

    public function addUser(User $user): self
    {
        if (!$this->users->contains($user)) {
            $this->users[] = $user;
            $user->setCountry($this);
        }

        return $this;
    }

    public function removeUser(User $user): self
    {
        if ($this->users->removeElement($user)) {
            // set the owning side to null (unless already changed)
            if ($user->getCountry() === $this) {
                $user->setCountry(null);
            }
        }

        return $this;
    }
}

<?php

namespace App\Entity;

use App\Repository\StateRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass=StateRepository::class)
 */
class State
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\ManyToOne(targetEntity=Country::class, inversedBy="states")
     */
    private $country;

    /**
     * @ORM\OneToMany(targetEntity=City::class, mappedBy="state")
     */
    private $cities;

    /**
     * @ORM\OneToMany(targetEntity=User::class, mappedBy="state")
     */
    private $users;

    public function __construct()
    {
        $this->cities = new ArrayCollection();
        $this->users = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getCountry(): ?Country
    {
        return $this->country;
    }

    public function setCountry(?Country $country): self
    {
        $this->country = $country;

        return $this;
    }

    /**
     * @return Collection|City[]
     */
    public function getCities(): Collection
    {
        return $this->cities;
    }

    public function addCity(City $city): self
    {
        if (!$this->cities->contains($city)) {
            $this->cities[] = $city;
            $city->setState($this);
        }

        return $this;
    }

    public function removeCity(City $city): self
    {
        if ($this->cities->removeElement($city)) {
            // set the owning side to null (unless already changed)
            if ($city->getState() === $this) {
                $city->setState(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|User[]
     */
    public function getUsers(): Collection
    {
        return $this->users;
    }

    public function addUser(User $user): self
    {
        if (!$this->users->contains($user)) {
            $this->users[] = $user;
            $user->setState($this);
        }

        return $this;
    }

    public function removeUser(User $user): self
    {
        if ($this->users->removeElement($user)) {
            // set the owning side to null (unless already changed)
            if ($user->getState() === $this) {
                $user->setState(null);
            }
        }

        return $this;
    }
}

城市

<?php

namespace App\Entity;

use App\Repository\CityRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass=CityRepository::class)
 */
class City
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\ManyToOne(targetEntity=Country::class, inversedBy="cities")
     */
    private $country;

    /**
     * @ORM\ManyToOne(targetEntity=State::class, inversedBy="cities")
     */
    private $state;

    /**
     * @ORM\OneToMany(targetEntity=User::class, mappedBy="city")
     */
    private $users;

    public function __construct()
    {
        $this->users = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getCountry(): ?Country
    {
        return $this->country;
    }

    public function setCountry(?Country $country): self
    {
        $this->country = $country;

        return $this;
    }

    public function getState(): ?State
    {
        return $this->state;
    }

    public function setState(?State $state): self
    {
        $this->state = $state;

        return $this;
    }

    /**
     * @return Collection|User[]
     */
    public function getUsers(): Collection
    {
        return $this->users;
    }

    public function addUser(User $user): self
    {
        if (!$this->users->contains($user)) {
            $this->users[] = $user;
            $user->setCity($this);
        }

        return $this;
    }

    public function removeUser(User $user): self
    {
        if ($this->users->removeElement($user)) {
            // set the owning side to null (unless already changed)
            if ($user->getCity() === $this) {
                $user->setCity(null);
            }
        }

        return $this;
    }
}

表单类型

<?php

namespace App\Form;

use App\Entity\User;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType; 
use App\Entity\Country;
use App\Entity\State;
use App\Entity\City;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormInterface;
use App\Repository\CountryRepository;
use App\Repository\StateRepository;
use App\Repository\CityRepository;
use Doctrine\ORM\EntityRepository;





class RegistrationFormType extends AbstractType
{
    private $countryRepository;
    private $StateRepository;
    private $stateCitiesRepository;

    public function __construct(
        CountryRepository $countryRepository,
        StateRepository $stateRepository,
        CityRepository $stateCitiesRepository
        ) {
        $this->countryRepository = $countryRepository;
        $this->countryStateRepository = $stateRepository;
        $this->stateCitiesRepository = $stateCitiesRepository;
}

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('first_name')
            ->add('last_name')
            ->add('gender', ChoiceType::class, array( 
                'choices'  => array( 
                   'Male' => true, 
                   'Female' => false, 
                ), 
             ))
            ->add('dob')
            ->add('mobile')
            ->add('address', TextareaType::class)
            ->add('country', EntityType::class, [
                'class' => Country::class,
                'label' => 'Country',
                'required' => true,
                'choice_label' => function(Country $country) {
                    return $country->getName();
                },
                'invalid_message' => 'You must select a Country',
                'placeholder' => 'Select Country',
            ])
            ->add('state', ChoiceType::class, [
                'choices' => [],
                'placeholder' => 'Select State',
            ])
            ->add('city', ChoiceType::class, [
                'choices' => [],
            ])        
            ->add('pincode')
            ->add('email')
            ->add('plainPassword', RepeatedType::class, [
                'mapped' => false,
                'attr' => ['autocomplete' => 'new-password'],
                'constraints' => [
                    new NotBlank([
                        'message' => 'Please enter a password',
                    ]),
                    new Length([
                        'min' => 6,
                        'minMessage' => 'Your password should be at least {{ limit }} characters',
                        // max length allowed by Symfony for security reasons
                        'max' => 4096,
                    ]),
                    new Regex([
                        'pattern'=>"/^\S*(?=\S{8,})(?=\S*[a-z])(?=\S*[A-Z])(?=\S*[\d])\S*$/", 
                        'message'=>" Password must be at least 6 characters: 1 uppercase, 1 lowercase,                  numbers, or symbols."
                    ])
                ],
                'type' => PasswordType::class,
                'invalid_message' => 'The password fields must match.',
                'options' => ['attr' => ['class' => 'password-field']],
                'required' => true,
                'first_options'  => ['label' => 'Password'],
                'second_options' => ['label' => 'Confirm Password'],
            ])
            ->add('agreeTerms', CheckboxType::class, [
                'mapped' => false,
                'constraints' => [
                    new IsTrue([
                        'message' => 'You should agree to our terms.',
                    ]),
                ],
            ])
            ->add('submit',SubmitType::class)
        ;

        //**************** Start State Form
        $addStateForm = function (FormInterface $form, $country_id) {

            $form->add('state', EntityType::class, [
                'label' => 'state',
                'placeholder' => 'Select state',
                'required' => true,
                'class' => State::class,
                'query_builder' => function (StateRepository $repository) use ( $country_id ) {
                    
                    return $repository->createQueryBuilder('c')
                    ->where('c.country = :id')
                    ->setParameter('id', $country_id)
                    ->orderBy('c.name', 'ASC')                    
                    ;
                    // echo "<pre>"; print_r(($sql->getQuery()->getArrayResult())); exit;
                },
                'choice_label' => 'name',
                'choice_value' => 'name',
                'constraints' => [
                    new NotBlank([
                        'message' => 'State is required',
                    ]),
                ],
            ]);
        };
        

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($addStateForm) {
                $country = $event->getData()->getCountry();
                $country_id = $country ? $country->getId() : null;
                $addStateForm($event->getForm(), $country_id);
            }
        );

        $builder->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) use ($addStateForm) {
                $data = $event->getData();

                $country_id = array_key_exists('country', $data) ? $data['country'] : null;
                

                $addStateForm($event->getForm(), $country_id);
            }
        );
        //**************** End State Form

        //**************** Start City Form

        $addCityForm = function (FormInterface $form, $state_id) {

            $form->add('city', EntityType::class, [
                'label' => 'city',
                'placeholder' => 'Select city',
                'required' => true,
                'class' => City::class,
                'query_builder' => function (CityRepository $repository) use ( $state_id ) {
                    
                    return $repository->createQueryBuilder('c')
                    ->where('c.state = :id')
                    ->setParameter('id', $state_id)
                    ->orderBy('c.name', 'ASC')                    
                    ;
                    // echo "<pre>"; print_r(($sql->getQuery()->getArrayResult())); exit;
                },
                'choice_label' => 'name',
                'choice_value' => 'name',
                'constraints' => [
                    new NotBlank([
                        'message' => 'City is required',
                    ]),
                ],
            ]);
        };
        

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($addCityForm) {
                $state = $event->getData()->getState();
                print_r($state);
                $state_id = $state ? $state->getId() : null;
                $addCityForm($event->getForm(), $state_id);
            }
        );

        $builder->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) use ($addCityForm) {
                $data = $event->getData();

                $state_id = array_key_exists('state', $data) ? $data['state'] : null;
                

                $addCityForm($event->getForm(), $state_id);
            }
        );

        //**************** End City Form


    
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    }
}

树枝

<script>
      $(document).ready(function() {

                var $country = $('#registration_form_country');
                var $state = $('#registration_form_state');
                
                // When country gets selected ...
                $country.change(function () {
                    // ... retrieve the corresponding form.
                    var $form = $(this).closest('form');
                    // Simulate form data, but only include the selected country value.
                    var data = {};
                    data[$country.attr('name')] = $country.val();
                    // Submit data via AJAX to the form's action path.
                    $.ajax({
                        url: $form.attr('action'),
                        type: $form.attr('method'),
                        data: data,
                        success: function (html) {
                            // Replace current state field ...
                            $('#registration_form_state').replaceWith(
                                // ... with the returned one from the AJAX response.
                                $(html).find('#registration_form_state')
                            );
                        }
                    });
                });

                
                // When state gets selected ...
                $state.change( function () {
                    // ... retrieve the corresponding form.
                    var $form = $(this).closest('form');
                    // Simulate form data, but only include the selected state value.
                    var data = {};
                    data[$state.attr('name')] = $state.val();
                    // Submit data via AJAX to the form's action path.
                    $.ajax({
                        url: $form.attr('action'),
                        type: $form.attr('method'),
                        data: data,
                        success: function (html) {
                            // Replace current city field ...
                            $('#registration_form_city').replaceWith(
                                // ... with the returned one from the AJAX response.
                                $(html).find('#registration_form_city')
                            );
                        }
                    });
                });

            });
        </script>

需要更改 ajax 调用脚本。

树枝

    <script>
            $(document).ready(function() {
                var $country = $('#registration_form_country');
                var $state = $('#registration_form_state');

                $country.change(function () {
                    var $form = $(this).closest('form');
                    var data = {};
                    data[$country.attr('name')] = $country.val();
                    $.ajax({
                        url: $form.attr('action'),
                        type: $form.attr('method'),
                        data: data,
                        success: function (html) {
                            $('#registration_form_state').replaceWith(
                                $(html).find('#registration_form_state')
                            );
                            $('#registration_form_city').replaceWith(
                                $(html).find('#registration_form_city')
                            );
                            $state = $('#registration_form_state');
                            $state.change( function () {
                            var $form = $(this).closest('form');
                            var data = {};
                            data[$state.attr('name')] = $state.val();
                            $.ajax({
                                url: $form.attr('action'),
                                type: $form.attr('method'),
                                data: data,
                                success: function (html) {
                                    $('#registration_form_city').replaceWith(
                                        $(html).find('#registration_form_city')
                                    );
                                }
                            });
                            });
                        }
                    });
                });
            });
        </script>