Drupal → Пример модуля для импорта товаров в Drupal Commerce 2

29.10.2021

Пример простейшего модуля для импорта товаров в Drupal Commerce 2 из csv файла. Способ подходит для импорта нескольких тысяч товаров, если их больше, то надо оптимизировать процесс создания очереди.

Содержимое основного файла с формой:

<?php

// src/Form/ProductsImportForm.php

namespace Drupal\products_import\Form;

use Drupal\commerce_price\Price;
use Drupal\commerce_product\Entity\Product;
use Drupal\commerce_product\Entity\ProductVariation;
use Drupal\commerce_product\ProductVariationStorageInterface;
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class ProductsImportForm extends FormBase {

  /**
   * {@inheritDoc}
   */
  public function getFormId(): string {
    return 'products_import_form';
  }

  /**
   * {@inheritDoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $form['file'] = [
      '#type' => 'file',
      '#title' => $this->t('File'),
      '#description' => $this->t('Select CSV file.'),
    ];

    $form['actions'] = [
      '#type' => 'actions',
    ];

    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Start import'),
      '#button_type' => 'primary',
    ];

    return $form;
  }

  /**
   * {@inheritDoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $file_destination = 'private://products.csv';
  
    // Upload file to filesystem without create file entity
    $files = \Drupal::request()->files->get('files', []); /** @var UploadedFile[] $files */
    if ($files['file']) {
      $file_system = \Drupal::service('file_system'); /** @var FileSystemInterface $file_system */
      $file_system->move($files['file']->getRealPath(), $file_destination, FileExists::Replace);
    }

    if (file_exists($file_destination) && is_readable($file_destination)) {
      // Create batch operations
      $operations = [];
      foreach ($this->csvToArray($file_destination) as $row) {
        $operations[] = [[static::class, 'importProduct'], [$row]];
      }

      // Run batch
      batch_set([
        'title' => $this->t('Products import'),
        'operations' => $operations,
      ]);
    }
  }

  /**
   * Convert csv file to array.
   */
  public function csvToArray(string $uri): array {
    $file_handle = fopen($uri, 'r');
    $header_row = fgetcsv($file_handle, 0, ';');
    $array = [];

    while (($row = fgetcsv($file_handle, 0, ';')) !== FALSE) {
      $array[] = array_combine($header_row, $row);
    }

    fclose($file_handle);
    return $array;
  }

  /**
   * Batch operation.
   */
  public static function importProduct(array $data, &$context): void {
    /** @var ProductVariationStorageInterface $variation_storage */
    $variation_storage = \Drupal::entityTypeManager()->getStorage('commerce_product_variation');

    // Try load exists variation
    if ($variation = $variation_storage->loadBySku($data['sku'])) {
      $product = $variation->getProduct();
    }

    // Create variation if not exists
    if (!$variation || !isset($product))  {
      if (!$variation) {
        $variation = ProductVariation::create([
          'type' => 'default',
          'sku' => $data['sku'],
        ]);
      }

      $product = Product::create([
        'type' => 'default',
        'stores' => 1,
        'variations' => $variation,
      ]);
    }

    // Set fields
    $variation->setTitle($data['title']);
    [$price_number, $price_currency] = explode(' ', $data['price']);
    $variation->setPrice(new Price($price_number, $price_currency));
    $product->setTitle($data['title']);
    $product->set('body', [
      'value' => $data['body'],
      'format' => 'full_html',
    ]);

    // Save product and variation
    $variation->save();
    $product->save();
  }

}

Из того, что можно доработать:

1. Сохранять только те товары и вариации, чьи данные изменились.
2. Добавить обработку ошибок и валидацию сущностей перед сохранением.
3. Сбор и вывод статистики (сколько создано, сколько обновлено, сколько ошибок).
4. Переписать на Queue API.
5. Вынести процесс импорта в сервис, чтобы была возможность запустить его из drush команды.
6. Написать drush команду и сделать автоматизацию импорта.

Готовый модуль на github. Возможно позже будет продвинутая версия со всеми доработками.

Почему не воспользоваться Feeds или Migrate API? Как правило импорт это сложный процесс включающий обработку входных данных, работу с зависимостями (термины, файлы и т.д.), различные опции и условия, отладка, масштабирование, поэтому удобнее держать всё в одном месте и контролировать весь процесс, чем мучаться с хуками Feeds или бесконечно импортировать конфиги миграций.

Написанное актуально для
Drupal Commerce 2
Похожие записи

Добавить комментарий