CRUD image uploader with Sylius

This post is available in french

The purpose of this article is to detail all the steps to perform an image upload in ths backoffice and display these images in the homepage

front

Image Title

back

Image Title

We will start by creating an Entity which we will call BannerImage

ainsi que son interface src/Entity/BannerImagephp

<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\Image;

/**
 * @ORM\Entity()
 */
class BannerImage extends Image implements BannerImageInterface
{
}

Its interface src/Entity/BannerImageInterface.php

<?php

declare(strict_types=1);

namespace App\Entity;

use Sylius\Component\Core\Model\ImageInterface;

interface BannerImageInterface extends ImageInterface
{
}

That's all. Everything is the ImageInterface model we inherit.

Let's update the database with

php bin/console doctrine:migrations:diff

then create this new entity

php bin/console doctrine:migrations:migrate

In the file config/packages/resources.yaml we define this entity

sylius_resource:
    resources:
          app.banner_image:
            driver: doctrine/orm
            classes:
                model: App\Entity\BannerImage

Let's create the grid that will allow us to display the entity in the dashboard

in the file config/packages/grids.yaml

sylius_grid:
    grids:
        app_admin_banner_image:
            driver:
                name: doctrine/orm
                options:
                    class: App\Entity\BannerImage
            actions:
                main:
                    create:
                        type: create
                item:
                    update:
                        type: update
                    delete:
                        type: delete

Finally, we create the route in the file config/routes.yaml

app_banner_image:
    resource: |
        alias: app.banner_image
        section: admin
        templates: SyliusAdminBundle:Crud
        grid: app_admin_banner_image
        redirect: index
    type: sylius.resource
    prefix: admin

We have the route, which calls our resource banner_image defined in resources.yaml and also in the route is defined which grid is linked to this resources. The grid is app_admin_banner_image which we defined in grids.yaml

In the terminal with the command

php bin/console debug:router we can see the new routes:

app_admin_banner_image_index            GET              ANY      ANY    /admin/banner-images/
app_admin_banner_image_create           GET|POST         ANY      ANY    /admin/banner-images/new
app_admin_banner_image_update           GET|PUT|PATCH    ANY      ANY    /admin/banner-images/{id}/edit
app_admin_banner_image_show             GET              ANY      ANY    /admin/banner-images/{id}
app_admin_banner_image_bulk_delete      DELETE           ANY      ANY    /admin/banner-images/bulk-delete
app_admin_banner_image_delete           DELETE           ANY      ANY    /admin/banner-images/{id}

If you go to your dashboard localhost/admin/banner-images/ you should have this result:

Image Title

From this moment clicking on Create takes us to this page but does not allow us to send an image to the server

Image Title

We will create the form which will allow us to send an image.

src/Form/Type/BannerImageType.php

<?php

declare(strict_types=1);

namespace App\Form\Type;

use Sylius\Bundle\CoreBundle\Form\Type\ImageType as BaseImageType;

class BannerImageType extends BaseImageType
{
}

Again, no need to fill it out, everything is in the ImageType class we inherit

We must declare this form as a service, in config/services.yaml add :

services:
    app.banner_image.type.form.type:
        class: App\Form\Type\BannerImageType
        tags:
            - { name: form.type }
        arguments: ['App\Entity\BannerImage']

and finally indicate it to our resources in config/packages/resources.yaml

sylius_resource:
    resources:
        app.banner_image:
            driver: doctrine/orm
            classes:
                model: App\Entity\BannerImage
                form: App\Form\Type\BannerImageType # on rajoute cette ligne

Here is the result on the Create page

Image Title

event uploader

It is still not possible to upload an image. We are going to create an event which will listen to the moment when the form is submitted to transfer the image. For the event it is necessary to create the listener src/Uploader/BannerImageUploadListener.php

<?php

declare(strict_types=1);

namespace App\Uploader;

use App\Entity\BannerImageInterface;
use Sylius\Component\Core\Uploader\ImageUploaderInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Webmozart\Assert\Assert;

class BannerImageUploadListener
{
    /** @var ImageUploaderInterface */
    private $uploader;

    public function __construct(ImageUploaderInterface $uploader)
    {
        $this->uploader = $uploader;
    }

    public function upload(GenericEvent $event): void
    {
        $subject = $event->getSubject();
        Assert::isInstanceOf($subject, BannerImageInterface::class);
        if ($subject->hasFile()) {
            $this->uploader->upload($subject);
        }
    }
}

We declare the listener in config/services.yaml

