如何嵌入一个集合的形式

编辑本页

任务类,并且在同一个表单中,您将能够编辑、创建和删除许多标签与该任务相关的对象。

让我们从创建一个任务实体:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
/ / src /实体/ Task.php名称空间应用程序实体使用学说常见的集合ArrayCollection使用学说常见的集合集合任务受保护的描述受保护的标签公共函数__construct()->标签=ArrayCollection ();}公共函数getDescription()字符串返回->描述;}公共函数setDescription(字符串描述无效->描述=描述;}公共函数getTags()集合返回->标签;}}

请注意

ArrayCollection是Doctrine特有的,类似于PHP数组,但提供了许多实用程序方法。

现在,创建一个标签类。正如你在上面看到的,a任务可以有很多标签对象:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/ / src /实体/ Tag.php名称空间应用程序实体标签私人的名字公共函数getName()字符串返回->名称;}公共函数setName(字符串的名字无效->name =的名字;}}

然后,创建一个表单类,以便a标签对象可以被用户修改:

12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20 21 22
/ / src /形式/ TagType.php名称空间应用程序形式使用应用程序实体标签使用ob娱乐下载组件形式AbstractType使用ob娱乐下载组件形式FormBuilderInterface使用ob娱乐下载组件OptionsResolverOptionsResolverTagType扩展AbstractType公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“名字”);}公共函数configureOptions(OptionsResolver解析器无效解析器->setDefaults ([“data_class”= >标签::类,]);}}

下面,让我们为任务实体,使用CollectionType领域的TagType形式。这将允许我们修改所有的标签a的要素任务就在任务表单本身中:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/ / src /形式/ TaskType.php名称空间应用程序形式使用应用程序实体任务使用ob娱乐下载组件形式AbstractType使用ob娱乐下载组件形式扩展核心类型CollectionType使用ob娱乐下载组件形式FormBuilderInterface使用ob娱乐下载组件OptionsResolverOptionsResolverTaskType扩展AbstractType公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“描述”);构建器->add (“标签”, CollectionType::类,“entry_type”= > TagType::类,“entry_options”= > [“标签”= >)));}公共函数configureOptions(OptionsResolver解析器无效解析器->setDefaults ([“data_class”= >任务::类,]);}}

控件中创建一个新表单TaskType

