Install and configure Akeneo Connector (Eco module)

Edit on GitHub

Installation

To install AkeneoPim, add AkeneoPimMiddlewareConnector by running the console command. Set akeneo/api-php-client version that you need:

composer require akeneo/api-php-client:^4.0.0 spryker-eco/akeneo-pim:^1.0.0 spryker-eco/akeneo-pim-middleware-connector:^1.0.0

Global Configuration

Add SprykerMiddleware to your project’s core namespaces:

$config[KernelConstants::CORE_NAMESPACES] = [
    'SprykerShop',
    'SprykerMiddleware',
    'SprykerEco',
    'Spryker',
];

To set up the Akeneo Connector eco module initial configuration, use the credentials you received from your PIM:

$config[AkeneoPimConstants::HOST] = '';
$config[AkeneoPimConstants::USERNAME] = '';
$config[AkeneoPimConstants::PASSWORD] = '';
$config[AkeneoPimConstants::CLIENT_ID] = '';
$config[AkeneoPimConstants::CLIENT_SECRET] = '';

Next, specify your paths to the additional map files:

$config[AkeneoPimMiddlewareConnectorConstants::LOCALE_MAP_FILE_PATH] = APPLICATION_ROOT_DIR . '/data/import/maps/locale_map.json';
$config[AkeneoPimMiddlewareConnectorConstants::ATTRIBUTE_MAP_FILE_PATH] = APPLICATION_ROOT_DIR . '/data/import/maps/attribute_map.json';
$config[AkeneoPimMiddlewareConnectorConstants::SUPER_ATTRIBUTE_MAP_FILE_PATH] = APPLICATION_ROOT_DIR . '/data/import/maps/super_attribute_map.json';

This being done, specify the ID of the category template that should be assigned to the imported categories:

$config[AkeneoPimMiddlewareConnectorConstants::FK_CATEGORY_TEMPLATE] = 1;

Next, specify the name of a tax set for the imported products:

$config[AkeneoPimMiddlewareConnectorConstants::TAX_SET] = 1;

Finally, specify the locales that should be imported to shops and stores in which imported products are to be available, and specify how prices should be mapped according to locales:

$config[AkeneoPimMiddlewareConnectorConstants::LOCALES_FOR_IMPORT] = [
    'de_DE',
    'de_AT',
];
$config[AkeneoPimMiddlewareConnectorConstants::ACTIVE_STORES_FOR_PRODUCTS] = [
    'DE',
    'AT'
];
$config[AkeneoPimMiddlewareConnectorConstants::LOCALES_TO_PRICE_MAP] = [
    'de_DE' => [
        'currency' => 'EUR',
        'type' => 'DEFAULT',
        'store' => 'DE',
    ],
    'en_US' => [
        'currency' => 'USD',
        'type' => 'DEFAULT',
        'store' => 'US',
    ],
];

Dependency Configuration

Add Middleware Process console command to src/Pyz/Zed/Console/ConsoleDependencyProvider.php in your project:

...
use SprykerMiddleware\Zed\Process\Communication\Console\ProcessConsole;
...

protected function getConsoleCommands(Container $container)
{
    $commands = [
        ...
        new ProcessConsole(),
    ];
    ...

    return $commands;
}

Create ProcessDependencyProvider on a project level for specifying ConfigurationPlugins. Add src/Pyz/Zed/Process/ProcessDependencyProvider.php file:

<?php

namespace Pyz\Zed\Process;

use SprykerEco\Zed\AkeneoPimMiddlewareConnector\Communication\Plugin\Configuration\AkeneoPimConfigurationProfilePlugin;
use SprykerEco\Zed\AkeneoPimMiddlewareConnector\Communication\Plugin\Configuration\DefaultAkeneoPimConfigurationProfilePlugin;
use SprykerMiddleware\Zed\Process\Communication\Plugin\Configuration\DefaultConfigurationProfilePlugin;
use SprykerMiddleware\Zed\Process\ProcessDependencyProvider as SprykerProcessDependencyProvider;

