Реализация импорта товаров из файлов поставщика (CSV/Excel/XML/JSON)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация импорта товаров из файлов поставщика (CSV/Excel/XML/JSON)
Средняя
~5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация импорта товаров из файлов поставщика (CSV/Excel/XML/JSON)

Поставщики присылают прайс-листы в том формате, который удобен им: кто-то в Excel, кто-то в XML, кто-то в CSV с нестандартным разделителем. Задача — построить систему импорта, которая работает с любым форматом через единый интерфейс и не требует отдельного кода под каждого поставщика.

Унифицированный интерфейс парсера

interface FileParserInterface
{
    /** @return iterable<array<string, mixed>> */
    public function parse(string $filePath): iterable;

    public function supports(string $mimeType, string $extension): bool;
}

Фабрика выбирает нужный парсер по расширению или MIME:

class FileParserFactory
{
    private array $parsers;

    public function make(string $filePath): FileParserInterface
    {
        $ext      = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        $mime     = mime_content_type($filePath);

        foreach ($this->parsers as $parser) {
            if ($parser->supports($mime, $ext)) return $parser;
        }
        throw new \RuntimeException("No parser for: {$ext} / {$mime}");
    }
}

CSV-парсер

class CsvParser implements FileParserInterface
{
    public function __construct(
        private string $delimiter  = ',',
        private string $enclosure  = '"',
        private bool   $hasHeader  = true,
    ) {}

    public function parse(string $filePath): iterable
    {
        $handle  = fopen($filePath, 'r');
        $headers = $this->hasHeader ? fgetcsv($handle, 0, $this->delimiter, $this->enclosure) : null;

        while ($row = fgetcsv($handle, 0, $this->delimiter, $this->enclosure)) {
            if (!array_filter($row)) continue; // пустая строка

            yield $headers
                ? array_combine($headers, $row)
                : $row;
        }
        fclose($handle);
    }

    public function supports(string $mimeType, string $extension): bool
    {
        return in_array($extension, ['csv', 'txt'])
            || str_contains($mimeType, 'csv');
    }
}

Разделитель и кодировка настраиваются через конфиг источника. Для Windows-1251 — обёртка через mb_convert_encoding построчно.

Excel-парсер через PhpSpreadsheet

class ExcelParser implements FileParserInterface
{
    public function parse(string $filePath): iterable
    {
        $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($filePath);
        $sheet       = $spreadsheet->getActiveSheet();
        $rows        = $sheet->toArray(null, true, true, false);
        $headers     = array_shift($rows);

        foreach ($rows as $row) {
            if (!array_filter($row)) continue;
            yield array_combine($headers, $row);
        }
    }

    public function supports(string $mimeType, string $extension): bool
    {
        return in_array($extension, ['xls', 'xlsx', 'ods']);
    }
}

Для больших Excel-файлов (>100 МБ) использовать \PhpOffice\PhpSpreadsheet\Reader\Xlsx с setReadDataOnly(true) и setLoadSheetsOnly(['Sheet1']) — снижает потребление памяти в 3–5 раз.

XML-парсер (потоковый)

class XmlParser implements FileParserInterface
{
    public function __construct(
        private string $itemTag = 'product',
    ) {}

    public function parse(string $filePath): iterable
    {
        $reader = new \XMLReader();
        $reader->open($filePath);

        while ($reader->read()) {
            if ($reader->nodeType === \XMLReader::ELEMENT
                && $reader->name === $this->itemTag) {
                $node  = new \SimpleXMLElement($reader->readOuterXml());
                yield $this->nodeToArray($node);
            }
        }
        $reader->close();
    }

    private function nodeToArray(\SimpleXMLElement $node): array
    {
        $result = [];
        foreach ($node->children() as $child) {
            $key = $child->getName();
            $result[$key] = $child->count() > 0
                ? $this->nodeToArray($child)
                : (string) $child;
        }
        foreach ($node->attributes() as $k => $v) {
            $result['@' . $k] = (string) $v;
        }
        return $result;
    }

    public function supports(string $mimeType, string $extension): bool
    {
        return $extension === 'xml' || str_contains($mimeType, 'xml');
    }
}

XMLReader читает файл потоково — не загружает весь документ в память.

JSON-парсер

class JsonParser implements FileParserInterface
{
    public function __construct(
        private string $itemsPath = 'products', // dot-notation: "data.items"
    ) {}

    public function parse(string $filePath): iterable
    {
        $content = file_get_contents($filePath);
        $data    = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        $items = data_get($data, $this->itemsPath) ?? $data;
        foreach ($items as $item) {
            yield $item;
        }
    }

    public function supports(string $mimeType, string $extension): bool
    {
        return $extension === 'json' || str_contains($mimeType, 'json');
    }
}

Для больших JSON-файлов — использовать halaxa/json-machine, которая читает JSON потоком без загрузки всего файла:

use JsonMachine\Items;

foreach (Items::fromFile($filePath, ['pointer' => '/products']) as $product) {
    yield (array) $product;
}

Маппинг колонок источника

Каждый поставщик использует свои названия колонок. Конфигурация хранится в БД:

{
  "sku":         "Артикул",
  "name":        "Наименование",
  "price":       "Цена руб.",
  "qty":         "Кол-во",
  "description": "Описание",
  "category":    "Раздел"
}

Трансформатор применяет маппинг перед передачей в импортёр:

class ColumnMapper
{
    public function transform(array $row, array $mapping): array
    {
        $result = [];
        foreach ($mapping as $internalKey => $sourceKey) {
            $result[$internalKey] = $row[$sourceKey] ?? null;
        }
        return $result;
    }
}

Pipeline импорта

FileParserFactory::make($file)
  └─> CsvParser / ExcelParser / XmlParser / JsonParser
        └─> итерируем строки
        └─> ColumnMapper::transform($row, $config->mapping)
        └─> ProductValidator::validate($mapped)    // пропустить невалидные
        └─> ProductUpsertJob::dispatch($mapped)    // в очередь

Обработка кодировок и BOM

private function detectAndConvert(string $content): string
{
    // UTF-8 BOM
    if (str_starts_with($content, "\xEF\xBB\xBF")) {
        $content = substr($content, 3);
    }

    $encoding = mb_detect_encoding($content, ['UTF-8', 'Windows-1251', 'ISO-8859-1'], true);
    if ($encoding && $encoding !== 'UTF-8') {
        $content = mb_convert_encoding($content, 'UTF-8', $encoding);
    }
    return $content;
}

Сроки реализации

  • CSV + Excel парсеры, маппинг колонок, базовый pipeline — 2 дня
  • XML (потоковый) + JSON + автодетект формата — +1 день
  • Конфигурация маппинга в UI + обработка кодировок + обработка ошибок — +1 день