Symfony2 Doctrine2 связь «многие ко многим» с двумя сторонними владельцами и Doctrine cmd line tools - PullRequest
15 голосов
/ 06 марта 2012

В моем проекте 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

[ SAVE ]

И редактор ServiceGroup:

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

Имя: [Группа A]

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

[x] Сервис 1

[] Сервис 2

[] Сервис 3

[ СОХРАНИТЬ ]

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

Проблема, с которой я столкнулся, связанас механизмом генерации схемы доктрины, при попытке обновить схему с помощью команды 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.Таким образом, он работает с текущей схемой, которую я встроил в базу данных с помощью той же команды, но пошагово, сначала, когда не определено отношение «многие ко многим», а затем только с одним определением отношения «многие ко многим» (для сервисного лица).Когда я добавил отношение «многие ко многим» ко второй сущности (ServiceGroup), мое приложение кажется работающим без проблем с точки зрения пользователя, но я больше не могу использовать команду «doctrine: schema: update».

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

ОБНОВЛЕНИЕ:

Я заметил, что мне нужно настроить отношение 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 класса сущности Service:

/**
 * 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) стороной, а также формы, которые обновляют как сущность, так и отношение при сохранении (формы по умолчанию для сущности Service достаточно, но дляServiceGroup я предоставил вышеупомянутые модификации).Консольные инструменты Symfony / Doctrine работают как шарм.Возможно, это можно решить лучше (проще?), Но для меня этого пока достаточно.

Ответы [ 4 ]

1 голос
/ 07 апреля 2016

Поведение по умолчанию для ассоциации «многие ко многим» будет иметь собственную сторону и обратную сторону.Если вы имеете дело со стороной-владельцем ассоциации, тогда ассоциация будет обрабатываться 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);
    }
}

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

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

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

0 голосов
/ 20 сентября 2013
0 голосов
/ 17 марта 2016

Вы пытались кодировать его с промежуточной сущностью и множественными связями вместо этого?

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

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

0 голосов
/ 06 марта 2012

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

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

        $this->services[] = $service;
        $service->addGroup($this);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...