Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
# PHP2021
# PHP2021
# Домашнее задание №13. Архитектура кода

В рамках разработки рабочего проекта есть необходимость доработки ранее написанного модуля парсера продукции из предоставляемых клиентом данных, для последующего размещения на платформе. На данный момент парсер может работать только с файлами формата XLSX. время от времени производители присылают разного рода XML файлы, которые сейчас приходится парсить в Excel, и в теории можно будет продумать подгрузку файлов других типов.

В качестве объекта анализа выступает модуль-парсер, который в данный момент состоит из запускаемого через консоль или через Битриксового агента скрипта, который последовательно экземпляр класса FeedsImporter и вызывает его метод run, внутри run создается FeedParser, который в свою очередь вызывает методы ExcelParser и только тогда заполняет временную таблицу данных из файла фида. Добавление еще одного парсера в такой системе выглядит как большое количество переписывания всего модуля.

Во-первых создадим интерфейс FileParserInterface, для того что бы можно было безболезненно добавлять парсеры других загружаемых файлов. На основе этого интерфейса создадим пока два парсера: для Excel (ExcelParser) и XML (XmlParser).

На данный момент такой задачи нет, но на всякий случай создадим интерфейс для парсера фидов (поиск файлов фида, настроек пользователя и т.д.) под потенциально разные задачи. В данный момент фиды есть только у Производителей в основном каталоге продукции, но, например, может потребоваться добавлять какие-то элементы в разрабатываемую на платформе в данный момент секцию библиотеки или в каталоги других типов пользователей. Поэтому создадим FeedParserInterface и к нему пока что одну реализацию интерфейса для загрузки данных в товарный каталог ManufacturerFeedParser.

Управлять всем будет ImportService, который будет использовать например все тем же битриксовым агентом при периодической выгрузке файлов из очереди.

Отдельно вынесем вспомогательные методы (ImportHelper.php) и запись данных во временную таблицу (FeedsImportTempTable.php)
21 changes: 21 additions & 0 deletions after/FeedParser/FeedParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Sitecore\Import\FeedParsers;

use Core\Logger\TableLogger;
use Core\Import\FileParser\FileParserInterface;

interface FeedParserInterface
{

public function __construct(TableLogger $logger);

public function parseFeedToTable(array $arFeed): void;

public function getFileParser(): FileParserInterface;

public function setFileParser(FileParserInterface $fileParser): void;

public function getErrors(): array;

}
72 changes: 72 additions & 0 deletions after/FeedParser/ManufacturerFeedParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Core\Import\FeedParser;

use Core\Import\FileParser\FileParserInterface;
use Core\Logger\TableLogger;
use Exception;


class ManufacturerFeedParser implements FeedParserInterface
{
private FileParserInterface $fileParser;

private TableLogger $logger;

private array $errors;

const IBLOCK_CODE_CATALOG_NEW = 'bitrix.catalog';

public function __construct(TableLogger $logger)
{
$this->logger = $logger;
}

public function parseFeedToTable(array $arFeed): void
{
$labelName = "import_{$arFeed['UF_USER_ID']}";
Debug::startTimeLabel($labelName);

if (empty($arFeed['file_path'])) {
$this->errors['Critical'][] = ['msg' => 'Не найдены файлы фида'];
$this->logger->addToLog('parse feed', 'error', ['msg' => 'Пустой массив путей к файлам фида', 'feed_id' => $this->feedId]);
throw new Exception('Пустой массив путей к файлам фида');
}

try {
if (isset($this->fileParser)) {
$this->fileParser->run($arFeed['UF_USER_ID'],$arFeed['ID'], $arFeed['file_path']);
} else {
throw new Exception('Не задан класс парсера файла');
}
} catch (\Exception $e) {
$this->logger->addToLog('parse feed', 'error', ['msg' => $e->getMessage(), 'feed_id' => $this->feedId]);
$this->errors['Critical'][] = ['msg' => $e->getMessage()];
}

$parseErrors = $this->fileParser->getErrors();
if (!empty($parseErrors)) {
$this->errors = array_merge($this->errors, $parseErrors);
}

Debug::endTimeLabel($labelName);
$arLabels = Debug::getTimeLabels();
$timeSpent = round($arLabels[$labelName]['time'], 2);
$this->logger->addToLog('parse feed', 'success', ['msg' => "Время обработки файла: $timeSpent сек", 'feed_id' => $this->feedId]);
}

public function getFileParser(): FileParserInterface
{
return $this->fileParser;
}

public function setFileParser(FileParserInterface $fileParser): void
{
$this->fileParser = $fileParser;
}

public function getErrors(): array
{
return $this->errors;
}
}
26 changes: 26 additions & 0 deletions after/FileParser/ExcelParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Core\Import\FileParser;

use Core\Import\Service\FeedsImportTempTable;

class ExcelParser implements FileParserInterface
{

private array $errors = [];

public function run(int $userId, int $feedId, string $filePath): void
{
/**
* Некоторая логика по обработке Excel файла фида находящегося по пути $filePath,
* в рамках которой формируем массив $arFeedData для добавления во временную таблицу фидов
* конкретному (пользователю) производителю $userId
*/
FeedsImportTempTable::addFeedData($arFeedData, $feedId);
}

public function getErrors(): array
{
return $this->errors;
}
}
14 changes: 14 additions & 0 deletions after/FileParser/FileParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Core\Import\FileParser;

