Handling multiple file uploads in Sonata Admin Bundle

11,641

For your solution to have multiple images for your company admin you have to organize your relation like there will be one junction entity which will point to sonata media entity in a ManyToOne relation and also to your products entity in a ManyToOne relation as well i have created this type of collection for one of needs for footer widgets which can have multiple images so you can map it for your products images as well in a similar way.

Footer Entity contains a property named as links which points to a junction entity FooterWidgetsHasMedia in OneToMany way,junction entity (FooterWidgetsHasMedia ) holds the relation to sonata media in addition i need multiple images for my each footer object and also for each image a in need a hover image too so my junction entity basically holds two properties that points to sonata media

FooterWidgets

/**
 * @Assert\NotBlank()
 * @ORM\OneToMany(targetEntity="Traffic\WidgetsBundle\Entity\FooterWidgetsHasMedia", mappedBy="footerWidget",cascade={"persist","remove"} )
 */
protected $links;


/**
 * Remove widgetImages
 *
 * @param \Application\Sonata\MediaBundle\Entity\Media $widgetImages
 */
public function removeLinks(\Traffic\WidgetsBundle\Entity\FooterWidgetsHasMedia $links)
{
    $this->links->removeElement($links);
}


/**
 * Get widgetImages
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getLinks()
{
    return $this->links;
}


/**
 * {@inheritdoc}
 */
public function setLinks($links)
{
    $this->links = new ArrayCollection();


    foreach ($links as $footerWidget) {
        $this->addLinks($footerWidget);
    }
}

/**
 * {@inheritdoc}
 */
public function addLinks(\Traffic\WidgetsBundle\Entity\FooterWidgetsHasMedia $links)
{
    $links->setFooterWidget($this);


    $this->links[] = $links;
}

Now my junction entity will points back to FooterWidgets and sonata media entity

FooterWidgetsHasMedia

Definitions of properties

/**
 * @var \Application\Sonata\MediaBundle\Entity\Media
 * @Assert\NotBlank()
 * @ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Media", cascade={"persist"}, fetch="LAZY")
 * @ORM\JoinColumn(name="media_id", referencedColumnName="id")
 */
protected $media;

/**
 * @var \Application\Sonata\MediaBundle\Entity\Media
 * @Assert\NotBlank()
 * @ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Media", cascade={"persist"}, fetch="LAZY")
 * @ORM\JoinColumn(name="media_hover_id", referencedColumnName="id")
 */
protected $mediaHover;

/**
 * @var \Traffic\WidgetsBundle\Entity\FooterWidgets
 * @Assert\NotBlank()
 * @ORM\ManyToOne(targetEntity="Traffic\WidgetsBundle\Entity\FooterWidgets", cascade={"persist","remove"} ,inversedBy="links", fetch="LAZY" )
 * @ORM\JoinColumn(name="footer_widget_id", referencedColumnName="id",nullable=true)
 */
protected $footerWidget;
/**
 * @var integer
 * @ORM\Column(name="position", type="integer")
 */
protected $position;


/**
 * @var boolean
 * @ORM\Column(name="enable", type="boolean")
 */
protected $enabled;

Generate getters and setters for above properties

Now you have to create new admin for your collection which references to junction entity FooterWidgetsHasMedia and configureFormFields will look something like below

FooterWidgetsHasMediaAdmin

protected function configureFormFields(FormMapper $formMapper)
{
    $link_parameters = array();

    if ($this->hasParentFieldDescription()) {
        $link_parameters = $this->getParentFieldDescription()->getOption('link_parameters', array());
    }

    if ($this->hasRequest()) {
        $context = $this->getRequest()->get('context', null);

        if (null !== $context) {
            $link_parameters['context'] = $context;
        }
    }

    $formMapper

        ->add('media', 'sonata_type_model_list', array('required' => false), array(
            'link_parameters' => $link_parameters
        ))
        ->add('mediaHover', 'sonata_type_model_list', array('required' => false), array(
            'link_parameters' => $link_parameters
        ))
        ->add('enabled', null, array('required' => false))
        ->add('link', 'text', array('required' => false))
        ->add('position', 'hidden')


    ;
}

And your company admin will have a new field in configureFormFields

FooterWidgetsAdmin

        ->add('links', 'sonata_type_collection', array(
                'cascade_validation' => false,
                'type_options' => array('delete' => false),
            ), array(

                'edit' => 'inline',
                'inline' => 'table',
                'sortable' => 'position',
                'link_parameters' => array('context' => 'widgets'),
                'admin_code' => 'sonata.admin.footer_widgets_has_media' /*here provide service name for junction admin */
            )
        )

Register admin service for your new admin as