12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
/ / src /控制器/ TaskController.php名称空间应用程序控制器使用应用程序实体标签使用应用程序实体任务使用应用程序形式TaskType使用ob娱乐下载FrameworkBundle控制器AbstractController使用ob娱乐下载组件HttpFoundation请求使用ob娱乐下载组件HttpFoundation响应TaskController扩展AbstractController公共函数(请求请求响应任务任务();// dummy code -添加一些示例标签的任务//(否则,模板将呈现一个空的标签列表)标签1标签();标签1->setName (“标签1”);任务->getTags ()->add (标签1);标签2标签();标签2->setName (标签2的);任务->getTags ()->add (标签2);//结束虚拟代码形式->createForm (TaskType::类,任务);形式->handleRequest (请求);如果形式->isSubmitted () & &形式->isValid ()) {/ /……做你的表单处理,如保存任务和标签实体返回->renderForm (“任务/ new.html.twig”, (“形式”= >形式]);}}

在模板中,现在可以遍历现有的模板TagType呈现它们的表单:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16
{/任务/ new.html #模板。树枝#}{#……#}{{form_start(form)}}{{form_row(form.description)}}<h3>标签h3><ul“标签”>{%形式上的标签。标签%}<>{{form_row(tag.name)}}>{%endfor%}ul>{{form_end(form)}}{#……#}

当用户提交表单时,提交的数据为标签字段用于构造ArrayCollection标签对象。对象上设置集合标签字段任务并且可以通过任务- > getTags ()

到目前为止,这工作得很好,但只是编辑现有的标签。它还不允许我们添加新标签或删除现有标签。

谨慎

您可以将嵌套集合嵌入到您喜欢的任何级别。但是,如果使用Xdebug,则可能会收到一个已达到'100'的最大函数嵌套级别,中止!错误。要解决此问题,请增加xdebug.max_nesting_levelPHP设置,或手动渲染每个表单字段使用form_row ()而不是一次呈现整个表单(例如form_widget(形式)).

错误。这是用allow_add选择:

12 3 4 5 6 7 8 9 10 11 12 13 14
/ / src /形式/ TaskType.php/ /……公共函数buildForm(FormBuilderInterface构建器数组,选项无效/ /……构建器->add (“标签”, CollectionType::类,“entry_type”= > TagType::类,“entry_options”= > [“标签”= >),“allow_add”= >真正的]);}

allow_add选项还会产生原型变量可供您选择。这个“原型”是一个小“模板”,它包含了用JavaScript动态创建任何新“标签”表单所需的所有HTML。

让我们从纯JavaScript (Vanilla JS)开始-如果你正在使用刺激,请参见下面。

要渲染原型,添加以下内容data-prototype属性为现有的< ul >在模板中:

1 2 3 4 5
{# data-index属性对于下面的JavaScript代码是必需的<ul“标签”材料指数{{form.tags |长度> 0 ?form.tags |最后的.vars.name + 1: 0}}data-prototype{{form_widget(form.tags.vars.prototype)|e('html_attr')}}>ul>

在渲染的页面上,结果看起来像这样:

1 2 3 4
<ul“标签”材料指数“0”data-prototype& lt;div比;& lt;标签类=“;要求“;比;__name__& lt;/标签比;& lt;div id =“;task_tags___name__“;比;& lt;div比;& lt;标签=“;task_tags___name___name“;类=“;要求“;比;的名字& lt;/标签比;& lt;输入类型=“;文本“;id =“;task_tags___name___name“;name =“;任务[标记][__name__][名称]“;要求=“;要求“;最大长度=“;255“;/比;& lt;/ div比;& lt;/ div比;& lt;/ div比;>ul>

现在添加一个按钮来动态添加一个新标签:

1
<按钮类型“按钮”“add_item_link”data-collection-holder-class“标签”>添加标签按钮>

提示

form.tags.vars.prototype表单元素看起来和感觉起来就像个体吗form_widget(标签。*)您的循环。这意味着你可以打电话form_widget ()form_row ()了form_label ()在上面。你甚至可以选择只呈现它的一个字段(例如的名字字段):

1
{{form_widget(form.tags.vars.prototype.name)|e}}

请注意

如果你渲染你的整个“标签”子表单一次(例如。form_row (form.tags)),data-prototype属性自动添加到包含div,您需要相应地调整下面的JavaScript。

现在添加一些JavaScript来读取这个属性,并在用户单击“添加标签”链接时动态添加新的标签表单。添加一个< >脚本标记在页面的某个地方,用JavaScript包含所需的功能:

1 2 3 4 5
文档.querySelectorAll (“.add_item_link”) .forEach (btn= >{btn.addEventListener (“点击”, addFormToCollection)});

addFormToCollection ()函数的工作是使用data-prototype属性在单击此链接时动态添加新表单。的data-prototypeHTML包含标签的文本输入名称为的元素任务[标记][__name__][名称]的idtask_tags___name___name.的__name__是一个占位符,您将用一个惟一的递增数字替换它(例如。任务[标记][3][名称]):

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
常量addFormToCollection =e) = >常量collectionHolder =文档.querySelector (“。”+ e.currentTarget.dataset.collectionHolderClass);常量项=文档.createElement (“李”);项。innerHTML = collectionHolder .dataset .prototype .replace(/ __name__ / g, collectionHolder.dataset.index);collectionHolder.appendChild(项);collectionHolder.dataset.index + +;};

现在,每当用户单击添加标签链接,一个新的子表单将出现在页面上。当表单提交时,任何新的标签表单都将转换为新的标签对象,并添加到标签的属性任务对象。

另请参阅

您可以在这里找到一个工作示例JSFiddle

刺激,把一切都包在一个< div >

