CRUD image uploader with Sylius
This post is available in frenchThe 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
back
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:
From this moment clicking on Create takes us to this page but does not allow us to send an image to the server
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
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
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.
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!