当前位置: 首页 > 知识库问答 >
问题:

Symfony2实体集合-如何添加/删除与现有实体的关联?

韦高谊
2023-03-14

我试图实现的是一个创建/编辑用户工具。可编辑字段有:

    null
    null

注意:findAllRolesExceptOwnedByUser()是一个自定义的存储库函数,返回所有角色(那些尚未分配给$User的角色)的子集。

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删除角色:


    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


    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)  

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

由于组和角色有相同的问题,我在这里跳过它们。如果我让角色发挥作用,我知道我也能在团队中做到这一点。

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

没有必要发布这些内容,因为它们工作得很好--它们返回所有角色/组的子集(那些未分配给用户的角色/组)。

用户类型:

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;
    }
}
    null
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');
    }
}
Property "id" is not public in class "Avocode\UserBundle\Entity\Role". Maybe you should create the method "setId()"?

但ID的setter不应是必需的。

>

  • 首先因为我不想创建一个新角色。我只想在现有角色和用户实体之间创建一个关系。
  • 即使我确实想创建一个新角色,它的ID也应该是自动生成的:

    /**

      null
    public function setId($id)
    {
        $this->id = $id;
        return $this;
    }
    
      null

    Case3UserManagementController->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;
        }
    

    不幸的是,这并不能改变任何事情。结果是CASE1(没有ID setter)或CASE2(有ID setter)。

    正在将CASCADE={“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;
    
    // ...
    
                    ->add('avoRoles', 'collection_list', array(
                            'type' => new RoleNameType(),
                            'allow_add' => true,
                            'allow_delete' => true,
                            'by_reference' => false,
                            'error_bubbling' => false,
                            'prototype' => true,
                         ));
    
    // ...
    
      null

    我尝试了很多不同的方法(上面只是最近的一种),在花了几个小时研究代码、谷歌搜索和寻找答案之后,我就是无法让它工作。

    任何帮助都将不胜感激。如果你需要知道什么,我会发布你需要的代码的任何部分。

  • 共有1个答案

    臧威
    2023-03-14

    我得出了同样的结论,即表单组件有问题,而且找不到一个简单的方法来修复它。然而,我提出了一个稍微不那么麻烦的解决方案,它是完全通用的;它没有任何实体/属性的硬编码知识,因此将修复它遇到的任何集合:

    这不需要您对您的实体进行任何更改。

    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);
    
                    }
                }
            }
        }
    }
    
    # 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...
        }
    }
    
     类似资料:
    • 举一个非常简单的一对多关系的例子(国家 实体的方法: 应该注意,<code>orphanRemove</code>设置为<code>true</code>。<code>StateTable</code>实体由一个客户端应用程序提供,该应用程序有兴趣将<code>State Table</code>中的实体关联<code>Country</code<(<code>countryId=67</code

    • 问题内容: 在我的一项工作中,我有以下代码: 这始终无法删除具有以下错误的实体: DELETE语句与REFERENCE约束“ FK966F0D9A66DB1E54”冲突。数据库“ TFADB”的表“ dbo.MonthlyReport_categories”的列“ MonthlyReport_id”中发生了冲突。 我如何指定映射,以便在删除报告时删除category集合中的元素? 问题答案: 级联

    • 问题内容: 以一对多关系(国家)的非常简单的例子。 国家(反面): StateTable(所有者): 尝试更新活动数据库事务(JTA或资源本地)中提供的(分离的)实体的方法: 应当注意,设置为。该实体由客户端应用程序提供,该客户端应用程序有兴趣将实体关联()更改为其他内容()(因此在JPA的反面,将子实体从其父(集合)迁移到另一个父(集合),反过来会反对)。 Hibernate提供程序发出DML语

    • 我有两个实体,用户和电影。他们是很多双向的关系。我的问题是,当我通过我的控制器删除一部电影时,它也会删除所有与该电影相关的实体。My user和this user角色和movie实体。我要做什么,从表中删除电影实体,并保持用户与他的角色,而不是删除他们所有。

    • 删除父实体时,我还想删除关联的子实体(从数据库中)。我试图在删除时使用级联,如下所示,但我一定做错了什么。 当对父实体对象调用删除时,我收到错误消息:“该实体仍在数据库的其他地方引用”。我可以确认该实体在数据库的其他地方引用的唯一地方是在下面的两个表中(如果我手动从数据库中删除子行,对父实体对象的删除调用工作正常)。在过去的9个小时里,我一直在阅读实体对象并尝试不同的东西。我做错了什么? 这是我的

    • 对于新学生和新课程的情况,它工作得很好,但是在新课程和现有学生的情况下,我会从数据库中抛出一个唯一的约束冲突。关系类似于下面。 在持久化之前,我尝试使用entitymanager查找一个学生,如果该学生存在,则使用该实例而不是新的学生实例。这也不起作用,它仍然尝试执行插入而不是更新