sonata.admin.footer_widgets_has_media:
    class: Traffic\WidgetsBundle\Admin\FooterWidgetsHasMediaAdmin
    tags:
        - { name: sonata.admin, manager_type: orm, group: "Widgets", label: "Footer Widgets Section Media" , show_in_dashboard: false }
    arguments:
        - ~
        - Traffic\WidgetsBundle\Entity\FooterWidgetsHasMedia
        - ~
    calls:
        - [ setTranslationDomain, [TrafficWidgetsBundle]]

Demo snap shot

enter image description here

You can find full code demo here Git Hub hope it makes sense

Share:
11,641
Reynier
Author by

Reynier

Updated on June 21, 2022

Comments

  • Reynier
    Reynier almost 2 years

    So, after research a lot and get no results (maybe I'm a bad searcher) I coming from this topics: SonataAdmin Bundle File Upload Error and SonataMediaBundle - how to upload images? I can't find a solution for my problem. I have a Entity Company and each company can have multiple files: PDF, DOC, XLS and some other mime/types. I think to use VichUploaderBundle but again docs only covers example for one to one relationship so my question is, any can give me some examples or ways to get this done? I mean upload files and attach them to company?

    EDIT1 working and testing

    As I said before I'm trying to integrate SonataMediaBundle into another admin module I have but I can't get it to work. What I did until now?

    Of course install and configure all bundles: SonataAdminBundle and SonataMediaBundle both are working fine

    Modified \Application\Sonata\MediaBundle\Entity\Media.php class to add the needed functionality by adding a ManyToMany relationship

    namespace Application\Sonata\MediaBundle\Entity;
    
    use Sonata\MediaBundle\Entity\BaseMedia as BaseMedia;
    use Doctrine\ORM\Mapping as ORM;
    
    class Media extends BaseMedia {
    
        /**
         * @var integer $id
         */
        protected $id;
    
        /**
         * @ORM\ManyToMany(targetEntity="PL\OrderBundle\Entity\Order", inversedBy="medias")
         * @ORM\JoinTable(name="order_has_media__media",
         *      joinColumns={@ORM\JoinColumn(name="media__media_id", referencedColumnName="id")},
         *      inverseJoinColumns={@ORM\JoinColumn(name="order_no_order", referencedColumnName="no_order")}
         * )
         */
        protected $orders;
    
        public function __construct() {
            $this->orders = new \Doctrine\Common\Collections\ArrayCollection();
        }
    
        /**
         * Get id
         *
         * @return integer $id
         */
        public function getId() {
            return $this->id;
        }
    
        public function setOrders(\PL\OrderBundle\Entity\Order $order) {
            $this->orders[] = $order;
        }
    
        public function getOrders() {
            return $this->orders;
        }
    
    }
    

    Adding the need fields in PL\OrderBundle\Entity\Order.php as follow:

    namespace PL\OrderBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity
     * @ORM\Table(name="tb_order")
     */
    class Order {
    
        /**
         * @ORM\Id
         * @ORM\Column(type="string", length=15, unique=true, nullable=false)
         */
        protected $no_order;
    
        /**
         * @ORM\ManyToOne(targetEntity="PL\CompanyBundle\Entity\Company", inversedBy="id")
         */
        protected $company;
    
        /**
         * @ORM\Column(type="string", length=15, unique=true)
         */
        protected $business_case;
    
        /**
         * @ORM\Column(type="integer", length=1)
         */
        protected $charge_status;
    
        /**
         * @ORM\Column(type="datetime")
         */
        protected $eta;
    
        /**
         * @ORM\Column(type="datetime")
         */
        protected $etd;
    
        /**
         * @ORM\Column(type="integer", length=1)
         */
        protected $transport_media;
    
        /**
         * @ORM\Column(type="integer", length=1)
         */
        protected $incoterm;
    
        /**
         * @ORM\Column(type="string", length=250)
         */
        protected $comments;
    
        /**
         * @ORM\ManyToMany(targetEntity="Application\Sonata\MediaBundle\Entity\Media", mappedBy="orders")
         */
        protected $medias;
    
        public function __construct() {
            $this->medias = new \Doctrine\Common\Collections\ArrayCollection();
        }
    
        public function setNoOrder($no_order) {
            $this->no_order = $no_order;
        }
    
        public function getNoOrder() {
            return $this->no_order;
        }
    
        public function setCompany(\PL\CompanyBundle\Entity\Company $company) {
            $this->company = $company;
        }
    
        public function getCompany() {
            return $this->company;
        }
    
        public function setBusinessCase($business_case) {
            $this->business_case = $business_case;
        }
    
        public function getBusinessCase() {
            return $this->business_case;
        }
    
        public function setChargeStatus($charge_status) {
            $this->charge_status = $charge_status;
        }
    
        public function getChargeStatus() {
            return $this->charge_status;
        }
    
        public function setETA($eta) {
            $this->eta = $eta;
        }
    
        public function getETA() {
            return $this->eta;
        }
    
        public function setETD($etd) {
            $this->etd = $etd;
        }
    
        public function getETD() {
            return $this->etd;
        }
    
        public function setTransportMedia($transport_media) {
            $this->transport_media = $transport_media;
        }
    
        public function getTransportMedia() {
            return $this->transport_media;
        }
    
        public function setIncoterm($incoterm) {
            $this->incoterm = $incoterm;
        }
    
        public function getIncoterm() {
            return $this->incoterm;
        }
    
        public function setComments($comments) {
            $this->comments = $comments;
        }
    
        public function getComments() {
            return $this->comments;
        }
    
        public function setMedias(\Application\Sonata\MediaBundle\Entity\Media $media) {
            $this->medias[] = $media;
        }
    
        public function addMedia(\Application\Sonata\MediaBundle\Entity\Media $media) {
            $this->medias[] = $media;
        }
    
        public function getMedias() {
            return $this->medias;
        }
    
    }
    

    Changed the configureFormFields in OrderAdmin.php file as follow:

    protected function configureFormFields(FormMapper $form) {
            $form
                    ->add('no_order', null, array('label' => 'No. Order'))
                    ->add('company', 'entity', array('class' => 'PL\CompanyBundle\Entity\Company', 'label' => 'Cliente'))
                    ->add('business_case', null, array('label' => 'BC'))
                    ->add('charge_status', 'choice', array('choices' => array(
                            "empty_value" => "Seleccione una opción",
                            "0" => "Ninguno",
                            "1" => "Proceso de Fabricacion",
                            "2" => "Pickup en destino",
                            "3" => "A la espera de recojo por cliente",
                            "4" => "Carga en transito",
                            "5" => "Carga arribada",
                            "6" => "En proceso de aduana",
                            "7" => "Entregado a cliente",
                            "8" => "En bodega"
                        ), "required" => true, 'label' => 'Estado de la carga'))
                    ->add('eta', null, array('label' => 'ETD'))
                    ->add('etd', null, array('label' => 'ETA'))
                    ->add('transport_media', 'choice', array('choices' => array("empty_value" => "Seleccione una opción", "0" => "EXW", "1" => "Maritimo", "2" => "Aereo"), "required" => true, 'label' => 'Via de Transporte'))
                    ->add('incoterm', 'choice', array('choices' => array(
                            "empty_value" => "Seleccione una opción",
                            "0" => "Ninguno",
                            "1" => "EWX",
                            "2" => "FOB",
                            "3" => "CIF",
                            "4" => "DDP"
                        ), "required" => true, 'label' => 'Incoterm'))
                    ->add('comments', null, array('label' => 'Comentarios'))
                    ->add('medias', 'sonata_type_collection', array(
                        'label' => 'Documentos',
                        'type_options' => array('delete' => true)), array(
                        'edit' => 'inline', 'inline' => 'table', 'sortable' => 'position')
            );
        }
    

    But this doesn't work since I can't upload any file and this is what I want upload many files from the same form and attach them to the order I'm creating. See the attached images for a visual I get when I access the create action:

    enter image description here enter image description here

    What I'm missing?

  • Dickriven Chellemboyee
    Dickriven Chellemboyee about 9 years
    In your code on github, I believe the class WidgetsBaseAdmin is missing
  • M Khalid Junaid
    M Khalid Junaid about 9 years
    @Chellem yes you can define your base admin or you can directly use sonata's base admin
  • Rishi
    Rishi over 8 years
    @MKhalidJunaid Hello I am trying to copy "background Image" using PHPCR but instead of using "Application\Sonata\MediaBundle\Entity\Media" I used a custom document that has it's own admin containing 3 file uploaders and other text fields. My issue is when I click on "Add" button it redirects me the my custom doc's admin and when saved, it doesn't redirect me back from where I came and doesnt appear to be linked to another document in phpcr tree. Any idea on missing config? Also could you add the "Application\Sonata\MediaBundle\Entity\Media" and "WidgetsBaseAdmin" files in your git repo?
  • Rishi
    Rishi over 8 years
    By custom class I meant an extension of ImagineBlock document and the URL generated on the "Add" button: localhost:8080/projects/web/admin/myproject/block/…
  • Rishi
    Rishi over 8 years
    In the end my aim is to do exactly what you showed in the picture, i.e being able to have an image and a collection of documents having images, all embedded in/linked to a single document.