diff --git a/UPGRADE.md b/UPGRADE.md index 8cd08b8..f48cf86 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -75,6 +75,12 @@ 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. +## Exception types + +Enabling client side tracking without the tag bag bundle now throws `\LogicException` instead of Webmozart's +`\InvalidArgumentException`, which is what Symfony uses for "this bundle needs that bundle". Adjust your test if you +asserted on the old type. + ## Failures no longer propagate `DispatchOnCommandBusSubscriber` catches and logs anything thrown while dispatching, at error level on the diff --git a/composer.json b/composer.json index 42004de..3b3ad26 100644 --- a/composer.json +++ b/composer.json @@ -19,12 +19,11 @@ "symfony/config": "^6.4 || ^7.4", "symfony/dependency-injection": "^6.4 || ^7.4", "symfony/event-dispatcher": "^6.4 || ^7.4", - "symfony/event-dispatcher-contracts": "^2.5 || ^3.0", + "symfony/event-dispatcher-contracts": "^3.0", "symfony/http-foundation": "^6.4 || ^7.4", "symfony/http-kernel": "^6.4 || ^7.4", "symfony/messenger": "^6.4 || ^7.4", - "symfony/service-contracts": "^2.5 || ^3.0", - "webmozart/assert": "^1.11" + "symfony/service-contracts": "^2.5 || ^3.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.50", @@ -48,6 +47,12 @@ "symfony/http-client": "^6.4 || ^7.4", "symfony/twig-bundle": "^6.4 || ^7.4" }, + "suggest": { + "nyholm/psr7": "A PSR-17 implementation, needed by the SDK unless your application already provides one", + "setono/consent-bundle": "Handle cookie/GDPR consent, enabling the consent option", + "setono/tag-bag-bundle": "Render the Meta pixel and fbq() calls in the browser, enabling client side tracking", + "symfony/http-client": "A PSR-18 implementation, needed by the SDK unless your application already provides one" + }, "prefer-stable": true, "autoload": { "psr-4": { diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 6bfff6a..d0e4401 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -4,8 +4,6 @@ namespace Setono\MetaConversionsApiBundle\DependencyInjection; -use Composer\InstalledVersions; -use Composer\Semver\VersionParser; use Setono\Consent\DefaultConsents; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -39,7 +37,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->arrayNode('client_side') ->info('Configuration for client side tracking'); - if (self::isTagBagBundleInstalled()) { + if (InstalledBundles::hasTagBagBundle()) { $clientSide->canBeDisabled(); } else { $clientSide->canBeEnabled(); @@ -106,10 +104,4 @@ public function getConfigTreeBuilder(): TreeBuilder return $treeBuilder; } - - private static function isTagBagBundleInstalled(): bool - { - return InstalledVersions::isInstalled('setono/tag-bag-bundle') && - InstalledVersions::satisfies(new VersionParser(), 'setono/tag-bag-bundle', '^3.0'); - } } diff --git a/src/DependencyInjection/InstalledBundles.php b/src/DependencyInjection/InstalledBundles.php new file mode 100644 index 0000000..e2a46ee --- /dev/null +++ b/src/DependencyInjection/InstalledBundles.php @@ -0,0 +1,29 @@ +hasParameter('kernel.bundles')) { + throw new \LogicException('The kernel.bundles parameter has not been set. Are you not using this in a Symfony application context?'); + } + + $bundles = $container->getParameter('kernel.bundles'); + if (!is_array($bundles) || !array_key_exists('SetonoTagBagBundle', $bundles)) { + throw new \LogicException('The SetonoTagBagBundle is not in the list of enabled bundles. ' . $requirement); + } + } + /** * @param array $config */ @@ -54,16 +78,7 @@ public function load(array $configs, ContainerBuilder $container): void } if ($config['client_side']['enabled']) { - $exceptionMessage = 'You need to install the setono/tag-bag-bundle ^3.0 to use the client side tracking'; - - Assert::true($container->hasParameter('kernel.bundles'), 'The kernel.bundles parameter has not been set. Are you not using this in a Symfony application context?'); - - $bundles = $container->getParameter('kernel.bundles'); - Assert::isArray($bundles); - Assert::keyExists($bundles, 'SetonoTagBagBundle', 'The SetonoTagBagBundle is not in the list of enabled bundles. ' . $exceptionMessage); - - Assert::true(InstalledVersions::isInstalled('setono/tag-bag-bundle'), $exceptionMessage); - Assert::true(InstalledVersions::satisfies(new VersionParser(), 'setono/tag-bag-bundle', '^3.0'), $exceptionMessage); + self::assertTagBagBundleIsAvailable($container); $loader->load('services/conditional/client_side.xml'); } diff --git a/tests/Integration/DependencyInjection/SetonoMetaConversionsApiExtensionTest.php b/tests/Integration/DependencyInjection/SetonoMetaConversionsApiExtensionTest.php index 6c1075e..a6883c3 100644 --- a/tests/Integration/DependencyInjection/SetonoMetaConversionsApiExtensionTest.php +++ b/tests/Integration/DependencyInjection/SetonoMetaConversionsApiExtensionTest.php @@ -16,6 +16,7 @@ use Setono\MetaConversionsApiBundle\EventSubscriber\StoreTestEventCodeSubscriber; use Setono\TagBagBundle\SetonoTagBagBundle; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\ContainerBuilder; #[CoversClass(SetonoMetaConversionsApiExtension::class)] final class SetonoMetaConversionsApiExtensionTest extends AbstractExtensionTestCase @@ -58,6 +59,29 @@ public function it_loads_client_side_event_subscribers_when_client_side_is_enabl $this->assertContainerBuilderHasService(AddLibraryToTagBagSubscriber::class); } + #[Test] + public function it_explains_itself_when_used_outside_a_symfony_application(): void + { + // A bare ContainerBuilder has no kernel.bundles, so the bundle cannot tell whether the tag bag bundle is + // registered. Symfony's convention for "this needs that" is a LogicException + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('kernel.bundles'); + + (new SetonoMetaConversionsApiExtension())->load([['client_side' => true]], new ContainerBuilder()); + } + + #[Test] + public function it_explains_itself_when_the_tag_bag_bundle_is_not_registered(): void + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.bundles', []); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('SetonoTagBagBundle is not in the list of enabled bundles'); + + (new SetonoMetaConversionsApiExtension())->load([['client_side' => true]], $container); + } + #[Test] public function it_does_not_load_client_side_event_subscribers_when_client_side_is_disabled(): void { diff --git a/tests/Integration/SetonoMetaConversionsApiBundleTest.php b/tests/Integration/SetonoMetaConversionsApiBundleTest.php index b672db7..82cdfed 100644 --- a/tests/Integration/SetonoMetaConversionsApiBundleTest.php +++ b/tests/Integration/SetonoMetaConversionsApiBundleTest.php @@ -71,7 +71,7 @@ protected static function createKernel(array $options = []): KernelInterface #[Test] public function it_throws_exception_if_client_side_is_enabled_but_tag_bag_is_not_enabled(): void { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(\LogicException::class); self::bootKernel(); }