class ProcessDependencyProvider extends SprykerProcessDependencyProvider
{
    /**
     * @return \SprykerMiddleware\Zed\Process\Dependency\Plugin\Configuration\ConfigurationProfilePluginInterface[]
     */
    protected function getConfigurationProfilePluginsStack(): array
    {
        $profileStack = parent::getConfigurationProfilePluginsStack();
        $profileStack[] = new DefaultConfigurationProfilePlugin();
        $profileStack[] = new AkeneoPimConfigurationProfilePlugin();
        $profileStack[] = new DefaultAkeneoPimConfigurationProfilePlugin();

        return $profileStack;
    }
}

Import Configuration

Firstly, extend the AkeneoPimMiddlewareConnector module on a project level. Create src/Pyz/Zed/AkeneoPimMiddlewareConnector folder.

Inside the module, implement plugins for writing data (categories, attributes, abstract and concrete products) into the shop. Add the following plugins to src/Pyz/Zed/AkeneoPimMiddlewareConnector/Communication/Plugin:

  • AttributeDataImporterPlugin
  • CategoryDataImporterPlugin
  • ProductAbstractDataImporterPlugin
  • ProductDataImporterPlugin

Find an examplary plugin implementation below.

ProductAbstractDataImporterPlugin.php

 <?php

namespace Pyz\Zed\AkeneoPimMiddlewareConnector\Communication\Plugin;

use Spryker\Zed\Kernel\Communication\AbstractPlugin;
use SprykerEco\Zed\AkeneoPimMiddlewareConnector\Dependency\Plugin\DataImporterPluginInterface;

class ProductAbstractDataImporterPlugin extends AbstractPlugin implements DataImporterPluginInterface
{
    /**
     * @api
     *
     * @param array $data
     *
     * @return void
     */
    public function import(array $data): void
    {
        $this->getFacade()->importProductsAbstract($data);
    }
}

Implement your own DataImporter for importing products to the shop database. It can be a business module inside the AkeneoPimMiddlewareConnector module. Example:

AkeneoDataImporter.php

 <?php

namespace Pyz\Zed\AkeneoPimMiddlewareConnector\Business\AkeneoDataImporter;

use Spryker\Zed\DataImport\Business\Model\DataSet\DataSetInterface;
use Spryker\Zed\DataImport\Business\Model\DataSet\DataSetStepBrokerInterface;
use Spryker\Zed\DataImport\Business\Model\Publisher\DataImporterPublisherInterface;
use Spryker\Zed\DataImportExtension\Dependency\Plugin\DataSetWriterPluginInterface;
use Spryker\Zed\EventBehavior\EventBehaviorConfig;

class AkeneoDataImporter implements AkeneoDataImporterInterface
{
    /**
     * @var \Spryker\Zed\DataImport\Business\Model\Publisher\DataImporterPublisherInterface
     */
    protected $dataImporterPublisher;

    /**
     * @var \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetStepBrokerInterface
     */
    protected $dataSetStepBroker;

    /**
     * @var \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetInterface
     */
    protected $dataSet;

    /**
     * @var \Spryker\Zed\DataImportExtension\Dependency\Plugin\DataSetWriterPluginInterface[]
     */
    protected $writerPlugins;

    /**
     * @param \Spryker\Zed\DataImport\Business\Model\Publisher\DataImporterPublisherInterface $dataImporterPublisher
     * @param \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetStepBrokerInterface $dataSetStepBroker
     * @param \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetInterface $dataSet
     * @param \Spryker\Zed\DataImportExtension\Dependency\Plugin\DataSetWriterPluginInterface|array $writerPlugins
     */
    public function __construct(
        DataImporterPublisherInterface $dataImporterPublisher,
        DataSetStepBrokerInterface $dataSetStepBroker,
        DataSetInterface $dataSet,
        array $writerPlugins = []
    ) {
        $this->dataImporterPublisher = $dataImporterPublisher;
        $this->dataSetStepBroker = $dataSetStepBroker;
        $this->dataSet = $dataSet;
        $this->writerPlugins = $writerPlugins;
    }

    /**
     * @param array $data
     *
     * @return void
     */
    public function import(array $data): void
    {
        EventBehaviorConfig::disableEvent();
        foreach ($data as $item) {
            $this->dataSet->exchangeArray($item);
            $this->dataSetStepBroker->execute($this->dataSet);
            /** @var DataSetWriterPluginInterface $writerPlugin */
            foreach ($this->writerPlugins as $writerPlugin) {
                $writerPlugin->write($this->dataSet);
            }
        }
        foreach ($this->writerPlugins as $writerPlugin) {
            $writerPlugin->write($this->dataSet);
        }
        EventBehaviorConfig::enableEvent();
        $this->dataImporterPublisher->triggerEvents();
    }
}

