In my Symdony2 project I've two related entities: Service and ServiceGroup. This should be many-to-many relationship, because each group can have many services, and each service can belongs to many groups. Moreover, I need a user interface to manage services and groups. So, when editing a Service, user should be able to choose to which groups it belongs. Analogously, when editing a ServiceGroup user should be able to choose which services belongs to this group. I've already achieved this by setting up a Many-To-Many relation in my Doctrine entites. Everything is working like a charm, including user interface build on custom form types in Symfony2 (I've used "entity" form field type to allow user to select services in ServiceGroup editor and groups in Service editor). The only problem I've is that I cannot use Doctrine command line to update database schema anymore.
Here is part of my Service entity source code:
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;
}
And here is part of my ServiceGroup entity source code:
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;
}
I using JoinTable in both cases, because this is the only way I found working when it comes to save relations in user interface editors, which looks like this:
Service editor:
Service editor
Name: [ Service 1 ]
Groups to which this service belongs:
[x] Group A
[ ] Group B
[ ] Group C
[ SAVE ]
And ServiceGroup editor:
Group editor
Name: [ Group A ]
Services belongs to this group:
[x] Service 1
[ ] Service 2
[ ] Service 3
[ SAVE ]
With this Many-To-Many configuration I'm able to use this editors (forms) without a problem, when using Many-To-Many without JoinTable annotation, I'm able to use only one form completely, and the second one is not saving changes in "Groups to which this service belongs" or "Services belongs to this group" option (depends on in which direction I'm setting mappedBy and inversedBy parameters in Many-To-Many annotation statement).
The problem I have is connected with doctrine schema generation mechanism, when trying to update schema using Symfony2 command:
php app/console doctrine:schema:update --dump-sql
I'm getting this exception:
[Doctrine\DBAL\Schema\SchemaException]
The table with name 'service_servicegroup' already exists.
It looks like Doctrine trying to create 'service_servicegroup' table for each JoinTable statement. So, it's working on current schema, which I've build in database using the same command, but step-by-step, first when no Many-To-Many relation defined and next with only one Many-To-Many relation definition (for Service entity). When I've added Many-To-Many relation to second entity (ServiceGroup), my application seams to be working without a problem from user point of view, but I'm not able to use 'doctrine:schema:update' command anymore.
I've no idea what is wrong with my code, maybe this relation should be implemented different way, or maybe it's a Doctrine bug/limitation. Any help or suggestion would be appreciated.
UPDATE:
I've noticed that what I need is to configure ManyToMany relation to have two owning sides. Default is having one owning side and one inverse side. Doctrine documentation tells that you can have two owning sides in ManyToMany relation, but doesn't explain it a lot. Anyone can give an example?
WORKAROUND:
I've found a workaround solutions, that maybe isn't ideal but it's working for me. Since there is no way to have two owning sides in many-to-many relation, I've changed Doctrine annotation for my entites. Service entity is now the owning side:
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;
}
And ServiceGroup entity is inverse side:
class ServiceGroup
{
/**
* @var ArrayCollection $services
* @ORM\ManyToMany(targetEntity="Service", mappedBy="groups", cascade={"all"})
*/
private $services;
}
Unfortunately, with this configuration, relation is updated only when updating Service entity. When I change $services in ServiceGroup object and persist it, the relation will be unchanged. So, I've change my Controller class, and with a help of small workaround solution, I've achieved expected result. This is a part of my Controller code, which is responsible for updating ServiceGroup entity (with a use of custom form type):
// 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);
}
This are implementations of addServiceGroup and removeServiceGroup methods of Service entity class:
/**
* 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);
}
}
Now I have working many-to-many relation with owning (Service) and inverse (ServiceGroup) side, and forms that updated both entity and relation when saving (default form for Service entity is enough, but for ServiceGroup I've provided above mentioned modifications). The Symfony/Doctrine console tools are working like a charm. This probably can be solved in better (simpler?) way, but for me this is enough for now.