Symfony 4,对象和子对象,缺少外键 [英] Symfony 4, Object and SubObjects, missing foreign keys

查看:35
本文介绍了Symfony 4,对象和子对象,缺少外键的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我找不到找到以下内容的窍门.

说我有两个 Entity : Main Minor Main 一对多次要 mainId 是外键字段.

我希望同时具有(次要)形式来创建 Minor 对象,以便用户可以从已经可用的列表中选择其 Main 对象Main 对象,以及(Main)形式来一次创建 Main 对象和可能同时创建许多不同的 Minor (子)对象的表单.

问题在于,在后一种情况下,我无法保存外键.

对于次要表格,我定义:

  $ builder-> add('minorTitle')-> add('Main',EntityType :: class,array('class'=>主类::类,'choice_label'=>"mainTtile",'标签'=>'主要的')) 

具有'data_class'=>次要的:: class ,它工作正常.

对于主表单,我尝试过:

  $ builder-> add('mainTitle')-> add('Minors',CollectionType :: class,array('entry_type'=>MinorType :: class,'allow_add'=>真的,'标签'=>'次要的'))'data_class'=>Main :: class` 

因此,次要表单确实是作为主表单中的子表单嵌入的.要添加更多子表单,我有 CollectionType 中建议的一些JS.>.为了避免在Minor子窗体中显示 Main 字段,我通过以下方式对 prototype 进行了修改:

  newWidget = newWidget.replace(newWidget.match(/\ B< div class ="form-group">< label class ="required" for ="main_Minors ___ name_Main"> Main< \/label>< select id ="main_Minors ___ name_Main" name ="main \ [Minors \] \ [__ name __ \] \ [Main \]" class ="form-control">.*< \//select> \ B/g),"); 

用户可以创建一个Main对象,也可以创建许多次要对象,但是前者的 id 不会保存为后者的外键.我试图通过类似(或变体)的方法在主控制器中修复问题:

 公共功能new(Request $ request):响应{$ em = $ this-> getDoctrine()-> getManager();$ main = new Main();$ form = $ this-> createForm(MainType :: class,$ main);$ form-> handleRequest($ request);if($ form-> isSubmitted()&& $ form-> isValid()){$ postData = $ request-> request-> get('main');$ minors = array();foreach($ postData ['Minors'] as $ key => $ obj){$ minors [$ key] = new Minor();$ minors [$ key]-> setMain($ main);$ minors [$ key]-> setMinorTitle($ obj ['minorTitle']);$ em-> persist($ minors [$ key]);}$ em-> persist($ main);$ em-&f; flush();} 

,但是它不起作用,或者它保存了两次相同的子对象(仅使用正确的外键保存了一次).

(也许我可以通过两个不同的MinorType类来解决,但我想避免这种情况)

谢谢

解决方案

只是一些提示.

  1. 您的表单类型应具有 data_class 选项设置为适当的类.
  2. 您的表单字段名称应与实体上的属性名称匹配.(默认情况下,所有属性名称都在camelCase中,小写的第一个字符...在symfony中)
  3. 在1.和2.之后,您只需调用 $ form-> getData()(或,如您可能已经注意到的,当您提供 createForm 调用一个实体,它将被表单组件修改-这可能并非总是有意的.请在不希望有的情况下考虑使用数据传输对象(DTO).
  4. 您的 CollectionType 字段应具有选项 byReference 设置为 false ,以便在设置字段上使用设置器( Main::setMinors ,在这种情况下).
  5. 通常一对多的一面(即 Main 类)可以摆脱:

     公共函数setMinors(array $ minors){foreach($ minor as $ minor){$ minor-> setMain($ this);//设置主线,只是为了安全}$ this->未成年人= $ minors;//设置属性Main.minors} 

    但是您也应该反过来在 setMain 中执行此操作(这也不是那么简单. setMinors 的替代方法是 addMinor removeMinor ,这两种解决方案都有其优点和成本,但是在表单方面,我认为它们是相当的)

  6. 如果您在 OneToMany 上设置了 cascade = {"PERSIST"} 选项(即 @ORM \ OneToMany(targetEntity ="App \ Entity \ Minor",级联= {"PERSIST"})),您不必显式调用所有未成年人的持久化,只要您持久化,它们就会持久化(并刷新) Main 对象/实例.

  7. 最后,或者为您的次要类型添加一个选项,以省略 main 表单字段,或者添加一个新的表单类型 MainMinorType (或其他),具有 main 表单字段(扩展 MinorType 并删除 main 字段).这消除了肮脏的骇客的必要; o)

但是,总的来说,如果您没有以双向关系将未成年人设置在主体上,则结果将无法明确定义.(假设一会儿,A与B有链接,但B没有与A的链接,但应该有链接,因为这是双向关系.这可能意味着必须建立链接.)也可能意味着应该删除该链接.因此,为了安全起见并清楚地传达意图,请设置双方!)最终,这可能是其无法按预期工作的原因.

更新

要详细说明第7点.您的 MinorType 可以这样修改:

  class MinorType扩展AbstractType {公共功能buildForm(FormBuilderInterface $ builder,array $ options){//...之前的其他字段if(empty($ options ['remove_main_field'])){//字段相同,但由于'if'而并非总是添加$ builder-> add('main',EntityType :: class,['class'=>主类::类,'choice_label'=>"mainTtile",'标签'=>'主要的']);}//...其余形式}公共功能configureOptions(OptionsResolver $ resolver){//也许是父母的电话...$ resolver-> setDefaults([//您的其他默认值'remove_main_field'=>false,//添加新选项,不言自明]);}} 

在您的 MainType 中,我在其中添加了新选项

 -> add('Minor',EntityType :: class,array('class'=>次要的:: class,'remove_main_field'=>是的,//<-这是新的)) 

现在,当主表单中的 embedded 时,这将从未成年人表单中删除该主字段.但是默认设置是不删除主字段,因此当您自己编辑次要对象时,将显示主字段,就像以前一样……除非我在代码中输入错误;否则)

I am not able to find the trick to get the following.

Say I have two Entity: Main and Minor, Main one-to-many Minor, mainId being the foreign key field.

I wish to have both a (Minor) form to create a Minor object, such that users may select its Main object from a list of already available Main objects, and a (Main) form to create a Main object and possibly many different Minor (sub)objects at once.

The issue is that in the latter case, I am not able to save the foreign key.

For the Minor form, I define:

$builder ->add('minorTitle')
         ->add('Main', EntityType::class, array(
               'class' => Main::class,
               'choice_label' => 'mainTtile',
               'label' => 'main'))

have 'data_class' => Minor::class, and it works fine.

For the Main form, I tried:

$builder
        ->add('mainTitle')
        ->add('Minors', CollectionType::class, array(
              'entry_type' => MinorType::class,
              'allow_add' => true,
              'label' => 'Minor'
              ))   

              'data_class' => Main::class`

