I have 3 tables : A,AB,B. AB is the middle table of table A and B.
for some reason I need a extra column on the middle table AB,So I have to split the many-to-many relation to 2 one-to-many relations( A has many AB, B also has many AB ).
then I found out that It seems like not possible to manage the relation. Can I manage(link/unlink) B in the edit page of A ?
any one have any idea ?
I ran in the same issue as you. In my case issue #1000 is related to this problem, too, because I used the primary keys of A and B as Compound key in AB (http://en.wikipedia.org/wiki/Compound_key)
After introducing a surrogate key AB.id I could workaround #1000 and can add and delete the Relation in the A-Form. As you can see in the screenshot attached, I can add a new Entry to AB (Factors is AB, Category is B and Regatta is A). But the Regatta/A Column should be associated to the Regatta/A-Form I'm currently using..
I'm new to SF and Sonata, but I'll try to find a workaround for. I'm pleased about every hint.

thanks @smlr , I'm happy that you figure it out , but Could you explain more about how you did it ?
If you can provide some example code that will be very helpful, thanks !
For the Entities you have to use two one-to-many relations between A-AB and AB-B to implement a many-to-many relationship with extra columns (http://docs.doctrine-project.org/en/2.1/reference/faq.html#how-can-i-add-columns-to-a-many-to-many-table.
First, create your AAdmin, ABAdmin and BAdmin Classes like you would, if those Classes wouldn't share any relation. After this, add to your sonata.admin.A service a Child sonata.admin.AB (Admin.yml or Admin.xml).
Than you can use the configureSideMenu() method in AAdmin to create a sidemenu and generate a link to ABAdmin.
It's important to add $parentAssociationMapping to your ABAdmin Class and adding some if-statements to improve the usability, if list and form is in a A-context.
If you know the mechanism, it's quite easy. Hope my example works for you..
# Admin.yml
# Add a Child in the Service of A
services:
sonata.admin.A:
class: Path\toBundle\Admin\AAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: YourGroup, label: "label.A" }
arguments:
- ~
- Path\toBundle\Entity\A
- 'SonataAdminBundle:CRUD'
calls:
- [ setTranslationDomain, [toBundle]]
- [ addChild, [@sonata.admin.AB]]
class AAdmin extends Admin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('name')
->add('data')
/* No add('AB') here.. as you see in my screenshot, I added it inline before. */
->end()
;
}
// ...
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
if (!$childAdmin && !in_array($action, array('edit'))) {
return;
}
$admin = $this->isChild() ? $this->getParent() : $this;
$id = $admin->getRequest()->get('id');
$menu->addChild(
$this->trans('admin.sidemenu.link_view_A'),
array('uri' => $admin->generateUrl('edit', array('id' => $id)))
);
$menu->addChild(
$this->trans('admin.sidemenu.link_view_AB'),
array('uri' => $admin->generateUrl('sonata.admin.AB.list', array('id' => $id)))
);
}
}
class ABAdmin extends Admin
{
protected $parentAssociationMapping = 'A'; // This does the trick..
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('B')
->add('AB_Data')
;
if (!$this->isChild())
$formMapper->add('A'); // Just add the A dropdown, if you are NOT in A-context
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('AB_Data')
->add('B')
;
}
protected function configureListFields(ListMapper $listMapper)
{
if (!$this->isChild())
$listMapper->addIdentifier('id')->addIdentifier('A');
$listMapper
->addIdentifier('B')
->addIdentifier('AB_Data');
}
}
?>
The relations in the AB Entity are defined as the following (I added a surrogate key Id)
class AB
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity="B", inversedBy="ABs")
* @ORM\JoinColumn(name="B_id", referencedColumnName="id", nullable=false)
*/
protected $B;
/**
* @ORM\ManyToOne(targetEntity="A", inversedBy="As")
* @ORM\JoinColumn(name="A_id", referencedColumnName="id", nullable=false)
*/
protected $A;
/**
* @ORM\Column(type="string", length=10, nullable=true)
*/
protected $AB_Data;
thanks @smlr, I will have a try.
Hello,
I'm trying to do the same, but I have a problem. Everything is working out except that I would like to add several AB entities from my A form just like you in your screenshot (if I understood well) but I still stuck.
And you wrote in your code : " /* No add('AB') here.. as you see in my screenshot, I added it inline before. */ "
What do you mean by "I added it inline before" ?
In my AB Admin I can't use 'sonata_type_collection' to add several AB entities at once...
In any case, thanks a lot @smlr without your example, I would be blocked yet, this is only a detail but I need it so if you have an issue : it would be great !
Finally, I fixed it by myself.
But when I try to add an entity A from A form with some AB entities it doesn't work.
It says : "[A_id, B_id, AB_data] : A_id is null"
Keep try'in
Finally fixed it too.
public function preUpdate($object)
{
$this->prePersist($object);
}
public function prePersist($object)
{
foreach($object->getAB() as $AB)
$AB->setA($object);
}
@belette-ninja can You tell me where You put that code? IT looks like EntityAdmin in sonata Admin; and how You where able to use Your functions ->getAB using the $object?
You have to put it in your A admin file. (A the owner of the association and B the children).
Secondly, if you followed what is above. You do have a getter for the AB attribute in your A entity. Don't forget to generate your getters/setters with :
php app/console doctrine:generate:entities App/YourBundle/Entity/A
php app/console doctrine:generate:entities App/YourBundle/Entity/B
php app/console doctrine:generate:entities App/YourBundle/Entity/AB
Before I start anything more complex I'd like to confirm what this solution provides or what I am expecting:
I have classes A and B from A I want to add Meny B and from B I want to see Many A's that can be associated with it. This creates ManyToMany between A and B but I require some data (confirmation that A reached B) in table in between - so AB is created with custom fields.
Now I want to populate AB from A by selecting many B destinations and have AB filled with id B_1 and A_n and from B I can see what different A's are connected to it. I don't quite understand why Child Admin is needed (but I dont understand that concept entirely yet) for this that's why I'm asking.
I'll try to follow Your advice but just one more thing. If I put the code in Admin (where prePersist functions reside etc.) how is it possible to get data that is usually accessed by the controller?
I am new to Symfony and Sonata and still learning so please forgive my ignorance ;).
@ethernal you should look at the SonataMediaBundle, we have a Gallery, linked to GalleryHasMedia, linked to a Media. So you have a GalleryAdmin, a GalleryHasMediaAdmin and MediaAdmin.
From the the GalleryAdmin it is possible to add a GalleryHasMedia, one side of the many-to-many, and the GalleryHasMediaAdmin has a field to the MediaAdmin, the other side of the many-to-many. With this pattern you can edit extra information from the GalleryHasMedia
@rande Thanks, I had everything in place and well, after changing sonata types it started working as expected. It was mistake on my part as I was thinking more about using multiple=true option of sonata_type_model to add many instances of users. Now I just need to filter the list that appears when adding "join-table" elements.
Most helpful comment
For the Entities you have to use two one-to-many relations between A-AB and AB-B to implement a many-to-many relationship with extra columns (http://docs.doctrine-project.org/en/2.1/reference/faq.html#how-can-i-add-columns-to-a-many-to-many-table.
First, create your AAdmin, ABAdmin and BAdmin Classes like you would, if those Classes wouldn't share any relation. After this, add to your sonata.admin.A service a Child sonata.admin.AB (Admin.yml or Admin.xml).
Than you can use the configureSideMenu() method in AAdmin to create a sidemenu and generate a link to ABAdmin.
It's important to add $parentAssociationMapping to your ABAdmin Class and adding some if-statements to improve the usability, if list and form is in a A-context.
If you know the mechanism, it's quite easy. Hope my example works for you..
# Admin.yml # Add a Child in the Service of A services: sonata.admin.A: class: Path\toBundle\Admin\AAdmin tags: - { name: sonata.admin, manager_type: orm, group: YourGroup, label: "label.A" } arguments: - ~ - Path\toBundle\Entity\A - 'SonataAdminBundle:CRUD' calls: - [ setTranslationDomain, [toBundle]] - [ addChild, [@sonata.admin.AB]]class AAdmin extends Admin { protected function configureFormFields(FormMapper $formMapper) { $formMapper ->with('General') ->add('name') ->add('data') /* No add('AB') here.. as you see in my screenshot, I added it inline before. */ ->end() ; } // ... protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) { if (!$childAdmin && !in_array($action, array('edit'))) { return; } $admin = $this->isChild() ? $this->getParent() : $this; $id = $admin->getRequest()->get('id'); $menu->addChild( $this->trans('admin.sidemenu.link_view_A'), array('uri' => $admin->generateUrl('edit', array('id' => $id))) ); $menu->addChild( $this->trans('admin.sidemenu.link_view_AB'), array('uri' => $admin->generateUrl('sonata.admin.AB.list', array('id' => $id))) ); } } class ABAdmin extends Admin { protected $parentAssociationMapping = 'A'; // This does the trick.. protected function configureFormFields(FormMapper $formMapper) { $formMapper ->add('B') ->add('AB_Data') ; if (!$this->isChild()) $formMapper->add('A'); // Just add the A dropdown, if you are NOT in A-context } protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper ->add('AB_Data') ->add('B') ; } protected function configureListFields(ListMapper $listMapper) { if (!$this->isChild()) $listMapper->addIdentifier('id')->addIdentifier('A'); $listMapper ->addIdentifier('B') ->addIdentifier('AB_Data'); } } ?>The relations in the AB Entity are defined as the following (I added a surrogate key Id)
class AB { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\ManyToOne(targetEntity="B", inversedBy="ABs") * @ORM\JoinColumn(name="B_id", referencedColumnName="id", nullable=false) */ protected $B; /** * @ORM\ManyToOne(targetEntity="A", inversedBy="As") * @ORM\JoinColumn(name="A_id", referencedColumnName="id", nullable=false) */ protected $A; /** * @ORM\Column(type="string", length=10, nullable=true) */ protected $AB_Data;