Symfony2 Doctrine2 Отношение многих-ко-многим с двумя инструментами Owning Sides и Doctrine cmd

В моем проекте Symdony2 у меня есть два связанных объекта: Service и ServiceGroup. Это должно быть отношение "многие ко многим", поскольку каждая группа может иметь множество сервисов, и каждая служба может принадлежать многим группам. Более того, мне нужен пользовательский интерфейс для управления службами и группами. Таким образом, при редактировании Сервиса пользователь должен иметь возможность выбирать, к каким группам он принадлежит. Аналогично, при редактировании пользователя ServiceGroup пользователь должен иметь возможность выбирать, какие службы относятся к этой группе. Я уже достиг этого, установив отношение "многие-ко-многим" в моей доктрине "Учение". Все работает как шарм, в том числе пользовательский интерфейс, построенный на пользовательских типах форм в Symfony2 (я использовал тип поля формы "сущность", чтобы позволить пользователю выбирать службы в редакторе и группах ServiceGroup в редакторе служб). Единственная проблема, с которой я сталкиваюсь, это то, что я больше не могу использовать командную строку Doctrine для обновления схемы базы данных.

Вот часть моего исходного кода службы:

class Service
{
    /**
     * @var ArrayCollection $groups
     * @ORM\ManyToMany(targetEntity="ServiceGroup")
     * @ORM\JoinTable(
     *      name="service_servicegroup",
     *      joinColumns={@ORM\JoinColumn(name="service_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="servicegroup_id", referencedColumnName="id")}
     * )
     */
    private $groups;
}

И вот часть моего исходного кода объекта ServiceGroup:

class ServiceGroup
{
    /**
     * @var ArrayCollection $services
     * @ORM\ManyToMany(targetEntity="Service")
     * @ORM\JoinTable(
     *      name="service_servicegroup",
     *      joinColumns={@ORM\JoinColumn(name="servicegroup_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="service_id", referencedColumnName="id")}
     * )
     */
    private $services;
}

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

Редактор служб:

Сервисный редактор

Имя: [Сервис 1]

Группы, к которым относится эта служба:

[x] Группа A

[] Группа B

[] Группа C

[ СОХРАНИТЬ]

И редактор ServiceGroup:

Редактор групп

Название: [Группа A]

Службы относятся к этой группе:

[x] Сервис 1

[] Сервис 2

[] Сервис 3

[ СОХРАНИТЬ]

С этой конфигурацией "многие-ко-многим" я могу использовать эти редакторы (формы) без проблем, при использовании много-ко-многим без аннотации JoinTable я могу использовать только одну форму полностью, и второй не сохраняет изменения в "Группы, к которым относится эта услуга" или "Службы относятся к этой группе" (зависит от того, в каком направлении я устанавливаю параметры MappedBy и inversedBy в заявлении аннотации Many-To-Many).

Проблема, которую я имею, связана с механизмом генерации схемы доктрины при попытке обновить схему с помощью команды Symfony2:

php app/console doctrine:schema:update --dump-sql

Я получаю это исключение:

[Doctrine\DBAL\Schema\SchemaException]                      
The table with name 'service_servicegroup' already exists.

Похоже, что Doctrine пытается создать таблицу "service_servicegroup" для каждого оператора JoinTable. Таким образом, он работает над текущей схемой, которую я создал в базе данных с использованием одной и той же команды, но поэтапно, сначала, когда отношение Many-to-Many определено и следующее с одним определением отношения Many-to-Many ( для объекта службы). Когда я добавил отношение Many-to-Many ко второй сущности (ServiceGroup), мои швы приложений работают без проблем с точки зрения пользователя, но я больше не могу использовать команду "doctrine: schema: update".

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

UPDATE:

Я заметил, что мне нужно настроить отношение ManyToMany к двум владельцам. По умолчанию используется одна собственная сторона и одна обратная сторона. Доктрина документация говорит, что вы можете иметь две стороны-владельца в отношении ManyToMany, но не объясняет это много. Любой может привести пример?

Временное решение:

Я нашел обходные решения, которые, возможно, не идеальны, но он работает для меня. Поскольку нет способа иметь две стороны владения во многих отношениях, я изменил аннотацию Doctrine для своих пользователей. Объект обслуживания теперь является стороной:

class Service
{
    /**
     * @var ArrayCollection $groups
     * @ORM\ManyToMany(targetEntity="ServiceGroup", inversedBy="services", cascade={"all"})
     * @ORM\JoinTable(
     *      name="service_to_group_assoc",
     *      joinColumns={@ORM\JoinColumn(name="service_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id")}
     * )
     */
    private $groups;
}

