Symfony 4,对象和子对象,缺少外键 [英] Symfony 4, Object and SubObjects, missing foreign keys
问题描述
我找不到找到以下内容的窍门.
说我有两个 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类来解决,但我想避免这种情况)
谢谢
只是一些提示.
- 您的表单类型应具有
data_class
选项设置为适当的类. - 您的表单字段名称应与实体上的属性名称匹配.(默认情况下,所有属性名称都在camelCase中,小写的第一个字符...在symfony中)
- 在1.和2.之后,您只需调用
$ form-> getData()
(或,如您可能已经注意到的,当您提供createForm 代码>调用一个实体,它将被表单组件修改-这可能并非总是有意的.请在不希望有的情况下考虑使用数据传输对象(DTO).
- 您的
CollectionType
字段应具有选项byReference
设置为false
,以便在设置字段上使用设置器(Main::setMinors
,在这种情况下). -
通常一对多的一面(即
Main
类)可以摆脱:公共函数setMinors(array $ minors){foreach($ minor as $ minor){$ minor-> setMain($ this);//设置主线,只是为了安全}$ this->未成年人= $ minors;//设置属性Main.minors}
但是您也应该不反过来在
setMain
中执行此操作(这也不是那么简单.setMinors
的替代方法是addMinor
和removeMinor
,这两种解决方案都有其优点和成本,但是在表单方面,我认为它们是相当的) 如果您在 - 最后,或者为您的次要类型添加一个选项,以省略
main
表单字段,或者添加一个新的表单类型MainMinorType
(或其他),具有main
表单字段(扩展MinorType
并删除main
字段).这消除了肮脏的骇客的必要; o)
OneToMany
上设置了 cascade = {"PERSIST"}
选项(即 @ORM \ OneToMany(targetEntity ="App \ Entity \ Minor",级联= {"PERSIST"})
),您不必显式调用所有未成年人的持久化,只要您持久化,它们就会持久化(并刷新) Main
对象/实例. 但是,总的来说,如果您没有以双向关系将未成年人设置在主体上,则结果将无法明确定义.(假设一会儿,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.
- your form types should have the
data_class
option set to the appropriate class. - 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)
- after 1. and 2. you get proper entities by just calling
$form->getData()
(or, as you might have noticed, when you give thecreateForm
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.) - your
CollectionType
field should have optionbyReference
set tofalse
, such that the setters get used on the collection field (Main::setMinors
, in this case). 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 tosetMinors
areaddMinor
andremoveMinor
, there are benefits and costs for either solution, but when it comes to forms, they are quite equivalent, I would say)on
Main
if you set thecascade={"PERSIST"}
option on theOneToMany
(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) theMain
object/instance.- Finally, either add an option to your minor type, to omit the
main
form field, or add a new form typeMainMinorType
(or whatever) that doesn't have themain
form field (extendMinorType
and remove themain
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屋!