How to implement many-to-many relationships in Sonata Media Bundle
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.
You need to rely on MediaBundle Gallery. In your entity you something like :
/** * @ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Gallery") * @ORM\JoinColumn(name="image", referencedColumnName="id") */private $images;
And then in your form, you'll be able to link a Gallery to your object with something like :
->add('images', 'sonata_type_model_list', array('required' => false), array('link_parameters' => array('context' => $context)))