Коллекция объектов Symfony2-как добавить / удалить ассоциацию с существующими объектами?


1. Краткий обзор

1.1 цели

то, что я пытаюсь достичь, - это инструмент создания/редактирования пользователя. Редактируемые поля:

  • имя пользователя (тип: текст)
  • plainPassword (тип: пароль)
  • почта (типа электронной почты)
  • группы (тип: коллекция)
  • avoRoles (тип: коллекция)

Примечание: последнее свойство не названа $роли потому что мой пользователь класс расширяет пользовательский класс FOSUserBundle и перезапись ролей принесла больше проблем. Чтобы избежать их, я просто решил сохранить свою коллекцию ролей под $avoRoles.

1.2 Пользовательский Интерфейс

мой шаблон состоит из 2 разделов:

  1. форму
  2. таблица отображения $userRepository - >findAllRolesExceptOwnedByUser ($user);

Примечание.: findAllRolesExceptOwnedByUser () - это пользовательская функция репозитория, которая возвращает подмножество всех ролей (еще не назначенных $user).

1.3 нужной функции

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

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)  

Примечание: только существующие роли и группы могут быть назначены пользователю. Если по какой-либо причине они не нашли не утверждать.


2. Код

в этом разделе я представляю / или кратко описываю код этого действия. Если описания недостаточно, и вам нужно увидеть код, просто скажите мне, и я вставлю его. Я не вставляю все это в первую очередь, чтобы избежать спама с ненужным кодом.

2.1 пользовательский класс

мой класс пользователя расширяет класс пользователя FOSUserBundle.

namespace AvocodeUserBundleEntity;

use FOSUserBundleEntityUser as BaseUser;
use DoctrineORMMapping as ORM;
use AvocodeCommonBundleCollectionsArrayCollection;
use SymfonyComponentValidatorExecutionContext;

/**
 * @ORMEntity(repositoryClass="AvocodeUserBundleRepositoryUserRepository")
 * @ORMTable(name="avo_user")
 */
class User extends BaseUser
{
    const ROLE_DEFAULT = 'ROLE_USER';
    const ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN';

    /**
     * @ORMId
     * @ORMColumn(type="integer")
     * @ORMgeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORMManyToMany(targetEntity="Group")
     * @ORMJoinTable(name="avo_user_avo_group",
     *      joinColumns={@ORMJoinColumn(name="user_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORMJoinColumn(name="group_id", referencedColumnName="id")}
     * )
     */
    protected $groups;

    /**
     * @ORMManyToMany(targetEntity="Role")
     * @ORMJoinTable(name="avo_user_avo_role",
     *      joinColumns={@ORMJoinColumn(name="user_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORMJoinColumn(name="role_id", referencedColumnName="id")}
     * )
     */
    protected $avoRoles;

    /**
     * @ORMColumn(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 класс роль

Моя Роль класс распространяется на Symfony компонент безопасности основных класса роль.

namespace AvocodeUserBundleEntity;

use DoctrineORMMapping as ORM;
use AvocodeCommonBundleCollectionsArrayCollection;
use SymfonyComponentSecurityCoreRoleRole as BaseRole;

/**
 * @ORMEntity(repositoryClass="AvocodeUserBundleRepositoryRoleRepository")
 * @ORMTable(name="avo_role")
 */
class Role extends BaseRole
{    
    /**
     * @ORMId
     * @ORMColumn(type="integer")
     * @ORMgeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORMColumn(type="string", unique="TRUE", length=255)
     */
    protected $name;

    /**
     * @ORMColumn(type="string", length=255)
     */
    protected $module;

