RSS Мои друзья Контакты

Magento - импорт/экспорт cli-terminal версия

Думаю многие сталкивались с импортом/экспортом данных и думаю многие знают, что в Magento - это одно из самых узких мест. Почему? Потому что структура базы данных достаточно сложная для того, чтобы вставлять данные через обычные SQL запросы, потому что стандартный импорт/экспорт с использованием профайлов работает медленно. Но так было до версии 1.5. Начиная с этой версии появился новый модуль для импорта и экспорта продуктов и пользователей. Через веб интерфейс все отлично работает, но что если нужно импортировать/экспортировать товары по крону? Для этого нужно написать cli версию.

Анализируем

Зайдем на страницу импорта (System -> Import/Export -> Import) и будем разбираться как все устроено.

Import/Export

Видим, что сначала нужно выбрать тип сущности, которую нужно импортировать, поведение импорта и файл. Существует 3 поведения импорта, в основном оно отвечает за то, как будут обработаны Custom Options для продукта:

  • append - всегда добавляет Custom Options в продукт, даже если такие уже есть
  • replace - удаляет старые и вставляет те, которые находятся в файле
  • delete - удаляет все продукты, которые находятся в импортируемом файле

При нажатии на кнопку "Check Data" - программа проверит файл на наличие ошибок. Если файл полностью корректный или же имеет допустимое количество ошибок, то появится кнопка Import, нажав на которую будет запущен непосредственно процесс импорта.

Для того, чтобы написать cli вариант нужно по сути просто скопировать функционал контроллера импорта. Давайте откроем файл app/code/core/Mage/ImportExport/controllers/Adminhtml/ImportController.php

class Mage_ImportExport_Adminhtml_ImportController extends Mage_Adminhtml_Controller_Action
{
    /**
     * Start import process action.
     *
     * @return void
     */
    public function startAction()
    {
        $data = $this->getRequest()->getPost();
        if ($data) {
            //prepare layout

            $importModel = Mage::getModel('importexport/import');

            try {
                $importModel->importSource();
                $importModel->invalidateIndex();
                $resultBlock->addAction('show', 'import_validation_container')
                    ->addAction('innerHTML', 'import_validation_container_header', $this->__('Status'));
            } catch (Exception $e) {
                $resultBlock->addError($e->getMessage());
                $this->renderLayout();
                return;
            }
            $resultBlock->addAction('hide', array('edit_form', 'upload_button', 'messages'))
                ->addSuccess($this->__('Import successfully done.'));
            $this->renderLayout();
        } else {
            $this->_redirect('*/*/index');
        }
    }

    /**
     * Validate uploaded files action.
     *
     * @return void
     */
    public function validateAction()
    {
        $data = $this->getRequest()->getPost();
        if ($data) {
            // prepare layout

            try {
                /** @var $import Mage_ImportExport_Model_Import */
                $import = Mage::getModel('importexport/import');
                $validationResult = $import->validateSource($import->setData($data)->uploadSource());

                // process validation result
            } catch (Exception $e) {
                $resultBlock->addNotice($this->__('Please fix errors and re-upload file'))
                    ->addError($e->getMessage());
            }
            $this->renderLayout();
        } else {
            $this->_getSession()->addError($this->__('Data is invalid or file is not uploaded'));
            $this->_redirect('*/*/index');
        }
    }
}

Я немного укоротил его. По сути нужно использовать код находящийся в конструкции try... catch и вместо того, чтобы устанавливать ошибки в сесию или блок просто выведем их на экран.

Приступаем к работе

Прежде чем начать, нужно обсудить интерфейс скрипта. Нужно, чтобы ему можно было передавать опции: какую сущность импортировать (продукт, пользователь), какое поведение использовать, путь к импортируемому файлу, ну и конечно же опцию verbose, с помощью которой можно следить за работой импорта. Дадим опциям отвечающим за поведение и путь к файлу значения по умолчанию replace и var/import/%entity_type%.csv соответственно.

Давайте напишем функцию, которая будет парсить опции из массива аргументов (работает только с длинными имена опций).