Implement facade methods for the AkeneoPimMiddlewareConnector module. Example:

class AkeneoPimMiddlewareConnectorFacade extends SprykerAkeneoPimMiddlewareConnectorFacade implements AkeneoPimMiddlewareConnectorFacadeInterface
...
    /**
     * @param array $data
     */
    public function importProductsAbstract(array $data): void
    {
        $this->getFactory()
            ->createProductAbstractImporter()
            ->import($data);
    }
...
...

Dataset Step Broker and Writer

Business Factory method is used for Importer creation. Determine the data writing approach and how you want to broke the payload. The AkeneoImporter you implemented usually expects the implementation of \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetStepBrokerInterface.

For better understanding, see the example of the AkeneoDataImporter creation for importing abstract products in AkeneoPimMiddlewareConnectorBusinessFactory.

AkeneoPimMiddlewareConnectorBusinessFactory

...
class AkeneoPimMiddlewareConnectorBusinessFactory extends SprykerAkeneoPimMiddlewareConnectorBusinessFactory
...

   /**
    * @return \Pyz\Zed\AkeneoPimMiddlewareConnector\Business\AkeneoDataImporter\AkeneoDataImporterInterface
    */
   public function createProductAbstractImporter()
   {
       return new AkeneoDataImporter(
           $this->createDataImporterPublisher(),
           $this->createProductAbstractImportDataSetStepBroker(),
           $this->createDataSet(),
           $this->getProvidedDependency(AkeneoPimMiddlewareConnectorDependencyProvider::PRODUCT_ABSTRACT_PROPEL_WRITER_PLUGINS)
       );
   }

   /**
    * @return \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetInterface
    */
   public function createDataSet()
   {
       return new DataSet();
   }

   /**
    * @return \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetStepBrokerInterface
    *
    * @throws \Spryker\Zed\Kernel\Exception\Container\ContainerKeyNotFoundException
    */
   public function createProductAbstractImportDataSetStepBroker()
   {
       $dataSetStepBroker = new DataSetStepBroker();
       $dataSetStepBroker->addStep(new ProductAbstractStep());

       return $dataSetStepBroker;
   }
...

As you can see, in DataSetStepBroker, you can add your own steps for preparing data for writers. You can find ready-made steps in the DataImport module or implement your own steps. Example:

ProductAbstractStep

<?php

namespace Pyz\Zed\AkeneoPimMiddlewareConnector\Business\DataImportStep;

use Generated\Shared\Transfer\SpyProductAbstractEntityTransfer;
use Pyz\Zed\DataImport\Business\Model\ProductAbstract\ProductAbstractHydratorStep;
use Spryker\Zed\DataImport\Business\Model\DataSet\DataSetInterface;

