akeneo/connector-mapping-bundle
Akeneo Connector Mapping Bundle provides configurable mapping infrastructure for Akeneo import/export connectors, letting developers define how source fields map to target fields and reuse mapping logic across jobs.
Read this first: akeneo/connector-mapping-bundle is a Symfony bundle from the Akeneo PIM ecosystem, not a Laravel package. There is no service provider, no facade, no config file to publish, no Artisan command. Any day-to-day leverage you get from it comes from calling its plain PHP classes directly, or — more realistically — from treating it as a reference implementation of column-to-attribute mapping while you write your own transform code.
So the first decision is a fork in the road:
| Your situation | What to do |
|---|---|
| You run Akeneo PIM and are extending/debugging a connector | Work inside Akeneo's Symfony app; use the bundle natively via its DI wiring. |
| You have a Laravel app and want "map vendor CSV columns to our attribute model" | Do not install this. Read its converters as a spec, then write ~100 lines of your own mapping layer. |
| You are pulling Akeneo data into Laravel over HTTP | Use Akeneo's REST API + a thin Laravel client. This bundle does not help you here. |
If you have decided to install it anyway (e.g. you genuinely need Akeneo's exact value-conversion semantics in a Laravel ETL job):
composer require akeneo/connector-mapping-bundle
Expect the install itself to be the first real test. Akeneo components pin symfony/* and PHP versions tightly; a Laravel app already carrying symfony/console, symfony/process, and symfony/http-foundation through its own dependency tree is a likely conflict. Verify with composer why-not akeneo/connector-mapping-bundle before you invest time.
Where to look first, in order:
src/ArrayConverter/ — the ArrayConverterInterface and its concrete implementations. The contract is effectively convert(array $item, array $options = []): array. This is the only surface you should call.*ConverterRegistry class (if present in your version) — how converters are looked up by name/type. This tells you whether you can resolve one by string key or must construct it.Resources/config/ — the service definitions. This is your copy-paste source. Laravel will not auto-discover these; you will manually re-declare the subset you need in a ServiceProvider, including constructor arguments.composer.json — PHP constraint, Symfony constraint, and whether it drags in akeneo/pim-community-dev or Oro packages. If it pulls Oro, stop: you are about to install a platform, not a library.Your first use case should be embarrassingly small: take one representative raw row (a supplier CSV line with locale/channel-suffixed columns) and pass it through one converter, dumping input and output. If you cannot get a single row through in an afternoon, the dependency is wrong for your project and the correct move is to reimplement the 60–100 lines of logic you actually need. That is a genuinely common outcome here and not a failure.
// tinker / a throwaway route / a test — anywhere you can dump
$converter = app(\App\Support\Mapping\ProductRowMapper::class);
dump($rawRow); // e.g. ['sku' => 'ABC', 'price-ecommerce-en_US' => '19.99', ...]
dump($converter->map($rawRow));
Wrap it immediately, expose nothing. The single most important pattern: no controller, job, or Blade-adjacent code should ever reference an Akeneo class. Put one thin class in app/Support/Mapping/ behind an interface you own, and let the bundle's converter be a private implementation detail. When the package rots (it will), you replace one constructor.
namespace App\Support\Mapping;
interface RowMapper
{
public function map(array $rawRow): array;
}
final class ConverterRowMapper implements RowMapper
{
public function __construct(
private \Akeneo\Tool\Component\Connector\ArrayConverter\ArrayConverterInterface $converter
) {}
public function map(array $rawRow): array
{
return $this->converter->convert($rawRow, [
'with_associations' => false,
'case_insensitive_keys' => true,
]);
}
}
Bind converters manually in a service provider. Laravel will not read Resources/config/*.yml. Translate the definitions you need by hand, and keep them in a dedicated provider (MappingServiceProvider) so the foreign wiring is isolated and greppable.
// app/Providers/MappingServiceProvider.php
public function register(): void
{
// Only bind what you actually use. Copy constructor args from Resources/config/.
$this->app->bind(ArrayConverterInterface::class, function ($app) {
return new \Akeneo\Component\Connector\ArrayConverter\StandardConverter(
$app->make(SomeFieldMapper::class), // ← verify against the real signature
);
});
$this->app->bind(RowMapper::class, ConverterRowMapper::class);
}
If the constructor signature is long or unstable, bind a pre-built singleton instead and treat it as immutable.
Compose a pipeline instead of hunting for one fat converter. Akeneo's idiom is small, single-purpose converters chained together. Mirror that in your code, where it is testable and visible:
final class ImportPipeline
{
/** @param RowMapper[] $steps */
public function __construct(private array $steps) {}
public function __invoke(array $row): array
{
foreach ($this->steps as $step) {
$row = $step->map($row);
}
return $row;
}
}
Typical order for a supplier-feed transform: normalise keys → split locale/channel-suffixed columns into proper scope structure → coerce units/booleans → validate required attributes → emit canonical array. The bundle helps most in the middle of that chain (scope splitting, value conversion), least at the ends.
Run it in queued jobs, on chunks, never in a request cycle. Converters are stateless and CPU-bound on array walks; a 50k-row supplier file will time out a web request. Chunk at the job level, resolve the mapper from the container per job, and hand output to a persistence step.
final class ImportProductChunk implements ShouldQueue
{
public function __construct(private array $rows) {}
public function handle(RowMapper $mapper, ProductWriter $writer): void
{
foreach ($this->rows as $row) {
$writer->upsert($mapper->map($row)); // or collect + bulk upsert
}
}
}
Test as a black box against fixtures. Golden-file testing is the right shape here: a fixture row in, an expected canonical array out, asserted with assertSame. Never assert on converter internals — you did not write them and they change on Akeneo's schedule. Include nasty fixtures up front: empty strings, 0 vs null, metric values with units, multi-select columns with , delimiters, and rows with a locale/channel column that is missing entirely.
If you are customising inside Akeneo (the one context where this package is genuinely first-class), the extension point is implementing ArrayConverterInterface and registering it in the bundle's DI config or converter registry — then referencing it from a job profile. Do not subclass an existing Akeneo converter.
There is no Laravel integration, and there won't be. Missing Kernel, unresolved compiler passes, @service references that mean nothing in Laravel, Oro/Doctrine entity metadata assumed present. Budget a day for "make it instantiate" and be prepared to abandon it. The pragmatic fallback — copying the two or three converters whose behaviour you need into app/Support/Mapping/ — is often the correct engineering call, not a cop-out.
Fail at composer require, not in production. Akeneo pins PHP minors and Symfony ranges aggressively. Run the install on a branch, read every conflict message, and check whether the package drags akeneo/pim-community-dev or Oro transitively. If it does, the dependency is a platform and you should not proceed.
Silent wrongness is the real risk, not exceptions. Mapping bugs do not throw — they write wrong attribute values at scale. The three classics to test explicitly:
price-ecommerce-en_US and price-mobile-en_US both landing in one field, last-write-wins.12.5 KG vs 12500 G) converted or not converted depending on a flag you did not pass.$options is loosely typed and fails silently. Converters read it with isset() and fall back to defaults — a missing or misspelled key produces wrong-shaped output, not an error. Pass options explicitly, define them in one constants class, and never rely on defaults you have not verified in source.
No logging hooks, so debug by dumping between steps. dump($item) after each pipeline stage is genuinely the fastest path to the problem. Do not step-debug into vendored Akeneo code; you will burn an hour in a call stack that belongs to a different framework.
Pin the exact version. Zero stars, zero dependents, zero score. Upstream will not fix bugs or patch for new Symfony/PHP releases. Use "akeneo/connector-mapping-bundle": "x.y.z" with no caret, commit the lock file, and treat any upgrade as a scheduled task with regression tests rather than a routine composer update.
Check whether you even need it. Later Akeneo majors absorbed connector-mapping functionality into core; on a legacy 2.x-era PIM the bundle may be redundant or actively conflicting with the native mapping step. Also confirm CE vs EE: some mapping surface was Enterprise-only in this era, so a CE instance may not expose what you are reading about.
Never fork it to add features. Forking a platform-internal bundle turns every Akeneo upgrade into a rebase exercise. The escalation ladder is: configuration → pre-processing outside the PIM (a Laravel job that normalises the file before it ever reaches Akeneo) → a custom converter registered via the interface → fork, costed as a recurring maintenance line.
Extension point is the interface, not inheritance. Implement ArrayConverterInterface yourself for custom behaviour. Subclassing Akeneo's converters couples you to internals with no semver stability and no upstream triage.
When the mapping requirement outgrows this — reusable templates, per-supplier saved mappings, richer transformation rules — that is a product/roadmap item, not a code patch. The realistic answers are configuration on top of the bundle, a normalisation step in your own Laravel pipeline, or a managed ETL (Airbyte/dbt) for the general case. This package is only the right tool when the target is Akeneo and the flow is a bulk connector profile.
How can I help you explore Laravel packages today?