Dit Petje – Multi-brand productontdekkingsplatform

Eén Symfony-codebase, drie merken: Dit Petje, Dit Horloge en Dit Parfum

Dit Petje

Drie schermafdrukken naast elkaar: de startpagina's van Dit Petje, Dit Horloge en Dit Parfum — dezelfde codebase, drie merken.

Over het platform

Dit Petje is een multi-channel productontdekkings­platform waarmee bezoekers per categorie producten doorzoeken, vergelijken en doorklikken naar aanbieders. Onder de motorkap draait MFP (Multi Feed Processing): één Symfony-codebase die drie consumentenmerken bedient — ditpetje.nl (petten en hoeden), dithorloge.nl (horloges) en ditparfum.nl (parfum) — en architectonisch is opgezet om N kanalen aan te kunnen via dynamic discovery (geen hardcoded limiet).

Samenwerking tussen merk en bouwer

Concept, marketing en innovatieplannen komen vanuit ten Bruggencate Marketing; ontwikkeling, doorontwikkeling en beheer nemen wij voor onze rekening. De rolverdeling zorgt ervoor dat marktinzicht en techniek hand-in-hand evolueren zonder dat één discipline de ander blokkeert.

Architectuur in één zin

Feed-bronnen → MFP Dashboard → kanaal-sites → gekoppelde socials. Producten worden centraal ingelezen, per kanaal gefilterd en gescoord, en pas getoond als ze door de display-gates komen.

Vijf feed-bronnen, één pijplijn

  • Awin (6 uur), Bol.com (12 uur), Amazon (24 uur), TradeTracker (12 uur) en Zalando (12 uur) — elke bron heeft zijn eigen schedule, validators en feed-status (active / blocked / paused).
  • Een global-sync orchestrator (feed:sync:global) downloadt elke bron één keer en routeert producten naar alle relevante kanalen — voorkomt dubbele downloads bij groei naar meer kanalen.
  • Per-bron parsing via een gedeelde FeedServiceTrait met centrale relevantie-detectie (Nederlandse compound-keywords als sportpet, winterpet worden expliciet ondersteund) — geen knip-en-plak per bron.
  • Geblokkeerde feeds markeren bestaande producten automatisch als niet-beschikbaar; gepauzeerde feeds skippen sync zonder data te verliezen.

Strikte kanaalscheiding

  • EAN-lookups inclusief channelId — voorkomt cross-channel matching wanneer hetzelfde product op meerdere kanalen verschijnt.
  • Per-channel config in channel.{id}.yaml — eigen prijs-floor/ceiling, keyword-allowlists, display-thresholds en validatieregels.
  • Per-channel Matomo site-IDs — analytics blijft schoon en aggregatie per merk klopt.
  • Display-gates per kanaal — min_display_score en require_image_for_display bepalen of een product zichtbaar wordt; admin-views zien alles, frontend alleen het kwaliteitsfilter.
  • Composite scoring combineert prijs, beschikbaarheid, beeld en feed-betrouwbaarheid tot één rankingscore per product per kanaal.

Drie merken, drie design-systemen

Elk merk heeft zijn eigen brand-design-system (kleur, typografie, tone) — geserveerd vanuit dezelfde Symfony-applicatie via een ChannelService die templates, routes en configuratie scopt. Brand-namen, domein-namen en channel-defaults worden nooit hardcoded; alles loopt via een channel-context. Dat maakt het toevoegen van een vierde merk een config-actie, geen rewrite.

Redactie en SEO/GEO/WCAG

  • Redactionele guides per merk met kanaalspecifieke auteur-defaults — beheerd in een eigen admin-omgeving.
  • Product structured data met offers, price, priceCurrency en availability; producten zonder afbeelding krijgen géén schema (Google-vereiste).
  • CLS-bewuste typografie — Inter met display=optional, icoonfont met expliciete dimensies tegen layout-shift.
  • Mobile-first ontwerp- en build-discipline op consumer-pages; SEO, GEO en WCAG worden tijdens ontwikkeling toegepast, niet achteraf.
  • Pinterest-export-feed en social-koppelingen per kanaal — content stroomt vanaf de site naar de connected socials.

Operationele weerbaarheid

  • Auto-maintenance — kanalen met nul beschikbare producten gaan automatisch in 503-modus (60s gecached); voorkomt lege productpagina's tijdens import-gaps.
  • /health endpoint dat altijd 200 JSON teruggeeft, gebruikt voor Docker-healthchecks (skipt maintenance- en rate-limit-subscribers).
  • Redis voor sessies én voor de live activity-feed in het dashboard — graceful degradation als Redis tijdelijk wegvalt.
  • Async messenger (twee transports: feed_sync + enrichment) houdt feed-imports en AI-verrijking buiten de request-cycle.
  • HTTP-smoke-tests per kanaal-domein als verplicht onderdeel van de release-flow — PHPUnit alleen dekt geen Traefik/Apache-config-issues.

Techniek en kwaliteit

  • Symfony 7.4 op PHP 8.3, Doctrine ORM, Twig 3.23 en Bootstrap 4.6 — productieversie v3.15.0.
  • 1693 tests / 4159 assertions via PHPUnit 11; PHPStan level 6 met 0-error-baseline over 439 bestanden; PHP-CS-Fixer (PER-CS2) en phplint per release.
  • Bitbucket Pipelines draait phplint, CS-Fixer, PHPStan en PHPUnit op elke master-merge en deployt vervolgens via SSH.
  • Docker + Traefik dev- en productiestack; MySQL met custom config; OPcache afgesteld op de codebase (14k+ files, max_accelerated_files=20000) tegen silent recompilation.

Resultaat

Drie merken, één onderhoudbare codebase, een gestage release-cadans (huidige major al op v3.x) en directe lijnen tussen marketingstrategie en technische uitvoering. Dit Petje fungeert ook als proeftuin voor patronen die terugkeren in opdrachtwerk — zoals de multi-brand-aanpak bij Stenenwinkeltje en in de Shopware Plugin Suite.

Benieuwd wat er mogelijk is?

Eén gesprek is vaak genoeg om te weten of het past. Binnen 24 uur een reactie, en zonder tussenpersonen.

  • Reactie binnen 24 uur, altijd persoonlijk
  • Na een half uur bellen weet u de orde van grootte
  • Vrijblijvend eerste gesprek, geen verplichtingen
  • Ook voor bestaande software van een andere partij