class ProductAbstractStep extends ProductAbstractHydratorStep
{
   /**
    * @param \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetInterface $dataSet
    *
    * @return void
    */
   protected function importProductAbstract(DataSetInterface $dataSet): void
   {
       $productAbstractEntityTransfer = new SpyProductAbstractEntityTransfer();
       $productAbstractEntityTransfer->setSku($dataSet[static::KEY_ABSTRACT_SKU]);

       $productAbstractEntityTransfer
           ->setColorCode($dataSet[static::KEY_COLOR_CODE])
           ->setFkTaxSet($dataSet[static::KEY_TAX_ID)
           ->setAttributes(json_encode($dataSet[static::KEY_ATTRIBUTES]))
           ->setNewFrom($dataSet[static::KEY_NEW_FROM])
           ->setNewTo($dataSet[static::KEY_NEW_TO]);

       $dataSet[static::DATA_PRODUCT_ABSTRACT_TRANSFER] = $productAbstractEntityTransfer;
   }

   /**
    * @param \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetInterface $dataSet
    *
    * @return void
    */
   protected function importProductCategories(DataSetInterface $dataSet): void
   {
       $dataSet[static::DATA_PRODUCT_CATEGORY_TRANSFER] = [];
   }
}

You can change default data mappers and translators for overriding keys or values. By default, Akeneo Connector Eco module has a list of predefined mappers, translators and validators for each import type, but it can be adjusted to meet your requirements. Check the middleware documentation for more details.

You also need to take care of that data that is to be written to the database. Two approaches can be used for that.

For attributes and categories, Spryker has implemented writer steps, so no plugins are required for that. Example:

AkeneoPimMiddlewareConnectorBusinessFactory

/**
* @return \Pyz\Zed\AkeneoPimMiddlewareConnector\Business\AkeneoDataImporter\AkeneoDataImporterInterface
*/
public function createCategoryImporter(): AkeneoDataImporterInterface
{
   return new AkeneoDataImporter(
       $this->createDataImporterPublisher(),
       $this->createCategoryImportDataSetStepBroker(),
       $this->createDataSet()
   );
}

/**
* @return \Spryker\Zed\DataImport\Business\Model\DataSet\DataSetStepBrokerInterface
*/
public function createCategoryImportDataSetStepBroker()
{
   $dataSetStepBroker = new DataSetStepBroker();
   $dataSetStepBroker->addStep($this->createCategoryWriteStep());

   return $dataSetStepBroker;
}

/**
* @return \Spryker\Zed\DataImport\Business\Model\DataImportStep\DataImportStepInterface
*/
public function createCategoryWriteStep()
{
   return new CategoryWriterStep($this->createCategoryReader());
}

/**
* @return \Spryker\Zed\CategoryDataImport\Business\Model\Reader\CategoryReader
*/
public function createCategoryReader(): CategoryReader
{
   return new CategoryReader();
}

The example demonstrates how you can skip adding plugins for writing data to the database.

Product import is a more complex operation, so Spryker provides bulk insertion plugins for that. They are faster than the writer steps.

You can use the existing plugins or create your own. The right way to add external plugins is to use dependency providers. We have two types of writer plugins: Propel plugins and PDO plugins. Check the examples for both of them below.

AkeneoPimMiddlewareConnectorBusinessFactory

/**
* @return \Pyz\Zed\AkeneoPimMiddlewareConnector\Business\AkeneoDataImporter\AkeneoDataImporterInterface
*/
public function createProductAbstractImporter()
{
   return new AkeneoDataImporter(
       $this->createDataImporterPublisher(),
       $this->createProductAbstractImportDataSetStepBroker(),
       $this->createDataSet(),
       $this->getProvidedDependency(AkeneoPimMiddlewareConnectorDependencyProvider::PRODUCT_ABSTRACT_PROPEL_WRITER_PLUGINS)
   );
}

AkeneoPimMiddlewareConnectorDependencyProvider

...
class AkeneoPimMiddlewareConnectorDependencyProvider extends SprykerAkeneoPimMiddlewareConnectorDependencyProvider
{
   public const PRODUCT_ABSTRACT_PROPEL_WRITER_PLUGINS = 'PRODUCT_ABSTRACT_PROPEL_WRITER_PLUGINS';

   /**
    * @param \Spryker\Zed\Kernel\Container $container
    *
    * @return \Spryker\Zed\Kernel\Container
    */
   protected function addProductAbstractPropelWriterPlugins(Container $container): Container
   {
       $container[static::PRODUCT_ABSTRACT_PROPEL_WRITER_PLUGINS] = function () {
           return [
               new ProductAbstractPropelWriterPlugin(),
           ];
       };

       return $container;
   }

...
}

When we use only ProductAbstractPropelWriterPlugin, ProductStores, ProductPrices, etc are not imported. If you want to import something other than products, you need to add more writer plugins.

For example, if you want to import a product store, provide one more plugin in dependency provider.

AkeneoPimMiddlewareConnectorDependencyProvider

/**
* @param \Spryker\Zed\Kernel\Container $container
*
* @return \Spryker\Zed\Kernel\Container
*/
protected function addProductAbstractPropelWriterPlugins(Container $container): Container
{
   $container[static::PRODUCT_ABSTRACT_PROPEL_WRITER_PLUGINS] = function () {
       return [
           new ProductAbstractPropelWriterPlugin(),
           new ProductAbstractStorePropelWriterPlugin(),
       ];
   };

   return $container;
}

In case you add more writer plugins, you might have to add more steps to dataset step broker.