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

68

1. Краткое описание

1.1 Цель

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

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

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

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

Класс 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 Класс ролей

Класс My Role расширяет класс ролей Symfony Security Component Core.

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

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

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

Модуль и описание отображаются как скрытые поля, потому что, когда администратор удаляет роль пользователя, эта роль должна быть добавлена ​​командой 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. Текущие/известные проблемы

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

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

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

Но параметр set для ID не требуется.

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

    /**

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

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

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

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

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

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

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

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

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

Добавление каскада = { "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, что-то изменил:

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

Итак, он что-то изменил, но в неправильном направлении.

4. Версии

4.1 Symfony2 v2.0.15

4.2 Doctrine2 v2.1.7

4.3 Версия FOSUserBundle: 6fb81861d84d460f1d070ceb8ec180aac841f7fa

5. Резюме

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

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

  • 31
    +1 за то, что был одним из самых обширных и хорошо написанных вопросов, которые я видел в течение долгого времени.
  • 0
    Проблема, вероятно, в типе формы. Не могли бы вы уточнить комментарий в RoleNameType : «Рендеринг пользовательского типа поля вместо элемента».
Показать ещё 6 комментариев
Теги:
forms
collections
entity

5 ответов

9
Лучший ответ

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

Я создал набор расширений формы для symfony2 (см. FormExtensionsBundle проект на github), и они включают в себя тип формы для обработки One/Many ToMany.

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

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

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

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

  • 0
    @RobMasters Я обновил ответ.
12

Я пришел к тому же выводу, что что-то не так с компонентом 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 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...
    }
}
  • 0
    Спасибо, что поделились. Прямо сейчас я работаю над разными частями своих приложений, но я обязательно опробую ваше решение, когда буду пересматривать / пересматривать код моего приложения.
8

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

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

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

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

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

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


    <span> role name </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 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 не завершен.

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

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

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

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

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

  • 0
    Спасибо за публикацию такого подробного вопроса и продолжение вашего решения, даже если это обходной путь. Вам когда-нибудь удавалось найти «чистое» решение этой проблемы?
  • 0
    Нет, но я скоро еще раз посмотрю на эту проблему, потому что что-то могло измениться в новой версии Symfony :)
6

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

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

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

  • 0
    О javascript: я прочитал эту кулинарную книгу, и мой код javascript работает точно так, как ожидалось (и РОЛИ правильно размещены в контроллере). Проблема чисто где-то в PHP-коде. Должно быть что-то не так с моим типом формы или классом сущности.
  • 0
    Ваше решение - то, что я сделал 2 месяца назад, когда впервые столкнулся с этой проблемой. Теперь по другим причинам мне пришлось переделать свои формы / представления, и обходной путь перестал работать. Я опубликую Дело 3 с ошибками, которые я получаю.
Показать ещё 1 комментарий
0

Вам нужно еще несколько объектов:
USER
id_user (тип: целое)
имя пользователя (тип: текст)
plainPassword (тип: пароль)
email (введите: email)


ГРУППЫ
id_group (тип: целое)
descripcion (тип: текст)


AVOROLES
id_avorole (тип: целое)
descripcion (тип: текст)


* user_group *
id_user_group (тип: целое)
id_user (type: integer) (это идентификатор пользователя)

id_group (type: integer) (это идентификатор объекта группы)


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

id_avorole (type: integer) (это идентификатор объекта avorole)


У вас может быть, например, что-то вроде этого:
Пользователь:
id: 3
имя пользователя: john
plainPassword: johnpw
email: [email protected]


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


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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню