From ab2f04bac5fb08f8d860ede155a89c5b51bfafe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joachim=20L=C3=B8vgaard?= Date: Mon, 7 Sep 2026 13:55:31 +0200 Subject: [PATCH] Tidy the public API before 1.0 The Roave BC check will lock this surface in, so decide on the parts that were public but unused. Keep the event context and document it as the way to pass data to your own listeners, adding a getContext() accessor; keep CommandInterface and document routing by interface; make the event properties readonly; and drop the two container parameters no service reads. Fixes #27 --- README.md | 17 +++++++++++++++++ UPGRADE.md | 9 +++++++++ .../SetonoMetaConversionsApiExtension.php | 2 -- src/Event/ConversionsApiEventRaised.php | 13 +++++++++++-- src/Message/Command/CommandInterface.php | 8 ++++++++ .../SetonoMetaConversionsApiExtensionTest.php | 2 -- .../Event/ConversionsApiEventRaisedTest.php | 10 ++++++++++ 7 files changed, 55 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 418771a..c1def47 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,9 @@ framework: 'Setono\MetaConversionsApiBundle\Message\Command\SendEvent': async ``` +Every command the bundle dispatches implements +`Setono\MetaConversionsApiBundle\Message\Command\CommandInterface`, so you can route them as a group instead. + With a transport, Messenger also retries a failed send and moves it to the failure transport when it keeps failing. What ends up in the transport is the finished payload: the user data is already normalised and hashed by the SDK, and @@ -249,6 +252,20 @@ final class AddCustomerToConversionsApiEvent You can also replace a step instead of adding to it: alias `PixelProviderInterface`, `FbpContextInterface` or `FbcContextInterface` to your own service, or register a listener above the corresponding populate priority. +### Passing context to your own listeners + +The second constructor argument of `ConversionsApiEventRaised` carries anything your listeners need but that must +never be sent to Meta, such as the order the event was raised for. The bundle never reads it. + +```php +$this->eventDispatcher->dispatch(new ConversionsApiEventRaised($event, ['order' => $order])); + +// in a listener +if ($event->hasContext('order')) { + $order = $event->getContext('order'); +} +``` + ### Why did my event not show up? Every listener that drops an event says so at debug level on the `setono_meta_conversions_api` Monolog channel: the diff --git a/UPGRADE.md b/UPGRADE.md index cacb97b..655d82b 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -75,6 +75,15 @@ anything to `framework.messenger`. `SendEvent` is dispatched on your application `?ConsentContextInterface $consentContext` and `bool $consentEnabled` / `bool $clientSideEnabled` / `bool $serverSideEnabled` arguments. Adapt subclasses, decorators and custom service definitions. +## Removed container parameters + +`setono_meta_conversions_api.client_side.enabled` and `setono_meta_conversions_api.server_side.enabled` are gone. No +service used them once the subscribers became conditional, and whether a side is enabled is visible from whether its +services exist. The `consent.*`, `pixels`, `cookies.*`, `filters.*` and `test_event_code.*` parameters are unchanged. + +`ConversionsApiEventRaised::$event` and `::$context` are now `readonly`. The `Event` object itself stays mutable, +which is what enrichment listeners need, but the properties can no longer be swapped out. `getContext()` is new. + ## Cookies New `cookies` options: diff --git a/src/DependencyInjection/SetonoMetaConversionsApiExtension.php b/src/DependencyInjection/SetonoMetaConversionsApiExtension.php index 49aff52..37f9102 100644 --- a/src/DependencyInjection/SetonoMetaConversionsApiExtension.php +++ b/src/DependencyInjection/SetonoMetaConversionsApiExtension.php @@ -34,8 +34,6 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('setono_meta_conversions_api.consent.enabled', $config['consent']['enabled']); $container->setParameter('setono_meta_conversions_api.consent.category', $config['consent']['category']); - $container->setParameter('setono_meta_conversions_api.client_side.enabled', $config['client_side']['enabled']); - $container->setParameter('setono_meta_conversions_api.server_side.enabled', $config['server_side']['enabled']); $container->setParameter('setono_meta_conversions_api.pixels', $config['pixels']); $container->setParameter('setono_meta_conversions_api.filters.user_agent', $config['filters']['user_agent']); diff --git a/src/Event/ConversionsApiEventRaised.php b/src/Event/ConversionsApiEventRaised.php index 1d914aa..c708b1b 100644 --- a/src/Event/ConversionsApiEventRaised.php +++ b/src/Event/ConversionsApiEventRaised.php @@ -49,9 +49,13 @@ final class ConversionsApiEventRaised extends StoppableEvent public const PRIORITY_SEND = -1000; /** - * @param array $context + * @param Event $event The event that will be rendered client side and sent server side. Listeners are expected + * to mutate it, which is how enrichment works + * @param array $context Anything your own listeners need but that must not be sent to Meta, for + * instance the order or the customer the event was raised for. The bundle + * never reads it */ - public function __construct(public Event $event, public array $context = []) + public function __construct(public readonly Event $event, public readonly array $context = []) { } @@ -59,4 +63,9 @@ public function hasContext(string $key): bool { return array_key_exists($key, $this->context); } + + public function getContext(string $key, mixed $default = null): mixed + { + return $this->context[$key] ?? $default; + } } diff --git a/src/Message/Command/CommandInterface.php b/src/Message/Command/CommandInterface.php index bce05c8..4e19073 100644 --- a/src/Message/Command/CommandInterface.php +++ b/src/Message/Command/CommandInterface.php @@ -4,6 +4,14 @@ namespace Setono\MetaConversionsApiBundle\Message\Command; +/** + * Implemented by every command the bundle dispatches, so they can be routed as a group: + * + * framework: + * messenger: + * routing: + * 'Setono\MetaConversionsApiBundle\Message\Command\CommandInterface': async + */ interface CommandInterface { } diff --git a/tests/Integration/DependencyInjection/SetonoMetaConversionsApiExtensionTest.php b/tests/Integration/DependencyInjection/SetonoMetaConversionsApiExtensionTest.php index ea945dc..6b0db05 100644 --- a/tests/Integration/DependencyInjection/SetonoMetaConversionsApiExtensionTest.php +++ b/tests/Integration/DependencyInjection/SetonoMetaConversionsApiExtensionTest.php @@ -43,8 +43,6 @@ public function it_sets_parameters(): void $this->assertContainerBuilderHasParameter('setono_meta_conversions_api.consent.enabled', false); $this->assertContainerBuilderHasParameter('setono_meta_conversions_api.consent.category', DefaultConsents::CONSENT_MARKETING); - $this->assertContainerBuilderHasParameter('setono_meta_conversions_api.client_side.enabled', true); - $this->assertContainerBuilderHasParameter('setono_meta_conversions_api.server_side.enabled', true); $this->assertContainerBuilderHasParameter('setono_meta_conversions_api.pixels', []); $this->assertContainerBuilderHasParameter('setono_meta_conversions_api.filters.user_agent', []); $this->assertContainerBuilderHasParameter('setono_meta_conversions_api.test_event_code.value', null); diff --git a/tests/Unit/Event/ConversionsApiEventRaisedTest.php b/tests/Unit/Event/ConversionsApiEventRaisedTest.php index 049f303..a555e32 100644 --- a/tests/Unit/Event/ConversionsApiEventRaisedTest.php +++ b/tests/Unit/Event/ConversionsApiEventRaisedTest.php @@ -51,6 +51,16 @@ public function it_has_context(): void self::assertFalse($event->hasContext('customer')); } + #[Test] + public function it_returns_context(): void + { + $event = new ConversionsApiEventRaised(new Event(Event::EVENT_VIEW_CONTENT), ['order' => 1]); + + self::assertSame(1, $event->getContext('order')); + self::assertNull($event->getContext('customer')); + self::assertSame('fallback', $event->getContext('customer', 'fallback')); + } + /** * The documented pipeline only holds as long as the bundle's own listeners keep their relative order, so this * pins it down. It is the contract integrators position their own listeners against