Приветствую вас, жители Хабры! Набравшись немного смелости, я решил написать свою первую статью, а точнее поделиться небольшим опытом, на, как мне кажется, интересную тему, а именно как в конфиге добавить загрузчик файлов в динамический массив.
Итак, начнем.
Сначала давайте создадим модуль и его базовую структуру.
Г-н/ImageDynamicConfig/registration.php
Mr/ImageDynamicConfig/etc/module.xml<Эphp \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Mr_ImageDynamicConfig', __DIR__ );
<Э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="Mr_ImageDynamicConfig" setup_version="1.0.0"/>
</config>
Далее начнем пошагово описывать все необходимые элементы: И первым делом создадим сам конфиг: Mr/ImageDynamicConfig/etc/adminhtml/system.xml
<Эxml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd ">
<system>
<tab id="mr" translate="label" sortOrder="400">
<label>Mr</label>
</tab>
<section id="swatch" translate="label" type="text" sortOrder="300" showInDefault="1" showInWebsite="1" showInStore="1">
<class>separator-top</class>
<label>Image Array Swatch</label>
<tab>mr</tab>
<resource>Mr_ImageDynamicConfig::config</resource>
<group id="image_serializer" translate="label" type="text" sortOrder="140" showInDefault="1" showInWebsite="1" showInStore="1">
<field id="image" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Image</label>
<frontend_model>Mr\ImageDynamicConfig\Block\Adminhtml\System\Config\ImageFields</frontend_model>
<backend_model>Mr\ImageDynamicConfig\Model\Config\Backend\Serialized\ArraySerialized</backend_model>
<upload_dir>var/uploads/swatch/image_serializer</upload_dir>
</field>
</group>
</section>
</system>
</config>
Для динамического массива строка Mr\ImageDynamicConfig\Block\Adminhtml\System\Config\ImageFields вообще не нов, а класс ImageFields отображает все основные столбцы и показывает, как они должны выглядеть Mr/ImageDynamicConfig/Block/Adminhtml/System/Config/ImageFields.php
<Эphp
declare(strict_types=1);
namespace Mr\ImageDynamicConfig\Block\Adminhtml\System\Config;
use Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray;
class ImageFields extends AbstractFieldArray
{
const IMAGE_FIELD = 'image';
const NAME_FIELD = 'name';
private $imageRenderer;
protected function _prepareToRender()
{
$this->addColumn(
self::IMAGE_FIELD,
[
'label' => __('Image'),
'renderer' => $this->getImageRenderer()
]
);
$this->addColumn(
self::NAME_FIELD,
[
'label' => __('Name'),
]
);
$this->_addAfter = false;
$this->_addButtonLabel = __('Add');
}
private function getImageRenderer()
{
if (!$this->imageRenderer) {
$this->imageRenderer = $this->getLayout()->createBlock(
\Mr\ImageDynamicConfig\Block\Adminhtml\Form\Field\ImageColumn::class,
'',
['data' => ['is_render_to_js_template' => true]]
);
}
return $this->imageRenderer;
}
}
здесь в методе _prepareToRender мы объявляем столбцы, которые будут в динамическом массиве, и если столбец имеет поле, отличное от текстового ввода, мы описываем рендерер для этого поля (метод getImageRenderer).
В строке 38 мы визуализируем блок \Mr\ImageDynamicConfig\Block\Adminhtml\Form\Field\ImageColumn, который даст нам html-код вместо ввода с выбором файлов и отображением файла.
Mr/ImageDynamicConfig/Block/Adminhtml/Form/Field/ImageColumn.php <Эphp
declare(strict_types=1);
namespace Mr\ImageDynamicConfig\Block\Adminhtml\Form\Field;
use Mr\ImageDynamicConfig\Block\Adminhtml\ImageButton;
class ImageColumn extends \Magento\Framework\View\Element\AbstractBlock
{
public function setInputName(string $value)
{
return $this->setName($value);
}
public function setInputId(string $value)
{
return $this->setId($value);
}
protected function _toHtml(): string
{
$imageButton = $this->getLayout()
->createBlock(ImageButton::class)
->setData('id', $this->getId())
->setData('name', $this->getName());
return $imageButton->toHtml();
}
}
В перегруженном методе _toHtml мы рендерим блок Mr\ImageDynamicConfig\Block\Adminhtml\ImageButton, который даст нам шаблон с html-кодом Mr/ImageDynamicConfig/Block/Adminhtml//ImageButton.php <Эphp
declare(strict_types=1);
namespace Mr\ImageDynamicConfig\Block\Adminhtml;
class ImageButton extends \Magento\Backend\Block\Template
{
protected $_template = 'Mr_ImageDynamicConfig::config/array_serialize/swatch_image.phtml';
private $assetRepository;
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Framework\View\Asset\Repository $assetRepository,
array $data = []
) {
$this->assetRepository = $assetRepository;
parent::__construct($context, $data);
}
public function getAssertRepository(): \Magento\Framework\View\Asset\Repository
{
return $this->assetRepository;
}
}
Нам нужен общедоступный метод getAssertRepository для отображения полного URL-адреса CSS-файла в шаблоне.
Mr/ImageDynamicConfig/view/adminhtml/templates/config/array_serialize/swatch_image.phtml <Эphp
/*** @var \Mr\ImageDynamicConfig\Block\Adminhtml\ImageButton $block */
$css = $block->getAssertRepository()->createAsset("Mr_ImageDynamicConfig::css/image_button.css");
?>
<link rel="stylesheet" type="text/css" media="all" href="<Эphp /* @escapeNotVerified */echo $css->getUrl() ?>"/>
<div class="upload-file" data-id="<?=$block->getId()?>">
<div class="upload-file__block upload-file__block_first">
<img class="upload-file__block__img" id="swatch_image_image_<?= $block->getId() ?>" src="">
</div>
<div class="upload-file__block">
<input class="upload-file__input" hidden type="file" name="<?= $block->getName() ?>" id="swatch_image_input_<?= $block->getId() ?>" value=""/>
<label class="upload-file__label" for="swatch_image_input_<?= $block->getId() ?>">
<?= __("File") ?>
</label>
</div>
<input class="upload-file__input" type="hidden" id="<?=$block->getId()?>">
</div>
<script type="text/javascript">
require(["jquery"], function (jq) {
jq(function () {
const id = "<?=$block->getId()?>"
const imageId = "swatch_image_image_<?=$block->getId()?>"
const data = jq("#" + id).
val(); if (data) { jq("#" + imageId).
attr("src", data) jq("#" + imageId).
attr("value", data)
}
});
});
</script>
Этот шаблон отображает входные данные для загрузки и вывода загруженного изображения.
С одной стороны, очень странное решение сделать скрытый ввод: <input class="upload-file__input" type="hidden" id="<?=$block->getId()?>">
а затем вставьте значение из него в тег img: jq(function () {
const id = "<?=$block->getId()?>"
const imageId = "swatch_image_image_<?=$block->getId()?>"
const data = jq("#" + id).
val(); if (data) { jq("#" + imageId).
attr("src", data) jq("#" + imageId).
attr("value", data)
}
});
Но когда Magento отображает форму в конфигурации, чтобы вставить туда значение, он пытается найти входные данные с идентификатором и записать это значение в значение.
Вот почему я сделал скрытый ввод и передал путь к изображению в исходный img с помощью jquery. Таким образом, мы рассмотрели frontend_model и способы отображения введенного изображения в динамическом массиве.
Теперь рассмотрим этап загрузки изображений.
Для этого используется backend_model, а в обычных случаях, когда нужно просто добавить в конфиг динамический массив, то мы его кидаем в backend_model Magento\Config\Model\Config\Backend\Serialized\ArraySerialized и это решает все наши проблемы, но ArraySerialized не работает с загрузкой и сохранением изображений и на основе этого делаем свой сериализатор массива Mr/ImageDynamicConfig/Model/Config/Backend/Serialized/ArraySerialized <Эphp
declare(strict_types=1);
namespace Mr\ImageDynamicConfig\Model\Config\Backend\Serialized;
use Magento\Framework\Serialize\Serializer\Json;
use Mr\ImageDynamicConfig\Block\Adminhtml\System\Config\ImageFields;
class ArraySerialized extends \Magento\Config\Model\Config\Backend\Serialized\ArraySerialized
{
private $imageUploaderFactory;
private $imageConfig;
public function __construct(
\Magento\Framework\Model\Context $context,
\Magento\Framework\Registry $registry,
\Magento\Framework\App\Config\ScopeConfigInterface $config,
\Magento\Framework\App\Cache\TypeListInterface $cacheTypeList,
\Mr\ImageDynamicConfig\Model\Config\ImageConfig $imageConfig,
\Mr\ImageDynamicConfig\Model\ImageUploaderFactory $imageUploaderFactory,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
array $data = [],
Json $serializer = null
) {
$this->imageUploaderFactory = $imageUploaderFactory;
$this->imageConfig = $imageConfig;
parent::__construct(
$context,
$registry,
$config,
$cacheTypeList,
$resource,
$resourceCollection,
$data,
$serializer
);
}
public function beforeSave(): ArraySerialized
{
$value = $this->getValue();
$value = $this->mapRows($value);
$this->setValue($value);
return parent::beforeSave();
}
private function mapRows(array $rows): array
{
$iconUploader = $this->imageUploaderFactory->create([
'path' => $this->getPath(),
'uploadDir' => $this->getUploadDir(),
]);
$uploadedFiles = $iconUploader->upload();
$swatches = $this->imageConfig->getSwatches();
foreach ($rows as $id => $data) {
if (isset($uploadedFiles[$id])) {
$rows[$id][ImageFields::IMAGE_FIELD] = $uploadedFiles[$id];
continue;
}
if (!isset($swatches[$id])) {
unset($swatches[$id]);
} else {
$rows[$id] = $this->matchRow($data, $swatches[$id]);
}
}
return $rows;
}
private function matchRow(array $row, array $configTabIcon): array
{
foreach ($row as $fieldName => $value) {
if (is_array($value) && $fieldName == ImageFields::IMAGE_FIELD) {
$row[ImageFields::IMAGE_FIELD] = $configTabIcon[ImageFields::IMAGE_FIELD];
}
}
return $row;
}
private function getUploadDir(): string
{
$fieldConfig = $this->getFieldConfig();
if (!array_key_exists('upload_dir', $fieldConfig)) {
throw new \Magento\Framework\Exception\LocalizedException(
__('The base directory to upload file is not specified.')
);
}
if (is_array($fieldConfig['upload_dir'])) {
$uploadDir = $fieldConfig['upload_dir']['value'];
if (array_key_exists('scope_info', $fieldConfig['upload_dir'])
&& $fieldConfig['upload_dir']['scope_info']
) {
$uploadDir = $this->_appendScopeInfo($uploadDir);
}
if (array_key_exists('config', $fieldConfig['upload_dir'])) {
$uploadDir = $this->getUploadDirPath($uploadDir);
}
} else {
$uploadDir = (string)$fieldConfig['upload_dir'];
}
return $uploadDir;
}
}
Здесь мы уделим немного внимания методу mapRows, в строках 50-54 загружаем изображение, в строках 56-66 модифицируем данные из конфига, добавляем/заменяем изображение в массив конфига и добавляем/обновляем оставшиеся поля тоже Класс ImageUploader: Mr/ImageDynamicConfig/Model/ImageUploader.php <Эphp
declare(strict_types=1);
namespace Mr\ImageDynamicConfig\Model;
use Magento\MediaStorage\Model\File\Uploader;
class ImageUploader
{
private $arrayFileModifier;
private $uploaderFactory;
private $uploadDir;
private $allowExtensions;
public function __construct(
\Mr\ImageDynamicConfig\Model\ArrayFileModifier $arrayFileModifier,
\Magento\MediaStorage\Model\File\UploaderFactory $uploaderFactory,
string $uploadDir,
array $allowExtensions
) {
$this->arrayFileModifier = $arrayFileModifier;
$this->uploaderFactory = $uploaderFactory;
$this->uploadDir = $uploadDir;
$this->allowExtensions = $allowExtensions;
}
public function upload(): array
{
$result = [];
$files = $this->arrayFileModifier->modify();
if (!$files) {
return $result;
}
foreach ($files as $id => $file) {
try {
$uploader = $this->uploaderFactory->create(['fileId' => $id]);
$uploader->setAllowedExtensions($this->allowExtensions);
$uploader->setAllowRenameFiles(true);
$uploader->addValidateCallback('size', $this, 'validateMaxSize');
$newFileName = $this->getNewFileName($uploader);
$uploader->save($this->uploadDir, $newFileName);
$result[$id] = $this->getFullFilPath($newFileName);
} catch (\Exception $e) {
throw new \Magento\Framework\Exception\LocalizedException(__('%1', $e->getMessage()));
}
}
return $result;
}
private function getNewFileName(Uploader $uploader): string
{
return sprintf(
'%s.%s',
uniqid(),
$uploader->getFileExtension()
);
}
private function getFullFilPath(string $filename): string
{
return sprintf(
'/%s/%s',
$this->uploadDir,
$filename
);
}
}
В этом классе есть строка $files = $this-> arrayFileModifier-> modify(); Этот модификатор нужен нам для преобразования массива, пришедшего к нам из такого вида:
так, чтобы было понятно загрузчику:
передать идентификатор $uploader = $this-> uploaderFactory-> create(['fileId' => $id]); и загрузчик знал, с чем работать.
И последняя загадка — класс для работы с конфигом Г-н/ImageDynamicConfig/Модель/Config/ImageConfig <Эphp
declare(strict_types=1);
namespace Mr\ImageDynamicConfig\Model\Config;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Serialize\SerializerInterface;
class ImageConfig
{
const XML_PATH_IMAGE_SERIALIZER = 'swatch/image_serializer/';
private $scopeConfig;
private $serializer;
public function __construct(
SerializerInterface $serializer,
ScopeConfigInterface $scopeConfig
) {
$this->scopeConfig = $scopeConfig;
$this->serializer = $serializer;
}
public function getSwatches(): array
{
$data = $this->scopeConfig->getValue(self::XML_PATH_IMAGE_SERIALIZER .
'image');
if (!$data) {
return [];
}
return $this->serializer->unserialize($data);
}
}
И сам результат:
Эпилог Репозиторий модулей на GitHub Я надеюсь, что кто-то найдет эту статью интересной и/или полезной.
Если у вас есть замечания/предложения/вопросы, добро пожаловать в комментарии.
Спасибо за внимание.
Теги: #php #Разработка электронной коммерции #magento #magento2 #динамические строки изображений
-
Новые Настройки Imap Для Gmail
19 Oct, 24 -
Если Вы Решили Внедрить Erp-Систему
19 Oct, 24 -
«Красивые Глаза» Как Симптом Болезни
19 Oct, 24