Symfony 2.8+,Doctrine继承和表单

4

在我开始之前,请注意我正在学习Symfony,所以请牢记这一点! 我只想理解它的工作原理。

这是我想要实现的:

我想使用Doctrine创建一个实体继承的工作crud示例。 这就是我的示例所看起来的样子:

  • 抽象父类:character
  • 子类1:Magician
  • 子类2:Warrior
  • 子类3:Archer

阅读了一些文档后,我决定使用Doctrine的STI(Single Table Inheritance)。

父类:

/**
 * 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();
}

子类 1:

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

子类2:

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

Child Class 3:

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

我已经成功在我的数据库中创建了表,并为测试目的成功加载了我的固定装置。我还使我的主视图工作(列出所有字符)。
我的问题: 现在我想能够使用单个表单创建、编辑和删除列表中的特定字符。例如,我将有一个'类型'选择字段,可以选择'战士'、'魔法师'或'弓箭手',然后我将能够填写所选实体的特定字段。所以,假设我在表单中选择了“战士”,那么我希望能够设置盔甲属性(当然还有父级属性),并将其保存在数据库中。
因为我的父类是抽象的,所以我不知道该如何做,因此无法根据该对象创建表单。
提前感谢您的帮助,我真的需要它! 附注:如果有更好的解决方案/实现,请不要犹豫!
1个回答

1
最简单的方法是提供所有字段,并根据“类型”值删除它们。
为此,您需要在客户端(用于显示目的)和服务器端(以便在实体中不能更改已删除的字段)实现逻辑。
在客户端上:
使用JavaScript隐藏每个“类型”更改时无法设置的类型(您可以使用JQuery和.hide()函数)。
在服务器端上:
向表单类型添加PRE_BIND事件,以从表单中删除字段。

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

您的表格应该长这样:

// ...

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();

// ...

编辑

要处理单表继承,您不能使用抽象类,基类必须是普通实体。

在您的表单中,只需将类设置为 AppBundle\Character

在创建角色的控制器操作中,您必须使用类似于以下内容来初始化实体:

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();
}

通过编辑和删除字符,您可以直接处理字符实体。
我建议不要让用户通过编辑更改类型,请参见Doctrine:更新SINGLE_TABLE继承的鉴别器

谢谢您的回答,但我仍然不明白如何创建包含所有字段的通用formType,然后在我的控制器中调用它。 - Elbarto
和其他通用的表单类型一样,您可以在控制器中直接创建它(就像我的例子中一样),也可以将它保存在一个分离的 formType 类中。但是它必须包含 "addEventListener" 块来解决您的问题。我不会为您制作有关表单的教程,那不是您的问题。您可以在这里找到所有信息:http://symfony.com/doc/current/book/forms.html。 - Alsatian
既然我不会实例化一个字符对象,我就将其设为抽象。如果STI不能理解,也许有更好的方法?最终,我的目的是学习并创建一个基本实体的运作crud示例,该实体具有几个子实体,并能够将子实体持久化到数据库中。 - Elbarto
你不能这样做。STI必须与普通基类一起使用。当你想编辑或删除你的角色时,你必须实例化它,否则你将不得不为每个子类创建操作。 - Alsatian
如果有人能够发布完整的最终结果,那将是非常感激的。我对于使用abstract或者不使用完全迷失了方向。使用ParentType()Child1Type()或者Child2Type()来创建表单等等。我在Stack Overflow上搜索了很久,但没有人公布结果,这真的很令人沮丧。由于关于这个主题存在如此多的帖子,我不想再开一个新的帖子。 - Delphine
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接