How to add images to products programmatically in Shopware 6
In this article, we will take a look at importing the images to products. In Shopware 6, the images are stored in a library, that is called simply “Media” and then assigned to the individual products.
Adding the product images manually
When we want to add some images to a product in s Shopware 6 store, we can of course do it manually via the backend. I will describe the process here briefly, mainly to point out, where you can check your images, when you add them programmatically later on.
Log in to the Administration and click on Catalogues and then Products:
In the list of products, select one product and in the menu, click on Edit:
In the product edit page, find the section, named “Media”. There, you can either upload files or open media and assign them to the product. In any case, you can also see any images, that were already assigned to the product, right here:
This approach is suitable for low volumes of images and products and for smaller stores. But quite often you need to import the product images from an external source, like an ERP system or some other e-commerce system, when you migrate it to Shopware 6. For those cases, especially if they repeat periodically, a programmatic approach is better.
Adding the product images programmatically
The images, that are to be assigned to products, can come from various sources. In this tutorial, I will cover the two of them, that I consider to be most common: importing images from an URL and from local files (that were for example uploaded earlier). In order to add the images to the products, two steps are necessary. First, put the images to the Media library. And second, assign them to the products.
Adding the images to the media library
As I have mentioned earlier, product images can come from various sources. So let’s create a class, that will handle all the cases an re-use the code within the class as much as possible. The code of the class is mostly explained by the inline comments, but after the sample, additional description follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
<?php declare(strict_types=1); namespace TestPlugin\Components; use Shopware\Core\Content\Media\File\FileSaver; use Shopware\Core\Content\Media\File\MediaFile; use Shopware\Core\Content\Media\MediaService; use Shopware\Core\Framework\Context; use Shopware\Core\Content\Media\Exception\DuplicatedMediaFileNameException; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; class ImageImport { const TEMP_NAME = 'image-import-from-url'; //prefix for temporary files, downloaded from URL const MEDIA_DIR = '/public/media/'; //relative path to Shopware's media directory const MEDIA_FOLDER = 'product'; //name of the folder in Shopware's media data structure private $mediaRepository; private $mediaService; private $fileSaver; /** * ImageImport constructor. * * @param EntityRepositoryInterface $mediaRepository * @param MediaService $mediaService * @param FileSaver $fileSaver */ public function __construct ( EntityRepositoryInterface $mediaRepository, MediaService $mediaService, FileSaver $fileSaver ) { $this->mediaRepository = $mediaRepository; $this->mediaService = $mediaService; $this->fileSaver = $fileSaver; } /** * Method, that downloads a file from a URL and returns an ID of a newly created media, based on it * * @param string $imageUrl * @param Context $context * @return string|null */ public function addImageToMediaFromURL (string $imageUrl, Context $context) { $mediaId = null; //process with the cache disabled $context->disableCache(function (Context $context) use ($imageUrl, &$mediaId): void { //parse the URL $filePathParts = explode('/', $imageUrl); $fileNameParts = explode('.', array_pop($filePathParts)); //get the file name and extension $fileName = $fileNameParts[0]; $fileExtension = $fileNameParts[1]; if ($fileName && $fileExtension) { //copy the file from the URL to the newly created local temporary file $filePath = tempnam(sys_get_temp_dir(), self::TEMP_NAME); file_put_contents($filePath, file_get_contents($imageUrl)); //create media record from the image $mediaId = $this->createMediaFromFile($filePath, $fileName, $fileExtension, $context); } }); return $mediaId; } /** * Method, that returns an ID of a newly created media, based on a local file from the Shopware's media directory * * @param string $fileName * @param string $directoryName * @param Context $context * @return string|null */ public function addImageToMediaFromFile (string $fileName, string $directoryName, Context $context) { //compose the path to file $filePath = dirname(__DIR__, 5) . self::MEDIA_DIR . $directoryName . '/' . $fileName; //get the file extension $fileNameParts = explode('.', $fileName); $fileExtension = $fileNameParts[1]; //create media record from the image and return its ID return $this->createMediaFromFile($filePath, $fileName, $fileExtension, $context); } /** * Method, that creates a new media record from a local file and returns its ID * * @param string $filePath * @param string $fileName * @param string $fileExtension * @param Context $context * @return string|null */ private function createMediaFromFile (string $filePath, string $fileName,string $fileExtension, Context $context) { $mediaId = null; //get additional info on the file $fileSize = filesize($filePath); $mimeType = mime_content_type($filePath); //create and save new media file to the Shopware's media library try { $mediaFile = new MediaFile($filePath, $mimeType, $fileExtension, $fileSize); $mediaId = $this->mediaService->createMediaInFolder(self::MEDIA_FOLDER, $context, false); $this->fileSaver->persistFileToMedia( $mediaFile, $fileName, $mediaId, $context ); } catch (DuplicatedMediaFileNameException $e) { echo($e->getMessage()); $mediaId = $this->mediaCleanup($mediaId, $context); } catch (\Exception $e) { echo($e->getMessage()); $mediaId = $this->mediaCleanup($mediaId, $context); } return $mediaId; } /** * Method, that takes care of deleting the newly created media record, if something goes wrong with saving data to it * * @param string $mediaId * @param Context $context * @return null */ private function mediaCleanup (string $mediaId, Context $context) { $this->mediaRepository->delete([['id' => $mediaId]], $context); return null; } } |
The class consists of multiple methods. The public methods addImageToMediaFromURL and addImageMediaFromFile are the entry points, that are intended to be called from outside the class. They both call the method createMediaFromFile, which is the one, that is actually handling the saving of the image to the Shopware media library. They are both pretty straightforward in what they do, so let’s just sum it up here: get the file, get the file name, get the file extension, call the createMediaFromFile method, get the new media ID and return it. No big surprises here, just note, that addImageToMediaFromURL runs with the cache disabled.
The purpose of the method createMediaFromFile is to create a new item in the media repository and return its ID. And it does just that. First, it gets file size and content type of the file, then it creates a new record in the media repository and then it saves the file and all the necessary information, using Shopware’s standard persistFileToMedia method from the FileSaver class.
There is also a method named mediaCleanup. It is not just a good example for a situation, when you need to delete something from the media library, but is also quite useful, when you are adding the images. The important thing to note here is, that the new MediaFile is created empty and would stay like that in the library, if the next step in the code, the persistFileToMedia, would fail for some reason, including the duplicate file name. That is why mediaCleanup method is called, when that happens and gets us rid of the empty record.
Also, if you look to createMediaFromFile, the method mediaCleanup gets called twice from there. Firstly, when the DuplicatedMediaFileNameException occurs and secondly, when any other exception occurs. It is just an example to mark the spot, where you might want to handle the situation, when the file with a specific name already exists, differently from handling other exceptions.
Assigning the images to the products
Now, that we have the mechanism for adding the images to the Shopware media library in place, we can create a method, that assigns them to our selected product. This part of the process is actually quite simple. It consists just from getting the image media IDs and saving them along with the product ID. The product – media relationship is stored in product_media table. We will write to it using the standard repository methods – in our example, using the create method.
In our example class, there is a method named testImageToProduct, that we will use to demonstrate, what we need. It will call both the methods addImageToMediaFromURL and addImageMediaFromFile of our ImportImage class to get the media IDs of images, that come from different sources and then assign them both to a product. In a real life scenario, you would probably just use one of them repeatedly to add multiple images from the same source.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<?php namespace TestPlugin; use Shopware\Core\Framework\Uuid\Uuid; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; use TestPlugin\Components\ImageImport; class ExampleClass { private $productMediaRepository; private $imageImport; public function __construct( EntityRepositoryInterface $productMediaRepository, ImageImport $imageImport ) { $this->productMediaRepository = $productMediaRepository; $this->imageImport = $imageImport; } public function testImageToProduct ($productId, $context) { $mediaIds = []; //import from URL to the media repository $mediaId = $this->imageImport->addImageToMediaFromURL('https://www.someurl.xyz/example_image.jpg', $context); if ($mediaId) { $mediaIds[] = $mediaId; } //import from a local file to the media repository $mediaId = $this->imageImport->addImageToMediaFromFile('some_local_image.png', 'test-uploads', $context); if ($mediaId) { $mediaIds[] = $mediaId; } foreach ($mediaIds as $mediaId) { //generate unique ID for the product-media row $productMediaId = Uuid::randomHex(); //add to the product_media repository $data = ['id' => $productMediaId, 'productId' => $productId, 'mediaId' => $mediaId]; $this->productMediaRepository->create([$data], $context); } } } |
Now both of the files, one from the URL and one from local disk, are stored in the media library and assigned to the product. For the sake of completeness, here is how services.xml (located in TestPlugin/src/Resources/config directory) would look like in our case:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="TestPlugin\Components\ImageImport"> <argument type="service" id="media.repository"/> <argument type="service" id="Shopware\Core\Content\Media\MediaService" /> <argument type="service" id="Shopware\Core\Content\Media\File\FileSaver" /> </service> <service id="TestPlugin\ExampleClass"> <argument type="service" id="product_media.repository"/> <argument type="service" id="TestPlugin\Components\ImageImport" /> </service> </services> </container> |
I might have missed it, but could you explain how to assign covers and generate thumbnails? The imported pictures show up on the product details page, but not in the category listing.
Thanks & Cheers!
HI, thanks for your question. That is a topic for an additional article, I guess. I am quite busy right now, but I will re-visit this topic in the future, that’s for sure.
Regarding the cover, I have tried just this so far:
$product->setCoverId($productMediaId);
Sounded good, but it has not worked for me, so more research will be needed here. If anyone has more info, feel free to put it into a comment.
You can use the ProductRepositroy for that:
“cover” =>[“mediaId” => $mediaId]
Hi,
After trying a bit, I found out that you can set the cover on the product directly like this:
$mediaId = $this->productImportMediaBuilder->build($url);
$product[‘media’][] = [
‘mediaId’ => $mediaId
];
$product[‘cover’] = [‘mediaId’ => $mediaId];
And create the product via the productRepository:
$this->productRepository->create([$product], $context);
Cheers
Thanks for the post, it saved my day. You may want to update how to set the cover image, this worked for me:
$this->productRepository->update([
[
‘id’ => $productId,
‘cover’ => [‘mediaId’ => $mediaId],
]
]);
great code snipped but one point you need to clear the tmp folder after the media file create otherwise the server is running out of space