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
Пробовали ли вы кодировать его с промежуточной сущностью и многими из нескольких ассоциаций?
Хотя это может и не быть тем, что вы хотите иметь, он может дать представление о том, как заставить его работать со многими для многих.
(И часто вы в конечном итоге понимаете, что промежуточный объект заслуживает того, чтобы быть там из-за некоторых атрибутов ассоциации.)
Ответ 4
Я сделал это вот так:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#many-to-many-bidirectional
и это работает как шарм