Changer l'image de la homepage (CRUD d'upload d'image)

Le but de cet article est de vous détailler toutes les étapes pour réaliser un upload d'image en back et afficher ces images en front sur la homepage

front

Image Title s

back

Image Title

Nous allons commencer par créer une Entity que nous appelerons src/Entity/BannerImage.php

<?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
{
}

ainsi que son interface src/Entity/BannerImageInterface.php

<?php

declare(strict_types=1);

namespace App\Entity;

use Sylius\Component\Core\Model\ImageInterface;

interface BannerImageInterface extends ImageInterface
{
}

C'est tout. Tout se trouve dans le modèle ImageInterface dont nous héritons

Mettons à jour la base de données avec

php bin/console doctrine:migrations:diff

puis créons cette nouvelle entité

php bin/console doctrine:migrations:migrate

Dans le fichier config/packages/resources.yaml nous définissons cette entité

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

Créations de la grille qui va nous permettre d'afficher l'entité dans le dashboard

dans le fichier 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

Enfin, création de la route dans 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

Nous avons la route, qui appelle notre ressource banner_image définie dans resources.yaml et également dans la route est définie quelle grille est relié à cette ressource. Grile app_admin_banner_image que nous avons définie dans grids.yaml

Dans le terminal avec la commande

php bin/console debug:router on peut constater les nouvelles 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}

Si vous vous rendez dans votre dashboard localhost/admin/banner-images/ vous devriez avoir ce résultat :

Image Title

A partir de ce moment cliquer sur Create nous amène à cette page mais ne nous permet pas d'envoyer d'image sur le serveur

Image Title

Nous allons créer le formulaire qui va nous permettre d'envoyer une 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
{
}

Encore une fois, pas besoin de le remplir, tout se trouve dans la classe ImageType dont nous héritons

Il faut déclarer ce form comme service, dans config/services.yaml rajoutez :

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

et enfin l'indiquer à notre ressource dans 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

Voici le résultat sur la page Create

Image Title

event uploader

Il n'est toujours pas possible d'uploader une image. On va créer un event qui va écouter le moment où le form est submit pour transférer l'image. Pour l'event il faut créer le 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);
        }
    }
}

On déclare le listener dans 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 }

L'uploader sera appelé juste avant de persist. L'event correspond à la ressource, ici notre ressource s'appelle app.banner_image

Si nous ajoutons des images, l'upload s'effectue correctement, maintenant nous allons les afficher dans le dashboard

Image Title

Pour ça il faut créer le template templates/_fieldImage.html.twig qu'on va utiliser dans la colonne avec comme contenu

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

data nous retourne l'information de la colonne, ici c'est la valeur du path qui est retourné, il faut encore indiquer à notre grille où est le template, dans 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

Nous pouvons maintenant ajouter une image, et la voir dans la liste.

Image Title

editer et supprimer l'image

Pour editer et supprimer l'image on utilise le même 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);
        }
    }
}

On ajoute deux tags à notre 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.banner_image.pre_update, method: edit }
        - { name: kernel.event_listener, event: app.banner_image.pre_delete, method: remove }

Maintenant les actions editer et supprimer fonctionne. La fonction edit supprime l'image actuelle avant d'ajouter la nouvelle.

Homepage

Il nous reste à afficher ces images sur la homepage. On crée le template :

templates/Homepage/_banner.html.twig

{{ render(controller('App\\Controller\\BannerImageController::getImages')) }}
<div class="ui hidden divider"></div>

Puis on va utiliser les events de sylius_ui, de cette façon on ne surcharge pas le template :

config/packages/sylius_ui.yaml

sylius_ui:
    events:
        sylius.shop.homepage:
            blocks:
                banner:
                    template: "Homepage/_banner.html.twig"

On va créer getImages l'action qu'on appelle dans le template, pour ça il faut créer le 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()
        ]);
    }
}

Nous avons besoin du repository, nous allons donc le créer 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);
    }
}

Dans notre action, nous appelons également un template qui boucle sur les 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 %}

Vous pouvez maintenant vous rendre sur la homepage et découvrir les images que vous avez uploadés !

Image Title