Cross-channel matching: the bug that only appears at site number two
An EAN lookup without channelId works fine — until the same product shows up on a second channel. How an audit in the MFP platform found five such leaks and which patterns made the wrong path impossible afterwards.
Cross-channel matching: the bug that only appears at site number two
One channel: everything works
A typical e-commerce codebase starts single-channel. One database, one product catalogue, one store. An EAN lookup is simply findByEan($ean): there is only one channel, so the query is unambiguous by definition. ProductRepository does its job, AiBulkService does its job, exports do their job. No ambiguity, no problem.
Two channels: everything seems to work
When a second channel lands — at MFP (Multi Feed Processing) that was first ditpetje.nl and dithorloge.nl, then ditparfum.nl — the nature of the problem changes silently. Products are now stored per channel, with the same EAN potentially active on multiple channels at once. findByEan($ean) still returns a product — just not always the product belonging to the channel the caller meant.
The failure mode is treacherous: no crash, no 500, no log line. Just a product detail page on ditpetje.nl showing, by accident, a watch from dithorloge.nl, or an AI enrichment job pairing caps copy with perfume data. The bug presents as bad content rather than a technical error, and slips past the usual alarm signals.
The audit and what it found
A targeted audit on the MFP platform traced every call to findBy(['id' => $ids]) and findBy(['ean' => $ean]) and looked for instances where the channelId criterion was missing. Five cross-channel leaks surfaced in:
- ProductService — bulk lookups that joined products across channel boundaries.
- AiBulkService — AI-enrichment batches into which a wrong channel could slip.
- OutputQueueService — exports that merged products from multiple channels into one feed.
- GuideController — editorial guides fetching products without a channel filter.
- ProductEnrichCommand — a CLI command operating across unintended channels.
The fix: make the wrong path impossible
The direct fix is to add channelId everywhere. The durable fix is different. At MFP the rule now is that all ProductRepository queries must carry a channelId criterion. No findBy(['ean' => $ean]) without a channel. No findBy(['id' => $ids]) without a channel. The repository is shaped so that the channel-less call yields a PHPStan error, or in stricter variants a type error. The wrong path becomes impossible to write, not merely discouraged.
The lesson for multi-anything systems
Cross-channel matching is a multi-tenant bug wearing a different uniform. Every codebase that one day moves from one to two tenants, channels, organisations or customers carries this risk — and most codebases only discover it when a customer screenshots a strange product page. The preventive work is cheap: a lint rule, a PHPStan rule, a review checklist. The reactive work is expensive: clearing data corruption, restoring customer trust, running retrospectives. Designing for N before there are 2 is the cheapest form of defensive engineering a multi-channel platform can afford.
Share this article
Curious what is possible?
One conversation is usually enough to tell whether it fits. A reply within 24 hours, and no middlemen.