Symfony 2.8+, Doctrine Inheritance and Forms

2019-07-04 06:34发布

问题:

Before i start, Note that I'm learning symfony so keep that in mind ! I just want to understand how it works. Here's what i am trying to achieve :

I would like to make a working crud example of entities inheritance using doctrine. So this is how my example looks like :

  • Abstract Parent class : character
  • Child class 1 : Magician
  • Child class 2 : Warrior
  • Child class 3 : Archer

So after reading some documentation i decided to use the STI (Single Table Inheritance) of Doctrine.

Parent class :

/**
 * Character
 *
 * @ORM\Table(name="character")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\CharacterRepository")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({"magician_db" = "Magician", "warrior_db" = "Warrior", "archer_db" = "Archer"})
 */

abstract class Character{
    protected id;
    protected name;
    public function getId();
    public function getName();
    public function setName();
}

Child Class 1 :

class Warrior extends Character{
       protected armor;
       public function battleShout();
}

Child Class 2:

class Magician extends Character{
       protected silk;
       public function spellAnnounce();
}

Child Class 3:

class Archer extends Character{
       protected leather;
       public function arrows();
}

I managed to create the table in my db, and i successfully loaded my fixtures for tests purposes. I also made my main view work (listing all characters).


My Problem : Now i want to be able to create, edit & delete a specific character in the list with a single form. So for example i would have a 'type' select field where i can select 'warrior' , 'magician' or 'archer' and then i would be able to fill in the specific fields of the chosen entity. So let's say i choose 'warrior' in the form, then i would like to be able to set the armor property (along with the parents one of course) and persist it in the database.

I don't know how to do it since my parent class is abstract so i can't create a form based on that object.

Thx in advance for your help, i really need it !

PS: If there is a better solution / implementation don't hesitate !

回答1:

The easiest way is to provide all fields and to remove them according to the 'type' value.

To do that you have to implement the logic on the client side (for displaying purpose) and server side (so that the removed fields cannot be changed in your entity).

On the client side :

  • Use javascript to hide the types which can't be set for each 'type' change (you can use JQuery and the .hide() function).

On the server side:

  • Add a PRE_BIND event to your form type, to remove the fields from the form :

http://symfony.com/doc/current/components/form/form_events.html#a-the-formevents-pre-submit-event

Your Form should look like :

// ...

use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;

$form = $formFactory->createBuilder()
    ->add('type', ChoiceType::class)
    ->add('armor')
    ->add('silk')
    ->add('leather')
    ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
        $submittedData = $event->getData();
        $form = $event->getForm();

        switch($submittedData['type'])
        {
            case 'warrior':
                $form->remove('silk');
                $form->remove('leather');
            break;
            case 'magician':
                $form->remove('armor');
                $form->remove('leather');
            break;
            case 'archer':
                $form->remove('armor');
                $form->remove('silk');
            break;
            default:
            throw new ...;
        }
    })
    ->getForm();

// ...

EDIT

To deal with Single Table Inheritance, you can't use an abstract class, the base class must be a normal entity.

In your form, just set the class as AppBundle\Character.

In your controller action which creates the character, you must initiate your entity with something like this :

if($request->isMethod('POST')){
    // form has been submitted
    switch($request->get('type'))
    {
        case 'warrior':
        $entity = new Warrior();
        ...
    }
}
else{
    // form has not been submitted, default : Warrior
    $entity = new Warrior();
}

By editing and removing the character, you can directly deal with the Character Entity.

I recommand to not let the user change the type by edit, see Doctrine: Update discriminator for SINGLE_TABLE Inheritance