Admin forms in Magento 2 are built with UI Components. There are different types of UI Components that allow us to create many types of fields in forms. One of them is the ImageUploader component.
In this tutorial, we'll see how to add an ImageUploader component to a form on the admin side that allows us to upload images.
Prerequisites
In this section we'll go over the basics of creating a module, adding a menu item on the admin side, and creating a grid that shows the images we uploaded before we start working on our form. If you already know how to do all of that or you just need to see how to create a form with the ImageUploader component, you can skip this section.
Create the Module
We'll quickly go over how to create a module in Magento 2. Create in app/code
the directories VENDOR/MODULE
where VENDOR
is your name or your company's name and MODULE
is the name of the module. We'll name this module ImageUploader
.
Make sure to replace all instances of VENDOR
with the vendor name you choose, as it will be in every code snippet throughout the tutorial.
Then, inside ImageUploader
create registration.php
with the following content:
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'VENDOR_ImageUploader', __DIR__);
Create a directory called etc
and inside it create module.xml
with the following content:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="VENDOR_ImageUploader" setup_version="0.0.1" />
</config>
These are the only files required to create a module. Next, we'll need to create the scripts to create a table in the database to store the path of the images we'll upload. Create a directory called Setup
inside ImageUploader
, then create the file InstallSchema.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Setup;
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\ModuleContextInterface;
class InstallSchema implements InstallSchemaInterface {
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
$imagesTableName = $setup->getTable('VENDOR_images');
if (!$setup->getConnection()->isTableExists($imagesTableName)) {
$imagesTable = $setup->getConnection()->newTable($imagesTableName)
->addColumn(
'image_id',
Table::TYPE_INTEGER,
null,
[
Table::OPTION_IDENTITY => true,
Table::OPTION_PRIMARY => true,
Table::OPTION_UNSIGNED => true,
Table::OPTION_NULLABLE => false,
],
'Image Id'
)
->addColumn(
'path',
Table::TYPE_TEXT,
255,
[
Table::OPTION_NULLABLE => false
],
'Image Path'
);
$setup->getConnection()->createTable($imagesTable);
}
$setup->endSetup();
}
}
Make sure to replace VENDOR
in the table name with the name of your vendor.
Now, we'll create the necessary models for our table. First, create the directories Api/Data
under ImageUploader
, and inside Data
create the file ImageInterface.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Api\Data;
interface ImageInterface {
const ID = 'image_id';
const PATH = 'path';
public function getPath ();
public function setPath ($value);
}
Then, create the directory Model
under ImageUploader
and inside it create Image.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Model;
use Magento\Framework\DataObject\IdentityInterface;
use Magento\Framework\Model\AbstractModel;
use VENDORE\ImageUploader\Api\Data\ImageInterface;
use VENDORE\ImageUploader\Model\ResourceModel\Image as ResourceModelImage;
class Image extends AbstractModel implements ImageInterface, IdentityInterface {
const CACHE_TAG = 'VENDOR_images';
public function getIdentities()
{
return [
self::CACHE_TAG . '_' . $this->getId(),
];
}
protected function _construct () {
$this->_init(ResourceModelImage::class);
}
public function getPath()
{
return $this->getData(self::PATH);
}
public function setPath($value)
{
return $this->setData(self::PATH, $value);
}
}
Make sure to replace all instances of VENDOR
with your vendor name.
Under the directory Model
create the directory ResourceModel
. Under ResourceModel
create the file Image.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class Image extends AbstractDb {
protected function _construct () {
return $this->_init('VENDOR_images', 'image_id');
}
}
Again, make sure to replace VENDOR
with your vendor name.
Under the ResourceModel
directory create a directory called Image
, and under Image
create Collection.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Model\ResourceModel\Image;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use VENDOR\ImageUploader\Model\Image;
use VENDOR\ImageUploader\Model\ResourceModel\Image as ResourceModelImage;
class Collection extends AbstractCollection {
protected function _construct()
{
$this->_init(Image::class, ResourceModelImage::class);
}
}
Finally, create the file di.xml
with the following content:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="VENDOR\ImageUploader\Api\Data\ImageInterface" type="VENDOR\ImageUploader\Api\Data\Image" />
</config>
Now, everything is ready for our models. To enable and compile our module, run the following commands in the root of the Magento project:
php bin/magento setup:upgrade
php bin/magento setup:di:compile
If no errors occur, our module has been created successfully.
Add the Routes
To add routes to be able to access our module on the admin side, create under etc
the directory adminhtml
, and inside adminhtml
create routes.xml
with the following content:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="admin">
<route id="imageuploader" frontName="imageuploader">
<module name="VENDOR_ImageUploader" />
</route>
</router>
</config>
Add a Menu Item on The Admin Side
In this section, we'll add a menu item to be able to access the page to upload images. First, create under etc
the file acl.xml
with the following content:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource id="VENDOR_ImageUploader::upload" title="Upload Images" translate="title" sortOrder="30" />
</resource>
</resources>
</acl>
</config>
Then, inside etc/adminhtml
create menu.xml
with the following content:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
<menu>
<add id="VENDOR_ImageUploader::images_uploader" title="Image Uploader" translate="title"
module="VENDOR_ImageUploader" sortOrder="20" resource="VENDOR_ImageUploader::upload" />
<add id="VENDOR_ImageUploader::images" title="All Images" translate="title"
module="VENDOR_ImageUploader" sortOrder="0" parent="VENDOR_ImageUploader::images_uploader"
action="imageuploader/images" resource="VENDOR_ImageUploader::upload" />
</menu>
</config>
This will create a menu item called Image Uploader and when clicking on it the submenu will include the item All Images.
Now, run the following command to compile the changes:
php bin/magento setup:di:compile
Go to the admin side now and login. You'll see in the sidebar a new menu item called Image Uploader.
Create the Image Listing Page
We'll now create the page All Images lead to, which will be a grid of the images uploaded.
Create first the directories Controller\Adminhtml\Images
under ImageUploader
. Then, create the file Index.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Controller\Adminhtml\Images;
class Index extends \Magento\Backend\App\Action {
/**
*
* @var \Magento\Framework\View\Result\PageFactory
*/
protected $resultPageFactory;
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\View\Result\PageFactory $resultPageFactory
)
{
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
}
public function execute()
{
/** @var \Magento\Backend\Model\View\Result\Page $resultPage */
$resultPage = $this->resultPageFactory->create();
$resultPage->setActiveMenu('VENDOR_ImageUploader::images_uploader');
$resultPage->getConfig()->getTitle()->prepend(__('Images'));
return $resultPage;
}
}
Then, under ImageUploader
create the directories view/adminhtml/layout
and inside layout
create imageuploader_images_index.xml
with the following content:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<uiComponent name="images_list" />
</referenceContainer>
</body>
</page>
After that, create under view/adminhtml
the directory ui_component
, and inside that directory create the file images_list.xml
with the following content:
<?xml version="1.0"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">images_list.images_list_data_source</item>
</item>
</argument>
<settings>
<buttons>
<button name="upload">
<url path="*/*/upload"/>
<class>primary</class>
<label translate="true">Upload Images</label>
</button>
</buttons>
<spinner>images_columns</spinner>
<deps>
<dep>images_list.images_list_data_source</dep>
</deps>
</settings>
<dataSource name="images_list_data_source" component="Magento_Ui/js/grid/provider">
<settings>
<storageConfig>
<param name="indexField" xsi:type="string">image_id</param>
</storageConfig>
<updateUrl path="mui/index/render"/>
</settings>
<dataProvider class="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider" name="images_list_data_source">
<settings>
<requestFieldName>id</requestFieldName>
<primaryFieldName>image_id</primaryFieldName>
</settings>
</dataProvider>
</dataSource>
<columns name="images_columns">
<column name="image_id" sortOrder="10">
<settings>
<filter>text</filter>
<dataType>text</dataType>
<label translate="true">ID</label>
</settings>
</column>
<column name="path" component="Magento_Ui/js/grid/columns/thumbnail" class="VENDOR\ImageUploader\Ui\Component\Columns\Thumbnail">
<settings>
<hasPreview>0</hasPreview>
<addField>false</addField>
<label translate="true">Thumbnail</label>
<sortable>false</sortable>
</settings>
</column>
</columns>
</listing>
This will create a grid listing that will show the uploaded images. It will show 2 columns, the ID and a thumbnail. To show the thumbnail, we need to create the class for the UI Component. Create under ImageUploader
the directories Ui\Component\Columns
, and inside Columns
create the file Thumbnail.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Ui\Component\Columns;
use Magento\Backend\Model\UrlInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Ui\Component\Listing\Columns\Column;
class Thumbnail extends Column {
/**
*
* @var StoreManagerInterface
*/
protected $storeManagerInterface;
public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
StoreManagerInterface $storeManagerInterface,
array $components = [],
array $data = []
) {
parent::__construct($context, $uiComponentFactory, $components, $data);
$this->storeManagerInterface = $storeManagerInterface;
}
public function prepareDataSource(array $dataSource)
{
foreach($dataSource["data"]["items"] as &$item) {
if (isset($item['path'])) {
$url = $this->storeManagerInterface->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) . $item['path'];
$item['path_src'] = $url;
$item['path_alt'] = $item['image_id'];
$item['path_link'] = $url;
$item['path_orig_src'] = $url;
}
}
return $dataSource;
}
}
This class will extend Magento\Ui\Component\Listing\Columns\Column
and override the function prepareDataSource
. In this function, we're making changes to the path
column to show a thumbnail. To be able to show a thumbnail using the component Magento_Ui/js/grid/columns/thumbnail
, we need to add to each row in the table the fields FIELD_src
, FIELD_alt
, FIELD_link
and FIELD_orig_src
, where FIELD
is the name of the field of the thumbnail, in this case, it's path
.
Finally, add the following to etc/di.xml
:
<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
<arguments>
<argument name="collections" xsi:type="array">
<item name="images_list_data_source" xsi:type="string">
VENDOR\ImageUploader\Model\ResourceModel\Image\Grid\Collection
</item>
</argument>
</arguments>
</type>
<virtualType name="VENDOR\ImageUploader\Model\ResourceModel\Image\Grid\Collection"
type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
<arguments>
<argument name="mainTable" xsi:type="string">VENDOR_images</argument>
<argument name="resourceModel" xsi:type="string">VENDOR\ImageUploader\Model\ResourceModel\Image
</argument>
</arguments>
</virtualType>
Our image listing page is ready. We need to compile the changes first:
php bin/magento setup:di:compile
Then, log in to the admin side again and click on ImageUploader -> All Images. A page with an empty table will show and a button to upload new images.
Now, we're ready to create the form and add the ImageUploader component to it.
Create UI Form
First, we'll need to create the controller that will handle the request. Create under ImageUploader\Controller\Adminhtml\Images
the file Upload.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Controller\Adminhtml\Images;
class Upload extends \Magento\Backend\App\Action {
/**
*
* @var \Magento\Framework\View\Result\PageFactory
*/
protected $resultPageFactory;
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\View\Result\PageFactory $resultPageFactory
)
{
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
}
public function execute()
{
/** @var \Magento\Backend\Model\View\Result\Page $resultPage */
$resultPage = $this->resultPageFactory->create();
$resultPage->setActiveMenu('VENDOR_ImageUploader::images_uploader');
$resultPage->getConfig()->getTitle()->prepend(__('Upload Image'));
return $resultPage;
}
}
Like Index.php
, this one just shows the page. We'll need to create the layout for this page. Create under view/adminhtml/layout
the file imageuploader_images_upload.xml
with the following content:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<uiComponent name="images_form" />
</referenceContainer>
</body>
</page>
This is just showing a UI Component called images_form
, which we'll create next. Create under view/adminhtml/ui_component
the file images_form.xml
with the following content:
<?xml version="1.0"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">images_form.images_form_data_source</item>
</item>
<item name="label" xsi:type="string" translate="true">Upload Images</item>
<item name="reverseMetadataMerge" xsi:type="boolean">true</item>
<item name="template" xsi:type="string">templates/form/collapsible</item>
<item name="config" xsi:type="array">
<item name="dataScope" xsi:type="string">data</item>
<item name="namespace" xsi:type="string">images_form</item>
</item>
</argument>
<settings>
<buttons>
<button name="save" class="VENDOR\ImageUploader\Block\Adminhtml\Form\UploadButton"/>
<button name="back" class="VENDOR\ImageUploader\Block\Adminhtml\Form\BackButton"/>
</buttons>
<deps>
<dep>images_form.images_form_data_source</dep>
</deps>
</settings>
<dataSource name="images_form_data_source">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="name" xsi:type="string">images_form_data_source</argument>
<argument name="class" xsi:type="string">VENDOR\ImageUploader\Ui\Component\Form\DataProvider</argument>
<argument name="primaryFieldName" xsi:type="string">image_id</argument>
<argument name="requestFieldName" xsi:type="string">id</argument>
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="submit_url" xsi:type="url" path="*/*/save"/>
</item>
</argument>
</argument>
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
</item>
</argument>
</dataSource>
<fieldset name="image">
<settings>
<label translate="true">Upload Images</label>
</settings>
<field name="image" formElement="imageUploader">
<settings>
<label translate="true">Images</label>
<componentType>imageUploader</componentType>
<validation>
<rule name="required-entry" xsi:type="boolean">true</rule>
</validation>
</settings>
<formElements>
<imageUploader>
<settings>
<allowedExtensions>jpg jpeg png</allowedExtensions>
<maxFileSize>2097152</maxFileSize>
<uploaderConfig>
<param xsi:type="string" name="url">imageuploader/images/tempUpload</param>
</uploaderConfig>
</settings>
</imageUploader>
</formElements>
</field>
</fieldset>
</form>
This just creates the UI Form component. Before we dive into the components and classes this form needs, let's look at the ImageUploader component:
<field name="image" formElement="imageUploader">
<settings>
<label translate="true">Images</label>
<componentType>imageUploader</componentType>
<validation>
<rule name="required-entry" xsi:type="boolean">true</rule>
</validation>
</settings>
<formElements>
<imageUploader>
<settings>
<allowedExtensions>jpg jpeg png</allowedExtensions>
<maxFileSize>2097152</maxFileSize>
<uploaderConfig>
<param xsi:type="string" name="url">imageuploader/images/tempUpload</param>
</uploaderConfig>
</settings>
</imageUploader>
</formElements>
</field>
Notice that we've defined this field as an ImageUploader through <componentType>imageUploader</componentType>
. We've also made this field required by adding <rule name="required-entry" xsi:type="boolean">true</rule>
to <validation>
. Then, inside <formElements>
, we're adding a couple of settings for the <imageUploader>
. allowedExtensions
allows us to specify the extensions allowed, which are in this case jpg
, jpeg
, and png
. maxFileSize
is the maximum size allowed for the file being uploaded. and finally, the param url
inside uploaderConfig
is the url
to send to the uploaded image for temporary upload. This is used to show a preview of the image being uploaded.
This form component needs a few PHP Classes to be created to be able to function correctly.
First, we've added 2 buttons, back and upload:
<button name="save" class="VENDOR\ImageUploader\Block\Adminhtml\Form\UploadButton"/>
<button name="back" class="VENDOR\ImageUploader\Block\Adminhtml\Form\BackButton"/>
So, let's create the classes for them. Create first the directories Block\Adminhtml\Form
. Then, create under Form
the file UploadButton.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Block\Adminhtml\Form;
use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
class UploadButton implements ButtonProviderInterface {
public function getButtonData()
{
return [
'label' => __('Upload'),
'class' => 'save primary',
'data_attribute' => [
'mage-init' => ['button' => ['event' => 'save']],
'form-role' => 'save',
],
'sort_order' => 90,
];
}
}
This button just takes the user to the save path. The save path is defined in images_form.xml
in this line:
<item name="submit_url" xsi:type="url" path="*/*/save"/>
Also, create the file BackButton.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Block\Adminhtml\Form;
use Magento\Backend\Model\UrlInterface;
use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
class BackButton implements ButtonProviderInterface {
/** @var UrlInterface */
protected $urlInterface;
public function __construct(
UrlInterface $urlInterface
)
{
$this->urlInterface = $urlInterface;
}
public function getButtonData()
{
return [
'label' => __('Back'),
'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()),
'class' => 'back',
'sort_order' => 10
];
}
public function getBackUrl()
{
return $this->urlInterface->getUrl('*/*/');
}
}
This button just takes the user back to the index page, which is the page that has the image listing.
Next, we need to create the following classes that are defined in images_form.xml
:
<argument name="class" xsi:type="string">VENDOR\ImageUploader\Ui\Component\Form\DataProvider</argument>
This is the data provider class. Usually, this class is very important when the form has an edit functionality. This is where the data for the record being edited is filled to show in the form fields.
Create under the directory Ui\Component\Form
the file DataProvider.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Ui\Component\Form;
use Magento\Framework\Registry;
class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider {
public function __construct(
string $name,
string $primaryFieldName,
string $requestFieldName,
Registry $registry,
\VENDOR\ImageUploader\Model\ResourceModel\Image\CollectionFactory $imageCollectionFactory,
array $meta = [],
array $data = []
)
{
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
$this->registry = $registry;
$this->collection = $imageCollectionFactory->create();
}
public function getData()
{
return [];
}
}
This just returns an empty array since we don't have the edit functionality in our form.
Finally, we'll create the controller that's supposed to handle the temporary upload of the image to show it in the preview, which is at the URL imageuploader/images/tempUpload
. So, create under Controller\Adminhtml\Images
the file TempUpload.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Controller\Adminhtml\Images;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\UrlInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\MediaStorage\Model\File\UploaderFactory;
use Magento\Store\Model\StoreManagerInterface;
class TempUpload extends \Magento\Backend\App\Action {
/**
*
* @var UploaderFactory
*/
protected $uploaderFactory;
/**
* @var Filesystem\Directory\WriteInterface
*/
protected $mediaDirectory;
/**
* @var StoreManagerInterface
*/
protected $storeManager;
public function __construct(
Context $context,
UploaderFactory $uploaderFactory,
Filesystem $filesystem,
StoreManagerInterface $storeManager
)
{
parent::__construct($context);
$this->uploaderFactory = $uploaderFactory;
$this->mediaDirectory = $filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
$this->storeManager = $storeManager;
}
public function execute()
{
$jsonResult = $this->resultFactory->create(ResultFactory::TYPE_JSON);
try {
$fileUploader = $this->uploaderFactory->create(['fileId' => 'image']);
$fileUploader->setAllowedExtensions(['jpg', 'jpeg', 'png']);
$fileUploader->setAllowRenameFiles(true);
$fileUploader->setAllowCreateFolders(true);
$fileUploader->setFilesDispersion(false);
$fileUploader->validate();
$result = $fileUploader->save($this->mediaDirectory->getAbsolutePath('tmp/imageUploader/images'));
$result['url'] = $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA)
. 'tmp/imageUploader/images/' . ltrim(str_replace('\\', '/', $result['file']), '/');
return $jsonResult->setData($result);
} catch (LocalizedException $e) {
return $jsonResult->setData(['errorcode' => 0, 'error' => $e->getMessage()]);
} catch (\Exception $e) {
error_log($e->getMessage());
error_log($e->getTraceAsString());
return $jsonResult->setData(['errorcode' => 0, 'error' => __('An error occurred, please try again later.')]);
}
}
}
Let's dissect this to understand it better:
- We're using
uploaderFactory
of typeMagento\MediaStorage\Model\File\UploaderFactory
to first get the uploaded image from the request. We pass itfileId
with the valueimage
, which is the name of the field. If it was named something else like "file" thenfileId
will befile
. - We're specifying some rules for validation next. We're setting the allowed extensions with
setAllowedExtensions
. We're also usingsetAllowRenameFiles
to allow renaming the file uploaded on upload,setAllowCreateFolders
to allow creating a folder if it doesn't exist, andsetFilesDispersion
to disable files dispersion. - Then, we're saving the temporary file to
tmp/imageUploader/images
under themedia
directory inpub
. - Finally, we're sending back the URL for the uploaded file.
Upload and Save Image
The last thing we need to implement is the Save controller. This controller will handle the submission of the image to finally upload it and save it in our database. To do that, create under Controller\Adminhtml\Images
the file Save.php
with the following content:
<?php
namespace VENDOR\ImageUploader\Controller\Adminhtml\Images;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\Framework\Validation\ValidationException;
use Magento\MediaStorage\Model\File\UploaderFactory;
class Save extends \Magento\Backend\App\Action {
/**
*
* @var UploaderFactory
*/
protected $uploaderFactory;
/**
* @var \VENDOR\ImageUploader\Model\ImageFactory
*/
protected $imageFactory;
/**
* @var Filesystem\Directory\WriteInterface
*/
protected $mediaDirectory;
public function __construct(
Context $context,
UploaderFactory $uploaderFactory,
Filesystem $filesystem,
\VENDOR\ImageUploader\Model\ImageFactory $imageFactory
)
{
parent::__construct($context);
$this->uploaderFactory = $uploaderFactory;
$this->imageFactory = $imageFactory;
$this->mediaDirectory = $filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
}
public function execute()
{
try {
if ($this->getRequest()->getMethod() !== 'POST' || !$this->_formKeyValidator->validate($this->getRequest())) {
throw new LocalizedException(__('Invalid Request'));
}
//validate image
$fileUploader = null;
$params = $this->getRequest()->getParams();
try {
$imageId = 'image';
if (isset($params['image']) && count($params['image'])) {
$imageId = $params['image'][0];
if (!file_exists($imageId['tmp_name'])) {
$imageId['tmp_name'] = $imageId['path'] . '/' . $imageId['file'];
}
}
$fileUploader = $this->uploaderFactory->create(['fileId' => $imageId]);
$fileUploader->setAllowedExtensions(['jpg', 'jpeg', 'png']);
$fileUploader->setAllowRenameFiles(true);
$fileUploader->setAllowCreateFolders(true);
$fileUploader->validateFile();
//upload image
$info = $fileUploader->save($this->mediaDirectory->getAbsolutePath('imageUploader/images'));
/** @var \VENDOR\ImageUploader\Model\Image */
$image = $this->imageFactory->create();
$image->setPath($this->mediaDirectory->getRelativePath('imageUploader/images') . '/' . $info['file']);
$image->save();
} catch (ValidationException $e) {
throw new LocalizedException(__('Image extension is not supported. Only extensions allowed are jpg, jpeg and png'));
} catch (\Exception $e) {
//if an except is thrown, no image has been uploaded
throw new LocalizedException(__('Image is required'));
}
$this->messageManager->addSuccessMessage(__('Image uploaded successfully'));
return $this->_redirect('*/*/index');
} catch (LocalizedException $e) {
$this->messageManager->addErrorMessage($e->getMessage());
return $this->_redirect('*/*/upload');
} catch (\Exception $e) {
error_log($e->getMessage());
error_log($e->getTraceAsString());
$this->messageManager->addErrorMessage(__('An error occurred, please try again later.'));
return $this->_redirect('*/*/upload');
}
}
}
This one is pretty similar to TempUpload
. Again, let's dissect it:
- We're validating the request by checking that the method is
POST
and the form key is valid. - We're getting the params that are passed to the request.
- We're checking if
$params['image']
is an image, then we're setting theimageId
to the first image. This is because theImageUploader
can accept multiple images. - After that, we're doing the same steps that we did in
TempUpload
. We're setting some options like the allowed extensions. - We're saving the file in
imageUploader/images
under themedia
directory inpub
. - We're creating a new instance of
Image
, which is the model for the table that we created in this module, and we're setting the path to the path of the image uploaded. After that, we're saving the model. - If no errors occur and everything works correctly, we're sending a success message and redirecting back to the index page that shows the image listing.
Our form, all its components, and controllers related to it are ready. We just need to compile our code:
php bin/magento setup:di:compile
After that is done, again, log in to the admin side of your store. Click on ImageUploader -> All Images -> Upload Images. You'll see a form with an image uploader. Try uploading an image by clicking on Upload next to the Images field. The image uploader field will upload the image using the route imageuploader/images/tempupload
which will upload the image to pub/media/tmp/imageuploader/images
, then returns the URL. Then, the ImageUploader field will show a preview of the file.
Click on the orange button Upload at the top right corner. This will send the form data to the endpoint imageuploader/images/save
. This one will save the image under pub/media/imageuploader/images
, then save the image with the path in the table we created for the module. If everything is correct, you'll be redirected to the listing page and you'll see the image you just uploaded.
Conclusion
This is how an ImageUploader component works! Next, you can try adding delete functionalities. You can also try adding other fields to the form, if necessary. It will work perfectly well.