Intereting Posts
как отправлять данные JSON с Android на php url? Mysqli – При открытии двух соединений с одним и тем же хостом с mysql, но с разными базами данных, mysqli открывает соединение один раз? Laravel Eloquent – Attach vs Sync Apache Virtual Host не анализирует PHP счетчик числа кликов по ссылкам в php Обрезаемая Неустранимая ошибка: Аргумент 1 передан? Symfony2.1 mod rewrite и строки запроса Существует ли транспортная реализация IPC для Thrift? или решения с низкой задержкой SOA Как отобразить валюту в индийском формате нумерации в PHP Форматирование текстового файла в формате JSON в PHP Как разобрать узел XML с тегом двоеточия с помощью PHP PHP mysqli wrapper: передача по ссылке с помощью __call () и call_user_func_array () MySql: если существует значение UPDATE else INSERT Почему DateTime :: createFromFormat () терпит неудачу и возвращает логическое значение в моем втором примере? Codeigniter PHP – загрузка представления в точке привязки

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

1. Быстрый обзор

1.1. Цель

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

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

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

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

Мой шаблон состоит из двух разделов:

  1. Форма пользователя
  2. Таблица, отображающая $ userRepository-> findAllRolesExceptOwnedByUser ($ user);

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

1.3 Желаемая функциональность

1.3.1 Добавить роль:


     КОГДА пользователь нажимает кнопку «+» (добавить) в таблице «Роли»  
     THEN jquery удаляет эту строку из таблицы Roles  
     И jquery добавляет новый элемент списка в форму пользователя (список avoRoles)

1.3.2 Удаление ролей:


     КОГДА пользователь нажимает кнопку «x» (удалить) в форме пользователя (список avoRoles)  
     THEN jQuery удаляет этот элемент списка из пользовательской формы (список avoRoles)  
     И jquery добавляет новую строку в таблицу ролей

1.3.3 Сохранить изменения:


     КОГДА пользователь нажимает кнопку «Zapisz» (сохранить)  
     Затем пользовательская форма представляет все поля (имя пользователя, пароль, email, avoRoles, группы)  
     И сохраняет avoRoles как ArrayCollection объектов Role (отношение ManyToMany)  
     И сохраняет группы как ArrayCollection объектов Role (отношение ManyToMany)  

Примечание. ТОЛЬКО существующие роли и группы могут быть назначены пользователю. Если по какой-либо причине они не найдены, форма не должна проверяться.


2. Код

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

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

Класс My 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 расширяет класс основной роли Symfony Security Component.

 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 Класс групп

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

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

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

2.6 UserType

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

Эта форма должна отображать:

  • скрытый идентификатор роли
  • Имя роли (ТОЛЬКО ЧИТАЙТЕ)
  • скрытый модуль (ТОЛЬКО ЧИТАЙТЕ)
  • скрытое описание (ТОЛЬКО ЧИТАЙТЕ)
  • Удалить (x)

Модуль и описание отображаются как скрытые поля, потому что, когда администратор удаляет роль у пользователя, эта роль должна быть добавлена ​​в таблицу jQuery to Roles Table – и в этой таблице есть столбцы Module и Description.

 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. Текущие / известные проблемы

3.1 Случай 1: конфигурация, указанная выше

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

 Property "id" is not public in class "Avocode\UserBundle\Entity\Role". Maybe you should create the method "setId()"? 

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

  1. Во-первых, я не хочу создавать NEW-роль. Я хочу просто создать связь между существующими объектами Role и User.
  2. Даже если бы я хотел создать новую роль, ее идентификатор должен быть автоматически сгенерирован:

    / **

    • @ORM \ Id
    • @ORM \ Колонка (тип = "целое число")
    • @ORM \ generateValue (strategy = "AUTO") * / protected $ id;

3.2 Случай 2: добавлен параметр set для свойства ID в объекте Role

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

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

Если я создаю нового пользователя и добавлю роль, тогда СОХРАНИТЬ … Что происходит:

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

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

3.3 Случай 3: Обходное решение, предложенное Джеппе

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

Какие изменения в 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 (без идентификатора), либо CASE2 (с установщиком ID).

3.4 Случай 4: как было предложено пользователем

Добавление 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; 

И изменение by_reference в false в 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 v2.1.7

4.3 Версия FOSUserBundle : 6fb81861d84d460f1d070ceb8ec180aac841f7fa

5. Резюме

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

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

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

Упрощенный общий метод обхода

Это не требует внесения каких-либо изменений в вашу организацию.

 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 // eg 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), и они включают тип формы для обработки отношений One / Many ToMany .

При написании данных добавление пользовательского кода в контроллер для обработки коллекций было неприемлемым – расширения форм должны были быть просты в использовании, работать из коробки и облегчать жизнь для нас, разработчиков, а не сложнее. Также .. запомнить .. СУХОЙ!

Поэтому мне пришлось переместить код добавления / удаления в другом месте – и подходящее место для этого было, естественно, EventListener 🙂

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

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

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

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

1.1 Почему это прекратило работать в моем случае?

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

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

Проблема заключалась в том, что моя пользовательская метка типа отображала свойство NAME как


     <span> имя роли </ span>

И поскольку он не был «только для чтения», компонент FORM ожидал получить NAME в POST.

Вместо этого только ID был POSTED, и, таким образом, FORM-компонент, предположительно, NAME равен NULL.

Это приводит к созданию ассоциации CASE 2 (3.2) ->, но перезаписывает ROLE NAME пустой строкой.

2. Итак, к чему это оборачивается?

2.1 Контроллер

Это обходное решение очень простое.

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

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

Для этого для работы вашего 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. Заключительные мысли

Тем не менее, я думаю, что это решение делает работу, которая

 $form->bindRequest($request); 

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

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

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

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

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

Jeppe Marianger-Lam и дружелюбный пользователь (от # symfony2 на IRC). Вы очень помогли. Ура!

Это то, что я сделал раньше – я не знаю, правильно ли это сделать, но это работает.

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

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

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


ГРУППЫ
id_group (type: integer)
descripcion (тип: текст)


AVOROLES
id_avorole (type: integer)
descripcion (тип: текст)


* USER_GROUP *
id_user_group (тип: целое число)
id_user (type: integer) (это идентификатор объекта пользователя)
id_group (type: integer) (это идентификатор в группе)


* USER_AVOROLES *
id_user_avorole (type: integer)
id_user (type: integer) (это идентификатор объекта пользователя)
id_avorole (type: integer) (это идентификатор объекта avorole)


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

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

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