function parseArgs(array $data) {
    $args    = array();
    $current = null;
    foreach ($data as $arg) {
        $match = array();
        if (preg_match('#^--([\w\d_-]{1,})$#', $arg, $match) || preg_match('#^-([\w\d_]{1,})$#', $arg, $match)) {
            $current = $match[1];
            $args[$current] = true;
        } else {
            if ($current) {
                $args[$current] = $arg;
            } else if (preg_match('#^([\w\d_]{1,})$#', $arg, $match)) {
                $args[$match[1]] = true;
            }
        }
    }

    return $args;
}

Чтобы работать с моделями Magento в отдельном скрипте, достаточно подключить файл app/Mage.php а также инициализировать объект приложения

require 'app/Mage.php';

Mage::app('admin');

Также оставим возможность запускать скрипт через http, чтобы можно было проверить побыстрее и по-проще. Для этого установим такую проверку

if (!empty($_SERVER['REQUEST_METHOD'])) {
    $args = $_GET;
} else {
    $args = parseArgs($_SERVER['argv']);
}

Посмотрим теперь на весь код скрипта

<?php
require 'app/Mage.php';

// implementation 'parseArgs' function
// implementation 'reindexAll' function
// implementation 'printLog' function

// check is a web request
if (!empty($_SERVER['REQUEST_METHOD'])) {
    $args = $_GET;
} else {
    $args = parseArgs($_SERVER['argv']);
}

try {
    // initialize mage app
    Mage::app('admin');

    // add default options
    $args = $args + array(
        'behavior' => 'replace',
        'source'   => Mage::getBaseDir('var') . DS . 'import' . DS . $args['entity'] . '.csv'
    );

    if (empty($args['entity'])) {
        throw new Exception('Entity type is missed');
    }

    if (empty($args['source']) || !is_readable($args['source'])) {
        throw new Exception(sprintf('Invalid source file "%s"', $args['source']));
    }

    $import = Mage::getModel('importexport/import')->setData(array(
        'entity'   => $args['entity'],
        'behavior' => $args['behavior']
    ));

    $result   = $import->validateSource($args['source']);
    $import->setValidationResult($result);

    $canForce = $import->getProcessedRowsCount() != $import->getInvalidRowsCount();
    $canForce = $canForce && $import->getErrorsLimit() > $import->getErrorsCount();
    if ($canForce || $result) {
        $result = $import->importSource();
        reindexAll();
    }

    if (isset($args['verbose'])) {
        printLog($import);
    }
} catch (Exception $e) {
    echo 'Script has thrown an exception: ', $e->getMessage(), "\n";
}

Проанализируем работу. Сначала подключаем сердце скрипта - Mage.php, создаем 3 вспомогательные функции, дальше получаем аргументы из командной строки или через http запрос, добавляем опции по умолчанию и проверяем их на корректность. Дальше создаем модель импорта с определенными параметрами и проверяем импортируемый файл на корректность. Потом проверяем можно ли сделать импорт, даже если есть ошибки в файле. Если файл успешно прошел проверку или можно импортировать с ошибками, то делаем это и реиндексируем данные в Magento. Если передана опция verbose, то выводим log-trace на экран.

Пример использования в командной строке

enej@linux:/home/pub/www/vv$ php -f import.php -- --entity catalog_product --source ~/Downloads/catalog_product_20111023_163631.csv --verbose
1: Begin data validation
2: Validation finished successfully
3: Checked rows: 60, checked entities: 49, invalid rows: 0, total errors: 0
4: Done import data validation
5: Begin import of "catalog_product" with "replace" behavior
6: Checked rows: 60, checked entities: 49, invalid rows: 0, total errors: 0
7: Import has been done successfuly.

Исходник можно скачать здесь. Реализация cli версии экспорта полностью аналогична импорту.

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

Комментариев: 2

  • Аноним
    Ответить 13 марта 2012 г., 20:42
    Подскажите а как сделать экспорт товаров, чтобы потом сделать импорт товаров?
    То есть выбрать нужные атрибуты для определенной категории чтобы получить файл в котором заполнить все данные? И сделать импорт
    • Сергей (Администратор)
      Ответить 14 марта 2012 г., 10:21
      Используйте стандартный импорт/экспорт и все