PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Akeneo PIM – po co PIM, REST API, mapowanie atrybutów, import przez cron

by Henryk Tews / wtorek, 12 maja 2020 / Opublikowano w Magento 2, PIM

Gdy sklep ma kilka tysięcy produktów z dziesiątkami atrybutów w kilku językach, zarządzanie danymi produktowymi bezpośrednio w Magento staje się prawdziwym bólem. Akeneo PIM rozwiązuje ten problem – centralizuje dane produktowe i dostarcza je do Magento (i innych kanałów) przez API. Pokazuję po co w ogóle PIM i jak wygląda integracja technicznie.

Po co PIM, skoro Magento ma atrybuty?

Magento ma system atrybutów, attribute sets i kategorii – to prawda. Ale jest zoptymalizowany pod kątem sprzedaży, nie zarządzania treścią. Problemy pojawiają się gdy:

  • Masz kilka kanałów sprzedaży (Magento, Amazon, allegro, własna aplikacja mobilna) i chcesz jednego źródła prawdy dla danych produktowych
  • Treści produktowe tworzy dział marketingu, a nie developer czy administrator Magento
  • Obsługujesz kilka języków i tłumaczenia wymagają workflow z akceptacją
  • Atrybutów jest kilkaset i ich struktura zmienia się często
  • Dane produktowe przychodzą z ERP i wymagają walidacji przed publikacją

Akeneo Community Edition jest darmowe i open source. Wersja Enterprise dodaje workflow, uprawnienia i zaawansowane reguły jakości danych.

Architektura integracji

Akeneo udostępnia REST API. Integracja z Magento 2 może działać na dwa sposoby:

  • Push z Akeneo – Akeneo wysyła dane do Magento przez webhook lub cron po każdej zmianie
  • Pull z Magento – Magento cyklicznie odpytuje Akeneo API i importuje zmiany

Najczęściej stosuje się podejście Pull z Magento, przez dedykowany moduł importu uruchamiany cronem. Dostępne są gotowe konektory – oficjalny od Akeneo oraz open-source’owy od Flagbit.

Akeneo REST API – podstawowe operacje

<?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);
    }

    public function getProduct(string $sku): array
    {
        if (empty($this->accessToken)) {
            $this->authenticate();
        }

        $response = $this->httpClient->get(
            $this->baseUrl . '/api/rest/v1/products/' . urlencode($sku),
            ['headers' => ['Authorization' => 'Bearer ' . $this->accessToken]]
        );

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

Mapowanie danych Akeneo na Magento 2

Dane z Akeneo mają specyficzną strukturę – atrybuty to tablica z wartościami per locale i per scope. Potrzebujesz warstwy mapującej:

<?php

declare(strict_types=1);

namespace Vendor\AkeneoConnector\Model;

class ProductMapper
{
    // Mapowanie atrybutów Akeneo na atrybuty Magento
    private array $attributeMap = [
        'name'              => 'name',
        'description'       => 'description',
        'short_description' => 'short_description',
        'price'             => 'price',
        'weight'            => 'weight',
        'ean'               => 'ean',
    ];

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

        // Atrybuty zlokalizowane i per-scope
        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
    {
        // Wartość zlokalizowana i per-scope
        foreach ($values as $value) {
            if ($value['locale'] === $locale && $value['scope'] === $scope) {
                return $value['data'];
            }
        }

        // Wartość per-scope bez locale
        foreach ($values as $value) {
            if ($value['locale'] === null && $value['scope'] === $scope) {
                return $value['data'];
            }
        }

        // Wartość globalna (bez locale i scope)
        foreach ($values as $value) {
            if ($value['locale'] === null && $value['scope'] === null) {
                return $value['data'];
            }
        }

        return null;
    }

    private function mapStatus(bool $enabled): int
    {
        return $enabled ? 1 : 2;
    }
}

Import cykliczny przez Magento cron

<?php

namespace Vendor\AkeneoConnector\Cron;

use Vendor\AkeneoConnector\Api\AkeneoClient;
use Vendor\AkeneoConnector\Model\ProductMapper;
use Magento\Catalog\Api\ProductRepositoryInterface;

class SyncProducts
{
    public function __construct(
        private AkeneoClient $akeneoClient,
        private ProductMapper $mapper,
        private ProductRepositoryInterface $productRepository,
        private \Psr\Log\LoggerInterface $logger
    ) {}

    public function execute(): void
    {
        $page = 1;

        do {
            $response = $this->akeneoClient->getProducts($page, 100);
            $products = $response['_embedded']['items'] ?? [];

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

            $page++;
            $totalPages = ceil($response['items_count'] / 100);

        } while ($page <= $totalPages);
    }

    private function syncProduct(array $akeneoProduct): void
    {
        $data = $this->mapper->map($akeneoProduct);

        try {
            $product = $this->productRepository->get($data['sku']);
        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
            $product = null;
        }

        // Aktualizacja lub tworzenie - dalsza logika importu
        $this->logger->info('Synced product: ' . $data['sku']);
    }
}

Podsumowanie

Akeneo PIM ma sens od pewnej skali – kilka tysięcy produktów, kilka języków, kilka kanałów sprzedaży. Poniżej tej granicy zarządzanie danymi bezpośrednio w Magento jest prostsze. Integracja techniczna przez REST API jest dobrze udokumentowana, a warstwa mapowania atrybutów to najważniejszy element całego rozwiązania – tutaj spędza się najwięcej czasu przy realnych wdrożeniach.

About Henryk Tews

Co możesz przeczytać następne

Extension Attributes – pełna implementacja z batch loadingiem, REST API, testy
UI Components – siatka danych, DataProvider, własne kolumny, konfiguracja XML
Optymalizacja wydajności – OPcache, Redis, N+1 queries, Blackfire, tabela priorytetów
  • Publikacje
  • O autorze
  • Kontakt

© 2026 Created by

GÓRA
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 Zawsze aktywne
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.
  • Zarządzaj opcjami
  • Zarządzaj serwisami
  • Zarządzaj {vendor_count} dostawcami
  • Przeczytaj więcej o tych celach
Zobacz preferencje
  • {title}
  • {title}
  • {title}