Реалізація експорту товарів із сайту в CSV/Excel/XML/JSON

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація експорту товарів із сайту в CSV/Excel/XML/JSON
Середня
~3-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

Експорт каталогу потрібен для різних задач: завантаження на маркетплейси, відправка партнерам, бекап, інтеграція з ERP/1С, аналітика. Кожен отримувач хоче свій формат та свою підмножину полів. Завдання — побудувати гнучкий експорт, який обслуговує всі ці сценарії без дублювання коду.

Архітектура: Builder + Writer

ExportBuilder
  └─> визначає набір полів, фільтри, сортування
  └─> ітерує дані БД чанками
  └─> передає рядки в ExportWriter

ExportWriter (один з):
  ├─> CsvWriter
  ├─> ExcelWriter
  ├─> XmlWriter
  └─> JsonWriter
interface ExportWriterInterface
{
    public function open(string $filePath): void;
    public function writeHeader(array $columns): void;
    public function writeRow(array $row): void;
    public function close(): string; // повертає шлях до файлу
}

CSV-писавець

class CsvWriter implements ExportWriterInterface
{
    private $handle;

    public function open(string $filePath): void
    {
        $this->handle = fopen($filePath, 'w');
        // BOM для коректного відкриття в Excel
        fwrite($this->handle, "\xEF\xBB\xBF");
    }

    public function writeHeader(array $columns): void
    {
        fputcsv($this->handle, $columns, ';', '"');
    }

    public function writeRow(array $row): void
    {
        fputcsv($this->handle, $row, ';', '"');
    }

    public function close(): string
    {
        fclose($this->handle);
        return $this->filePath;
    }
}