    /**
     * @ORMColumn(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 группы класса

поскольку у меня та же проблема с группами, что и с ролями, я пропускаю их здесь. Если я получаю рабочие роли, я знаю, что могу сделать то же самое с группами.

контроллер 2.4

namespace AvocodeUserBundleController;

use SymfonyBundleFrameworkBundleControllerController;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationRedirectResponse;
use SymfonyComponentSecurityCoreSecurityContext;
use JMSSecurityExtraBundleAnnotationSecure;
use AvocodeUserBundleEntityUser;
use AvocodeUserBundleFormTypeUserType;

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 пользовательских репозиториев

это не обязательно, чтобы опубликовать это, так как они работают просто отлично-они возвращают подмножество всех ролей / групп (те, которые не назначены пользователь.)

2.6 UserType

тип пользователя:

namespace AvocodeUserBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

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' => 'AvocodeUserBundleEntityUser',
        );

        // adding password validation if password field was rendered

        if($this->options['password'])
          $options['validation_groups'][] = 'password';

        return $options;
    }
}

2.7 RoleNameType

эта форма должна оказывать:

  • скрытый идентификатор роли
  • имя роли (только для чтения)
  • скрытый модуль (только для чтения)
  • скрытое описание (только для чтения)
  • удалить кнопку (x)

модуль и описание отображаются как скрытые поля, потому что когда админ удаляет роль от пользователя, эта роль должна быть добавлена jQuery в таблицу ролей - и эта таблица имеет столбцы модуля и описания.

namespace AvocodeUserBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

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' => 'AvocodeUserBundleEntityRole');
    }
}

3. Текущие известные проблемы

3.1 Пример 1: конфигурация как указано выше

вышеуказанная конфигурация возвращает ошибку:

Property "id" is not public in class "AvocodeUserBundleEntityRole". Maybe you should create the method "setId()"?

но setter для ID не требуется.

  1. даже если я хочу создать новую роль, ее идентификатор должен быть автоматически сгенерирован:

    /**

    • @ORMId
    • @ORMColumn (type="integer")
    • @ORMgeneratedValue(strategy= " AUTO") */ защищенный $ИД;

3.2 случай 2: Добавлен сеттер для свойства ID в роли entity

я думаю, что это неправильно, но я сделал это просто для уверенности. После добавления этого кода в сущность роли:

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

если я создам нового пользователя и добавлю роль, то сохраните... Что происходит:

  1. новый пользователь создан
  2. новый пользователь имеет роль с назначенным желаемым идентификатором (yay!)
  3. но имя этой роли перезаписывается пустой строкой (облом!)

очевидно, что это не то, что я хочу. Я не хочу редактировать/перезаписывать роли. Я просто хочу добавить связь между их и пользователя.

3.3 Случай 3: обходной путь, предложенный Jeppe

когда я впервые столкнулся с этой проблемой я в конечном итоге с обходным путем, то же самое, что предложил Йеппе. Сегодня (по другим причинам) мне пришлось переделать свою форму/представление, и обходной путь перестал работать.

какие изменения в 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;
    }

к сожалению, это ничего не меняет.. результаты либо CASE1 (без установки идентификатора) или Вариант 2 (с кодом сеттер).

3.4 случай 4: как предложено userfriendly

добавление cascade={"persist", "remove"} к отображению.

/**
 * @ORMManyToMany(targetEntity="Group", cascade={"persist", "remove"})
 * @ORMJoinTable(name="avo_user_avo_group",
 *      joinColumns={@ORMJoinColumn(name="user_id", referencedColumnName="id")},
 *      inverseJoinColumns={@ORMJoinColumn(name="group_id", referencedColumnName="id")}
 * )
 */
protected $groups;

/**
 * @ORMManyToMany(targetEntity="Role", cascade={"persist", "remove"})
 * @ORMJoinTable(name="avo_user_avo_role",
 *      joinColumns={@ORMJoinColumn(name="user_id", referencedColumnName="id")},
 *      inverseJoinColumns={@ORMJoinColumn(name="role_id", referencedColumnName="id")}
 * )
 */
protected $avoRoles;

и changeing by_reference до ложные in FormType:

// ...

                ->add('avoRoles', 'collection_list', array(
                        'type' => new RoleNameType(),
                        'allow_add' => true,
                        'allow_delete' => true,
                        'by_reference' => false,
                        'error_bubbling' => false,
                        'prototype' => true,
                     ));

// ...

и сохранение кода обходного пути, предложенного в 3.3, что-то изменило:

  1. связь между Пользователем и ролью была не создан
  2. .. но роль имя сущности было перезаписано пустой строкой (как в 3.2)

так.. это действительно что-то изменило, но не в ту сторону.

4. Версии

4.1 Symfony2 v2.0. 15

4.2 использует Doctrine2 В2.1.7

4.3 версия FOSUserBundle: 6fb81861d84d460f1d070ceb8ec180aac841f7fa

5. Резюме

я пробовал много различных подходов (выше приведены только самые последние из них) и после часы, потраченные на изучение кода, google и поиск ответа, я просто не мог заставить это работать.

любая помощь будет высоко оценили. Если вам нужно что-то знать, я опубликую любую часть кода, которая вам нужна.

5 67

5 ответов:

Я пришел к тому же выводу, что что-то не так с компонентом формы и не вижу простой способ, чтобы исправить это. Тем не менее, я придумал немного менее громоздкое обходное решение, которое полностью универсально; у него нет никаких жестко закодированных знаний о сущностях/атрибутах, поэтому исправит любую коллекцию, с которой он сталкивается:

более простой, общий метод обхода

Это не требует от вас вносить какие-либо изменения в ваш сущность.

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

                }
            }
        }
    }
}

назвать новый 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...
    }
}

