4

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 !

Elbarto
  • 1,213
  • 1
  • 14
  • 18

1 Answers1

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

Community
  • 1
  • 1
Alsatian
  • 3,086
  • 4
  • 25
  • 40
  • Thanks for answering, i still don't see how i would create the generic formType containing every fields and then calling it in my controller. – Elbarto Jun 08 '16 at 12:18
  • Like every others generic formTypes. You can create it directly in the controller (like in my example) or save it in a separated formType class. But it must contain the "addEventListener" block to solve your problem. I won't make you a tutorial about Forms. That's not your question. Everything is here : http://symfony.com/doc/current/book/forms.html – Alsatian Jun 08 '16 at 12:26
  • Sorry i pressed enter i couldn't edit my previous comment for few mns. What i meant to say is : isn't a form supposed to be based on an entity ? cause i did create a formType called CharacterType. But then in my controller, since my character class is abstract i can't do this : $char = new Character() and then $form = $this->createForm(CharacterType::class, $char); I'm still learning so maybe i didn't understand forms well enough. – Elbarto Jun 08 '16 at 12:33
  • Uh. I didn't notice it was an abstract class. When you are dealing with Single Table Inheritance, your base entity shouldn't be abstract. I'm editing the answer. – Alsatian Jun 08 '16 at 12:43
  • Well since i won't instanciate a character object, i made it abstract. If it doesn't make sense with STI maybe there's a better way ? In the end the purpose for me here is to learn and make a working crud example of a base entity that has few children entities and being able to persist the children in the database. – Elbarto Jun 08 '16 at 12:49
  • You cannot. A STI must work with a normal base. When you want to edit or remove your character you must instanciate it, otherwise you will have to create actions for each child. – Alsatian Jun 08 '16 at 12:53
  • For example when you want to remove you can create an action removeCharacter(Character $character), and this action is compatible with all your children. But doctrine will instanciate $character as child. Don't worry about abstract or not. Simply don't use it as abstract and it will also work as you expect it. – Alsatian Jun 08 '16 at 12:55
  • Okey, Thanks a lot for your time and answers. I will try tonight to make it work based on the information you just gave me. I'll accept your solution as soon as i succeeded. – Elbarto Jun 08 '16 at 12:57
  • This is not true: "When you are dealing with Single Table Inheritance, your base entity shouldn't be abstract." Defining STI's base `Abstract` is perfectly fine. All you need is to establish which type you will use before finalizing the form. – SteveB Jan 19 '17 at 08:14
  • It would grateful if someone could publish the full final result.. I am completly lost with `abstract` or not. With `ParentType()` or `Child1Type()` or `Child2Type()` use to create form, Etc. I crawled SO and no one publish the result, this is so frustrating and as there exist so many posts about that subject, I don't want to open a new one.. – Delphine Jul 03 '17 at 12:55