Excel-писавець (потоковий, без зайвої пам'яті)

Для великих каталогів PhpSpreadsheet споживає величезну кількість пам'яті. Використовуємо openspout/openspout — працює потоково:

class ExcelWriter implements ExportWriterInterface
{
    private \OpenSpout\Writer\XLSX\Writer $writer;

    public function open(string $filePath): void
    {
        $this->writer = new \OpenSpout\Writer\XLSX\Writer();
        $this->writer->openToFile($filePath);
    }

    public function writeHeader(array $columns): void
    {
        $cells = array_map(fn($c) => \OpenSpout\Common\Entity\Cell::fromValue($c), $columns);
        $this->writer->addRow(new \OpenSpout\Common\Entity\Row($cells, null));
    }

    public function writeRow(array $row): void
    {
        $cells = array_map(fn($v) => \OpenSpout\Common\Entity\Cell::fromValue($v), $row);
        $this->writer->addRow(new \OpenSpout\Common\Entity\Row($cells, null));
    }

    public function close(): string
    {
        $this->writer->close();
        return $this->filePath;
    }
}

openspout пише рядки безпосередньо в ZIP-потік .xlsx файлу — споживання пам'яті ~10 МБ незалежно від розміру файлу.

XML-писавець

class XmlWriter implements ExportWriterInterface
{
    private \XMLWriter $xml;

    public function open(string $filePath): void
    {
        $this->xml = new \XMLWriter();
        $this->xml->openUri($filePath);
        $this->xml->startDocument('1.0', 'UTF-8');
        $this->xml->startElement('products');
    }

    public function writeHeader(array $columns): void
    {
        $this->columns = $columns; // зберігаємо для writeRow
    }

    public function writeRow(array $row): void
    {
        $this->xml->startElement('product');
        foreach (array_combine($this->columns, $row) as $key => $value) {
            $this->xml->startElement($key);
            $this->xml->text((string) $value);
            $this->xml->endElement();
        }
        $this->xml->endElement();
    }

    public function close(): string
    {
        $this->xml->endElement(); // products
        $this->xml->endDocument();
        $this->xml->flush();
        return $this->filePath;
    }
}

JSON-писавець (NDJSON для потокової обробки)

Для великих експортів замість одного великого JSON-масиву — NDJSON (один рядок = один об'єкт):

class NdjsonWriter implements ExportWriterInterface
{
    private $handle;
    private array $columns;

    public function writeRow(array $row): void
    {
        $obj = array_combine($this->columns, $row);
        fwrite($this->handle, json_encode($obj, JSON_UNESCAPED_UNICODE) . "\n");
    }
}

Стандартний JSON-масив також підтримується через JsonWriter, але для файлів >50 МБ NDJSON переважніший.

Builder: формування запиту та ітерація

class ProductExportBuilder
{
    private array $fields = ['sku', 'name', 'price', 'qty'];
    private array $filters = [];
    private int   $chunkSize = 1000;

    public function withFields(array $fields): self
    {
        $this->fields = $fields;
        return $this;
    }

    public function withFilter(string $field, mixed $value): self
    {
        $this->filters[$field] = $value;
        return $this;
    }

    public function export(ExportWriterInterface $writer, string $filePath): ExportResult
    {
        $writer->open($filePath);
        $writer->writeHeader($this->fields);

        $total = 0;
        $query = $this->buildQuery();

        $query->chunk($this->chunkSize, function ($products) use ($writer, &$total) {
            foreach ($products as $product) {
                $row = array_map(fn($f) => $this->resolveField($product, $f), $this->fields);
                $writer->writeRow($row);
                $total++;
            }
        });

        $filePath = $writer->close();
        return new ExportResult($total, $filePath);
    }

    private function buildQuery(): \Illuminate\Database\Eloquent\Builder
    {
        $query = Product::query()->orderBy('id');
        foreach ($this->filters as $field => $value) {
            $query->where($field, $value);
        }
        return $query;
    }

    private function resolveField(Product $product, string $field): mixed
    {
        return match ($field) {
            'category'    => $product->category?->name,
            'images'      => implode(',', $product->images->pluck('url')->all()),
            'attributes'  => $this->formatAttributes($product),
            default       => $product->{$field},
        };
    }
}

Асинхронний експорт великих файлів

Для каталогів >10 000 позицій експорт виконується у фоновому режимі:

class ExportProductsJob implements ShouldQueue
{
    public int $timeout = 600;

    public function handle(ProductExportBuilder $builder): void
    {
        $filePath = storage_path("exports/products_{$this->exportId}.{$this->format}");
        $writer   = ExportWriterFactory::make($this->format);

        $result = $builder
            ->withFields($this->config['fields'])
            ->withFilter('source_id', $this->config['source_id'] ?? null)
            ->export($writer, $filePath);

        ExportFile::find($this->exportId)->update([
            'status'       => 'ready',
            'file_path'    => $filePath,
            'total_rows'   => $result->total,
            'completed_at' => now(),
        ]);

        // Сповістити користувача про готовність
        $this->user->notify(new ExportReadyNotification($this->exportId));
    }
}

Конфігуровані шаблони експорту

Різні отримувачі хочуть різні набори полів. Шаблони зберігаються в БД:

{
  "name": "Для Яндекс.Маркета",
  "format": "xml",
  "fields": ["sku", "name", "price", "qty", "description", "category", "images", "brand"],
  "filters": {"in_stock": true},
  "transform": {
    "price": "round:2",
    "images": "first_only"
  }
}

Розповсюдження готового файлу

public function download(int $exportId): BinaryFileResponse
{
    $export = ExportFile::findOrFail($exportId);
    $this->authorize('download', $export);

    if ($export->status !== 'ready') {
        abort(202, 'Export is not ready yet');
    }

    return response()->download(
        storage_path($export->file_path),
        "products_{$export->created_at->format('Y-m-d_H-i')}.{$export->format}",
        ['Content-Type' => $this->mimeType($export->format)]
    );
}

Терміни реалізації

  • CSV + Excel (openspout) + Builder з чанкуванням — 2 дні
  • XML + JSON/NDJSON + асинхронний Job + сповіщення — +1 день
  • Шаблони експорту в БД, трансформації полів, завантаження — +1 день