app.listener.banner_image_upload:
        class: App\Uploader\BannerImageUploadListener
        autowire: true
        autoconfigure: false
        public: false
        tags:
            - { name: kernel.event_listener, event: app.banner_image.pre_create, method: upload }

The uploader will be called just before persisting. The event corresponds to the resource, here our resource is called app.banner_image

If we add images, the upload is done correctly, now we will display them in the dashboard

Image Title

For that you have to create the template templates/_fieldImage.html.twig that we will use in the column with as content

<img class="ui small bordered image" src="{{ data|imagine_filter('sylius_small') }}" />

data returns the column information, here its the value path that is returned, we still need our grid where the template is, in config/packages/grids.yaml :

app_admin_banner_image:
    driver:
        name: doctrine/orm
        options:
            class: App\Entity\BannerImage
    fields:
        path:
            type: twig
            options:
                template: "_fieldImage.html.twig" # ici on indique le template

We can now add an image, and see it in the list.

Image Title

editer et supprimer l'image

To edit and delete the image we use the same listener.

src/Uploader/BannerImageUploadListener.php

<?php

declare(strict_types=1);

namespace App\Uploader;

use App\Entity\BannerImageInterface;
use Sylius\Component\Core\Uploader\ImageUploaderInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Webmozart\Assert\Assert;

class BannerImageUploadListener
{
    /** @var ImageUploaderInterface */
    private $uploader;

    public function __construct(ImageUploaderInterface $uploader)
    {
        $this->uploader = $uploader;
    }

    public function upload(GenericEvent $event): void
    {
        $subject = $event->getSubject();
        Assert::isInstanceOf($subject, BannerImageInterface::class);
        if ($subject->hasFile()) {
            $this->uploader->upload($subject);
        }
    }

    public function edit(GenericEvent $event): void
    {
        $subject = $event->getSubject();

        // remove older file
        if ($subject->hasFile()) {
            $this->uploader->remove($subject->getPath());
            Assert::isInstanceOf($subject, BannerImageInterface::class);
            $this->uploader->upload($subject);
        }
    }

    public function remove(GenericEvent $event): void
    {
        $subject = $event->getSubject();
        Assert::isInstanceOf($subject, BannerImageInterface::class);
        if ($subject->hasFile()) {
            $this->uploader->remove($subject);
        }
    }
}

We add two tags to our listener

app.listener.banner_image_upload:
    class: App\Uploader\BannerImageUploadListener
    autowire: true
    autoconfigure: false
    public: false
    tags:
        - { name: kernel.event_listener, event: app.banner_image.pre_create, method: upload }
        - { name: kernel.event_listener, event: app.seo_image.pre_update, method: edit }
        - { name: kernel.event_listener, event: app.seo_image.pre_delete, method: remove }

Now the edit and delete actions work. The edit function deletes the current image before adding the new one.

Homepage

We still have to display these images on the homepage. We will create the template which displays the current image

templates/Homepage/_banner.html.twig

Then we're going to use the blocks events, so in the file config/packages/sylius_ui.yaml

sylius_ui:
    events:
        sylius.shop.homepage:
            blocks:
                banner:
                    template: "Homepage/_banner.html.twig"
{{ render(controller('App\\Controller\\BannerImageController::getImages')) }}
<div class="ui hidden divider"></div>

We are going to create the action we call in the template: getImages, for that we have to create the controller src/Controller/BannerImageController.php

<?php

namespace App\Controller;

use App\Repository\BannerImageRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class BannerImageController extends AbstractController
{
    /**
     * This controller is called directly via the render() function
     */
    public function getImages(BannerImageRepository $bannerImage)
    {
        return $this->render('Homepage/_images.html.twig', [
            'images' => $bannerImage->findAll()
        ]);
    }
}

We need the repository, so let's create it src/Repository/BannerImageRepository.php

<?php

namespace App\Repository;

use App\Entity\BannerImage;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @method BannerImage|null find($id, $lockMode = null, $lockVersion = null)
 * @method BannerImage|null findOneBy(array $criteria, array $orderBy = null)
 * @method BannerImage[]    findAll()
 * @method BannerImage[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class BannerImageRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, BannerImage::class);
    }
}

In our action, we also call a template that loops over the images.

templates/Homepage/_images.html.twig

{% for image in images %}
    <img style="width: 50%; float: right;" class="ui fluid image" src="{{ "/media/image" ~ asset(image.path) }}">
{% endfor %}

You can now go to the homepage and discover the images you have uploaded!

Image Title