Laravel + Akeneo PIM: een product data pipeline die schaalt
Terug naar blog

Laravel + Akeneo PIM: een product data pipeline die schaalt

AuthorRuthger Idema
17 september 20269 min leestijd

Eén productwijziging in Akeneo moet binnen seconden in je webshop staan. Niet na een nachtelijke full sync van 80.000 producten. Niet na een handmatige export.

Laravel + Akeneo PIM: een product data pipeline die schaalt

Eén productwijziging in Akeneo moet binnen seconden in je webshop staan. Niet na een nachtelijke full sync van 80.000 producten. Niet na een handmatige export. Direct, betrouwbaar, en zonder dat je catalogus tijdelijk half gevuld is.

Dat is het verschil tussen een PIM die werkt en een PIM die alleen op papier bestaat. Wij bouwen die pipeline met Laravel als laag tussen Akeneo en de winkel. Akeneo is de bron van waarheid voor productdata. Laravel is de vertaler, de wachtrij en het vangnet.

In dit artikel laten we zien hoe die pipeline eruitziet: hoe je data ophaalt, transformeert, media verwerkt, completeness bewaakt en incrementeel synchroniseert. Met concrete keuzes, geen abstracte architectuurplaatjes.

Waarom Laravel tussen Akeneo en je webshop

Je kunt Akeneo rechtstreeks aan je webshop koppelen. Voor een catalogus van 500 producten werkt dat. Daarboven loop je vast.

Akeneo modelleert data zoals een product manager denkt: families, attributen, varianten, locales, channels. Je webshop wil platte, kant-en-klare productobjecten. Tussen die twee werelden zit transformatie. Die logica wil je niet in Akeneo proppen en ook niet in je frontend.

Laravel is de juiste plek omdat:

  • Queues out-of-the-box beschikbaar zijn (Redis/Horizon) voor het verwerken van duizenden producten zonder timeouts.
  • Eloquent een schone staging-laag geeft waar je ruwe Akeneo-data kunt opslaan voordat je publiceert.
  • Events en jobs je een audit trail geven: wat is wanneer gewijzigd en waarom.
  • Scheduler incrementele sync betrouwbaar inplant.

De pipeline draait apart van je webshop. Valt de sync om, dan blijft de winkel gewoon draaien op de laatst bekende data. Dat is geen detail. Dat is het hele punt.

Voor maatwerk in deze laag zetten wij bewust Laravel in. Geen plugin die 80% doet, maar code die exact jouw datamodel volgt.

Data ophalen: REST API en de keuze tussen polling en events

Akeneo biedt een REST API met OAuth2. Je authenticeert, krijgt een access token (geldig 1 uur), en haalt producten op via gepagineerde endpoints. De standaard page size is 100, het maximum is 100.

Twee manieren om wijzigingen op te pikken:

1. Polling met updated filter

Je vraagt periodiek alle producten op die sinds tijdstip X zijn gewijzigd:

php
$response = $this->akeneo->get('/api/rest/v1/products', [
    'search' => json_encode([
        'updated' => [[
            'operator' => '>',
            'value' => $lastSync->format('Y-m-d H:i:s'),
        ]],
    ]),
    'pagination_type' => 'search_after',
    'limit' => 100,
]);

Gebruik altijd search_after paginatie, niet offset-based. Offset-paginatie breekt zodra de dataset tijdens het ophalen verandert. search_after is cursor-based en stabiel.

2. Akeneo Events API (webhooks)

Akeneo Enterprise stuurt sinds versie 5.0 events naar een webhook-endpoint bij wijzigingen (product.updated, product.created, product.removed). Laravel ontvangt de payload, valideert de HMAC-signature en zet een job op de queue.

Onze aanbeveling: combineer beide. Events voor near-realtime updates, polling als nachtelijk vangnet voor gemiste events. Webhooks falen soms. Een dagelijkse reconciliatie-sync vangt de gaten.

AanpakLatencyBetrouwbaarheidAkeneo-versie
PollingMinutenHoogAlle
Events/webhooksSecondenMiddel (kan falen)EE 5.0+
Beide gecombineerdSecondenHoogEE 5.0+

Transformatie: van Akeneo-attributen naar webshop-product

Dit is waar de meeste tijd in gaat zitten. Akeneo levert productdata als geneste structuur per attribuut, per locale, per channel:

json
{
  "values": {
    "name": [{"locale": "nl_NL", "scope": "ecommerce", "data": "Bureaustoel Ergo"}],
    "price": [{"locale": null, "scope": null, "data": [{"amount": "299.00", "currency": "EUR"}]}]
  }
}