1 2 3 4 5 6 7
<div{{stimulus us_controller('form-collection')data-form-collection-index-value{{form.tags |长度> 0 ?form.tags |最后的.vars.name + 1: 0}}data-form-collection-prototype-value{{form_widget(form.tags.vars.prototype)|e('html_attr')}}><ul{{stimulus us_target('form-collection', 'collectionContainer')}}>ul><按钮类型“按钮”{{stimulus - action('form-collection', 'addCollectionElement')}}>添加标签按钮>div>

然后创建控制器:

12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20
/ /资产/控制器/ form-collection_controller.js进口{控制器}“@hotwired /刺激”出口默认的扩展控制器静态目标= [“collectionContainer”静态值= {指数数量原型字符串,} addCollectionElement(事件){常量项=文档.createElement (“李”);项。innerHTML =.prototypeValue.replace (/ __name__ / g.indexValue);.collectionContainerTarget.appendChild(项);.indexValue + +;}}

任务类:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/ / src /实体/ Task.php名称空间应用程序实体/ /……任务/ /……公共函数addTag(标签标签无效->标签->add (标签);}公共函数removeTag(标签标签无效/ /……}}

接下来,添加一个by_reference选项。标签字段并设置为

12 3 4 5 6 7 8 9 10 11 12
/ / src /形式/ TaskType.php/ /……公共函数buildForm(FormBuilderInterface构建器数组,选项无效/ /……构建器->add (“标签”, CollectionType::类,/ /……“by_reference”= >]);}

通过这两个更改,当表单提交时,每个更改都是新的标签对象添加到任务类,通过调用addTag ()方法。在此更改之前,它们是通过调用在表单内部添加的任务- > getTags() - >添加标签($).这很好,但是强制使用“加法器”方法使得处理这些问题变得很新鲜标签对象更容易(特别是如果你使用Doctrine,你将在接下来学习!)。

谨慎

你必须创造这两个addTag ()而且removeTag ()方法,否则表单仍将使用setTag ()即使by_reference.你会学到更多关于removeTag ()方法。

谨慎

ob娱乐下载Symfony只能进行复数到单数的转换(例如从标签属性addTag ()方法)的英语单词。用任何其他语言编写的代码都不能正常工作。

为了保存Doctrine的新标签,您需要考虑更多的事情。首先,除非遍历所有的new标签对象和调用entityManager - >保存(标签)在每一个,你会收到一个来自Doctrine的错误:

1 2 3
通过关系“App\ entity \Task#tags”发现了一个新实体,该关系没有配置为实体的级联持久化操作…

方法自动“级联”持久化操作任务对象到任何相关标记。要做到这一点,请添加级联选择您的元数据:

  • 属性
  • YAML
  • XML
1 2 3 4 5 6
/ / src /实体/ Task.php/ /……#[ORM\ManyToMany(targetEntity: Tag::class, cascade: ['persist'])]受保护的标签

第二个潜在的问题涉及拥有侧和反向侧教义关系。在这个例子中,如果关系的“拥有”方是“Task”,那么持久化将很好地工作,因为标记被正确地添加到Task中。但是,如果所属方在“Tag”上,那么您将需要做更多的工作,以确保修改关系的正确一方。

诀窍是确保每个“标签”上都设置了单个“任务”。做到这一点的一种方法是向addTag (),由表单类型since调用by_reference设置为

12 3 4 5 6 7 8 9 10 11 12 13
/ / src /实体/ Task.php/ /……公共函数addTag(标签标签无效//多对多关联:标签->addTask ();//多对一关联:标签->setTask ();->标签->add (标签);}

如果你想addTask (),确保你有一个合适的方法,看起来像这样:

1 2 3 4 5 6 7 8 9
/ / src /实体/ Tag.php/ /……公共函数addTask(任务任务无效如果(!->任务->包含(任务)) {->任务->add (任务);}}

allow_delete类型:

12 3 4 5 6 7 8 9 10 11 12
/ / src /形式/ TaskType.php/ /……公共函数buildForm(FormBuilderInterface构建器数组,选项无效/ /……构建器->add (“标签”, CollectionType::类,/ /……“allow_delete”= >真正的]);}