Так прошел год, и этот вопрос стал весьма популярным. С тех пор Symfony изменилась, мои навыки и знания также улучшились, как и мой нынешний подход к этой проблеме.

Я создал набор расширений форм для symfony2 (см. FormExtensionsBundle проект на github) и они включают в себя тип формы для обработки Один / много "горящие" снижения цен отношения.

при написании этих, добавление пользовательского кода к контроллеру, чтобы обработка коллекций была неприемлемой - расширения форм должны были быть простыми в использовании, работать из коробки и облегчать жизнь разработчикам из США, а не усложнять. Также.. помнить.. Сухой!

поэтому мне пришлось переместить код добавления / удаления ассоциаций куда - то еще-и правильное место для этого, естественно, было EventListener :)

посмотреть EventListener / CollectionUploadListener.php файл, чтобы увидеть, как мы справляемся с этим сейчас.

PS. Копирование код здесь не нужен, самое главное, что такие вещи на самом деле должны обрабатываться в EventListener.

1. Обходное решение

обходное решение, предложенное Jeppe Marianger-Lam, на данный момент является единственным, о котором я знаю.

1.1 почему он перестал работать в моем случае?

Я изменил свой RoleNameType (по другим причинам) на:

  • ID (скрытый)
  • имя (пользовательский тип - метка)
  • модуль и описание (скрытый, только для чтения)

проблема была моя метка пользовательского типа отрисованное свойство NAME как


    <span> role name </span>

и поскольку он не был" только для чтения", компонент формы ожидал получить имя в сообщении.

вместо этого был отправлен только идентификатор, и, таким образом, предположительное имя компонента формы равно NULL.

это приводит к случаю 2 (3.2) - > создание ассоциации, но перезапись имени роли с пустой строкой.

2. Итак, что именно это обходное решение?

2.1 контроллер

этот способ очень простой.

в вашем контроллере, прежде чем вы проверите форму, вы должны получить отправленные объекты identyficators и получить соответствующие объекты, а затем установить их в свой объект.

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

и ниже кода обходного пути в функции 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;
}

для этого работает ваш сеттер (в данном случае в User.РНР лицо) должен быть:

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. Последние мысли

тем не менее, я думаю, что этот обходной путь делает работу это

$form->bindRequest($request);

должны сделать! Это либо я делаю что-то неправильно, либо тип формы коллекции symfony не завершен.

есть некоторые серьезные изменения в компоненте формы приходя в symfony 2.1, надеюсь, это будет исправлено.

PS. Если я делаю что-то неправильно...

... пожалуйста, напишите, как это должно быть сделано! Я был бы рад увидеть быстрое, легкое и" чистое " решение.

PS2. Особая благодарность к:

Йеппе Marianger-лям и удобной для пользователя (с использованием Symfony2 на IRC). Вы мне очень помогли. Ура!

это то, что я делала раньше - я не знаю, если это "правильный" способ сделать это, но это работает.

когда вы получаете результаты из представленной формы (т. е. непосредственно перед или сразу после if($form->isValid())), просто спросите список ролей, а затем удалите их все из сущности (сохранение списка в качестве переменной). В этом списке просто просмотрите их все, попросите репозиторий для сущности роли, которая соответствует идентификаторам, и добавьте их в свою сущность пользователя перед вами persist и flush.

Я только что просмотрел документацию Symfony2, потому что я что-то вспомнил о prototype для коллекций форм, и это оказалось:http://symfony.com/doc/current/cookbook/form/form_collections.html - в нем есть примеры того, как правильно работать с javascript добавлять и удалять типы коллекций в формах. Возможно, сначала попробуйте этот подход, а затем попробуйте то, что я упомянул выше, если вы не можете заставить его работать :)

вам нужно еще несколько объектов:
пользователей
id_user (тип: integer)
имя пользователя (тип: текст)
plainPassword (тип: пароль)
электронная почта (тип: электронная почта)


группы
id_group (тип: integer)
Описание (Тип: текст)


AVOROLES
id_avorole (тип: integer)
дескрипция (тип: текст)


*европейский уровень, уровень*
id_user_group (тип:integer)
id_user (тип: integer) (это идентификатор объекта user)
id_group (тип: integer) (это идентификатор объекта группы)


* USER_AVOROLES*
id_user_avorole (тип:integer)
id_user (тип: integer) (это идентификатор объекта user)
id_avorole (тип: целое число) (это идентификатор на avorole сущность)


вы можете, например, что-то вроде этого:
пользователь:
id: 3
имя пользователя: john
простой пароль: johnpw
электронная почта: john@email.com


группа:
id_group: 5
descripcion: группа 5


европейский уровень, уровень
id_user_group: 1
id_user: 3
id_group: 5
*пользователь может иметь много групп, поэтому в другой строке *