How to implement many-to-many relationships in Sonata Media Bundle
sonata-admin, symfony
Solution
I had the same problem as you, but I've figured it out.
First of all, you might want to choose for a one-to-many/many-to-one relationship (using an intermediate entity) instead of a many-to-many relationship. Why? Because this allows for additional columns, such as a `position` column. This way you can reorder the images any way you want. In a many-to-many relationship, the link table only has two columns: the id's of the associated tables.
From the Doctrine documentation:
(...) frequently you want to associate additional attributes with an association, in which case you introduce an association class. Consequently, the direct many-to-many association disappears and is replaced by one-to-many/many-to-one associations between the 3 participating classes.
So I added this to my Product mapping file: (as you can see I'm using YAML as my configuration file format)
oneToMany:
images:
targetEntity: MyBundle\Entity\ProductImage
mappedBy: product
orderBy:
position: ASC
And I created a new ProductImage mapping file:
MyBundle\Entity\ProductImage:
type: entity
table: product_images
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
position:
type: integer
manyToOne:
product:
targetEntity: MyBundle\Entity\Product
inversedBy: images
image:
targetEntity: Application\Sonata\MediaBundle\Entity\Media
Using the command line (`php app/console doctrine:generate:entities MyBundle`) I created / updated the corresponding entities (`Product` and `ProductImage`).
Next, I created/updated the Admin classes. ProductAdmin.php:
class ProductAdmin extends Admin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
// define other form fields
->add('images', 'sonata_type_collection', array(
'required' => false
), array(
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'position',
))
;
}
ProductImageAdmin.php:
class ProductImageAdmin extends Admin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('image', 'sonata_type_model_list', array(
'required' => false
), array(
'link_parameters' => array(
'context' => 'product_image'
)
))
->add('position', 'hidden')
;
}
Don't forget to add both of them as services. If you don't want a link to the ProductImage form to be displayed on the dashboard, add the `show_in_dashboard: false` tag. (how you do this depends on the configuration format (yaml/xml/php) you use)
After this I had the admin form working correctly, however I still had some problems trying to save products. I had to perform the following steps in order to fix all problems:
First, I had to configure cascade persist operations for the Product entity. Again, how to do this depends on your configuration format. I'm using yaml, so in the `images` one-to-many relationship, I added the cascade property:
oneToMany:
images:
targetEntity: MyBundle\Entity\ProductImage
mappedBy: product
orderBy:
position: ASC
cascade: ["persist"]
That got it working (or so I thought), but I noticed that the `product_id` in the database was set to `NULL`. I solved this by adding `prePersist()` and `preUpdate()` methods to the `ProductAdmin` class:
public function prePersist($object)
{
foreach ($object->getImages() as $image) {
$image->setProduct($object);
}
}
public function preUpdate($object)
{
foreach ($object->getImages() as $image) {
$image->setProduct($object);
}
}
... and added a single line to the `addImages()` method of the `Product` entity:
public function addImage(\MyBundle\Entity\ProductImage $images)
{
$images->setProduct($this);
$this->images[] = $images;
return $this;
}
This worked for me, now I can add, change, reorder, delete, etc. images to/from my Products.
Problem
I am trying to relate SonataMediaBundle to another Entity: Products with a relation ManyToMany. The schema and relation are well created. However, when I edit or create a new product, I try to add a button where I can search a media file through the media library and a button to upload a new file. For a relation OneToMany, this is easily done in `Admin\ProductAdmin::configureFormFields` by adding: ``` ->add('image', 'sonata_type_model_list', array( 'required' => false ), array( 'link_parameters' => array( 'context' => 'default', 'provider' => 'sonata.media.provider.image' ) )) ``` So I get the same 3 icons as they already been used in the Gallery of SonataMediaBundle (add from library, upload and delete) BUT on the relation ManyToMany it isn't possible! Because every time I choose a media, it replaces the previous one. So I can't select multiple media types. I thought on using the same way as The Gallery (`galleryHasMedia`) ``` ->add('galleryHasMedias', 'sonata_type_collection', array( 'by_reference' => false ), array( 'edit' => 'inline', 'inline' => 'table', 'sortable' => 'position', 'link_parameters' => array('context' => $context) )) ``` However, it is really complex. How can I choose or upload multiple media files on another Entity through a ManyToMany Relation?