use Core\Import\Service\FeedsImportTempTable;

interface FileParserInterface
{

public function run(int $userId, int $feedId, string $filePath): void;

public function getErrors(): array;

}
26 changes: 26 additions & 0 deletions after/FileParser/XmlParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Core\Import\FileParser;

use Core\Import\Service\FeedsImportTempTable;

class XmlParser implements FileParserInterface
{

private array $errors = [];

public function run(int $userId, int $feedId, string $filePath): void
{
/**
* Некоторая логика по обработке Xml файла фида находящегося по пути $filePath,
* в рамках которой формируем массив $arFeedData для добавления во временную таблицу фидов
* конкретному (пользователю) производителю $userId
*/
FeedsImportTempTable::addFeedData($arFeedData, $feedId);
}

public function getErrors(): array
{
return $this->errors;
}
}
18 changes: 18 additions & 0 deletions after/Service/FeedsImportTempTable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Core\Import\Service;

use Bitrix\Main\Entity;

class FeedsImportTempTable extends Entity\DataManager
{
public static function addFeedData(array $arFeedData, int $feedId): void
{
foreach ($arFeedData as $row) {
/**
* некоторая логика по добавлению данных фида в таблицу временных данных
*/
}
}

}
109 changes: 109 additions & 0 deletions after/Service/FeedsImporter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Core\Import\Service;

use Core\Import\Logger\TableLogger;
use Core\Import\Tools\ImportHelper;
use Core\Import\FeedParser\FeedParserInterface;
use Core\Import\FileParser\FileParserInterface;
use Exception;
use Bitrix\Catalog\Model;
use CFile;

class FeedsImporter
{
private int $feedId;

private TableLogger $logger;

private ImportHelper $importHelper;

private FeedParserInterface $feedParser;

private array $arFeedInfo;

private int $iblockIdCatalog;

public function __construct(ImportHelper $importHelper, TableLogger $logger, FeedParserInterface $feedParser)
{
$this->importHelper = $importHelper;
$this->logger = $logger;
$this->feedParser = $feedParser;
$this->obElement = $this->obElement = new \CIBlockElement();
$this->iblockIdCatalog = Core::getInstance()->getIblockId($feedParser::IBLOCK_CODE_CATALOG_NEW);
}

public function importFeed(int $feedId): void
{
$this->feedId = $feedId;
try {
$this->arFeedInfo = $this->importHelper->getFeedInfo($this->feedId);
$this->fileParser = $this->importHelper->getFileParserInterface((int)$this->arFeedInfo['UF_FILE_ID']);
$this->feedParser->setFileParser($this->fileParser);
$this->feedParser->parseFeedToTable($this->arFeedInfo);
$this->importItems();
} catch (Exception $e) {
$this->logger->addToLog('feed import', 'error', ['msg' => $e->getMessage(), 'feed_id' => $this->feedId]);
throw new Exception($e->getMessage());
}
}

/**
* Импортируем товары из временной таблицы
*/
private function importItems(): void
{
$resItems = FeedsImportTempTable::getList([
'select' => ['*'],
'filter' => ['feed_id'=> (int)$this->arFeedInfo['UF_FILE_ID']]
]);

$count = 0;

while ($row = $resItems->fetch()) {
$arElementFounded = $this->findElement($row['ekn']);
if ($row['errors'] != '') {
continue;
}

if (!empty($arElementFounded)) {
$this->updateElement($arElementFounded['ID'], $row);
} else {
$this->addElement($row);
}

$count++;
}

$this->countItems = $count;
}


private function findElement(string $ekn): array
{
/*
* Некоторая логика по поиску сущестующих элементов каталога
*/
return $arElement;
}


private function updateElement(int $id, array $row): void
{
/*
* Некоторая логика по подготовке данных для обновления существующего элемента каталога
*/
$this->obElement->SetPropertyValues($id, $this->iblockIdCatalog, $arPropertyValues);
$this->obElement->Update($arFields);
}


private function addElement(array $row): bool
{
/*
* Некоторая логика по подготовке данных для добавления нового элемента в каталог
*/
$this->obElement->Add($arFields);
}

}
35 changes: 35 additions & 0 deletions after/Tools/ImportHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

use Core\Import\FileParser\FileParserInterface;
use Core\Import\FileParser\XmlParser;
use Core\Import\FileParser\ExcelParser;

class ImportHelper
{
public static function getFileParserInterface(string $fileType): FileParserInterface
{
switch ($fileType) {
case 'application/xml':
return new XmlParser();

case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
return new ExcelParser();

default:
throw new Exception('Ошибка чтения файла: неверный тип файла');
}

}

public static function getFeedInfo(int $feedId): array
{
$filePath = $_SERVER['DOCUMENT_ROOT'].\CFile::GetPath($fileId);
$arFile = \CFile::MakeFileArray($filePath);

if (!file_exists($arFile['tmp_name'])) {
throw new Exception('Ошибка чтения файла: файл отсутствует');
}

return $arFile;
}
}
4 changes: 4 additions & 0 deletions after/after.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading