php Symfony2 实体集合 - 如何添加/删除与现有实体的关联?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11089861/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Symfony2 collection of Entities - how to add/remove association with existing entities?
提问by ioleo
1. Quick overview
1. 快速概览
1.1 Goal
1.1 目标
What I'm trying to achieve is a create/edit user tool. Editable fields are:
我想要实现的是创建/编辑用户工具。可编辑的字段有:
- username (type: text)
- plainPassword (type: password)
- email (type: email)
- groups (type: collection)
- avoRoles (type: collection)
- 用户名(类型:文本)
- 普通密码(类型:密码)
- 电子邮件(类型:电子邮件)
- 组(类型:集合)
- avoRoles(类型:集合)
Note: the last property is not named $rolesbecouse my User class is extending FOSUserBundle's User class and overwriting roles brought more problems. To avoid them I simply decided to store my collection of roles under $avoRoles.
注意:最后一个属性没有命名为$roles,因为我的 User 类扩展了 FOSUserBundle 的 User 类,覆盖角色带来了更多问题。为了避免它们,我只是决定将我的角色集合存储在$avoRoles 下。
1.2 User Interface
1.2 用户界面
My templateconsists of 2 sections:
我的模板由两部分组成:
- User form
- Table displaying $userRepository->findAllRolesExceptOwnedByUser($user);
- 用户表单
- 显示 $userRepository->findAllRolesExceptOwnedByUser($user); 的表
Note: findAllRolesExceptOwnedByUser() is a custom repository function, returns a subset of all roles (those not yet assigned to $user).
注意: findAllRolesExceptOwnedByUser() 是一个自定义存储库函数,返回所有角色(尚未分配给 $user 的角色)的子集。
1.3 Desired functionality
1.3 期望的功能
1.3.1 Add role:
1.3.1 添加角色:
WHEN user clicks "+" (add) button in Roles table
THEN jquery removes that row from Roles table
AND jquery adds new list item to User form (avoRoles list)
1.3.2 Remove roles:
1.3.2 删除角色:
WHEN user clicks "x" (remove) button in User form (avoRoles list)
THEN jquery removes that list item from User form (avoRoles list)
AND jquery adds new row to Roles table
1.3.3 Save changes:
1.3.3 保存更改:
WHEN user clicks "Zapisz" (save) button
THEN user form submits all fields (username, password, email, avoRoles, groups)
AND saves avoRoles as an ArrayCollection of Role entities (ManyToMany relation)
AND saves groups as an ArrayCollection of Role entities (ManyToMany relation)
Note: ONLY existing Roles and Groups can be assigned to User. If for any reason they are not found the form should not validate.
注意:只能将现有角色和组分配给用户。如果出于任何原因未找到它们,则不应验证该表单。
2. Code
2. 代码
In this section I present/or shortly describe code behind this action. If description is not enough and you need to see the code just tell me and I'll paste it. I'm not pasteing it all in the first place to avoid spamming you with unnecessary code.
在本节中,我将介绍/或简要描述此操作背后的代码。如果描述不够,您需要查看代码,请告诉我,我将粘贴它。我不是一开始就粘贴所有内容,以避免用不必要的代码向您发送垃圾邮件。
2.1 User class
2.1 用户类
My User class extends FOSUserBundle user class.
我的 User 类扩展了 FOSUserBundle 用户类。
namespace Avocode\UserBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Avocode\CommonBundle\Collections\ArrayCollection;
use Symfony\Component\Validator\ExecutionContext;
/**
* @ORM\Entity(repositoryClass="Avocode\UserBundle\Repository\UserRepository")
* @ORM\Table(name="avo_user")
*/
class User extends BaseUser
{
const ROLE_DEFAULT = 'ROLE_USER';
const ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN';
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\generatedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\ManyToMany(targetEntity="Group")
* @ORM\JoinTable(name="avo_user_avo_group",
* joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
/**
* @ORM\ManyToMany(targetEntity="Role")
* @ORM\JoinTable(name="avo_user_avo_role",
* joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
* )
*/
protected $avoRoles;
/**
* @ORM\Column(type="datetime", name="created_at")
*/
protected $createdAt;
/**
* User class constructor
*/
public function __construct()
{
parent::__construct();
$this->groups = new ArrayCollection();
$this->avoRoles = new ArrayCollection();
$this->createdAt = new \DateTime();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set user roles
*
* @return User
*/
public function setAvoRoles($avoRoles)
{
$this->getAvoRoles()->clear();
foreach($avoRoles as $role) {
$this->addAvoRole($role);
}
return $this;
}
/**
* Add avoRole
*
* @param Role $avoRole
* @return User
*/
public function addAvoRole(Role $avoRole)
{
if(!$this->getAvoRoles()->contains($avoRole)) {
$this->getAvoRoles()->add($avoRole);
}
return $this;
}
/**
* Get avoRoles
*
* @return ArrayCollection
*/
public function getAvoRoles()
{
return $this->avoRoles;
}
/**
* Set user groups
*
* @return User
*/
public function setGroups($groups)
{
$this->getGroups()->clear();
foreach($groups as $group) {
$this->addGroup($group);
}
return $this;
}
/**
* Get groups granted to the user.
*
* @return Collection
*/
public function getGroups()
{
return $this->groups ?: $this->groups = new ArrayCollection();
}
/**
* Get user creation date
*
* @return DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
}
2.2 Role class
2.2 角色类
My Role class extends Symfony Security Component Core Role class.
我的角色类扩展了 Symfony 安全组件核心角色类。
namespace Avocode\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Avocode\CommonBundle\Collections\ArrayCollection;
use Symfony\Component\Security\Core\Role\Role as BaseRole;
/**
* @ORM\Entity(repositoryClass="Avocode\UserBundle\Repository\RoleRepository")
* @ORM\Table(name="avo_role")
*/
class Role extends BaseRole
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\generatedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", unique="TRUE", length=255)
*/
protected $name;
/**
* @ORM\Column(type="string", length=255)
*/
protected $module;
/**
* @ORM\Column(type="text")
*/
protected $description;
/**
* Role class constructor
*/
public function __construct()
{
}
/**
* Returns role name.
*
* @return string
*/
public function __toString()
{
return (string) $this->getName();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
* @return Role
*/
public function setName($name)
{
$name = strtoupper($name);
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set module
*
* @param string $module
* @return Role
*/
public function setModule($module)
{
$this->module = $module;
return $this;
}
/**
* Get module
*
* @return string
*/
public function getModule()
{
return $this->module;
}
/**
* Set description
*
* @param text $description
* @return Role
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* @return text
*/
public function getDescription()
{
return $this->description;
}
}
2.3 Groups class
2.3 组别类
Since I've got the same problem with groups as with roles, I'm skipping them here. If I get roles working I know I can do the same with groups.
由于我对组和角色有同样的问题,我在这里跳过它们。如果我让角色发挥作用,我知道我可以对团体做同样的事情。
2.4 Controller
2.4 控制器
namespace Avocode\UserBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\SecurityContext;
use JMS\SecurityExtraBundle\Annotation\Secure;
use Avocode\UserBundle\Entity\User;
use Avocode\UserBundle\Form\Type\UserType;
class UserManagementController extends Controller
{
/**
* User create
* @Secure(roles="ROLE_USER_ADMIN")
*/
public function createAction(Request $request)
{
$em = $this->getDoctrine()->getEntityManager();
$user = new User();
$form = $this->createForm(new UserType(array('password' => true)), $user);
$roles = $em->getRepository('AvocodeUserBundle:User')
->findAllRolesExceptOwned($user);
$groups = $em->getRepository('AvocodeUserBundle:User')
->findAllGroupsExceptOwned($user);
if($request->getMethod() == 'POST' && $request->request->has('save')) {
$form->bindRequest($request);
if($form->isValid()) {
/* Persist, flush and redirect */
$em->persist($user);
$em->flush();
$this->setFlash('avocode_user_success', 'user.flash.user_created');
$url = $this->container->get('router')->generate('avocode_user_show', array('id' => $user->getId()));
return new RedirectResponse($url);
}
}
return $this->render('AvocodeUserBundle:UserManagement:create.html.twig', array(
'form' => $form->createView(),
'user' => $user,
'roles' => $roles,
'groups' => $groups,
));
}
}
2.5 Custom repositories
2.5 自定义仓库
It is not neccesary to post this since they work just fine - they return a subset of all Roles/Groups (those not assigned to user).
发布此内容不是必需的,因为它们工作正常 - 它们返回所有角色/组(未分配给用户的角色/组)的子集。
2.6 UserType
2.6 用户类型
UserType:
用户类型:
namespace Avocode\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class UserType extends AbstractType
{
private $options;
public function __construct(array $options = null)
{
$this->options = $options;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('username', 'text');
// password field should be rendered only for CREATE action
// the same form type will be used for EDIT action
// thats why its optional
if($this->options['password'])
{
$builder->add('plainpassword', 'repeated', array(
'type' => 'text',
'options' => array(
'attr' => array(
'autocomplete' => 'off'
),
),
'first_name' => 'input',
'second_name' => 'confirm',
'invalid_message' => 'repeated.invalid.password',
));
}
$builder->add('email', 'email', array(
'trim' => true,
))
// collection_list is a custom field type
// extending collection field type
//
// the only change is diffrent form name
// (and a custom collection_list_widget)
//
// in short: it's a collection field with custom form_theme
//
->add('groups', 'collection_list', array(
'type' => new GroupNameType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => true,
'error_bubbling' => false,
'prototype' => true,
))
->add('avoRoles', 'collection_list', array(
'type' => new RoleNameType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => true,
'error_bubbling' => false,
'prototype' => true,
));
}
public function getName()
{
return 'avo_user';
}
public function getDefaultOptions(array $options){
$options = array(
'data_class' => 'Avocode\UserBundle\Entity\User',
);
// adding password validation if password field was rendered
if($this->options['password'])
$options['validation_groups'][] = 'password';
return $options;
}
}
2.7 RoleNameType
2.7 角色名称类型
This form is supposed to render:
这种形式应该呈现:
- hidden Role ID
- Role name (READ ONLY)
- hidden module (READ ONLY)
- hidden description (READ ONLY)
- remove (x) button
- 隐藏角色 ID
- 角色名称(只读)
- 隐藏模块(只读)
- 隐藏描述(只读)
- 删除 (x) 按钮
Module and description are rendered as hidden fields, becouse when Admin removes a role from a User, that role should be added by jQuery to Roles Table - and this table has Module and Description columns.
模块和描述呈现为隐藏字段,因为当管理员从用户中删除角色时,该角色应该由 jQuery 添加到角色表 - 该表具有模块和描述列。
namespace Avocode\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class RoleNameType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('', 'button', array(
'required' => false,
)) // custom field type rendering the "x" button
->add('id', 'hidden')
->add('name', 'label', array(
'required' => false,
)) // custom field type rendering <span> item instead of <input> item
->add('module', 'hidden', array('read_only' => true))
->add('description', 'hidden', array('read_only' => true))
;
}
public function getName()
{
// no_label is a custom widget that renders field_row without the label
return 'no_label';
}
public function getDefaultOptions(array $options){
return array('data_class' => 'Avocode\UserBundle\Entity\Role');
}
}
3. Current/known Problems
3. 当前/已知问题
3.1 Case 1: configuration as quoted above
3.1 情况 1:如上引用的配置
The above configuration returns error:
以上配置返回错误:
Property "id" is not public in class "Avocode\UserBundle\Entity\Role". Maybe you should create the method "setId()"?
But setter for ID should not be required.
但是不应该需要 ID 的 setter。
- First becouse I don't want to create a NEW role. I want just to create a relation between existing Role and User entities.
Even if I did want to create a new Role, it's ID should be auto-generated:
/**
- @ORM\Id
- @ORM\Column(type="integer")
- @ORM\generatedValue(strategy="AUTO") */ protected $id;
- 首先是因为我不想创建新角色。我只想在现有角色和用户实体之间创建关系。
即使我确实想创建一个新角色,它的 ID 也应该是自动生成的:
/**
- @ORM\Id
- @ORM\Column(type="整数")
- @ORM\generatedValue(strategy="AUTO") */ protected $id;
3.2 Case 2: added setter for ID property in Role entity
3.2 案例2:在Role实体中为ID属性添加setter
I think it's wrong, but I did it just to be sure. After adding this code to Role entity:
我认为这是错误的,但我这样做只是为了确定。将此代码添加到 Role 实体后:
public function setId($id)
{
$this->id = $id;
return $this;
}
If I create new user and add a role, then SAVE... What happens is:
如果我创建新用户并添加角色,则保存...会发生什么:
- New user is created
- New user has role with the desired ID assigned (yay!)
- but that role's name is overwritten with empty string(bummer!)
- 新用户已创建
- 新用户具有分配了所需 ID 的角色(是的!)
- 但该角色的名称被空字符串覆盖(糟糕!)
Obviously, thats not what I want. I don't want to edit/overwrite roles. I just want to add a relation between them and the User.
显然,这不是我想要的。我不想编辑/覆盖角色。我只想在他们和用户之间添加一个关系。
3.3 Case 3: Workaround suggested by Jeppe
3.3 案例 3:Jeppe 建议的解决方法
When I first encountered this problem I ended up with a workaround, the same that Jeppe suggested. Today (for other reasons) I had to remake my form/view and the workaround stopped working.
当我第一次遇到这个问题时,我最终找到了一个解决方法,与 Jeppe 建议的相同。今天(由于其他原因)我不得不重新制作我的表单/视图并且解决方法停止工作。
What changes in Case3 UserManagementController -> createAction:
Case3 UserManagementController -> createAction 有什么变化:
// in createAction
// instead of $user = new User
$user = $this->updateUser($request, new User());
//and below updateUser function
/**
* Creates mew iser and sets its properties
* based on request
*
* @return User Returns configured user
*/
protected function updateUser($request, $user)
{
if($request->getMethod() == 'POST')
{
$avo_user = $request->request->get('avo_user');
/**
* Setting and adding/removeing groups for user
*/
$owned_groups = (array_key_exists('groups', $avo_user)) ? $avo_user['groups'] : array();
foreach($owned_groups as $key => $group) {
$owned_groups[$key] = $group['id'];
}
if(count($owned_groups) > 0)
{
$em = $this->getDoctrine()->getEntityManager();
$groups = $em->getRepository('AvocodeUserBundle:Group')->findById($owned_groups);
$user->setGroups($groups);
}
/**
* Setting and adding/removeing roles for user
*/
$owned_roles = (array_key_exists('avoRoles', $avo_user)) ? $avo_user['avoRoles'] : array();
foreach($owned_roles as $key => $role) {
$owned_roles[$key] = $role['id'];
}
if(count($owned_roles) > 0)
{
$em = $this->getDoctrine()->getEntityManager();
$roles = $em->getRepository('AvocodeUserBundle:Role')->findById($owned_roles);
$user->setAvoRoles($roles);
}
/**
* Setting other properties
*/
$user->setUsername($avo_user['username']);
$user->setEmail($avo_user['email']);
if($request->request->has('generate_password'))
$user->setPlainPassword($user->generateRandomPassword());
}
return $user;
}
Unfortunately this does not change anything.. the results are either CASE1 (with no ID setter) or CASE2 (with ID setter).
不幸的是,这并没有改变任何东西。结果是 CASE1(没有 ID 设置器)或 CASE2(有 ID 设置器)。
3.4 Case 4: as suggested by userfriendly
3.4 情况 4:按照用户友好的建议
Adding cascade={"persist", "remove"} to mapping.
将级联 ={"persist", "remove"} 添加到映射中。
/**
* @ORM\ManyToMany(targetEntity="Group", cascade={"persist", "remove"})
* @ORM\JoinTable(name="avo_user_avo_group",
* joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
/**
* @ORM\ManyToMany(targetEntity="Role", cascade={"persist", "remove"})
* @ORM\JoinTable(name="avo_user_avo_role",
* joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
* )
*/
protected $avoRoles;
And changeing by_referenceto falsein FormType:
并在 FormType中将 by_reference 更改为false:
// ...
->add('avoRoles', 'collection_list', array(
'type' => new RoleNameType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'error_bubbling' => false,
'prototype' => true,
));
// ...
And keeping workaround code suggested in 3.3 did change something:
保留 3.3 中建议的解决方法代码确实改变了一些东西:
- Association between user and role was not created
- .. but Role entity's name was overwritten by empty string (like in 3.2)
- 未创建用户和角色之间的关联
- .. 但角色实体的名称被空字符串覆盖(如 3.2)
So.. it did change something but in the wrong direction.
所以..它确实改变了一些东西,但方向错误。
4. Versions
4. 版本
4.1 Symfony2 v2.0.15
4.1 Symfony2 v2.0.15
4.2 Doctrine2 v2.1.7
4.2 Doctrine2 v2.1.7
4.3 FOSUserBundle version: 6fb81861d84d460f1d070ceb8ec180aac841f7fa
4.3 FOSUserBundle 版本:6fb81861d84d460f1d070ceb8ec180aac841f7fa
5. Summary
5. 总结
I've tried many diffrent approaches (above are only the most recent ones) and after hours spent on studying code, google'ing and looking for the answer I just couldn't get this working.
我尝试了许多不同的方法(以上只是最新的方法),在花了几个小时学习代码、谷歌搜索和寻找答案后,我就是无法得到这个结果。
Any help will be greatly appreciated. If you need to know anything I'll post whatever part of code you need.
任何帮助将不胜感激。如果您需要了解任何信息,我会发布您需要的任何代码部分。
采纳答案by ioleo
So a year has passed, and this question has become quite popular. Symfony has changed since, my skills and knowledge have also improved, and so has my current approach to this problem.
一年过去了,这个问题已经很流行了。从那以后,Symfony 发生了变化,我的技能和知识也有所提高,我目前解决这个问题的方法也是如此。
I've created a set of form extensions for symfony2 (see FormExtensionsBundleproject on github) and they include a form type for handleing One/Many ToManyrelationships.
我为 symfony2 创建了一组表单扩展(请参阅github 上的FormExtensionsBundle项目),它们包括用于处理单/多对多关系的表单类型。
While writing these, adding custom code to your controller to handle collections was unacceptable - the form extensions were supposed to be easy to use, work out-of-the-box and make life easier on us developers, not harder. Also.. remember.. DRY!
在编写这些代码时,将自定义代码添加到您的控制器来处理集合是不可接受的——表单扩展应该易于使用、开箱即用并使我们的开发人员的生活更轻松,而不是更难。还有..记住..干!
So I had to move the add/remove associations code somewhere else - and the right place to do it was naturally an EventListener :)
所以我不得不将添加/删除关联代码移到其他地方 - 而正确的地方自然是一个 EventListener :)
Have a look at the EventListener/CollectionUploadListener.phpfile to see how we handle this now.
查看EventListener/CollectionUploadListener.php文件,看看我们现在如何处理这个问题。
PS. Copying the code here is unnecessary, the most important thing is that stuff like that should actually be handled in the EventListener.
附注。复制这里的代码是不必要的,最重要的是这样的东西应该在EventListener中实际处理。
回答by RobMasters
I've come to the same conclusion that there's something wrong with the Form component and can't see an easy way to fix it. However, I've come up with a slightly less cumbersome workaround solution that is completely generic; it doesn't have any hard-coded knowledge of entities/attributes so will fix any collection it comes across:
我得出了同样的结论,即 Form 组件有问题,无法找到一种简单的方法来修复它。但是,我想出了一个稍微不那么麻烦的解决方案,它是完全通用的;它没有任何实体/属性的硬编码知识,因此将修复它遇到的任何集合:
Simpler, generic workaround method
更简单、通用的解决方法
This doesn't require you to make any changes to your entity.
这不需要您对实体进行任何更改。
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Form\Form;
# In your controller. Or possibly defined within a service if used in many controllers
/**
* Ensure that any removed items collections actually get removed
*
* @param \Symfony\Component\Form\Form $form
*/
protected function cleanupCollections(Form $form)
{
$children = $form->getChildren();
foreach ($children as $childForm) {
$data = $childForm->getData();
if ($data instanceof Collection) {
// Get the child form objects and compare the data of each child against the object's current collection
$proxies = $childForm->getChildren();
foreach ($proxies as $proxy) {
$entity = $proxy->getData();
if (!$data->contains($entity)) {
// Entity has been removed from the collection
// DELETE THE ENTITY HERE
// e.g. doctrine:
// $em = $this->getDoctrine()->getEntityManager();
// $em->remove($entity);
}
}
}
}
}
Call the new cleanupCollections()method before persisting
cleanupCollections()在持久化之前调用新方法
# in your controller action...
if($request->getMethod() == 'POST') {
$form->bindRequest($request);
if($form->isValid()) {
// 'Clean' all collections within the form before persisting
$this->cleanupCollections($form);
$em->persist($user);
$em->flush();
// further actions. return response...
}
}
回答by ioleo
1. The workaround solution
1. 变通方案
The workaround solution suggested by Jeppe Marianger-Lam is at the moment the only one working I know of.
Jeppe Marianger-Lam 建议的解决方法是目前我所知道的唯一可行的解决方案。
1.1 Why did it stop working in my case?
1.1 为什么它在我的案例中停止工作?
I changed my RoleNameType (for other reasons) to:
我将我的 RoleNameType(出于其他原因)更改为:
- ID (hidden)
- name (custom type - label)
- module & description (hidden, read-only)
- 身(隐藏)
- 名称(自定义类型 - 标签)
- 模块和描述(隐藏,只读)
The problem was my custom type label rendered NAME property as
问题是我的自定义类型标签将 NAME 属性呈现为
<span> role name </span>
And since it was not "read only" the FORM component expected to get NAME in POST.
并且由于它不是“只读”的,因此 FORM 组件期望在 POST 中获得 NAME。
Instead only ID was POSTed, and thus FORM component assumed NAME is NULL.
而是只发布了 ID,因此 FORM 组件假定 NAME 为 NULL。
This lead to CASE 2 (3.2) -> creating association, but overwriting ROLE NAME with an empty string.
这导致 CASE 2 (3.2) -> 创建关联,但用空字符串覆盖 ROLE NAME。
2. So, what exacly is this workaround about?
2. 那么,这个变通方法到底是什么?
2.1 Controller
2.1 控制器
This workaround is very simple.
此解决方法非常简单。
In your controller, before you VALIDATE the form, you have to fetch the posted entity identyficators and get matching entities, then set them to your object.
在您的控制器中,在您验证表单之前,您必须获取发布的实体标识符并获取匹配的实体,然后将它们设置为您的对象。
// example action
public function createAction(Request $request)
{
$em = $this->getDoctrine()->getEntityManager();
// the workaround code is in updateUser function
$user = $this->updateUser($request, new User());
$form = $this->createForm(new UserType(), $user);
if($request->getMethod() == 'POST') {
$form->bindRequest($request);
if($form->isValid()) {
/* Persist, flush and redirect */
$em->persist($user);
$em->flush();
$this->setFlash('avocode_user_success', 'user.flash.user_created');
$url = $this->container->get('router')->generate('avocode_user_show', array('id' => $user->getId()));
return new RedirectResponse($url);
}
}
return $this->render('AvocodeUserBundle:UserManagement:create.html.twig', array(
'form' => $form->createView(),
'user' => $user,
));
}
And below the workaround code in updateUser function:
在 updateUser 函数中的解决方法代码下方:
protected function updateUser($request, $user)
{
if($request->getMethod() == 'POST')
{
// getting POSTed values
$avo_user = $request->request->get('avo_user');
// if no roles are posted, then $owned_roles should be an empty array (to avoid errors)
$owned_roles = (array_key_exists('avoRoles', $avo_user)) ? $avo_user['avoRoles'] : array();
// foreach posted ROLE, get it's ID
foreach($owned_roles as $key => $role) {
$owned_roles[$key] = $role['id'];
}
// FIND all roles with matching ID's
if(count($owned_roles) > 0)
{
$em = $this->getDoctrine()->getEntityManager();
$roles = $em->getRepository('AvocodeUserBundle:Role')->findById($owned_roles);
// and create association
$user->setAvoRoles($roles);
}
return $user;
}
For this to work your SETTER (in this case in User.php entity) must be:
为此,您的 SETTER(在这种情况下在 User.php 实体中)必须是:
public function setAvoRoles($avoRoles)
{
// first - clearing all associations
// this way if entity was not found in POST
// then association will be removed
$this->getAvoRoles()->clear();
// adding association only for POSTed entities
foreach($avoRoles as $role) {
$this->addAvoRole($role);
}
return $this;
}
3. Final thoughts
3. 最后的想法
Still, I think this workaround is doing the job that
不过,我认为这种解决方法正在做的工作
$form->bindRequest($request);
should do! It's either me doing something wrong, or symfony's Collection form type is not complete.
应该做!要么是我做错了什么,要么是 symfony 的 Collection 表单类型不完整。
There are some major changes in Form componentcomeing in symfony 2.1, hopefully this will be fixed.
symfony 2.1中的 Form 组件有一些重大变化,希望这会得到修复。
PS. If it's me doing something wrong...
附注。如果是我做错了...
... please post the way it should be done! I'd be glad to see a quick, easy and "clean" solution.
...请张贴它应该做的方式!我很高兴看到一个快速、简单和“干净”的解决方案。
PS2. Special thanks to:
PS2。特别感谢:
Jeppe Marianger-Lam and userfriendly (from #symfony2 on IRC). You've been very helpful. Cheers!
Jeppe Marianger-Lam 和用户友好(来自 IRC 上的 #symfony2)。你很有帮助。干杯!
回答by Jeppe Mariager-Lam
This is what I have done before - I don't know if it's the 'right' way to do it, but it works.
这是我以前做过的 - 我不知道这是否是“正确”的方法,但它有效。
When you get the results from the submitted form (i.e., just before or right after if($form->isValid())), simply ask the list of the roles, then remove them all from the entity (saving the list as a variable). With this list, simply loop through them all, ask the repository for the role entity that matches the ID's, and add these to your user entity before you persistand flush.
当您从提交的表单中获得结果时(即,就在 之前或之后if($form->isValid())),只需询问角色列表,然后将它们从实体中全部删除(将列表保存为变量)。使用此列表,只需遍历它们,向存储库询问与 ID 匹配的角色实体,然后将这些添加到您的用户实体中,然后再添加persist和flush。
I just searched through the Symfony2 documentation because I remembered something about prototypefor form collections, and this turned up: http://symfony.com/doc/current/cookbook/form/form_collections.html- It has examples of how to deal correctly with javascript add and remove of collection types in forms. Perhaps try this approach first, and then try what I mentioned above afterwards if you cannot get it to work :)
我刚刚搜索了 Symfony2 文档,因为我想起了一些关于prototype表单集合的内容,结果出现了:http: //symfony.com/doc/current/cookbook/form/form_collections.html- 它有如何正确处理的示例javascript 添加和删除表单中的集合类型。也许先尝试这种方法,然后再尝试我上面提到的方法,如果你不能让它工作:)
回答by user1895187
You need some more entities:
USER
id_user (type: integer)
username (type: text)
plainPassword (type: password)
email (type: email)
您需要更多实体:
USER
id_user (type: integer)
username (type: text)
plainPassword (type: password)
email (type: email)
GROUPS
id_group (type: integer)
descripcion (type: text)
GROUPS
id_group(类型:整数)
描述(类型:文本)
AVOROLES
id_avorole (type: integer)
descripcion (type: text)
AVOROLES
id_avorole(类型:整数)
描述(类型:文本)
*USER_GROUP*
id_user_group (type:integer)
id_user (type:integer) (this is the id on the user entity)
id_group (type:integer) (this is the id on the group entity)
* USER_GROUP*
id_user_group (type:integer)
id_user (type:integer) (这是用户实体上的id)
id_group (type:integer) (这是群组实体上的id)
*USER_AVOROLES*
id_user_avorole (type:integer)
id_user (type:integer) (this is the id on the user entity)
id_avorole (type:integer) (this is the id on the avorole entity)
* USER_AVOROLES*
id_user_avorole (type:integer)
id_user (type:integer)(这是用户实体上的id)
id_avorole(type:integer)(这是avorole实体上的id)
You can have for example something like this:
user:
id: 3
username: john
plainPassword: johnpw
email: [email protected]
例如,您可以使用以下内容:
用户:
id:3
用户名:john
plainPassword:johnpw
电子邮件:[email protected]
group:
id_group: 5
descripcion: group 5
组:
id_group:5
描述:组 5
user_group:
id_user_group: 1
id_user: 3
id_group: 5
*this user can have many groups so in another row *
user_group:
id_user_group: 1
id_user: 3
id_group: 5
*这个用户可以有很多组,所以在另一行*

