Translatable image entity


#1

Hello,

I’ve created a new entity (Banner) with translation support (BannerTranslation), but also I added the ImagesAwareInterface to the entity (BannerTranslation), the events app.banner.pre_create and app.banner.pre_update are activated but it gives me the following error:

Expected an instance of Sylius\Component\Core\Model\ImagesAwareInterface. Got: App\Entity\Banner

It’s because the Banner Entity doesn’t have the ImagesAwareInterface… how can I solve this?

Images aren’t translatable?

Thank you!


#2

Where is the error thrown from?
If the class implementing the ImagesAwareInterface is BannerTranslation, then you should use the events for that class, not for Banner.


#3

I’ve created the Banner entity for using it in the admin section, so I can manage from it.
The error is coming from the admin when I try to create/update a banner record with some image on it (translation).

I have defined the events this way in the services.yaml

app.listener.images_upload:
    class: Sylius\Bundle\CoreBundle\EventListener\ImagesUploadListener
    parent: sylius.listener.images_upload
    autowire: true
    autoconfigure: false
    public: false
    tags:
        - { name: kernel.event_listener, event: app.banner.pre_create, method: uploadImages }
        - { name: kernel.event_listener, event: app.banner.pre_update, method: uploadImages }

if I use the :

app.banner_translation.pre_create

It gives me a different error:

An exception occurred while executing 'INSERT INTO app_banner_image (type, path, owner_id) VALUES (?, ?, ?)' with params [null, null, 2]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'path' cannot be null

I think it’s because the event is not getting triggered…

How I can attach an event to the translation entity?

Thank you.


#4

Probably you forgot to register the BannerTranslation as a resource, so there is no event triggered by the resource layer.


#5

This is my resoures.yaml

sylius_resource:
resources:
    app.banner:
        driver: doctrine/orm
        classes:
            model: App\Entity\Banner
            form: App\Form\Type\BannerType
        translation:
            classes:
                model: App\Entity\BannerTranslation
                form: App\Enity\BannerTranslationType
    app.banner_image:
        driver: doctrine/orm
        classes:
            model: App\Entity\BannerImage
            form: App\Form\Type\BannerImageType

I Should add a separated resource “app.banner_translation” just for that? The translations are working just fine, but I cannot trigger the event…

Also I did sylius:debug:resource and I have as result:

+---------------------------------------------+
| Alias                                       |
+---------------------------------------------+
| app.banner                                  |
| app.banner_image                            |
| app.banner_translation

You have a working example with an entity with images inside translations?

Thanks


#6

I see. The problem is that those events area triggered inside the resource controller and are not propagated for resource’s children.
You will have to implement your own listener for that which will take the Banner and upload images for it’s BannerTranslations.

You could also register a listener on app.banner.pre_create and app.banner.pre_update that will trigger app.banner_translation.pre_create and app.banner_translation.pre_update. I’m not sure I’m serious about that though. :smiley:


#7

Oh, I see…

But I’m not sure how to register or trigger this event.
In the docs I don’t think this is documented.
Can you provide some sort of example?

Thank you,


#8

You don’t think it’s documented?
Did you check if it is?
https://docs.sylius.com/en/1.3/book/architecture/events.html
The “Tip” at the top will led you to https://symfony.com/doc/current/event_dispatcher.html.


#9

Solved,

I did the following:

1. Create custom Image upload Listener from the original:

<?php

declare(strict_types=1);

namespace App\Listener;

use Sylius\Component\Core\Model\ImagesAwareInterface;
use Sylius\Component\Core\Uploader\ImageUploaderInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Webmozart\Assert\Assert;

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

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

    public function uploadImages(GenericEvent $event): void
    {

        $subject = $event->getSubject();
       
        if ($subject->getTranslations() != null) {

            foreach ($subject->getTranslations() as $translation) {
                $locale = $translation->getLocale();
                Assert::isInstanceOf($subject->getTranslation($locale), ImagesAwareInterface::class);
                $this->uploadSubjectImages($subject->getTranslation($locale));
            }

        }

    }

    private function uploadSubjectImages(ImagesAwareInterface $subject): void
    {
        $images = $subject->getImages();
        foreach ($images as $image) {
            if ($image->hasFile()) {
                $this->uploader->upload($image);
            }

            // Upload failed? Let's remove that image.
            if (null === $image->getPath()) {
                $images->removeElement($image);
            }
        }
    }
}

2. Register Custom listener as service:

app.listener.banner_translation.images_upload:
        class: App\Listener\BannerTranslationImagesUploadListener
        parent: sylius.listener.images_upload
        autowire: true
        autoconfigure: false
        public: false
        tags:
            - { name: kernel.event_listener, event: app.banner.pre_create, method: uploadImages }
            - { name: kernel.event_listener, event: app.banner.pre_update, method: uploadImages }

So, when the app.banner.pre_create event is triggered it will execute the uploadImages modified function in the new custom listener. And it will take the translation entity and upload the images.

I tested this just with one channel and one language. I think that maybe some issues will show up with more languages… but the trick it’s done.

Thank you @vvasiloi for your help.


#10

You don’t need this statement because the translations will never be null, they’re initialized as an empty collection by default.

You also don’t need those lines, just leave $this->uploadSubjectImages($translation) inside the loop.
It will work regardless the number of languages or channels.


#11

Perfect, I will give it a try.

Again, thank you.