From e9548d9f38787ce76ce8e5c1d7f8dc53a71d2415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Tue, 27 Jan 2026 15:14:01 +0100 Subject: [PATCH] feat: tracking integration with GTM and CMP --- .env | 3 - config/domain_config.yaml | 18 +++ config/services.yaml | 8 +- .../Booking/Create/Step4Controller.php | 5 +- .../Booking/Create/SuccessController.php | 7 +- src/EventListener/DomainThemeListener.php | 29 +--- src/Model/DomainConfig.php | 46 ++++++ src/Service/DomainConfigProvider.php | 75 +++++++++ src/Twig/AppExtension.php | 2 + src/Twig/AppRuntime.php | 33 +++- templates/_partials/_tracking.html.twig | 12 ++ templates/base.html.twig | 10 ++ .../booking/create/authenticate.html.twig | 4 + templates/booking/create/index.html.twig | 1 - templates/booking/create/step_1.html.twig | 4 + templates/booking/create/step_2.html.twig | 4 + templates/booking/create/step_3.html.twig | 4 + templates/booking/create/step_4.html.twig | 4 + templates/booking/create/success.html.twig | 19 +++ .../EventListener/DomainThemeListenerTest.php | 71 +++++++-- tests/Service/DomainConfigProviderTest.php | 145 ++++++++++++++++++ 21 files changed, 453 insertions(+), 51 deletions(-) create mode 100644 config/domain_config.yaml create mode 100644 src/Model/DomainConfig.php create mode 100644 src/Service/DomainConfigProvider.php create mode 100644 templates/_partials/_tracking.html.twig create mode 100644 tests/Service/DomainConfigProviderTest.php diff --git a/.env b/.env index 5fe9859..da5f393 100644 --- a/.env +++ b/.env @@ -85,6 +85,3 @@ OAUTH_ENCRYPTION_KEY=580084fd179e67399467f59ee96658ac # postgresql+advisory://db_user:db_password@localhost/db_name LOCK_DSN=flock ###< symfony/lock ### - -# Domain-to-theme mapping (JSON format) -DOMAIN_THEME_MAP='{"ski-boarderweek.de":"sbw","semesterende-skireisen.de":"ser","snowzone.net":"snz"}' diff --git a/config/domain_config.yaml b/config/domain_config.yaml new file mode 100644 index 0000000..b63abfc --- /dev/null +++ b/config/domain_config.yaml @@ -0,0 +1,18 @@ +parameters: + domain_config: + ep-reisen.de: + theme: base + gtm_id: GTM-56LWRF + cmp_url: https://consent.cookiefirst.com/sites/my.ep-reisen.de-28eabc35-7085-4d8f-b450-522bd1a5247c/consent.js + ski-boarderweek.de: + theme: sbw + gtm_id: GTM-MS2C8CH + cmp_url: https://consent.cookiefirst.com/sites/my.ski-boarderweek.de-d3641c34-8183-4b8d-b56e-c052e7a9223e/consent.js + semesterende-skireisen.de: + theme: ser + gtm_id: GTM-MNKDKL9 + cmp_url: https://consent.cookiefirst.com/sites/my.semesterende-skireisen.de-c9b3422d-eb62-4ce4-b8f7-91e9b509e26f/consent.js + snowzone.net: + theme: snz + gtm_id: GTM-M96CKL + cmp_url: https://consent.cookiefirst.com/sites/my.snowzone.net-8e6aea0c-7d93-42a0-a822-29fff2aa5613/consent.js diff --git a/config/services.yaml b/config/services.yaml index 18c05d8..c0dc233 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -3,13 +3,15 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration +imports: + - { resource: domain_config.yaml } + parameters: travel_info_base_url: '%env(APP_TRAVEL_INFO_BASE_URL)%' terms_and_conditions_url: '%env(APP_TERMS_AND_CONDITIONS_URL)%' path_to_keys: '%kernel.project_dir%/config/secret' bpn_debug: '%env(APP_BPN_DEBUG)%' default_booking_status: '%env(DEFAULT_BOOKING_STATUS)%' - domain_theme_map: '%env(json:DOMAIN_THEME_MAP)%' # Body dimensions choices for BodyDimensionsType body_dimensions.height_choices: @@ -147,7 +149,7 @@ services: tags: - { name: monolog.processor } - App\EventListener\DomainThemeListener: + App\Service\DomainConfigProvider: arguments: - $domainThemeMap: '%domain_theme_map%' + $domainConfig: '%domain_config%' diff --git a/src/Controller/Booking/Create/Step4Controller.php b/src/Controller/Booking/Create/Step4Controller.php index ff42a04..767d765 100644 --- a/src/Controller/Booking/Create/Step4Controller.php +++ b/src/Controller/Booking/Create/Step4Controller.php @@ -92,8 +92,11 @@ class Step4Controller extends AbstractController ); } - // Success: Store booking number in flash and clear session + // Success: Store booking data in flash for conversion tracking + $summaryData = $this->summaryDataService->getSummaryData($bookingCreateDto); $this->addFlash('booking_number', $bookingResponse->bookingNumber); + $this->addFlash('booking_total', $summaryData->payableAmount); + $this->addFlash('booking_travel_name', $bookingCreateDto->travel->label); $this->clearTravelDataCache($bookingCreateDto); $this->bookingService->clearBookingDto($request, BookingDto::MODE_CREATE); diff --git a/src/Controller/Booking/Create/SuccessController.php b/src/Controller/Booking/Create/SuccessController.php index 0f3b004..080f865 100644 --- a/src/Controller/Booking/Create/SuccessController.php +++ b/src/Controller/Booking/Create/SuccessController.php @@ -23,7 +23,10 @@ class SuccessController extends AbstractController #[Route('/bookings/create/success', name: 'app_booking_create_success')] public function success(Request $request): Response { - $bookingNumber = $request->getSession()->getFlashBag()->get('booking_number')[0] ?? null; + $flashBag = $request->getSession()->getFlashBag(); + $bookingNumber = $flashBag->get('booking_number')[0] ?? null; + $bookingTotal = $flashBag->get('booking_total')[0] ?? null; + $travelName = $flashBag->get('booking_travel_name')[0] ?? null; $returnUrl = $this->bookingService->getReturnUrl($request); // Redirect to return URL if no booking number (direct access or refresh) @@ -33,6 +36,8 @@ class SuccessController extends AbstractController return $this->render('booking/create/success.html.twig', [ 'bookingNumber' => $bookingNumber, + 'bookingTotal' => $bookingTotal, + 'travelName' => $travelName, 'returnUrl' => $returnUrl, ]); } diff --git a/src/EventListener/DomainThemeListener.php b/src/EventListener/DomainThemeListener.php index 1144794..4f65391 100644 --- a/src/EventListener/DomainThemeListener.php +++ b/src/EventListener/DomainThemeListener.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\EventListener; +use App\Service\DomainConfigProvider; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -12,13 +13,10 @@ use Symfony\Component\HttpKernel\KernelEvents; class DomainThemeListener { public const THEME_ATTRIBUTE = '_theme'; - private const DEFAULT_THEME = 'base'; + public const DOMAIN_CONFIG_ATTRIBUTE = '_domain_config'; - /** - * @param array $domainThemeMap - */ public function __construct( - private readonly array $domainThemeMap, + private readonly DomainConfigProvider $domainConfigProvider, ) { } @@ -29,24 +27,9 @@ class DomainThemeListener } $request = $event->getRequest(); - $host = $request->getHost(); + $config = $this->domainConfigProvider->getConfigForHost($request->getHost()); - $theme = $this->resolveTheme($host); - $request->attributes->set(self::THEME_ATTRIBUTE, $theme); - } - - private function resolveTheme(string $host): string - { - $host = strtolower($host); - - foreach ($this->domainThemeMap as $pattern => $theme) { - $pattern = strtolower($pattern); - - if ($host === $pattern || str_ends_with($host, '.' . $pattern)) { - return $theme; - } - } - - return self::DEFAULT_THEME; + $request->attributes->set(self::THEME_ATTRIBUTE, $config->theme); + $request->attributes->set(self::DOMAIN_CONFIG_ATTRIBUTE, $config); } } diff --git a/src/Model/DomainConfig.php b/src/Model/DomainConfig.php new file mode 100644 index 0000000..3f0bb2b --- /dev/null +++ b/src/Model/DomainConfig.php @@ -0,0 +1,46 @@ + + */ + private array $configCache = []; + + /** + * @param array $domainConfig + */ + public function __construct( + private readonly array $domainConfig, + private readonly RequestStack $requestStack, + ) { + } + + /** + * Returns the configuration for the current request host. + */ + public function getConfig(): DomainConfig + { + $request = $this->requestStack->getCurrentRequest(); + + if (null === $request) { + return DomainConfig::default(); + } + + return $this->getConfigForHost($request->getHost()); + } + + /** + * Returns the configuration for a specific host. + * + * @param string $host The host to resolve configuration for + */ + public function getConfigForHost(string $host): DomainConfig + { + $host = strtolower($host); + + if (isset($this->configCache[$host])) { + return $this->configCache[$host]; + } + + foreach ($this->domainConfig as $pattern => $config) { + $pattern = strtolower($pattern); + + if ($host === $pattern || str_ends_with($host, '.'.$pattern)) { + $domainConfig = DomainConfig::fromArray($config); + $this->configCache[$host] = $domainConfig; + + return $domainConfig; + } + } + + $this->configCache[$host] = DomainConfig::default(); + + return $this->configCache[$host]; + } +} diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index b12ada0..272f377 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -40,6 +40,8 @@ class AppExtension extends AbstractExtension new TwigFunction('is_hidden', [AppRuntime::class, 'isHidden']), new TwigFunction('collect_invalid_field_labels', [AppRuntime::class, 'collectInvalidFieldLabels']), new TwigFunction('booking_theme', [AppRuntime::class, 'getBookingTheme']), + new TwigFunction('gtm_id', [AppRuntime::class, 'getGtmId']), + new TwigFunction('cmp_url', [AppRuntime::class, 'getCmpUrl']), ]; } } diff --git a/src/Twig/AppRuntime.php b/src/Twig/AppRuntime.php index fbb6063..5033021 100644 --- a/src/Twig/AppRuntime.php +++ b/src/Twig/AppRuntime.php @@ -9,6 +9,7 @@ use App\EventListener\DomainThemeListener; use App\Form\Model\BookingDto; use App\Form\Service\CreateFieldStateProvider; use App\Form\Service\EditFieldStateProvider; +use App\Model\DomainConfig; use App\Service\ParticipantEligibilityService; use Symfony\Component\Form\FormView; use Symfony\Component\HttpFoundation\RequestStack; @@ -234,14 +235,42 @@ class AppRuntime implements RuntimeExtensionInterface * Defaults to 'base' if no theme is set. */ public function getBookingTheme(): string + { + return $this->getDomainConfig()->theme; + } + + /** + * Returns the GTM container ID for the current domain. + * + * Returns an empty string if no GTM ID is configured for this domain. + */ + public function getGtmId(): string + { + return $this->getDomainConfig()->gtmId; + } + + /** + * Returns the CMP (Consent Management Platform) URL for the current domain. + * + * Returns an empty string if no CMP URL is configured for this domain. + */ + public function getCmpUrl(): string + { + return $this->getDomainConfig()->cmpUrl; + } + + private function getDomainConfig(): DomainConfig { $request = $this->requestStack->getCurrentRequest(); if (null === $request) { - return 'base'; + return DomainConfig::default(); } - return $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE, 'base'); + return $request->attributes->get( + DomainThemeListener::DOMAIN_CONFIG_ATTRIBUTE, + DomainConfig::default() + ); } /** diff --git a/templates/_partials/_tracking.html.twig b/templates/_partials/_tracking.html.twig new file mode 100644 index 0000000..4cb59ee --- /dev/null +++ b/templates/_partials/_tracking.html.twig @@ -0,0 +1,12 @@ + diff --git a/templates/base.html.twig b/templates/base.html.twig index 5c5087d..d79303b 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -8,6 +8,16 @@ + {% if cmp_url() is not empty %} + + + {% endif %} {% block stylesheets %} {{ encore_entry_link_tags('app') }} {% endblock %} diff --git a/templates/booking/create/authenticate.html.twig b/templates/booking/create/authenticate.html.twig index a6db461..bead0eb 100644 --- a/templates/booking/create/authenticate.html.twig +++ b/templates/booking/create/authenticate.html.twig @@ -95,6 +95,10 @@ + {% include '_partials/_tracking.html.twig' with { + 'url': '/trichter_Z1/schritt0_login_oder_gast', + 'title': 'Login oder Gast' + } %} {% endblock %} {% block footer %} diff --git a/templates/booking/create/index.html.twig b/templates/booking/create/index.html.twig index a881359..23a71e2 100644 --- a/templates/booking/create/index.html.twig +++ b/templates/booking/create/index.html.twig @@ -6,7 +6,6 @@ date_id: params.dateId, hotel_id: params.hotelId, agency: params.agency, - theme: params.theme, r: params.returnUrl }) }}" hx-trigger="load" diff --git a/templates/booking/create/step_1.html.twig b/templates/booking/create/step_1.html.twig index 2ca7de2..e237321 100644 --- a/templates/booking/create/step_1.html.twig +++ b/templates/booking/create/step_1.html.twig @@ -101,4 +101,8 @@ {{ form_rest(form) }} {{ form_end(form) }} + {% include '_partials/_tracking.html.twig' with { + 'url': '/trichter_Z1/schritt1_zimmerauswahl', + 'title': 'Zimmerauswahl' + } %} {% endblock %} diff --git a/templates/booking/create/step_2.html.twig b/templates/booking/create/step_2.html.twig index b057df7..2cf0f98 100644 --- a/templates/booking/create/step_2.html.twig +++ b/templates/booking/create/step_2.html.twig @@ -75,4 +75,8 @@ {{ form_rest(form) }} {{ form_end(form) }} + {% include '_partials/_tracking.html.twig' with { + 'url': '/trichter_Z1/schritt2_teilnehmende_leistungen', + 'title': 'Teilnehmende und Leistungen' + } %} {% endblock %} diff --git a/templates/booking/create/step_3.html.twig b/templates/booking/create/step_3.html.twig index 32a9e71..5526322 100644 --- a/templates/booking/create/step_3.html.twig +++ b/templates/booking/create/step_3.html.twig @@ -85,4 +85,8 @@ {{ form_rest(form) }} {{ form_end(form) }} + {% include '_partials/_tracking.html.twig' with { + 'url': '/trichter_Z1/schritt3_zahlungsart', + 'title': 'Zahlungsart' + } %} {% endblock %} diff --git a/templates/booking/create/step_4.html.twig b/templates/booking/create/step_4.html.twig index 85501f3..a4637e8 100644 --- a/templates/booking/create/step_4.html.twig +++ b/templates/booking/create/step_4.html.twig @@ -567,4 +567,8 @@ {{ form_rest(form) }} {{ form_end(form) }} + {% include '_partials/_tracking.html.twig' with { + 'url': '/trichter_Z1/schritt4_uerbersicht_bestaetigung', + 'title': 'Übersicht und Bestätigung' + } %} {% endblock %} diff --git a/templates/booking/create/success.html.twig b/templates/booking/create/success.html.twig index 664d679..0be2e1f 100644 --- a/templates/booking/create/success.html.twig +++ b/templates/booking/create/success.html.twig @@ -55,4 +55,23 @@ + {% if bookingTotal is not null and travelName is not null %} + + {% endif %} {% endblock %} diff --git a/tests/EventListener/DomainThemeListenerTest.php b/tests/EventListener/DomainThemeListenerTest.php index 117c021..ceb1927 100644 --- a/tests/EventListener/DomainThemeListenerTest.php +++ b/tests/EventListener/DomainThemeListenerTest.php @@ -5,8 +5,11 @@ declare(strict_types=1); namespace App\Tests\EventListener; use App\EventListener\DomainThemeListener; +use App\Model\DomainConfig; +use App\Service\DomainConfigProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -21,9 +24,9 @@ class DomainThemeListenerTest extends TestCase public function testSetsThemeAttributeForMatchingDomain(): void { - $listener = new DomainThemeListener([ - 'sbw-reisen' => 'sbw', - 'ser-reisen' => 'ser', + $listener = $this->createListener([ + 'sbw-reisen.de' => ['theme' => 'sbw'], + 'ser-reisen.de' => ['theme' => 'ser'], ]); $request = Request::create('https://www.sbw-reisen.de/bookings/create'); @@ -34,10 +37,32 @@ class DomainThemeListenerTest extends TestCase $this->assertSame('sbw', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE)); } + public function testSetsDomainConfigAttribute(): void + { + $listener = $this->createListener([ + 'sbw-reisen.de' => [ + 'theme' => 'sbw', + 'gtm_id' => 'GTM-123', + 'cmp_url' => 'https://cmp.example.com', + ], + ]); + + $request = Request::create('https://www.sbw-reisen.de/bookings/create'); + $event = new RequestEvent($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST); + + $listener($event); + + $config = $request->attributes->get(DomainThemeListener::DOMAIN_CONFIG_ATTRIBUTE); + $this->assertInstanceOf(DomainConfig::class, $config); + $this->assertSame('sbw', $config->theme); + $this->assertSame('GTM-123', $config->gtmId); + $this->assertSame('https://cmp.example.com', $config->cmpUrl); + } + public function testSetsDefaultThemeForUnknownDomain(): void { - $listener = new DomainThemeListener([ - 'sbw-reisen' => 'sbw', + $listener = $this->createListener([ + 'sbw-reisen.de' => ['theme' => 'sbw'], ]); $request = Request::create('https://www.ep-reisen.de/bookings/create'); @@ -50,8 +75,8 @@ class DomainThemeListenerTest extends TestCase public function testMatchesPartialDomain(): void { - $listener = new DomainThemeListener([ - 'ser-reisen' => 'ser', + $listener = $this->createListener([ + 'ser-reisen.de' => ['theme' => 'ser'], ]); $request = Request::create('https://portal.ser-reisen.de/bookings'); @@ -64,8 +89,8 @@ class DomainThemeListenerTest extends TestCase public function testIgnoresSubRequests(): void { - $listener = new DomainThemeListener([ - 'sbw-reisen' => 'sbw', + $listener = $this->createListener([ + 'sbw-reisen.de' => ['theme' => 'sbw'], ]); $request = Request::create('https://www.sbw-reisen.de/bookings/create'); @@ -78,22 +103,23 @@ class DomainThemeListenerTest extends TestCase public function testFirstMatchWins(): void { - $listener = new DomainThemeListener([ - 'reisen' => 'generic', - 'sbw-reisen' => 'sbw', + // When a host matches multiple patterns, the first pattern in iteration order wins + $listener = $this->createListener([ + 'portal.example.com' => ['theme' => 'portal'], + 'example.com' => ['theme' => 'main'], ]); - $request = Request::create('https://www.sbw-reisen.de/bookings/create'); + $request = Request::create('https://deep.portal.example.com/bookings/create'); $event = new RequestEvent($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST); $listener($event); - $this->assertSame('generic', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE)); + $this->assertSame('portal', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE)); } public function testEmptyMapReturnsDefaultTheme(): void { - $listener = new DomainThemeListener([]); + $listener = $this->createListener([]); $request = Request::create('https://www.sbw-reisen.de/bookings/create'); $event = new RequestEvent($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST); @@ -105,8 +131,8 @@ class DomainThemeListenerTest extends TestCase public function testMatchesLocalhostForDevelopment(): void { - $listener = new DomainThemeListener([ - 'localhost' => 'sbw', + $listener = $this->createListener([ + 'localhost' => ['theme' => 'sbw'], ]); $request = Request::create('http://localhost/bookings/create'); @@ -116,4 +142,15 @@ class DomainThemeListenerTest extends TestCase $this->assertSame('sbw', $request->attributes->get(DomainThemeListener::THEME_ATTRIBUTE)); } + + /** + * @param array $domainConfig + */ + private function createListener(array $domainConfig): DomainThemeListener + { + $requestStack = new RequestStack(); + $provider = new DomainConfigProvider($domainConfig, $requestStack); + + return new DomainThemeListener($provider); + } } diff --git a/tests/Service/DomainConfigProviderTest.php b/tests/Service/DomainConfigProviderTest.php new file mode 100644 index 0000000..0446b61 --- /dev/null +++ b/tests/Service/DomainConfigProviderTest.php @@ -0,0 +1,145 @@ +createProvider([ + 'example.com' => [ + 'theme' => 'custom', + 'gtm_id' => 'GTM-123', + 'cmp_url' => 'https://cmp.example.com', + ], + ], 'example.com'); + + $config = $provider->getConfig(); + + $this->assertSame('custom', $config->theme); + $this->assertSame('GTM-123', $config->gtmId); + $this->assertSame('https://cmp.example.com', $config->cmpUrl); + } + + public function testReturnsConfigForSubdomainMatch(): void + { + $provider = $this->createProvider([ + 'example.com' => [ + 'theme' => 'custom', + 'gtm_id' => 'GTM-456', + 'cmp_url' => 'https://cmp.example.com', + ], + ], 'www.example.com'); + + $config = $provider->getConfig(); + + $this->assertSame('custom', $config->theme); + $this->assertSame('GTM-456', $config->gtmId); + } + + public function testReturnsDefaultConfigForUnknownDomain(): void + { + $provider = $this->createProvider([ + 'example.com' => [ + 'theme' => 'custom', + 'gtm_id' => 'GTM-123', + 'cmp_url' => 'https://cmp.example.com', + ], + ], 'unknown.com'); + + $config = $provider->getConfig(); + + $this->assertSame(DomainConfig::DEFAULT_THEME, $config->theme); + $this->assertSame('', $config->gtmId); + $this->assertSame('', $config->cmpUrl); + } + + public function testReturnsDefaultConfigWhenNoRequest(): void + { + $requestStack = new RequestStack(); + $provider = new DomainConfigProvider([], $requestStack); + + $config = $provider->getConfig(); + + $this->assertSame(DomainConfig::DEFAULT_THEME, $config->theme); + } + + public function testHandlesMissingConfigValues(): void + { + $provider = $this->createProvider([ + 'example.com' => [ + 'theme' => 'custom', + ], + ], 'example.com'); + + $config = $provider->getConfig(); + + $this->assertSame('custom', $config->theme); + $this->assertSame('', $config->gtmId); + $this->assertSame('', $config->cmpUrl); + } + + public function testCachesConfigForSameHost(): void + { + $provider = $this->createProvider([ + 'example.com' => [ + 'theme' => 'custom', + 'gtm_id' => 'GTM-123', + 'cmp_url' => 'https://cmp.example.com', + ], + ], 'example.com'); + + $config1 = $provider->getConfigForHost('example.com'); + $config2 = $provider->getConfigForHost('example.com'); + + $this->assertSame($config1, $config2); + } + + public function testFirstMatchWins(): void + { + // When a host matches multiple patterns, the first pattern in iteration order wins + // Here 'deep.portal.example.com' matches both 'portal.example.com' (via subdomain) + // and 'example.com' (via subdomain), but portal.example.com is checked first + $provider = $this->createProvider([ + 'portal.example.com' => ['theme' => 'portal'], + 'example.com' => ['theme' => 'main'], + ], 'deep.portal.example.com'); + + $config = $provider->getConfig(); + + $this->assertSame('portal', $config->theme); + } + + public function testCaseInsensitiveMatching(): void + { + $provider = $this->createProvider([ + 'Example.COM' => [ + 'theme' => 'custom', + ], + ], 'EXAMPLE.com'); + + $config = $provider->getConfig(); + + $this->assertSame('custom', $config->theme); + } + + /** + * @param array $domainConfig + */ + private function createProvider(array $domainConfig, string $host): DomainConfigProvider + { + $request = Request::create('https://'.$host.'/'); + $requestStack = new RequestStack(); + $requestStack->push($request); + + return new DomainConfigProvider($domainConfig, $requestStack); + } +}