И объект ServiceGroup обратный:

class ServiceGroup
{
    /**
     * @var ArrayCollection $services
     * @ORM\ManyToMany(targetEntity="Service", mappedBy="groups", cascade={"all"})
     */
    private $services;
}

К сожалению, при этой конфигурации отношение обновляется только при обновлении объекта службы. Когда я изменяю $services в объекте ServiceGroup и сохраняю его, отношение не изменится. Итак, я изменил свой класс Controller, и с помощью небольшого решения обходного решения я достиг ожидаемого результата. Это часть моего кода контроллера, который отвечает за обновление объекта ServiceGroup (с использованием настраиваемого типа формы):

// before update - get copy of currently related services:
$services = clone $group->getServices();

// ...

// when $form->isValid() etc. updating the ServiceGroup entity:

// add selected services to group
foreach($group->getServices() as $service)
{
    $service->addServiceGroup($group);
    $services->removeElement($service);
}

// remove unselected services from group
foreach($services as $service)
{
    $service->removeServiceGroup($group);
}

Это реализация методов addServiceGroup и removeServiceGroup класса сущностей службы:

/**
 * Add group
 *
 * @param ServiceGroup $groups
 */
public function addServiceGroup(ServiceGroup $groups)
{
    if(!in_array($groups, $this->groups->toArray()))
    {
        $this->groups[] = $groups;
    }
}

/**
 * Remove group
 *
 * @param ServiceGroup $groups
 */
public function removeServiceGroup(ServiceGroup $groups)
{
    $key = $this->groups->indexOf($groups);

    if($key!==FALSE) 
    {
        $this->groups->remove($key);
    }
}

Теперь у меня есть отношение "многие ко многим" с собственностью (Service) и обратная (ServiceGroup) сторона и формы, которые обновляют как сущность, так и отношение при сохранении (форма по умолчанию для объекта службы достаточно, но для ServiceGroup я приведенные выше модификации). Инструменты консоли Symfony/Doctrine работают как шарм. Вероятно, это можно решить лучше (проще?), Но для меня этого пока достаточно.

Ответы

Ответ 1

Поведение по умолчанию для ассоциации "многие-ко-многим" будет иметь собственную сторону и обратную сторону. Если вы имеете дело со стороной, владеющей ассоциацией, то ассоциация будет обрабатываться ORM. Однако, если вы имеете дело с обратной стороной, вам нужно явно обработать это.

В вашем случае у вас есть mappedBy="groups" в свойстве $services класса ServiceGroup. Это означает, что эта связь между Сервисом и ServiceGroup поддерживается свойством $groups объекта Service. Таким образом, Service становится собственником этой ассоциации.

Теперь, скажем, вы создаете или обновляете объект Service. Здесь, когда вы добавляете объекты ServiceGroup к этому объекту Service и сохраняете объект Service, все сущности и соответствующие ассоциации автоматически создаются ORM.

Но если вы создаете или обновляете объект ServiceGroup, добавляя к нему Service сущности и сохраняя объект ServiceGroup, ассоциация не, созданная ORM, В качестве обходного пути вы должны явно добавить объект ServiceGroup в объект Service.

// in ServiceGroup.php

public function addService(Service $service)
{
    $service->addServiceGroup($this);
    $this->services->add($service);
}

// in Service.php
public function addServiceGroup(ServiceGroup $group)
{
    if (!$this->serviceGroups->contains($group)) {
        $this->serviceGroups->add($group);
    }
}

Что нужно иметь в виду

  • Убедитесь, что вы инициализировали коллекции $services и $serviceGroups в доктринах во время создания объекта, т.е. в конструкторах.
  • Вам нужно иметь 'by_reference' => false в поле services в FormType для ServiceGroup.

Ссылка: http://symfony.com/doc/2.8/cookbook/form/form_collections.html

Ответ 2

Пример обеспечения того, чтобы сторона-владелец (Сервис) всегда обновлялась.

class ServiceGroup
{
    public function addService($service)
    {
        if (!$service) return;

        $this->services[] = $service;
        $service->addGroup($this);
    }
}

Ответ 3

Пробовали ли вы кодировать его с промежуточной сущностью и многими из нескольких ассоциаций?

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

(И часто вы в конечном итоге понимаете, что промежуточный объект заслуживает того, чтобы быть там из-за некоторых атрибутов ассоциации.)