现在,需要将一些代码放入removeTag ()的方法任务

12 3 4 5 6 7 8 9 10 11 12
/ / src /实体/ Task.php/ /……任务/ /……公共函数removeTag(标签标签无效->标签->removeElement (标签);}}

allow_delete选项意味着如果集合的项在提交时没有发送,则相关数据将从服务器上的集合中删除。为了在HTML表单中工作,必须在提交表单之前删除要删除的集合项的DOM元素。

首先,在每个标签表单中添加一个“delete this tag”链接:

12 3 4 5 6 7 8 9 10 11 12 13 14
文档.querySelectorAll (ul。标签s li') .forEach (标签) = >{addTagFormDeleteLink(tag)})/ /……从上面看剩下的街区常量addFormToCollection =e) = >/ /……//添加一个删除链接到新表单addTagFormDeleteLink(项);}

addTagFormDeleteLink ()函数看起来是这样的:

12 3 4 5 6 7 8 9 10 11 12
常量addTagFormDeleteLink =) = >常量removeFormButton =文档.createElement (“按钮”);removeFormButton。innerText =实现“删除此标签”;item.append (removeFormButton);removeFormButton.addEventListener (“点击”, (e) => {e.b preventdefault ();//删除标签表单的liitem.remove ();});}

当一个标签表单从DOM中删除并提交时,被删除的标签对象将不包含在传递给的集合中setTags ().根据您的持久性层,这可能不足以实际删除被删除对象之间的关系标签而且任务对象。

当以这种方式删除对象时,您可能需要多做一点工作,以确保对象之间的关系任务被移除的标签被正确移除。

在《主义》中,你有关系的两个方面:拥有的一面和相反的一面。通常在这种情况下,您将有一个多对一的关系,删除的标记将消失并正确地保存(添加新标记也很容易)。

但是如果你有一对多的关系或者多对多的关系的mappedBy在Task实体上(意味着Task是“逆”面),您将需要做更多的工作来正确地保存已删除的标记。

在这种情况下,您可以修改控制器以删除已删除标记上的关系。假设你有一些编辑()正在处理任务的“更新”的动作:

12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
/ / src /控制器/ TaskController.php/ /……使用应用程序实体任务使用学说常见的集合ArrayCollectionTaskController扩展AbstractController公共函数编辑id,请求请求, EntityManagerInterfaceentityManager响应如果= = =任务entityManager->getRepository(任务::类)->找到(id)) {->createNotFoundException (“未为id找到任务”id);}originalTagsArrayCollection ();//创建数据库中当前Tag对象的ArrayCollectionforeach任务->getTags ()作为标签) {originalTags->add (标签);}editForm->createForm (TaskType::类,任务);editForm->handleRequest (请求);如果editForm->isSubmitted () & &editForm->isValid ()) {//删除标签和任务之间的关系foreachoriginalTags作为标签) {如果= = =任务->getTags ()->包含(标签)) {//从标签中删除任务标签->getTasks ()->removeElement (任务);//如果它是一个多对一关系,像这样删除关系/ /标签- > setTask(空);entityManager->persist (标签);//如果你想完全删除标签,你也可以这样做/ / entityManager - >删除美元($标签);}}entityManager->persist (任务);entityManager->冲洗();//重定向到某个编辑页面返回->redirectToRoute (“task_edit”, (“id”= >id]);}/ /……渲染一些表单模板}}

如您所见,正确地添加和删除元素可能很棘手。除非您有一个Task是“拥有”方的多对多关系,否则您将需要做额外的工作,以确保在每个Tag对象本身上正确地更新关系(无论是添加新标记还是删除现有标记)。

此工作,包括代码示例,是根据创作共用BY-SA 3.0许可证。
ob娱乐下载Symfony 6.2支持通过苏禄人
sulu-logo副本 使用Sketch创建。
ob娱乐下载Symfony 6.2支持通过Les-Tilleuls.coop