From 0bdf38ece209c1982d8c8a278c93efa3fd2c2781 Mon Sep 17 00:00:00 2001 From: davirocha Date: Tue, 3 Feb 2026 10:28:36 -0300 Subject: [PATCH 1/2] chore: allow Symfony 7 compatibility --- composer.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 9382609..2d4ef07 100644 --- a/composer.json +++ b/composer.json @@ -12,14 +12,14 @@ } ], "require": { - "php": ">=8.1", - "symfony/http-foundation": "^6.1", - "symfony/http-kernel": "^6.1", - "symfony/dependency-injection": "^6.1", - "symfony/config": "^6.1", - "symfony/console": "^6.1", - "symfony/twig-bridge": "^6.1", - "symfony/yaml": "^6.1" + "php": ">=8.1", + "symfony/http-foundation": "^6.4 || ^7.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0", + "symfony/twig-bridge": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0" }, "require-dev": { "twig/twig": "~2.0|~3.0", From 30536d181ab29012ad23437d776a4d0edba24995 Mon Sep 17 00:00:00 2001 From: davirocha Date: Fri, 6 Feb 2026 08:50:32 -0300 Subject: [PATCH 2/2] fix: tests with setcookie refactor --- DependencyInjection/Configuration.php | 2 +- Resources/config/services.yml | 3 + Subscriber/FeatureFlagCookieSubscriber.php | 29 ++++++ .../PercentageCookieIntegrationTest.php | 97 +++++++++++++++++++ Tests/Toggle/Conditions/PercentageTest.php | 46 +++++---- Toggle/Conditions/Percentage.php | 27 ++++-- 6 files changed, 173 insertions(+), 31 deletions(-) create mode 100644 Subscriber/FeatureFlagCookieSubscriber.php create mode 100644 Tests/Integration/PercentageCookieIntegrationTest.php diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index c0b1654..1a00c42 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -12,7 +12,7 @@ class Configuration implements ConfigurationInterface /** * @return TreeBuilder */ - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('d_zunke_feature_flags'); $rootNode = $treeBuilder->getRootNode(); diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 7f9df98..1248552 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -66,3 +66,6 @@ services: - '@dz.feature_flags.conditions_bag' tags: - { name: console.command } + + DZunke\FeatureFlagsBundle\Subscriber\FeatureFlagCookieSubscriber: + tags: [ 'kernel.event_subscriber' ] diff --git a/Subscriber/FeatureFlagCookieSubscriber.php b/Subscriber/FeatureFlagCookieSubscriber.php new file mode 100644 index 0000000..f072f4b --- /dev/null +++ b/Subscriber/FeatureFlagCookieSubscriber.php @@ -0,0 +1,29 @@ + 'onKernelResponse', + ]; + } + + public function onKernelResponse(ResponseEvent $event): void + { + $request = $event->getRequest(); + + if (!$request->attributes->has('_feature_flag_cookie')) { + return; + } + + $cookie = $request->attributes->get('_feature_flag_cookie'); + $event->getResponse()->headers->setCookie($cookie); + } +} diff --git a/Tests/Integration/PercentageCookieIntegrationTest.php b/Tests/Integration/PercentageCookieIntegrationTest.php new file mode 100644 index 0000000..8d8308a --- /dev/null +++ b/Tests/Integration/PercentageCookieIntegrationTest.php @@ -0,0 +1,97 @@ +requestStack = new RequestStack(); + $this->percentage = new Percentage($this->requestStack); + $this->subscriber = new FeatureFlagCookieSubscriber(); + } + + public function testItSetsCookieWhenCookieDoesNotExist(): void + { + $request = new Request(); + $this->requestStack->push($request); + + $config = [ + 'percentage' => 100, + 'cookie' => '_feature_flag_cookie', + 'lifetime' => 3600, + ]; + + $result = $this->percentage->validate($config); + + self::assertTrue($result); + + self::assertTrue($request->attributes->has('_feature_flag_cookie')); + + $cookie = $request->attributes->get('_feature_flag_cookie'); + self::assertInstanceOf(Cookie::class, $cookie); + self::assertSame('_feature_flag_cookie', $cookie->getName()); + self::assertSame('1', $cookie->getValue()); + + $response = new Response(); + + $event = new ResponseEvent( + $this->createMock(HttpKernelInterface::class), + $request, + HttpKernelInterface::MAIN_REQUEST, + $response + ); + + $this->subscriber->onKernelResponse($event); + + $cookies = $response->headers->getCookies(); + self::assertCount(1, $cookies); + self::assertSame('_feature_flag_cookie', $cookies[0]->getName()); + } + + public function testItReturnsCookieValueWhenCookieAlreadyExists(): void + { + $request = new Request(); + $request->cookies->set('_feature_flag_cookie', '1'); + $this->requestStack->push($request); + + $config = [ + 'percentage' => 0, + 'cookie' => '_feature_flag_cookie', + ]; + + $result = $this->percentage->validate($config); + + self::assertTrue($result); + + self::assertFalse($request->attributes->has('_feature_flag_cookie')); + + $response = new Response(); + + $event = new ResponseEvent( + $this->createMock(HttpKernelInterface::class), + $request, + HttpKernelInterface::MAIN_REQUEST, + $response + ); + + $this->subscriber->onKernelResponse($event); + + self::assertCount(0, $response->headers->getCookies()); + } +} diff --git a/Tests/Toggle/Conditions/PercentageTest.php b/Tests/Toggle/Conditions/PercentageTest.php index 6753712..56527c2 100644 --- a/Tests/Toggle/Conditions/PercentageTest.php +++ b/Tests/Toggle/Conditions/PercentageTest.php @@ -41,38 +41,44 @@ public function testItThrowsExceptionWhenPercentageIsNotSet() $sut = new Percentage($this->requestStackMock); $sut->validate([], 'nothing'); } - - public function testItReturnsTrueWhenCookieIsAlreadySet() + public function testItReturnsTrueWhenCookieIsAlreadySet(): void { - $parameterBagMock = $this->createMock(ParameterBag::class); - $parameterBagMock->method('has')->willReturn(true); - $parameterBagMock->method('get')->willReturn(1); - - $requestMock = $this->createMock(Request::class); - $requestMock->cookies = $parameterBagMock; + $request = new Request( + cookies: [ + 'percentage' => 1, + ] + ); $requestStackMock = $this->createMock(RequestStack::class); - $requestStackMock->method('getMainRequest')->willReturn($requestMock); + $requestStackMock + ->method('getMainRequest') + ->willReturn($request); $sut = new Percentage($requestStackMock); - $this->assertTrue($sut->validate([ - 'percentage' => 798, - ])); + + $this->assertTrue( + $sut->validate([ + 'percentage' => 798, + ]) + ); } - public function testItReturnsBoolWhenCookieIsNotSet() + public function testItReturnsBoolWhenCookieIsNotSet(): void { - $parameterBagMock = $this->createMock(ParameterBag::class); - $parameterBagMock->method('has')->willReturn(true); - - $requestMock = $this->createMock(Request::class); - $requestMock->cookies = $parameterBagMock; + $request = new Request(cookies: []); $requestStackMock = $this->createMock(RequestStack::class); - $requestStackMock->method('getMainRequest')->willReturn($requestMock); + $requestStackMock + ->method('getMainRequest') + ->willReturn($request); $sut = new Percentage($requestStackMock); - self::assertIsBool($sut->validate(['percentage' => 3])); + + self::assertIsBool( + $sut->validate([ + 'percentage' => 3, + ]) + ); } public function testToString() diff --git a/Toggle/Conditions/Percentage.php b/Toggle/Conditions/Percentage.php index f8da3e1..d6f207f 100644 --- a/Toggle/Conditions/Percentage.php +++ b/Toggle/Conditions/Percentage.php @@ -2,6 +2,7 @@ namespace DZunke\FeatureFlagsBundle\Toggle\Conditions; +use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -23,26 +24,32 @@ public function __construct(RequestStack $requestStack) /** * @param mixed $config - * @param null $argument + * @param null $argument * @return bool * @throws \Exception */ - public function validate($config, $argument = null) + + public function validate($config, $argument = null): bool { $config = $this->formatConfig($config); + $request = $this->requestStack->getMainRequest(); - if ($this->requestStack->getMainRequest()->cookies->has($config['cookie'])) { - return (bool)$this->requestStack->getMainRequest()->cookies->get($config['cookie']); + if ($request?->cookies->has($config['cookie'])) { + return (bool) $request->cookies->get($config['cookie']); } - $value = (int)($this->generateRandomNumber() < $config['percentage']); - setcookie( - $config['cookie'], - $value, - time() + $config['lifetime'] + $value = (int) ($this->generateRandomNumber() < $config['percentage']); + + $request?->attributes->set( + '_feature_flag_cookie', + new Cookie( + $config['cookie'], + (string)$value, + time() + $config['lifetime'] + ) ); - return (bool)$value; + return (bool) $value; } private function formatConfig($config)