Реалізація імпорту товарів з файлів постачальника (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 МБ) використовувати 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 потоком без завантаження всього файлу.

Маппінг колонок джерела

Кожний постачальник використовує свої назви колонок. Конфігурація зберігається у БД:

{
  "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;
    }
}

Конвеєр імпорту

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 парсери, маппінг колонок, базовий конвеєр — 2 дні
  • XML (потоковий) + JSON + автодетект формату — +1 день
  • Конфігурація маппінгу в UI + обробка кодувань + обробка помилок — +1 день