Je webshop wil dit:

json
{ "name": "Bureaustoel Ergo", "price": 299.00, "currency": "EUR" }

Bouw per attribuuttype een dedicated transformer. Een SimpleSelectTransformer resolvet de optiecode naar een label. Een PriceCollectionTransformer pakt het juiste valutabedrag. Een MetricTransformer rekent eenheden om. Registreer ze in een mapping zodat je nieuwe attributen toevoegt zonder de kernlogica aan te raken.

Belangrijke valkuilen die we steeds tegenkomen:

  • Locale en scope niet hardcoden. Een product kan per channel andere waarden hebben. Stuur de juiste scope mee.
  • Optielabels cachen. Anders doe je per product een extra API-call om option_1 naar "Zwart" te vertalen. Cache de hele attribute-options tabel lokaal.
  • Reference entities apart syncen. Merken, kleuren en materialen leven als losse entiteiten in Akeneo. Sync die eerst, dan pas de producten die ernaar verwijzen.

Sla het ruwe Akeneo-resultaat op in een staging-tabel. Transformeer daarna in een aparte stap. Zo kun je een transformatiebug oplossen en opnieuw draaien zonder Akeneo opnieuw te bevragen.

Media: afbeeldingen en documenten

Akeneo slaat media op als asset-referenties of als file-attributen. De API geeft je een download-URL, geen binary in de productpayload.

Onze media-flow:

  1. Bij transformatie verzamel je de media-codes per product.
  2. Een aparte DownloadMediaJob haalt elk bestand op via /api/rest/v1/media-files/{code}/download.
  3. Vergelijk de Akeneo-checksum met wat je al hebt. Onveranderd? Skip de download.
  4. Genereer afgeleide formaten (WebP, thumbnails) en push naar je CDN of object storage.

Download media nooit synchroon tijdens de productsync. Een product met 8 afbeeldingen blokkeert anders je hele queue. Splits het: productdata eerst publiceren, media asynchroon nadat de download klaar is.

Voor grote catalogi scheelt checksum-vergelijking enorm. Wij zagen bij een klant met 40.000 producten dat 95% van de media bij een dagelijkse sync ongewijzigd was. Door alleen de 5% nieuwe bestanden te downloaden ging de media-stap van 3 uur naar 9 minuten.

Completeness: alleen complete producten publiceren

Akeneo berekent per channel en locale een completeness-percentage: hoeveel verplichte attributen zijn gevuld. Dit is je belangrijkste publicatiegate.

Een half ingevuld product hoort niet in je winkel. Geen prijs, geen beschrijving, geen hoofdafbeelding: niet publiceren. Akeneo geeft completeness terug per product:

php
$completeness = collect($product['completenesses'])
    ->firstWhere(fn ($c) => $c['scope'] === 'ecommerce' && $c['locale'] === 'nl_NL');

if (($completeness['data']['percent'] ?? 0) < 100) {
    $this->unpublish($product['identifier']);
    return;
}

Hanteer een drempel die bij je business past. 100% voor de NL-locale, maar misschien 80% voor een nieuwe markt waar je vertalingen nog binnenkomen. Maak die drempel configureerbaar per channel.

Belangrijk: een product dat onder de drempel zakt moet actief gedepubliceerd worden, niet genegeerd. Anders blijft oude data in de winkel staan terwijl Akeneo zegt dat het product onvolledig is.

Log elke completeness-beslissing. Als de marketingafdeling vraagt waarom product X niet online staat, wil je binnen 10 seconden het antwoord hebben: "mist de productbeschrijving voor nl_NL, completeness 87%."

Incremental sync: het verschil tussen 4 uur en 4 seconden

Full sync is voor de eerste keer en voor disaster recovery. Daarna draai je incrementeel. Het verschil is dramatisch.

De kern is je last_synced_at timestamp. Sla die per sync-run op nadat de run volledig geslaagd is. Niet eerder. Faalt de run halverwege, dan wil je de volgende keer vanaf hetzelfde punt opnieuw beginnen.

Praktische opbouw met Laravel:

php
// Scheduler: elke 5 minuten incrementeel
$schedule->job(new IncrementalSyncJob)->everyFiveMinutes()->withoutOverlapping();

// Elke nacht: reconciliatie tegen volledige catalogus
$schedule->job(new ReconciliationSyncJob)->dailyAt('03:00');
withoutOverlapping() voorkomt dat twee syncs over elkaar heen draaien. Cruciaal als een sync soms langer duurt dan je interval.