So the Minor form is indeed embedded as a subform within the Main one. To add more subforms, I have some JS as suggested in CollectionType. To avoid to display the Main field in the Minor subforms, I have hacked a little the prototype, by something like:

newWidget = newWidget.replace(newWidget.match(/\B<div class="form-group"><label class="required" for="main_Minors___name_Main">Main<\/label><select id="main_Minors___name_Main" name="main\[Minors\]\[__name__\]\[Main\]" class="form-control">.*<\/select>\B/g),""); 

A user is able to create a Main object, and many Minor ones too, but the id of the former is not saved as the foreign keys of the latter ones. I have tried to fix things within the Main Controller by something like (or variants):

public function new(Request $request): Response {
    $em = $this->getDoctrine()->getManager();
    $main = new Main();
    $form = $this->createForm(MainType::class, $main);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $postData = $request->request->get('main');

        $minors = array();
        foreach($postData['Minors'] as $key => $obj){
            $minors[$key]= new Minor();
            $minors[$key]->setMain($main);
            $minors[$key]->setMinorTitle($obj['minorTitle']);
            $em->persist($minors[$key]);
        }

        $em->persist($main);
        $em->flush();
   }

but either it does not work, or it saves twice the same subobject (only once with the correct foreign key).

(Maybe, I could fix by two different MinorType classes, but I would like to avoid that)

Thanks

解决方案

Just a number of hints.

  1. your form types should have the data_class option set to the appropriate class.
  2. your form field names should match the property name on the entity. (and by default, all property names are in camelCase, lowercase first char ... in symfony)
  3. after 1. and 2. you get proper entities by just calling $form->getData() (or, as you might have noticed, when you give the createForm call an entity, it will be modified by the form component - this might not always be intended. consider Data Transfer Objects (DTO) for when it's not intended.)
  4. your CollectionType field should have option byReference set to false, such that the setters get used on the collection field (Main::setMinors, in this case).
  5. usually the one-to-many side (i.e. Main class) can get away with:

    public function setMinors(array $minors) {
        foreach($minors as $minor) {
            $minor->setMain($this); // set the main, just to be safe
        }
        $this->minors = $minors;    // set the property Main.minors
    }
    

    but you should not do this in setMain in reverse too (it's also not so trivial. alternative to setMinors are addMinor and removeMinor, there are benefits and costs for either solution, but when it comes to forms, they are quite equivalent, I would say)

  6. on Main if you set the cascade={"PERSIST"} option on the OneToMany (i.e. @ORM\OneToMany(targetEntity="App\Entity\Minor", cascade={"PERSIST"})), you don't have to explicitly call persist on all minors, they will get persisted as soon as you persist (and flush) the Main object/instance.

  7. Finally, either add an option to your minor type, to omit the main form field, or add a new form type MainMinorType (or whatever) that doesn't have the main form field (extend MinorType and remove the main field). This removes the necessity for dirty hacks ;o)

However, overall, if you don't set the minors on the main in a bi-directional relationship, the results are not clearly defined. (just assume for a moment, A has a link to B, but B doesn't have a link to A, but should have, because it's a bi-directional relationship. It could mean, that the link has to be established. It could also mean, that the link should be removed. So, to be safe and clearly communicate what is intended, set both sides!) And ultimately, this might be the reason it doesn't work as intended.

update

To elaborate on point 7. Your MinorType could be amended like this:

class MinorType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        // ... other fields before
        if(empty($options['remove_main_field'])) {
            // field is the same, but isn't added always, due to 'if'
            $builder->add('main', EntityType::class, [
               'class' => Main::class,
               'choice_label' => 'mainTtile',
               'label' => 'main'
            ]);
        }
        // ... rest of form
    }

    public function configureOptions(OptionsResolver $resolver) {
        // maybe parent call ...
        $resolver->setDefaults([
            // your other defaults
            'remove_main_field' => false, // add new option, self-explanatory
        ]);
    }
}

in your MainType you had, the following, to which I added the new option

       ->add('Minor', EntityType::class, array(
               'class' => Minor::class,
               'remove_main_field' => true, // <-- this is new
       ))

now, this will remove the main field from your minors forms, when it's embedded in your main form. the default is however, to not remove the main field, so when you edit a minor by itself, the main field will be rendered, as it was before ... unless I made a mistake in my code ;o)

这篇关于Symfony 4,对象和子对象,缺少外键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