PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Akeneo PIM – why a PIM, REST API, attribute mapping, cron import

by Henryk Tews / Tuesday, 12 May 2020 / Published in Magento 2, PIM

When a shop has several thousand products with dozens of attributes in several languages, managing product data directly in Magento becomes a real pain. Akeneo PIM solves this – it centralises product data and delivers it to Magento (and other channels) through an API. I show why you would want a PIM and what the technical integration looks like.

Why a PIM when Magento has attributes?

Magento has an attribute system, attribute sets and categories – that is true. But it is optimised for selling, not content management. Problems arise when:

  • You have multiple sales channels (Magento, Amazon, Allegro, mobile app) and want a single source of truth for product data
  • Product content is created by the marketing department, not a developer or Magento admin
  • You support multiple languages and translations require an approval workflow
  • You have several hundred attributes and their structure changes frequently
  • Product data comes from an ERP and requires validation before publishing

Akeneo Community Edition is free and open source. The Enterprise version adds workflows, permissions and advanced data quality rules.

Integration architecture

Akeneo exposes a REST API. Integration with Magento 2 can work two ways:

  • Push from Akeneo – Akeneo sends data to Magento via webhook or cron after each change
  • Pull from Magento – Magento periodically polls the Akeneo API and imports changes

The Pull approach is most common – a dedicated import module triggered by a cron job. Ready-made connectors are available: the official one from Akeneo and an open-source one from Flagbit.

Akeneo REST API – basic operations

<?php

declare(strict_types=1);

namespace Vendor\AkeneoConnector\Api;

class AkeneoClient
{
    private string $accessToken = '';

    public function __construct(
        private string $baseUrl,
        private string $clientId,
        private string $clientSecret,
        private string $username,
        private string $password,
        private \GuzzleHttp\Client $httpClient
    ) {}

    private function authenticate(): void
    {
        $response = $this->httpClient->post($this->baseUrl . '/api/oauth/v1/token', [
            'auth' => [$this->clientId, $this->clientSecret],
            'json' => [
                'grant_type' => 'password',
                'username'   => $this->username,
                'password'   => $this->password,
            ],
        ]);

        $data = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
        $this->accessToken = $data['access_token'];
    }

    public function getProducts(int $page = 1, int $limit = 100): array
    {
        if (empty($this->accessToken)) $this->authenticate();

        $response = $this->httpClient->get($this->baseUrl . '/api/rest/v1/products', [
            'headers' => ['Authorization' => 'Bearer ' . $this->accessToken],
            'query'   => [
                'page'       => $page,
                'limit'      => $limit,
                'with_count' => true,
                'locales'    => 'pl_PL,en_US',
                'scope'      => 'ecommerce',
            ],
        ]);

        return json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
    }
}

Mapping Akeneo data to Magento 2

Data from Akeneo has a specific structure – attributes are an array of values per locale and per scope. You need a mapping layer:

<?php

declare(strict_types=1);

class ProductMapper
{
    private array $attributeMap = [
        'name'              => 'name',
        'description'       => 'description',
        'short_description' => 'short_description',
        'price'             => 'price',
        'weight'            => 'weight',
    ];

    public function map(array $akeneoProduct, string $locale = 'pl_PL', string $scope = 'ecommerce'): array
    {
        $magentoData = [
            'sku'        => $akeneoProduct['identifier'],
            'type_id'    => 'simple',
            'visibility' => 4,
            'status'     => $akeneoProduct['enabled'] ? 1 : 2,
        ];

        foreach ($akeneoProduct['values'] as $attributeCode => $values) {
            $magentoCode = $this->attributeMap[$attributeCode] ?? $attributeCode;
            $value       = $this->extractValue($values, $locale, $scope);
            if ($value !== null) {
                $magentoData[$magentoCode] = $value;
            }
        }

        return $magentoData;
    }

    private function extractValue(array $values, string $locale, string $scope): mixed
    {
        // Locale + scope specific
        foreach ($values as $value) {
            if ($value['locale'] === $locale && $value['scope'] === $scope) return $value['data'];
        }
        // Scope only
        foreach ($values as $value) {
            if ($value['locale'] === null && $value['scope'] === $scope) return $value['data'];
        }
        // Global (no locale, no scope)
        foreach ($values as $value) {
            if ($value['locale'] === null && $value['scope'] === null) return $value['data'];
        }
        return null;
    }
}

Periodic import via Magento cron

<?php

namespace Vendor\AkeneoConnector\Cron;

class SyncProducts
{
    public function __construct(
        private \Vendor\AkeneoConnector\Api\AkeneoClient $akeneoClient,
        private \Vendor\AkeneoConnector\Model\ProductMapper $mapper,
        private \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
        private \Psr\Log\LoggerInterface $logger
    ) {}

    public function execute(): void
    {
        $page = 1;
        do {
            $response   = $this->akeneoClient->getProducts($page, 100);
            $products   = $response['_embedded']['items'] ?? [];
            $totalPages = (int) ceil($response['items_count'] / 100);

            foreach ($products as $akeneoProduct) {
                try {
                    $this->syncProduct($akeneoProduct);
                } catch (\Exception $e) {
                    $this->logger->error('Sync failed: ' . $akeneoProduct['identifier'], [
                        'error' => $e->getMessage(),
                    ]);
                }
            }
            $page++;
        } while ($page <= $totalPages);
    }

    private function syncProduct(array $akeneoProduct): void
    {
        $data = $this->mapper->map($akeneoProduct);
        $this->logger->info('Synced: ' . $data['sku']);
    }
}

Summary

Akeneo PIM makes sense from a certain scale – a few thousand products, multiple languages, multiple sales channels. Below that threshold managing data directly in Magento is simpler. The technical integration through the REST API is well documented, and the attribute mapping layer is the most important part of the whole solution – that is where most time is spent in real implementations.

About Henryk Tews

What you can read next

Xdebug – configuration, PHPStorm, debugging Magento plugins
Strategy pattern in PHP – and how Magento 2 uses it in pricing
PimCore – CMS + PIM + DAM, object classes, Data Hub GraphQL, Magento integration

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}