Aandachtspunten voor betrouwbare incrementele sync:

  • Idempotente jobs. Een product twee keer verwerken mag geen dubbele data of fouten geven. Gebruik updateOrCreate op een unieke identifier.
  • Deletes apart afhandelen. Een verwijderd product verschijnt niet in een updated-filter. Daarom heb je de reconciliatie-sync of product.removed events nodig.
  • Klokverschil incalculeren. Trek een veiligheidsmarge van enkele minuten af van je last_synced_at. Beter een paar producten dubbel verwerken dan er één missen door tijdzone- of klokverschil.
  • Horizon voor monitoring. Je wilt zien hoeveel jobs er wachten, falen en hoe lang ze duren. Failed jobs gaan automatisch de retry in.

Het resultaat: een prijswijziging in Akeneo staat binnen één scheduler-cyclus in de winkel. Bij events binnen seconden. De volledige catalogus blijft intussen ongemoeid.

De volledige flow in één overzicht

Zo hangt het aan elkaar:

  1. Trigger — webhook event of scheduler tikt de sync af.
  2. Fetch — Laravel haalt gewijzigde producten op via de Akeneo REST API met search_after.
  3. Stage — ruwe data gaat in een staging-tabel.
  4. Transform — per-attribuut transformers maken er platte productobjecten van.
  5. Media — aparte jobs downloaden en optimaliseren gewijzigde bestanden.
  6. Gate — completeness-check bepaalt publiceren of depubliceren.
  7. Publish — schone data gaat naar je webshop-database of API.
  8. Recordlast_synced_at wordt pas bijgewerkt na succes.

Elke stap is een aparte, herhaalbare job. Valt stap 5 om, dan blijven stappen 1 tot 4 intact. Je herstart precies waar het misging.

Dit patroon werkt of je nu naar een Magento-store, een Shopify Plus winkel of een volledig custom storefront publiceert. De PIM-pipeline staat los van het winkelplatform. Dat is precies waarom je hem in Laravel bouwt en niet in het winkelplatform zelf.

Wil je sparren over jouw catalogusomvang en welke aanpak past? Neem contact op — we kijken zonder verkooppraatje naar je situatie.

Veelgestelde vragen

Heb ik Akeneo Enterprise Edition nodig voor deze pipeline?

Nee. De REST API en completeness-data zitten ook in de Community Edition. De Events API (webhooks) is wel Enterprise-only vanaf versie 5.0. Met alleen Community draai je polling-based sync via de scheduler. Dat geeft je updates binnen minuten in plaats van seconden, wat voor de meeste catalogi prima is.

Hoe groot mag mijn catalogus zijn voor incremental sync?

Wij draaien deze opzet bij catalogi van enkele honderden tot ruim 100.000 producten. De full sync schaalt lineair met je catalogusgrootte, maar die draai je zelden. Incremental sync verwerkt alleen wijzigingen, dus de dagelijkse belasting hangt af van hoeveel je product managers muteren, niet van de totale omvang. Met Laravel queues en Horizon parallelliseer je de verwerking moeiteloos.

Wat gebeurt er als de Akeneo API tijdens een sync uitvalt?

Niets in je winkel. De webshop draait door op de laatst gepubliceerde data. De Laravel-job faalt, gaat de retry-cyclus in met exponential backoff, en last_synced_at blijft op het oude punt staan. Zodra Akeneo weer bereikbaar is, pakt de volgende run alle gemiste wijzigingen op. Dat is het voordeel van de tussenlaag.

Kan ik dezelfde pipeline naar meerdere webshops publiceren?

Ja. Akeneo werkt met channels (scopes) waarmee je per verkoopkanaal andere data definieert. In de transformatielaag map je elk channel naar een eigen publicatiebestemming. Eén Akeneo-bron, één Laravel-pipeline, meerdere storefronts. Elk kanaal krijgt zijn eigen completeness-drempel en locales.

Hoe verhoudt dit zich tot een PIM-koppeling die direct op Magento aansluit?

Een directe koppeling is sneller op te zetten maar koppelt je transformatielogica vast aan één platform. Migreer je later naar een ander winkelsysteem, dan bouw je opnieuw. De Laravel-tussenlaag is platformonafhankelijk en geeft je staging, retry en audit trail die kant-en-klare connectors missen. Lees ook onze casus over Magento 2 en Akeneo PIM voor de platformkant van het verhaal.

Ruthger Idema

Geschreven door Ruthger Idema

15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.

Meer over ons team →
Deel dit artikel:

Wil je jouw e-commerce naar het volgende niveau?

Plan een vrijblijvend gesprek met onze experts over Magento, Shopify of Laravel maatwerk.

Plan een Tech Check