From 0d213c40ec3b4cdcc52c227a85ef0b68459ecea8 Mon Sep 17 00:00:00 2001 From: chadha Date: Thu, 6 Feb 2020 16:49:28 +0100 Subject: [PATCH 1/4] RadioVoter fix psalm --- Voters/RatioVoter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Voters/RatioVoter.php b/Voters/RatioVoter.php index 4e0a5cc..365361d 100644 --- a/Voters/RatioVoter.php +++ b/Voters/RatioVoter.php @@ -23,7 +23,7 @@ class RatioVoter implements VoterInterface use VoterTrait; /** - * @var SessionInterface + * @var SessionInterface|null */ protected $session; @@ -40,7 +40,7 @@ class RatioVoter implements VoterInterface /** * @param SessionInterface|null $session */ - public function __construct(SessionInterface $session = null) + public function __construct(?SessionInterface $session = null) { $this->session = $session; } From cc05e3a834ad462d081db06a78dd7a781054d369 Mon Sep 17 00:00:00 2001 From: Sebastian Becker Date: Fri, 4 Feb 2022 11:39:12 +0100 Subject: [PATCH 2/4] Update to Symfony 6 and PHP 8 Added: - added support for PHP 8 and 8.1 - added support for Symfony 5.4 and 6 - added use of attributes Changed: - teardown in ControllerListenerTest More setup than teardown Removed: - removed support for PHP 7 and lower - removed support for Symfony 3 and 4 - removed annotations --- .gitignore | 2 - {Configuration => Attributes}/Feature.php | 20 ++-- CHANGELOG.md | 18 ++++ DependencyInjection/Configuration.php | 19 ++-- .../EcnFeatureToggleExtension.php | 11 ++- EventListener/ControllerListener.php | 64 ++++++------- Exception/VoterNotFoundException.php | 4 +- README.md | 15 ++- Service/FeatureService.php | 28 +----- Tests/EcnFeatureToggleBundleTest.php | 13 +-- .../EventListener/ControllerListenerTest.php | 94 +++++-------------- .../Fixture/FooControllerFeatureAtClass.php | 6 +- .../FooControllerFeatureAtClassAndMethod.php | 10 +- .../Fixture/FooControllerFeatureAtInvoke.php | 7 +- .../Fixture/FooControllerFeatureAtMethod.php | 6 +- .../FooControllerFeatureAtStaticMethod.php | 6 +- .../Fixture/FooControllerFeatureAtClass.php | 2 - Tests/Service/FeatureServiceTest.php | 18 ++-- Tests/Twig/FeatureToggleExtensionTest.php | 11 ++- Tests/Twig/FeatureToggleNodeTest.php | 9 +- Tests/Twig/IntegrationTest.php | 19 +++- Tests/Voters/RatioVoterTest.php | 26 ++--- Tests/Voters/RequestHeaderVoterTest.php | 10 +- Tests/Voters/ScheduleVoterTest.php | 9 +- Tests/Voters/VoterRegistryTest.php | 3 +- Twig/FeatureToggleExtension.php | 8 +- Voters/RatioVoter.php | 23 ++--- Voters/RequestHeaderVoter.php | 21 +---- Voters/ScheduleVoter.php | 17 ++-- Voters/VoterInterface.php | 1 - Voters/VoterRegistry.php | 4 +- Voters/VoterTrait.php | 5 +- composer.json | 26 +++-- phpunit.xml.dist | 28 +++--- psalm.without_twig.xml | 2 - psalm.xml | 2 - 36 files changed, 249 insertions(+), 318 deletions(-) rename {Configuration => Attributes}/Feature.php (58%) diff --git a/.gitignore b/.gitignore index ee2b4cc..6189702 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,5 @@ build/ vendor/ composer.lock phpunit.xml -/.idea /ECNFeatureToggleBundle.iml -/.idea/ /.phpunit.result.cache diff --git a/Configuration/Feature.php b/Attributes/Feature.php similarity index 58% rename from Configuration/Feature.php rename to Attributes/Feature.php index feadd31..b1c651b 100644 --- a/Configuration/Feature.php +++ b/Attributes/Feature.php @@ -10,26 +10,24 @@ * file that was distributed with this source code. */ -namespace Ecn\FeatureToggleBundle\Configuration; +namespace Ecn\FeatureToggleBundle\Attributes; -use Doctrine\Common\Annotations\Annotation\Required; -use Doctrine\Common\Annotations\Annotation\Target; +use Attribute; +use Symfony\Contracts\Service\Attribute\Required; /** * @author Márk Sági-Kazár - * - * @Annotation() - * - * @Target({"CLASS", "METHOD"}) */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] class Feature { /** * Feature name to be checked. * - * @var string - * - * @Required() + * @param string $name */ - public $name = ""; + #[Required] + public function __construct(public string $name = "") + { + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b8d5aa..1149032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,24 @@ # Change Log +## 3.0.0 - 2022-02-01 + +### Added + +- added support for PHP 8 and 8.1 +- added support for Symfony 5.4 and 6 +- added use of attributes + +### Changed + +- ControllerFilterEvent to ControllerEvent + +### Removed + +- removed support for PHP 7 and lower +- removed support for Symfony 3 and 4 +- removed annotations + ## 2.0.0 - 2019-12-25 ### Added diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 7fb265b..08f8419 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -12,6 +12,7 @@ namespace Ecn\FeatureToggleBundle\DependencyInjection; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -26,24 +27,18 @@ class Configuration implements ConfigurationInterface { /** * {@inheritdoc} - * - * @psalm-suppress PossiblyNullReference - * @psalm-suppress PossiblyUndefinedMethod - * @psalm-suppress RedundantCondition - * @psalm-suppress UndefinedMethod */ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('ecn_feature_toggle'); - if (method_exists($treeBuilder, 'getRootNode')) { - $rootNode = $treeBuilder->getRootNode(); - } else { - /** @psalm-suppress UndefinedMethod */ - /** @psalm-suppress DeprecatedMethod */ - $rootNode = $treeBuilder->root('ecn_feature_toggle'); - } + /** @var ArrayNodeDefinition $rootNode */ + $rootNode = $treeBuilder->getRootNode(); + /** + * @psalm-suppress PossiblyNullReference + * @psalm-suppress PossiblyUndefinedMethod + */ $rootNode ->children() ->arrayNode('default') diff --git a/DependencyInjection/EcnFeatureToggleExtension.php b/DependencyInjection/EcnFeatureToggleExtension.php index 2feee09..c6505a7 100644 --- a/DependencyInjection/EcnFeatureToggleExtension.php +++ b/DependencyInjection/EcnFeatureToggleExtension.php @@ -12,10 +12,11 @@ namespace Ecn\FeatureToggleBundle\DependencyInjection; +use Exception; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\DependencyInjection\Loader; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; /** * This is the class that loads and manages your bundle configuration @@ -30,20 +31,20 @@ class EcnFeatureToggleExtension extends Extension * @param array $configs * @param ContainerBuilder $container * - * @throws \Exception + * @throws Exception */ public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $features = array_key_exists('features', $config) ? $config['features'] : []; - $default = array_key_exists('default', $config) ? $config['default'] : []; + $features = $config['features'] ?? []; + $default = $config['default'] ?? []; $container->setParameter('features', $features); $container->setParameter('default', $default); - $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.xml'); } } diff --git a/EventListener/ControllerListener.php b/EventListener/ControllerListener.php index 73af2eb..e6a04c1 100644 --- a/EventListener/ControllerListener.php +++ b/EventListener/ControllerListener.php @@ -12,72 +12,63 @@ namespace Ecn\FeatureToggleBundle\EventListener; -use Doctrine\Common\Annotations\Reader; -use \Doctrine\Persistence\Proxy; -use Ecn\FeatureToggleBundle\Configuration\Feature; +use Closure; +use Doctrine\Persistence\Proxy; +use Ecn\FeatureToggleBundle\Attributes\Feature; use Ecn\FeatureToggleBundle\Service\FeatureService; -use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use JetBrains\PhpStorm\ArrayShape; +use ReflectionAttribute; +use ReflectionClass; +use ReflectionException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * @author Márk Sági-Kazár */ class ControllerListener implements EventSubscriberInterface { - /** - * @var Reader - */ - protected $reader; + protected FeatureService $featureService; /** - * @var FeatureService - */ - protected $featureService; - - /** - * @param Reader $reader * @param FeatureService $featureService */ - public function __construct(Reader $reader, FeatureService $featureService) + public function __construct(FeatureService $featureService) { - $this->reader = $reader; $this->featureService = $featureService; } /** - * @param FilterControllerEvent $event - * - * @psalm-suppress DeprecatedClass - * + * @param ControllerEvent $event * - * @throws \ReflectionException + * @throws ReflectionException */ - public function onKernelController(FilterControllerEvent $event): void + public function onKernelController(ControllerEvent $event): void { // We can't resolve the controller name from non-array callables. $controller = $event->getController(); - if ((!$controller instanceof \Closure) - && \is_object($controller) + if ((!$controller instanceof Closure) + && is_object($controller) && method_exists($controller, '__invoke') ) { $controller = [$controller, '__invoke']; } - if (!\is_array($controller)) { + if (!is_array($controller)) { return; } - $className = $this->getRealClass(is_object($controller[0]) ? \get_class($controller[0]) : $controller[0]); + /** @var class-string */ + $className = $this->getRealClass(is_object($controller[0]) ? $controller[0]::class : $controller[0]); - /** @psalm-suppress ArgumentTypeCoercion */ - $object = new \ReflectionClass($className); + $object = new ReflectionClass($className); $method = $object->getMethod($controller[1]); - $controllerAnnotations = $this->reader->getClassAnnotations($object); - $actionAnnotations = $this->reader->getMethodAnnotations($method); + $controllerAnnotations = $object->getAttributes(); + $actionAnnotations = $method->getAttributes(); $this->checkFeature($controllerAnnotations); $this->checkFeature($actionAnnotations); @@ -86,6 +77,7 @@ public function onKernelController(FilterControllerEvent $event): void /** * {@inheritDoc} */ + #[ArrayShape([KernelEvents::CONTROLLER => "string"])] public static function getSubscribedEvents(): array { return [ @@ -96,14 +88,16 @@ public static function getSubscribedEvents(): array /** * Checks for features in annotations. * - * @param array $annotations + * @param array $attributes * * @throws NotFoundHttpException If a feature is found, but not enabled. */ - private function checkFeature(array $annotations): void + private function checkFeature(array $attributes): void { - foreach ($annotations as $feature) { - if (($feature instanceof Feature) && !$this->featureService->has($feature->name)) { + /** @var ReflectionAttribute $feature */ + foreach ($attributes as $feature) { + $featureInstance = $feature->newInstance(); + if (($featureInstance instanceof Feature) && !$this->featureService->has($featureInstance->name)) { throw new NotFoundHttpException(); } } diff --git a/Exception/VoterNotFoundException.php b/Exception/VoterNotFoundException.php index 674d34b..d88b675 100644 --- a/Exception/VoterNotFoundException.php +++ b/Exception/VoterNotFoundException.php @@ -12,9 +12,11 @@ namespace Ecn\FeatureToggleBundle\Exception; +use RuntimeException; + /** * @author Pierre Groth */ -class VoterNotFoundException extends \RuntimeException +class VoterNotFoundException extends RuntimeException { } diff --git a/README.md b/README.md index 11568e3..d0fb4c7 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ In order to install ECNFeatureToggleBundle, you need at least -- PHP 7.2 or greater -- Symfony 3.4, Symfony 4.2 or greater +- PHP 8.0 or greater +- Symfony 5.4 or greater ## Installation @@ -21,6 +21,11 @@ In order to install ECNFeatureToggleBundle, you need at least $ composer require ecn/featuretoggle-bundle ``` +Functionality of twig is only optional. Require it to your composer if you need it to use it. +```bash +$ composer require twig/twig +``` + ### Step 2: Activate the bundle @@ -105,12 +110,12 @@ if ($this->get('feature')->has('MyNewFeature')) { ## Voters -In order to decide if a feature is available or not, voters are being used. Currently there are five voters included. +In order to decide if a feature is available or not, voters are being used. Currently, there are five voters included. ### AlwaysTrueVoter -This is the default voter and it will always pass. So if you have a feature defined, it will always be displayed. +This is the default voter, and it will always pass. So if you have a feature defined, it will always be displayed. The full configuration for using this voter looks like this: @@ -143,7 +148,7 @@ This voter passes on a given ratio between 0 and 1, which makes it suitable for The higher the ratio, the more likely the voter will pass. A value of 1 will make it pass every time, 0 will make it never pass. -Additionally, the result of the first check can be bound to the users session. This is useful if you need a feature +Additionally, the result of the first check can be bound to the users' session. This is useful if you need a feature to be persistent across multiple requests. To enable this, just set `sticky` to `true`. If you want to use this voter, this is the full configuration: diff --git a/Service/FeatureService.php b/Service/FeatureService.php index 14a86f9..cd6dc58 100644 --- a/Service/FeatureService.php +++ b/Service/FeatureService.php @@ -12,7 +12,6 @@ namespace Ecn\FeatureToggleBundle\Service; -use Ecn\FeatureToggleBundle\Exception\VoterNotFoundException; use Ecn\FeatureToggleBundle\Voters\VoterInterface; use Ecn\FeatureToggleBundle\Voters\VoterRegistry; @@ -23,20 +22,11 @@ class FeatureService { /** * Contains the defined features - * - * @var array */ - protected $features; + protected array $features; - /** - * @var array - */ - protected $defaultVoter; - - /** - * @var VoterRegistry - */ - protected $voterRegistry; + protected array $defaultVoter; + protected VoterRegistry $voterRegistry; /** * FeatureService constructor @@ -58,22 +48,14 @@ public function __construct(array $features, array $defaultVoter, VoterRegistry * @param string $feature * * @return bool - * - * @throws VoterNotFoundException */ public function has(string $feature): bool { - if (!array_key_exists($feature, $this->features)) { + if (!isset($this->features[$feature])) { return false; } - try { - $voter = $this->initVoter($feature); - - return $voter->pass(); - } catch (VoterNotFoundException $exception) { - throw $exception; - } + return $this->initVoter($feature)->pass(); } /** diff --git a/Tests/EcnFeatureToggleBundleTest.php b/Tests/EcnFeatureToggleBundleTest.php index d75bb66..aa1894b 100644 --- a/Tests/EcnFeatureToggleBundleTest.php +++ b/Tests/EcnFeatureToggleBundleTest.php @@ -23,10 +23,7 @@ */ class EcnFeatureToggleBundleTest extends TestCase { - /** - * @var ContainerBuilder - */ - private $configuration; + private ContainerBuilder $configuration; /** * Test if the default configuration for a feature @@ -37,7 +34,7 @@ class EcnFeatureToggleBundleTest extends TestCase public function testDefaultSettings(): void { $this->createConfiguration([ - 'features' => ['testfeature' => []] + 'features' => ['testFeature' => []] ]); // Load feature config @@ -45,15 +42,13 @@ public function testDefaultSettings(): void $default = $this->configuration->getParameter('default'); $this->assertEquals(['voter' => 'AlwaysTrueVoter', 'params' => []], $default); - $this->assertEquals([], $features['testfeature']['params']); + $this->assertEquals([], $features['testFeature']['params']); } /** - * @param array $config - * * @throws Exception */ - private function createConfiguration($config = []): void + private function createConfiguration(array $config = []): void { $this->configuration = new ContainerBuilder(); diff --git a/Tests/EventListener/ControllerListenerTest.php b/Tests/EventListener/ControllerListenerTest.php index 1dbe9cb..1916afb 100644 --- a/Tests/EventListener/ControllerListenerTest.php +++ b/Tests/EventListener/ControllerListenerTest.php @@ -12,24 +12,21 @@ namespace Ecn\FeatureToggleBundle\Tests\EventListener; -use Doctrine\Common\Annotations\AnnotationException; -use Doctrine\Common\Annotations\AnnotationReader; -use Ecn\FeatureToggleBundle\Configuration\Feature; +use Ecn\FeatureToggleBundle\Attributes\Feature; use Ecn\FeatureToggleBundle\EventListener\ControllerListener; use Ecn\FeatureToggleBundle\Service\FeatureService; -use Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\FooControllerFeatureAtClass; use Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\__CG__\Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\FooControllerFeatureAtClass as ProxyFooControllerFeatureAtClass; +use Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\FooControllerFeatureAtClass; use Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\FooControllerFeatureAtClassAndMethod; use Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\FooControllerFeatureAtInvoke; use Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\FooControllerFeatureAtMethod; use Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\FooControllerFeatureAtStaticMethod; use Ecn\FeatureToggleBundle\Voters\VoterRegistry; -use PHPUnit\Framework\MockObject\MockBuilder; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ReflectionException; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Kernel; @@ -40,55 +37,22 @@ */ class ControllerListenerTest extends TestCase { - /** - * @var ControllerListener - */ - private $listener; - - /** - * @var Request - */ - private $request; + private ControllerListener $listener; + private Request $request; + private ControllerEvent $event; /** - * @var FilterControllerEvent - */ - private $event; - - /** - * @var FeatureService&MockObject - */ - private $mockListener; - - /** - * Set up tests + * Set up Tests */ public function setUp(): void { - $this->listener = new ControllerListener( - new AnnotationReader(), - new FeatureService([], [], new VoterRegistry()) - ); - $this->request = $this->createRequest(); + $this->listener = new ControllerListener(new FeatureService([], [], new VoterRegistry())); + $this->request = new Request([], [], []); - // trigger the autoloading of the @Feature annotation + // trigger to autoload the @Feature annotation class_exists(Feature::class); } - /** - * @return Request - */ - protected function createRequest(): Request - { - return new Request([], [], []); - } - - public function tearDown(): void - { - $this->listener = null; - $this->request = null; - } - /** * Test Annotation Feature at method * @@ -100,7 +64,7 @@ public function testFeatureAnnotationAtMethod(): void $controller = new FooControllerFeatureAtMethod(); - $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->event = $this->getControllerEvent([$controller, 'barAction'], $this->request); $this->listener->onKernelController($this->event); } @@ -116,23 +80,17 @@ public function testFeatureAnnotationAtStaticMethod(): void $controller = new FooControllerFeatureAtStaticMethod(); - $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->event = $this->getControllerEvent([$controller, 'barAction'], $this->request); $this->listener->onKernelController($this->event); } - /** - * @param $controller - * @param Request $request - * - * @return FilterControllerEvent - */ - protected function getFilterControllerEvent($controller, Request $request): FilterControllerEvent + protected function getControllerEvent(object|array|string $controller, Request $request): ControllerEvent { - /** @var Kernel|MockBuilder $mockKernel */ - $mockKernel = $this->getMockForAbstractClass(Kernel::class, ['', '']); + /** @var Kernel $mockKernel */ + $mockKernel = $this->getMockForAbstractClass(HttpKernelInterface::class); - return new FilterControllerEvent($mockKernel, $controller, $request, HttpKernelInterface::MASTER_REQUEST); + return new ControllerEvent($mockKernel, $controller, $request, HttpKernelInterface::MAIN_REQUEST); } /** @@ -145,7 +103,7 @@ public function testFeatureAnnotationAtClass(): void $this->expectException(NotFoundHttpException::class); $controller = new FooControllerFeatureAtClass(); - $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->event = $this->getControllerEvent([$controller, 'barAction'], $this->request); $this->listener->onKernelController($this->event); } @@ -160,7 +118,7 @@ public function testFeatureAnnotationAtClassAndMethod(): void $this->expectException(NotFoundHttpException::class); $controller = new FooControllerFeatureAtClassAndMethod(); - $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->event = $this->getControllerEvent([$controller, 'barAction'], $this->request); $this->listener->onKernelController($this->event); } @@ -176,7 +134,7 @@ public function testFeatureAnnotationAtObjectInvoke(): void $controller = new FooControllerFeatureAtInvoke(); - $this->event = $this->getFilterControllerEvent($controller, $this->request); + $this->event = $this->getControllerEvent($controller, $this->request); $this->listener->onKernelController($this->event); } @@ -192,16 +150,13 @@ public function testFeatureProxyExtendsAnnotation(): void $controller = new ProxyFooControllerFeatureAtClass(); - $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->event = $this->getControllerEvent([$controller, 'barAction'], $this->request); $this->listener->onKernelController($this->event); } /** - * @param callable $controller - * * @throws ReflectionException - * @throws AnnotationException * * @dataProvider callableDataProvider */ @@ -212,22 +167,19 @@ public function testAvoidClosure(callable $controller): void $featureService->expects($this->never())->method('has'); $listener = new ControllerListener( - new AnnotationReader(), $featureService ); - $event = $this->getFilterControllerEvent($controller, $this->request); + /** @psalm-suppress InvalidArgument */ + $event = $this->getControllerEvent($controller, $this->request); $listener->onKernelController($event); } - /** - * @return array - */ public function callableDataProvider(): array { return [ - [static function () {return 'test';}] + [static fn() => 'test'] ]; } } diff --git a/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php b/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php index 0b3f094..56cb245 100644 --- a/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php +++ b/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php @@ -2,11 +2,9 @@ namespace Ecn\FeatureToggleBundle\Tests\EventListener\Fixture; -use Ecn\FeatureToggleBundle\Configuration\Feature; +use Ecn\FeatureToggleBundle\Attributes\Feature; -/** - * @Feature("feature") - */ +#[Feature(name: 'feature')] class FooControllerFeatureAtClass { public function barAction() diff --git a/Tests/EventListener/Fixture/FooControllerFeatureAtClassAndMethod.php b/Tests/EventListener/Fixture/FooControllerFeatureAtClassAndMethod.php index 917dd87..f009ba1 100644 --- a/Tests/EventListener/Fixture/FooControllerFeatureAtClassAndMethod.php +++ b/Tests/EventListener/Fixture/FooControllerFeatureAtClassAndMethod.php @@ -2,16 +2,12 @@ namespace Ecn\FeatureToggleBundle\Tests\EventListener\Fixture; -use Ecn\FeatureToggleBundle\Configuration\Feature; +use Ecn\FeatureToggleBundle\Attributes\Feature; -/** - * @Feature("feature") - */ +#[Feature(name: 'feature')] class FooControllerFeatureAtClassAndMethod { - /** - * @Feature("feature") - */ + #[Feature(name: 'feature')] public function barAction() { } diff --git a/Tests/EventListener/Fixture/FooControllerFeatureAtInvoke.php b/Tests/EventListener/Fixture/FooControllerFeatureAtInvoke.php index 402341b..8468ef7 100644 --- a/Tests/EventListener/Fixture/FooControllerFeatureAtInvoke.php +++ b/Tests/EventListener/Fixture/FooControllerFeatureAtInvoke.php @@ -2,14 +2,11 @@ namespace Ecn\FeatureToggleBundle\Tests\EventListener\Fixture; -use Ecn\FeatureToggleBundle\Configuration\Feature; - +use Ecn\FeatureToggleBundle\Attributes\Feature; class FooControllerFeatureAtInvoke { - /** - * @Feature("feature") - */ + #[Feature(name: 'feature')] public function __invoke() { } diff --git a/Tests/EventListener/Fixture/FooControllerFeatureAtMethod.php b/Tests/EventListener/Fixture/FooControllerFeatureAtMethod.php index 150239b..f3be7a8 100644 --- a/Tests/EventListener/Fixture/FooControllerFeatureAtMethod.php +++ b/Tests/EventListener/Fixture/FooControllerFeatureAtMethod.php @@ -2,13 +2,11 @@ namespace Ecn\FeatureToggleBundle\Tests\EventListener\Fixture; -use Ecn\FeatureToggleBundle\Configuration\Feature; +use Ecn\FeatureToggleBundle\Attributes\Feature; class FooControllerFeatureAtMethod { - /** - * @Feature("feature") - */ + #[Feature(name: 'feature')] public function barAction() { } diff --git a/Tests/EventListener/Fixture/FooControllerFeatureAtStaticMethod.php b/Tests/EventListener/Fixture/FooControllerFeatureAtStaticMethod.php index cee95e3..60293b1 100644 --- a/Tests/EventListener/Fixture/FooControllerFeatureAtStaticMethod.php +++ b/Tests/EventListener/Fixture/FooControllerFeatureAtStaticMethod.php @@ -2,13 +2,11 @@ namespace Ecn\FeatureToggleBundle\Tests\EventListener\Fixture; -use Ecn\FeatureToggleBundle\Configuration\Feature; +use Ecn\FeatureToggleBundle\Attributes\Feature; class FooControllerFeatureAtStaticMethod { - /** - * @Feature("feature") - */ + #[Feature(name: 'feature')] public static function barAction() { } diff --git a/Tests/EventListener/Fixture/__CG__/Ecn/FeatureToggleBundle/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php b/Tests/EventListener/Fixture/__CG__/Ecn/FeatureToggleBundle/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php index 3bdd6f6..b488e7d 100644 --- a/Tests/EventListener/Fixture/__CG__/Ecn/FeatureToggleBundle/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php +++ b/Tests/EventListener/Fixture/__CG__/Ecn/FeatureToggleBundle/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php @@ -2,8 +2,6 @@ namespace Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\__CG__\Ecn\FeatureToggleBundle\Tests\EventListener\Fixture; -use Ecn\FeatureToggleBundle\Configuration\Feature; - class FooControllerFeatureAtClass extends \Ecn\FeatureToggleBundle\Tests\EventListener\Fixture\FooControllerFeatureAtClass { } diff --git a/Tests/Service/FeatureServiceTest.php b/Tests/Service/FeatureServiceTest.php index 62e669c..43a9e92 100644 --- a/Tests/Service/FeatureServiceTest.php +++ b/Tests/Service/FeatureServiceTest.php @@ -30,7 +30,7 @@ public function testIfFeatureMatches(): void { // Define a feature $features = [ - 'testfeature' => [ + 'testFeature' => [ 'voter' => 'AlwaysTrueVoter', 'params' => [] ] @@ -43,8 +43,8 @@ public function testIfFeatureMatches(): void // Create service $service = new FeatureService($features, $default, $this->getRegistry()); - $this->assertTrue($service->has('testfeature')); - $this->assertFalse($service->has('unknownfeature')); + $this->assertTrue($service->has('testFeature')); + $this->assertFalse($service->has('unknownFeature')); } /** @@ -54,7 +54,7 @@ public function testDefaultVoter(): void { // Define a feature $features = [ - 'testfeature' => [ + 'testFeature' => [ 'voter' => null, 'params' => [] ] @@ -67,21 +67,21 @@ public function testDefaultVoter(): void // Create service $service = new FeatureService($features, $default, $this->getRegistry()); - $this->assertTrue($service->has('testfeature')); - $this->assertFalse($service->has('unknownfeature')); + $this->assertTrue($service->has('testFeature')); + $this->assertFalse($service->has('unknownFeature')); } /** * Test new feature */ - public function testFeatureUnknownedVoter(): void + public function testFeatureUnknownVoter(): void { $this->expectException(VoterNotFoundException::class); $this->expectExceptionMessage('No voter with this alias: "testVoter" is registered'); // Define a feature $features = [ - 'testfeature' => [ + 'testFeature' => [ 'voter' => 'testVoter', 'params' => [] ] @@ -94,7 +94,7 @@ public function testFeatureUnknownedVoter(): void // Create service $service = new FeatureService($features, $default, $this->getRegistry()); - $service->has('testfeature'); + $service->has('testFeature'); } /** diff --git a/Tests/Twig/FeatureToggleExtensionTest.php b/Tests/Twig/FeatureToggleExtensionTest.php index 6ae78ff..f64194a 100644 --- a/Tests/Twig/FeatureToggleExtensionTest.php +++ b/Tests/Twig/FeatureToggleExtensionTest.php @@ -27,8 +27,8 @@ public function testCallable(): void { // Define response map for service stub $map = [ - ['testfeature', true], - ['unknownfeature', false] + ['testFeature', true], + ['unknownFeature', false] ]; /** @var MockObject&FeatureService $service */ @@ -46,7 +46,8 @@ public function testCallable(): void $functions = $extension->getFunctions(); // Check if functions are returned as array - $this->assertIsArray($functions); + // removed because TokenParser is always returned in an array + # $this->assertIsArray($functions); // Check if the function is a twig function $this->assertInstanceOf(TwigFunction::class, $functions[0]); @@ -54,10 +55,10 @@ public function testCallable(): void $callable = $functions[0]->getCallable(); // Check if callable returns true for a known feature - $this->assertTrue($callable('testfeature')); + $this->assertTrue($callable('testFeature')); // Check if callable returns false for an unknown feature - $this->assertFalse($callable('unknownfeature')); + $this->assertFalse($callable('unknownFeature')); } } diff --git a/Tests/Twig/FeatureToggleNodeTest.php b/Tests/Twig/FeatureToggleNodeTest.php index e849eff..c48001b 100644 --- a/Tests/Twig/FeatureToggleNodeTest.php +++ b/Tests/Twig/FeatureToggleNodeTest.php @@ -13,6 +13,7 @@ namespace Ecn\FeatureToggleBundle\Tests\Twig; use Ecn\FeatureToggleBundle\Twig\FeatureToggleNode; +use Twig\Environment; use Twig\Node\Expression\NameExpression; use Twig\Node\Node; use Twig\Node\PrintNode; @@ -25,15 +26,17 @@ class FeatureToggleNodeTest extends NodeTestCase { /** * @dataProvider getTests + * + * @param FeatureToggleNode $node + * @param string $source + * @param Environment $environment + * @param bool $isPattern */ public function testCompile($node, $source, $environment = null, $isPattern = false) { parent::testCompile($node, $source, $environment = null, $isPattern = false); } - /** - * {@inheritDoc} - */ public function getTests(): array { $tests = []; diff --git a/Tests/Twig/IntegrationTest.php b/Tests/Twig/IntegrationTest.php index 3d31880..139bce1 100644 --- a/Tests/Twig/IntegrationTest.php +++ b/Tests/Twig/IntegrationTest.php @@ -9,9 +9,10 @@ * file that was distributed with this source code. */ -namespace Ecn\FeatureToggleBundle\Twig; +namespace Ecn\FeatureToggleBundle\Tests\Twig; use Ecn\FeatureToggleBundle\Service\FeatureService; +use Ecn\FeatureToggleBundle\Twig\FeatureToggleExtension; use Ecn\FeatureToggleBundle\Voters\AlwaysTrueVoter; use Ecn\FeatureToggleBundle\Voters\VoterRegistry; use Twig\Test\IntegrationTestCase; @@ -28,6 +29,14 @@ public function getFixturesDir(): string /** * @dataProvider getTests + * + * @param string $file + * @param string $message + * @param string $condition + * @param array $templates + * @param bool $exception + * @param array $outputs + * @param string $deprecation */ public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = ''): void { @@ -37,6 +46,14 @@ public function testIntegration($file, $message, $condition, $templates, $except /** * @dataProvider getLegacyTests * @group legacy + * + * @param string $file + * @param string $message + * @param string $condition + * @param array $templates + * @param bool $exception + * @param array $outputs + * @param string $deprecation */ public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = ''): void { diff --git a/Tests/Voters/RatioVoterTest.php b/Tests/Voters/RatioVoterTest.php index cae3913..04e0c1d 100644 --- a/Tests/Voters/RatioVoterTest.php +++ b/Tests/Voters/RatioVoterTest.php @@ -24,7 +24,7 @@ */ class RatioVoterTest extends TestCase { - public $stickyValues = []; + public array $stickyValues = []; public function testLowRatioVoterPass(): void { @@ -98,7 +98,7 @@ public function testStickyRatioVoterPass(): void { $voter = $this->getRatioVoter(0.5, true); $initialPass = $voter->pass(); - $this->stickyValues = ['_ecn_featuretoggle_ratiotest' => $initialPass]; + $this->stickyValues = ['_ecn_featuretoggle_ratioTest' => $initialPass]; if ($initialPass) { $requiredHits = $this->executeTestIteration($voter) === 100; @@ -112,28 +112,28 @@ public function testStickyRatioVoterPass(): void /** * Callback for session stub * - * @param $key + * @param string $key * * @return bool */ - public function hasStickyCallback($key): bool + public function hasStickyCallback(string $key): bool { - return array_key_exists($key, $this->stickyValues); + return isset($this->stickyValues[$key]); } /** * Callback for session stub * - * @param $key + * @param string $key * - * @return mixed|null + * @return bool|null */ - public function getStickyCallback($key) + public function getStickyCallback(string $key): bool|null { return $this->stickyValues[$key] ?? null; } - protected function getRatioVoter($ratio, $sticky = false, $hasSession = true): RatioVoter + protected function getRatioVoter(float $ratio, bool $sticky = false, bool $hasSession = true): RatioVoter { $session = null; @@ -150,23 +150,23 @@ protected function getRatioVoter($ratio, $sticky = false, $hasSession = true): R $params = ['ratio' => $ratio, 'sticky' => $sticky]; $voter = new RatioVoter($session); - $voter->setFeature('ratiotest'); + $voter->setFeature('ratioTest'); $voter->setParams($params); return $voter; } /** - * Executes the tests n time returning the number of passes + * Executes the Tests n time returning the number of passes * * @param RatioVoter $ratioVoter - * @param int $iterationCount * * @return int */ - private function executeTestIteration(RatioVoter $ratioVoter, $iterationCount = 100): int + private function executeTestIteration(RatioVoter $ratioVoter): int { $hits = 0; + $iterationCount = 100; for ($i = 1; $i <= $iterationCount; $i++) { if ($ratioVoter->pass()) { diff --git a/Tests/Voters/RequestHeaderVoterTest.php b/Tests/Voters/RequestHeaderVoterTest.php index 847119d..7d026ec 100644 --- a/Tests/Voters/RequestHeaderVoterTest.php +++ b/Tests/Voters/RequestHeaderVoterTest.php @@ -34,12 +34,12 @@ public function testIsArrayNotAssociative(): void public function testNoCurrentRequestInRequestStack(): void { - $voter = $this->getRequestHeaderVoter(new RequestStack(), null); + $voter = $this->getRequestHeaderVoter(new RequestStack(), []); $this->assertFalse($voter->pass()); } - private function getRequestHeaderVoter(RequestStack $requestStack, $requestHeaders = null): RequestHeaderVoter + private function getRequestHeaderVoter(RequestStack $requestStack, array $requestHeaders = []): RequestHeaderVoter { $voter = new RequestHeaderVoter(); $voter->setRequest($requestStack); @@ -50,14 +50,14 @@ private function getRequestHeaderVoter(RequestStack $requestStack, $requestHeade public function testNoRequestHeadersProvided(): void { - $voter = $this->getRequestHeaderVoter($this->getFakeRequestStack(), null); + $voter = $this->getRequestHeaderVoter($this->getFakeRequestStack(), []); $this->assertFalse($voter->pass()); } - private function getFakeRequestStack($headers = []): RequestStack + private function getFakeRequestStack(array $headers = []): RequestStack { - $fakeRequest = Request::create('/', 'GET'); + $fakeRequest = Request::create('/'); $fakeRequest->headers->add($headers); $requestStack = new RequestStack(); diff --git a/Tests/Voters/ScheduleVoterTest.php b/Tests/Voters/ScheduleVoterTest.php index 99897ae..aae0162 100644 --- a/Tests/Voters/ScheduleVoterTest.php +++ b/Tests/Voters/ScheduleVoterTest.php @@ -13,6 +13,7 @@ namespace Ecn\FeatureToggleBundle\Tests\Voters; use DateTime; +use DateTimeInterface; use Ecn\FeatureToggleBundle\Voters\ScheduleVoter; use PHPUnit\Framework\TestCase; @@ -23,12 +24,12 @@ class ScheduleVoterTest extends TestCase { public function testNoScheduleIsSet(): void { - $voter = $this->getScheduleVoter(null); + $voter = $this->getScheduleVoter(''); $this->assertTrue($voter->pass()); } - protected function getScheduleVoter($schedule): ScheduleVoter + protected function getScheduleVoter(string $schedule): ScheduleVoter { $voter = new ScheduleVoter(); $voter->setParams(['schedule' => $schedule]); @@ -45,14 +46,14 @@ public function testInvalidScheduleIsSet(): void public function testEarlierScheduleIsSet(): void { - $voter = $this->getScheduleVoter((new DateTime())->modify('-1 second')->format(DateTime::RSS)); + $voter = $this->getScheduleVoter((new DateTime())->modify('-1 second')->format(DateTimeInterface::RSS)); $this->assertTrue($voter->pass()); } public function testLaterScheduleIsSet(): void { - $voter = $this->getScheduleVoter((new DateTime())->modify('+1 second')->format(DateTime::RSS)); + $voter = $this->getScheduleVoter((new DateTime())->modify('+1 second')->format(DateTimeInterface::RSS)); $this->assertFalse($voter->pass()); } diff --git a/Tests/Voters/VoterRegistryTest.php b/Tests/Voters/VoterRegistryTest.php index e0c11b2..593e638 100644 --- a/Tests/Voters/VoterRegistryTest.php +++ b/Tests/Voters/VoterRegistryTest.php @@ -49,6 +49,7 @@ public function testUnknownVoterException(): void $registry = new VoterRegistry(); $registry->addVoter($voterOne, 'myFirstTestVoter'); - $unknownVoter = $registry->getVoter('unknownVoter'); + // unknownVoter should throw VoterNotFoundException + $registry->getVoter('unknownVoter'); } } diff --git a/Twig/FeatureToggleExtension.php b/Twig/FeatureToggleExtension.php index 4288f9c..6d0d18f 100644 --- a/Twig/FeatureToggleExtension.php +++ b/Twig/FeatureToggleExtension.php @@ -1,4 +1,5 @@ [] */ - public function getTokenParsers(): array + #[Pure] public function getTokenParsers(): array { return [ new FeatureToggleTokenParser(), diff --git a/Voters/RatioVoter.php b/Voters/RatioVoter.php index 365361d..6aec2f7 100644 --- a/Voters/RatioVoter.php +++ b/Voters/RatioVoter.php @@ -22,20 +22,9 @@ class RatioVoter implements VoterInterface { use VoterTrait; - /** - * @var SessionInterface|null - */ - protected $session; - - /** - * @var float - */ - protected $ratio = 0.5; - - /** - * @var bool - */ - protected $sticky = false; + protected ?SessionInterface $session; + protected float $ratio = 0.5; + protected bool $sticky = false; /** * @param SessionInterface|null $session @@ -50,8 +39,8 @@ public function __construct(?SessionInterface $session = null) */ public function setParams(array $params): void { - $this->ratio = array_key_exists('ratio', $params) ? $params['ratio'] : 0.5; - $this->sticky = array_key_exists('sticky', $params) ? $params['sticky'] : false; + $this->ratio = $params['ratio'] ?? 0.5; + $this->sticky = $params['sticky'] ?? false; } /** @@ -76,7 +65,7 @@ public function pass(): bool protected function getStickyRatioPass(): bool { if (null === $this->session) { - throw new InvalidArgumentException(sprintf('The service "%s" has a dependency on the session', get_class($this))); + throw new InvalidArgumentException(sprintf('The service "%s" has a dependency on the session', $this::class)); } $sessionKey = '_ecn_featuretoggle_'.$this->feature; diff --git a/Voters/RequestHeaderVoter.php b/Voters/RequestHeaderVoter.php index c4bf5fe..b5283ce 100644 --- a/Voters/RequestHeaderVoter.php +++ b/Voters/RequestHeaderVoter.php @@ -22,29 +22,18 @@ final class RequestHeaderVoter implements VoterInterface { use VoterTrait; - /** - * @var array - */ - private $headers = []; - - /** - * @var Request|null - */ - private $request; - - /** - * @var bool - */ - private $checkHeaderValues = false; + private array $headers = []; + private ?Request $request = null; + private bool $checkHeaderValues = false; /** * {@inheritdoc} */ public function setParams(array $params): void { - $headers = array_key_exists('headers', $params) ? $params['headers'] : null; + $headers = $params['headers'] ?? null; - $this->checkHeaderValues = $headers ? static::isAssociativeArray($headers) : false; + $this->checkHeaderValues = $headers && RequestHeaderVoter::isAssociativeArray($headers); $this->headers = $headers; } diff --git a/Voters/ScheduleVoter.php b/Voters/ScheduleVoter.php index dc95b78..07e09f7 100644 --- a/Voters/ScheduleVoter.php +++ b/Voters/ScheduleVoter.php @@ -12,6 +12,9 @@ namespace Ecn\FeatureToggleBundle\Voters; +use DateTime; +use Exception; + /** * @author Márk Sági-Kazár */ @@ -21,17 +24,15 @@ final class ScheduleVoter implements VoterInterface /** * A valid DateTime representation - * - * @var string|null */ - private $schedule; + private string $schedule = ''; /** * {@inheritdoc} */ public function setParams(array $params): void { - $this->schedule = array_key_exists('schedule', $params) ? $params['schedule'] : null; + $this->schedule = $params['schedule'] ?? ''; } /** @@ -40,16 +41,16 @@ public function setParams(array $params): void public function pass(): bool { // Don't pass if schedule is invalid - if (null === $this->schedule) { + if ('' === $this->schedule) { return true; } try { - $schedule = new \DateTime($this->schedule); - } catch (\Throwable $e) { + $schedule = new DateTime($this->schedule); + } catch (Exception) { return false; } - return new \DateTime() >= $schedule; + return new DateTime() >= $schedule; } } diff --git a/Voters/VoterInterface.php b/Voters/VoterInterface.php index b5afbde..c69824f 100644 --- a/Voters/VoterInterface.php +++ b/Voters/VoterInterface.php @@ -21,7 +21,6 @@ interface VoterInterface * Add additional parameters from the feature definition * * @param array $params - * */ public function setParams(array $params): void; diff --git a/Voters/VoterRegistry.php b/Voters/VoterRegistry.php index d535479..f14d0ec 100644 --- a/Voters/VoterRegistry.php +++ b/Voters/VoterRegistry.php @@ -22,7 +22,7 @@ class VoterRegistry /** * @var array */ - private $voters = []; + private array $voters = []; /** * @param VoterInterface $voter The voter service to add @@ -44,7 +44,7 @@ public function addVoter(VoterInterface $voter, string $alias): void */ public function getVoter(string $alias): VoterInterface { - if (array_key_exists($alias, $this->voters)) { + if (isset($this->voters[$alias])) { return $this->voters[$alias]; } diff --git a/Voters/VoterTrait.php b/Voters/VoterTrait.php index de3a3dd..1c1c5ec 100644 --- a/Voters/VoterTrait.php +++ b/Voters/VoterTrait.php @@ -19,10 +19,7 @@ */ trait VoterTrait { - /** - * @var string - */ - private $feature = ""; + private string $feature = ""; /** * {@inheritdoc} diff --git a/composer.json b/composer.json index adff497..edb215a 100644 --- a/composer.json +++ b/composer.json @@ -10,20 +10,23 @@ } ], "require": { - "php": "^7.2", - "symfony/framework-bundle": "~3.4.31|~4.2.7|^4.3.4", - "doctrine/annotations": "~1.6", - "doctrine/common": "2.10.0" + "php": "~8.0 | ~8.1", + "symfony/framework-bundle": "^5.4 | ^6.0", + "doctrine/common": "3.2.1" }, "require-dev": { - "phpunit/phpunit": "^8.2|^7.5", - "squizlabs/php_codesniffer": "^3.3", - "escapestudios/symfony2-coding-standard": "^3.4.1", - "vimeo/psalm": "~3.4" + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.6", + "escapestudios/symfony2-coding-standard": "^3.12", + "vimeo/psalm": "~4.21", + "jetbrains/phpstorm-attributes": "^1.0" }, "conflict": { "phpunit/phpunit": "<5.4.3" }, + "suggest": { + "twig/twig": "^3.3" + }, "autoload": { "psr-4": { "Ecn\\FeatureToggleBundle\\": "" @@ -39,10 +42,15 @@ "psalm-no-twig": "vendor/bin/psalm --config=psalm.without_twig.xml", "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover build/coverage.xml" }, - "minimum-stability": "dev", + "minimum-stability": "stable", "extra": { "branch-alias": { "dev-master": "2.0-dev" } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true + } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2384922..1d6b09b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,20 @@ + - - + + + ./ + + + ./Resources + ./Tests + ./vendor + + ./Tests @@ -11,15 +24,4 @@ ./Tests/Twig - - - - ./ - - ./Resources - ./Tests - ./vendor - - - diff --git a/psalm.without_twig.xml b/psalm.without_twig.xml index 5788677..2d76777 100644 --- a/psalm.without_twig.xml +++ b/psalm.without_twig.xml @@ -2,7 +2,6 @@ @@ -23,7 +22,6 @@ - diff --git a/psalm.xml b/psalm.xml index 7d0c441..fba50fe 100644 --- a/psalm.xml +++ b/psalm.xml @@ -2,7 +2,6 @@ @@ -22,7 +21,6 @@ - From 471f7489621bbc6fdec6a4dbd16754fc770ce40c Mon Sep 17 00:00:00 2001 From: "Becker, Sebastian" Date: Fri, 12 May 2023 14:04:18 +0200 Subject: [PATCH 3/4] PHP >=8.1, Symfony File Structure, Refactoring Tools - symfony file structure - support php >=8.1 - added refactoring tools --- .gitattributes | 2 +- .gitignore | 1 + .php-cs-fixer.src.php | 27 ++++++++ .php-cs-fixer.tests.php | 30 +++++++++ CHANGELOG.md | 6 ++ EcnFeatureToggleBundle.php | 2 +- composer.json | 43 +++++++------ {Resources/config => config}/services.xml | 2 +- phpcs.xml | 4 +- phpunit.xml.dist | 32 +++++----- psalm.without_twig.xml | 8 ++- psalm.xml | 6 +- rector.php | 56 +++++++++++++++++ {Attributes => src/Attributes}/Feature.php | 8 +-- .../Compiler/VoterCompilerPass.php | 5 +- .../DependencyInjection}/Configuration.php | 10 +-- .../EcnFeatureToggleExtension.php | 14 ++--- .../EventListener}/ControllerListener.php | 25 +++----- .../Exception}/VoterNotFoundException.php | 7 +-- {Service => src/Service}/FeatureService.php | 12 ++-- {Twig => src/Twig}/FeatureToggleExtension.php | 10 +-- {Twig => src/Twig}/FeatureToggleNode.php | 3 +- .../Twig}/FeatureToggleTokenParser.php | 5 +- {Voters => src/Voters}/AlwaysFalseVoter.php | 3 +- {Voters => src/Voters}/AlwaysTrueVoter.php | 3 +- {Voters => src/Voters}/RatioVoter.php | 14 ++--- {Voters => src/Voters}/RequestHeaderVoter.php | 9 ++- {Voters => src/Voters}/ScheduleVoter.php | 9 ++- {Voters => src/Voters}/VoterInterface.php | 9 ++- {Voters => src/Voters}/VoterRegistry.php | 5 +- {Voters => src/Voters}/VoterTrait.php | 7 +-- .../EcnFeatureToggleBundleTest.php | 16 +++-- .../EventListener/ControllerListenerTest.php | 61 +++++++++++-------- .../Fixture/FooControllerFeatureAtClass.php | 2 +- .../FooControllerFeatureAtClassAndMethod.php | 2 +- .../Fixture/FooControllerFeatureAtInvoke.php | 2 +- .../Fixture/FooControllerFeatureAtMethod.php | 2 +- .../FooControllerFeatureAtStaticMethod.php | 2 +- .../Fixture/FooControllerFeatureAtClass.php | 2 +- .../Service/FeatureServiceTest.php | 27 ++++---- .../Twig/FeatureToggleExtensionTest.php | 16 ++--- .../Twig/FeatureToggleNodeTest.php | 15 ++--- .../functions/feature/feature_disabled.test | 0 .../functions/feature/feature_enabled.test | 0 .../tags/feature/feature_disabled.test | 0 .../tags/feature/feature_enabled.test | 0 {Tests => tests}/Twig/IntegrationTest.php | 30 ++++----- .../Voters/AlwaysFalseVoterTest.php | 3 +- .../Voters/AlwaysTrueVoterTest.php | 3 +- {Tests => tests}/Voters/RatioVoterTest.php | 53 +++++++--------- .../Voters/RequestHeaderVoterTest.php | 3 +- {Tests => tests}/Voters/ScheduleVoterTest.php | 9 +-- {Tests => tests}/Voters/VoterRegistryTest.php | 13 ++-- {Tests => tests}/Voters/VoterTraitTest.php | 3 +- 54 files changed, 374 insertions(+), 267 deletions(-) create mode 100644 .php-cs-fixer.src.php create mode 100644 .php-cs-fixer.tests.php rename {Resources/config => config}/services.xml (97%) create mode 100644 rector.php rename {Attributes => src/Attributes}/Feature.php (75%) rename {DependencyInjection => src/DependencyInjection}/Compiler/VoterCompilerPass.php (93%) rename {DependencyInjection => src/DependencyInjection}/Configuration.php (90%) rename {DependencyInjection => src/DependencyInjection}/EcnFeatureToggleExtension.php (92%) rename {EventListener => src/EventListener}/ControllerListener.php (82%) rename {Exception => src/Exception}/VoterNotFoundException.php (76%) rename {Service => src/Service}/FeatureService.php (90%) rename {Twig => src/Twig}/FeatureToggleExtension.php (88%) rename {Twig => src/Twig}/FeatureToggleNode.php (97%) rename {Twig => src/Twig}/FeatureToggleTokenParser.php (91%) rename {Voters => src/Voters}/AlwaysFalseVoter.php (94%) rename {Voters => src/Voters}/AlwaysTrueVoter.php (94%) rename {Voters => src/Voters}/RatioVoter.php (85%) rename {Voters => src/Voters}/RequestHeaderVoter.php (89%) rename {Voters => src/Voters}/ScheduleVoter.php (84%) rename {Voters => src/Voters}/VoterInterface.php (78%) rename {Voters => src/Voters}/VoterRegistry.php (94%) rename {Voters => src/Voters}/VoterTrait.php (84%) rename {Tests => tests}/EcnFeatureToggleBundleTest.php (81%) rename {Tests => tests}/EventListener/ControllerListenerTest.php (76%) rename {Tests => tests}/EventListener/Fixture/FooControllerFeatureAtClass.php (87%) rename {Tests => tests}/EventListener/Fixture/FooControllerFeatureAtClassAndMethod.php (89%) rename {Tests => tests}/EventListener/Fixture/FooControllerFeatureAtInvoke.php (88%) rename {Tests => tests}/EventListener/Fixture/FooControllerFeatureAtMethod.php (88%) rename {Tests => tests}/EventListener/Fixture/FooControllerFeatureAtStaticMethod.php (88%) rename {Tests => tests}/EventListener/Fixture/__CG__/Ecn/FeatureToggleBundle/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php (89%) rename {Tests => tests}/Service/FeatureServiceTest.php (89%) rename {Tests => tests}/Twig/FeatureToggleExtensionTest.php (85%) rename {Tests => tests}/Twig/FeatureToggleNodeTest.php (80%) rename {Tests => tests}/Twig/Fixtures/functions/feature/feature_disabled.test (100%) rename {Tests => tests}/Twig/Fixtures/functions/feature/feature_enabled.test (100%) rename {Tests => tests}/Twig/Fixtures/tags/feature/feature_disabled.test (100%) rename {Tests => tests}/Twig/Fixtures/tags/feature/feature_enabled.test (100%) rename {Tests => tests}/Twig/IntegrationTest.php (79%) rename {Tests => tests}/Voters/AlwaysFalseVoterTest.php (95%) rename {Tests => tests}/Voters/AlwaysTrueVoterTest.php (95%) rename {Tests => tests}/Voters/RatioVoterTest.php (76%) rename {Tests => tests}/Voters/RequestHeaderVoterTest.php (99%) rename {Tests => tests}/Voters/ScheduleVoterTest.php (80%) rename {Tests => tests}/Voters/VoterRegistryTest.php (91%) rename {Tests => tests}/Voters/VoterTraitTest.php (96%) diff --git a/.gitattributes b/.gitattributes index 3cf0600..6631cbf 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -Tests/ export-ignore +tests/ export-ignore .editorconfig export-ignore .gitattributes export-ignore .gitignore export-ignore diff --git a/.gitignore b/.gitignore index 6189702..2198cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.lock phpunit.xml /ECNFeatureToggleBundle.iml /.phpunit.result.cache +.php-cs-fixer.cache diff --git a/.php-cs-fixer.src.php b/.php-cs-fixer.src.php new file mode 100644 index 0000000..3a4d418 --- /dev/null +++ b/.php-cs-fixer.src.php @@ -0,0 +1,27 @@ +in(__DIR__.'/src') +; + +return (new PhpCsFixer\Config()) + ->setRules([ + // Symfony Coding Standard (includes PSR2 and PSR12) but disable some settings to not violate the current Otelo Coding Standard + '@Symfony' => true, + '@Symfony:risky' => true, + // So declare_strict_type can be on the same line as the opening tag + 'blank_line_after_opening_tag' => false, + 'linebreak_after_opening_tag' => false, + // Would otherwise remove @inheritdoc tags from classes does not inherit. + 'phpdoc_no_useless_inheritdoc' => false, + // Would otherwise remove @param, @return and @var tags that don't provide any useful information. + 'no_superfluous_phpdoc_tags' => false, + // In @PHP80Migration:risky containing 'declare_strict_types' is not needed on interfaces, + // so you can remove them there @see https://github.com/Automattic/phpcs-neutron-standard/issues/20 + '@PHP81Migration' => true, + '@DoctrineAnnotation' => true, + // Risky formatting. Use --allow-risky=true in the command to use them. + '@PHP80Migration:risky' => true, + ]) + ->setFinder($finder) +; diff --git a/.php-cs-fixer.tests.php b/.php-cs-fixer.tests.php new file mode 100644 index 0000000..7ec7050 --- /dev/null +++ b/.php-cs-fixer.tests.php @@ -0,0 +1,30 @@ +in(__DIR__.'/tests') +; + +return (new PhpCsFixer\Config()) + ->setRules([ + // Symfony Coding Standard (includes PSR2 and PSR12) but disable some settings to not violate the current Otelo Coding Standard + '@Symfony' => true, + '@Symfony:risky' => true, + // So declare_strict_type can be on the same line as the opening tag + 'blank_line_after_opening_tag' => false, + 'linebreak_after_opening_tag' => false, + // Would otherwise remove @inheritdoc tags from classes does not inherit. + 'phpdoc_no_useless_inheritdoc' => false, + // Would otherwise remove @param, @return and @var tags that don't provide any useful information. + 'no_superfluous_phpdoc_tags' => false, + // In @PHP80Migration:risky containing 'declare_strict_types' is not needed on interfaces, + // so you can remove them there @see https://github.com/Automattic/phpcs-neutron-standard/issues/20 + '@PHP81Migration' => true, + '@DoctrineAnnotation' => true, + // Risky formatting. Use --allow-risky=true in the command to use them. + '@PHP80Migration:risky' => true, + '@PHPUnit100Migration:risky' => true, + // avoid that tests get void as return value (for setup is fine -> add manually) + 'void_return' => false, + ]) + ->setFinder($finder) +; diff --git a/CHANGELOG.md b/CHANGELOG.md index 1149032..52a4d51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 3.1.0 - 2023-05-12 + +### Added + +- added support for PHP >=8 +- added refactoring tools (php-cs-fixer, rector) ## 3.0.0 - 2022-02-01 diff --git a/EcnFeatureToggleBundle.php b/EcnFeatureToggleBundle.php index 0859aa7..3b8e935 100644 --- a/EcnFeatureToggleBundle.php +++ b/EcnFeatureToggleBundle.php @@ -12,8 +12,8 @@ namespace Ecn\FeatureToggleBundle; use Ecn\FeatureToggleBundle\DependencyInjection\Compiler\VoterCompilerPass; -use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; /** * @author Pierre Groth diff --git a/composer.json b/composer.json index edb215a..cf52a6e 100644 --- a/composer.json +++ b/composer.json @@ -10,42 +10,51 @@ } ], "require": { - "php": "~8.0 | ~8.1", - "symfony/framework-bundle": "^5.4 | ^6.0", - "doctrine/common": "3.2.1" + "php": "^8.1", + "symfony/framework-bundle": "^5.4 || ^6.0", + "doctrine/common": "^3.4.3" }, "require-dev": { - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.6", - "escapestudios/symfony2-coding-standard": "^3.12", - "vimeo/psalm": "~4.21", - "jetbrains/phpstorm-attributes": "^1.0" + "phpunit/phpunit": "^10.1.3", + "squizlabs/php_codesniffer": "^3.7.2", + "escapestudios/symfony2-coding-standard": "^3.13.0", + "vimeo/psalm": "^5.11.0", + "jetbrains/phpstorm-attributes": "^1.0", + "friendsofphp/php-cs-fixer": "^3.16.0", + "rector/rector": "*", + "roave/security-advisories": "dev-master", + "twig/twig": "3.6.0" }, "conflict": { - "phpunit/phpunit": "<5.4.3" + "phpunit/phpunit": "<10.0" }, "suggest": { - "twig/twig": "^3.3" + "twig/twig": "^3.6.0" }, "autoload": { "psr-4": { - "Ecn\\FeatureToggleBundle\\": "" - }, - "exclude-from-classmap": [ - "Tests/" - ] + "Ecn\\FeatureToggleBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Ecn\\FeatureToggleBundle\\Tests\\": "tests/" + } }, "scripts": { "test": "vendor/bin/phpunit", "test-no-twig": "vendor/bin/phpunit --testsuite without_twig", "psalm": "vendor/bin/psalm", "psalm-no-twig": "vendor/bin/psalm --config=psalm.without_twig.xml", - "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover build/coverage.xml" + "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover build/coverage.xml", + "rector": "vendor/bin/rector process --dry-run", + "cs-fixer-src": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.src.php --verbose --diff --allow-risky=yes --dry-run", + "cs-fixer-tests": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.tests.php --verbose --diff --allow-risky=yes --dry-run" }, "minimum-stability": "stable", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "config": { diff --git a/Resources/config/services.xml b/config/services.xml similarity index 97% rename from Resources/config/services.xml rename to config/services.xml index 18da193..a251a32 100644 --- a/Resources/config/services.xml +++ b/config/services.xml @@ -2,7 +2,7 @@ + xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> diff --git a/phpcs.xml b/phpcs.xml index f4fd054..870440e 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,9 +1,9 @@ - PSR2 Coding Standard on EcnFeatureToggleBundle + Modified PSR12 Coding Standard on EcnFeatureToggleBundle ./ - */Tests/* + */tests/* */vendor/* diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1d6b09b..e1a5010 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,27 +1,27 @@ - - - + + + + ./tests + + + ./tests + ./tests/Twig + + + - ./ + ./src - ./Resources - ./Tests + ./config + ./tests ./vendor - - - - ./Tests - - - ./Tests - ./Tests/Twig - - + diff --git a/psalm.without_twig.xml b/psalm.without_twig.xml index 2d76777..51f118d 100644 --- a/psalm.without_twig.xml +++ b/psalm.without_twig.xml @@ -2,13 +2,15 @@ - + - + - + diff --git a/psalm.xml b/psalm.xml index fba50fe..5855613 100644 --- a/psalm.xml +++ b/psalm.xml @@ -2,11 +2,13 @@ - + - + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..6e7068e --- /dev/null +++ b/rector.php @@ -0,0 +1,56 @@ +paths([ + __DIR__.'/src', + __DIR__.'/tests', + ]); + + $rectorConfig->phpVersion(PhpVersion::PHP_81); + + // register a single rule + $rectorConfig->rules([ + InlineConstructorDefaultToPropertyRector::class, + AddParamTypeSplFixedArrayRector::class, + ]); + + $rectorConfig->skip([ + RemoveAnnotationRector::class, + RemoveUselessParamTagRector::class, + IntersectionTypesRector::class, + MixedTypeRector::class, + UnionTypesRector::class, + ClassPropertyAssignToConstructorPromotionRector::class, + AddSeeTestAnnotationRector::class, + RenameClassRector::class, + ]); + + // define sets of rules + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_81, + PHPUnitLevelSetList::UP_TO_PHPUNIT_100, + PHPUnitSetList::PHPUNIT_100, + PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES, + PHPUnitSetList::PHPUNIT_CODE_QUALITY, + SymfonySetList::SYMFONY_62, + SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES, + SymfonySetList::SYMFONY_CODE_QUALITY, + ]); +}; diff --git a/Attributes/Feature.php b/src/Attributes/Feature.php similarity index 75% rename from Attributes/Feature.php rename to src/Attributes/Feature.php index b1c651b..bd8da1b 100644 --- a/Attributes/Feature.php +++ b/src/Attributes/Feature.php @@ -1,5 +1,4 @@ - */ -#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Feature { /** @@ -27,7 +25,7 @@ class Feature * @param string $name */ #[Required] - public function __construct(public string $name = "") + public function __construct(public string $name = '') { } } diff --git a/DependencyInjection/Compiler/VoterCompilerPass.php b/src/DependencyInjection/Compiler/VoterCompilerPass.php similarity index 93% rename from DependencyInjection/Compiler/VoterCompilerPass.php rename to src/DependencyInjection/Compiler/VoterCompilerPass.php index 88a2490..f413821 100644 --- a/DependencyInjection/Compiler/VoterCompilerPass.php +++ b/src/DependencyInjection/Compiler/VoterCompilerPass.php @@ -1,5 +1,4 @@ -addMethodCall( 'addVoter', - [new Reference($id), $attributes["alias"]] + [new Reference($id), $attributes['alias']] ); } } diff --git a/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php similarity index 90% rename from DependencyInjection/Configuration.php rename to src/DependencyInjection/Configuration.php index 08f8419..e835b73 100644 --- a/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -1,5 +1,4 @@ -children() @@ -46,7 +46,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->children() ->scalarNode('voter')->defaultValue('AlwaysTrueVoter')->end() - ->variableNode('params')->defaultValue(array())->end() + ->variableNode('params')->defaultValue([])->end() ->end() ->end() @@ -55,7 +55,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->children() ->scalarNode('voter')->defaultValue(null)->end() - ->variableNode('params')->defaultValue(array())->end() + ->variableNode('params')->defaultValue([])->end() ->end() ->end() diff --git a/DependencyInjection/EcnFeatureToggleExtension.php b/src/DependencyInjection/EcnFeatureToggleExtension.php similarity index 92% rename from DependencyInjection/EcnFeatureToggleExtension.php rename to src/DependencyInjection/EcnFeatureToggleExtension.php index c6505a7..6a8643b 100644 --- a/DependencyInjection/EcnFeatureToggleExtension.php +++ b/src/DependencyInjection/EcnFeatureToggleExtension.php @@ -1,5 +1,4 @@ -setParameter('features', $features); $container->setParameter('default', $default); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader->load('services.xml'); } } diff --git a/EventListener/ControllerListener.php b/src/EventListener/ControllerListener.php similarity index 82% rename from EventListener/ControllerListener.php rename to src/EventListener/ControllerListener.php index e6a04c1..a6b3cb5 100644 --- a/EventListener/ControllerListener.php +++ b/src/EventListener/ControllerListener.php @@ -1,5 +1,4 @@ -getController(); - if ((!$controller instanceof Closure) - && is_object($controller) + if ((!$controller instanceof \Closure) + && \is_object($controller) && method_exists($controller, '__invoke') ) { $controller = [$controller, '__invoke']; } - if (!is_array($controller)) { + if (!\is_array($controller)) { return; } /** @var class-string */ - $className = $this->getRealClass(is_object($controller[0]) ? $controller[0]::class : $controller[0]); + $className = $this->getRealClass(\is_object($controller[0]) ? $controller[0]::class : $controller[0]); - $object = new ReflectionClass($className); + $object = new \ReflectionClass($className); $method = $object->getMethod($controller[1]); $controllerAnnotations = $object->getAttributes(); @@ -77,7 +72,7 @@ public function onKernelController(ControllerEvent $event): void /** * {@inheritDoc} */ - #[ArrayShape([KernelEvents::CONTROLLER => "string"])] + #[ArrayShape([KernelEvents::CONTROLLER => 'string'])] public static function getSubscribedEvents(): array { return [ @@ -90,11 +85,11 @@ public static function getSubscribedEvents(): array * * @param array $attributes * - * @throws NotFoundHttpException If a feature is found, but not enabled. + * @throws NotFoundHttpException if a feature is found, but not enabled */ private function checkFeature(array $attributes): void { - /** @var ReflectionAttribute $feature */ + /** @var \ReflectionAttribute $feature */ foreach ($attributes as $feature) { $featureInstance = $feature->newInstance(); if (($featureInstance instanceof Feature) && !$this->featureService->has($featureInstance->name)) { diff --git a/Exception/VoterNotFoundException.php b/src/Exception/VoterNotFoundException.php similarity index 76% rename from Exception/VoterNotFoundException.php rename to src/Exception/VoterNotFoundException.php index d88b675..2e4ad67 100644 --- a/Exception/VoterNotFoundException.php +++ b/src/Exception/VoterNotFoundException.php @@ -1,5 +1,4 @@ - */ -class VoterNotFoundException extends RuntimeException +class VoterNotFoundException extends \RuntimeException { } diff --git a/Service/FeatureService.php b/src/Service/FeatureService.php similarity index 90% rename from Service/FeatureService.php rename to src/Service/FeatureService.php index cd6dc58..82cd618 100644 --- a/Service/FeatureService.php +++ b/src/Service/FeatureService.php @@ -1,5 +1,4 @@ -defaultVoter['voter']; $voter = $this->voterRegistry->getVoter($voterName); - $defaultParams = $this->defaultVoter['params']; $params = $featureDefinition['params'] ?: $defaultParams; diff --git a/Twig/FeatureToggleExtension.php b/src/Twig/FeatureToggleExtension.php similarity index 88% rename from Twig/FeatureToggleExtension.php rename to src/Twig/FeatureToggleExtension.php index 6d0d18f..8dfffd8 100644 --- a/Twig/FeatureToggleExtension.php +++ b/src/Twig/FeatureToggleExtension.php @@ -1,5 +1,4 @@ -hasFeature(...)), ]; } /** * @return array [] */ - #[Pure] public function getTokenParsers(): array + #[Pure] + public function getTokenParsers(): array { return [ - new FeatureToggleTokenParser(), + new FeatureToggleTokenParser(), ]; } diff --git a/Twig/FeatureToggleNode.php b/src/Twig/FeatureToggleNode.php similarity index 97% rename from Twig/FeatureToggleNode.php rename to src/Twig/FeatureToggleNode.php index efea52b..e39f58a 100644 --- a/Twig/FeatureToggleNode.php +++ b/src/Twig/FeatureToggleNode.php @@ -1,5 +1,4 @@ -expect(Token::NAME_TYPE)->getValue(); $stream->expect(Token::BLOCK_END_TYPE); - $feature = $this->parser->subparse([$this, 'decideFeatureEnd'], true); + $feature = $this->parser->subparse($this->decideFeatureEnd(...), true); $stream->expect(Token::BLOCK_END_TYPE); return new FeatureToggleNode($name, $feature, $lineno, $this->getTag()); diff --git a/Voters/AlwaysFalseVoter.php b/src/Voters/AlwaysFalseVoter.php similarity index 94% rename from Voters/AlwaysFalseVoter.php rename to src/Voters/AlwaysFalseVoter.php index e7906b5..c218def 100644 --- a/Voters/AlwaysFalseVoter.php +++ b/src/Voters/AlwaysFalseVoter.php @@ -1,5 +1,4 @@ -session) { - throw new InvalidArgumentException(sprintf('The service "%s" has a dependency on the session', $this::class)); + throw new \InvalidArgumentException(sprintf('The service "%s" has a dependency on the session', static::class)); } $sessionKey = '_ecn_featuretoggle_'.$this->feature; @@ -81,14 +79,16 @@ protected function getStickyRatioPass(): bool } /** - * Check if the ratio passes + * Check if the ratio passes. * * @return bool + * + * @throws \Exception */ protected function getRatioPass(): bool { $ratio = $this->ratio * 100; - return rand(0, 99) < $ratio; + return random_int(0, 99) < $ratio; } } diff --git a/Voters/RequestHeaderVoter.php b/src/Voters/RequestHeaderVoter.php similarity index 89% rename from Voters/RequestHeaderVoter.php rename to src/Voters/RequestHeaderVoter.php index b5283ce..60bb465 100644 --- a/Voters/RequestHeaderVoter.php +++ b/src/Voters/RequestHeaderVoter.php @@ -1,5 +1,4 @@ -checkHeaderValues = $headers && RequestHeaderVoter::isAssociativeArray($headers); - $this->headers = $headers; + $this->checkHeaderValues = $headers && self::isAssociativeArray($headers); + $this->headers = $headers; } /** @@ -84,6 +83,6 @@ public function pass(): bool */ public static function isAssociativeArray(array $arr): bool { - return array_keys($arr) !== range(0, count($arr) - 1); + return array_keys($arr) !== range(0, \count($arr) - 1); } } diff --git a/Voters/ScheduleVoter.php b/src/Voters/ScheduleVoter.php similarity index 84% rename from Voters/ScheduleVoter.php rename to src/Voters/ScheduleVoter.php index 07e09f7..7cb05c7 100644 --- a/Voters/ScheduleVoter.php +++ b/src/Voters/ScheduleVoter.php @@ -1,5 +1,4 @@ -schedule); + $schedule = new \DateTime($this->schedule); } catch (Exception) { return false; } - return new DateTime() >= $schedule; + return new \DateTime() >= $schedule; } } diff --git a/Voters/VoterInterface.php b/src/Voters/VoterInterface.php similarity index 78% rename from Voters/VoterInterface.php rename to src/Voters/VoterInterface.php index c69824f..4bee766 100644 --- a/Voters/VoterInterface.php +++ b/src/Voters/VoterInterface.php @@ -1,5 +1,4 @@ - */ trait VoterTrait { - private string $feature = ""; + private string $feature = ''; /** * {@inheritdoc} diff --git a/Tests/EcnFeatureToggleBundleTest.php b/tests/EcnFeatureToggleBundleTest.php similarity index 81% rename from Tests/EcnFeatureToggleBundleTest.php rename to tests/EcnFeatureToggleBundleTest.php index aa1894b..bda4932 100644 --- a/Tests/EcnFeatureToggleBundleTest.php +++ b/tests/EcnFeatureToggleBundleTest.php @@ -1,5 +1,4 @@ -createConfiguration([ - 'features' => ['testFeature' => []] + 'features' => ['testFeature' => []], ]); // Load feature config $features = $this->configuration->getParameter('features'); $default = $this->configuration->getParameter('default'); - $this->assertEquals(['voter' => 'AlwaysTrueVoter', 'params' => []], $default); - $this->assertEquals([], $features['testFeature']['params']); + $this->assertSame(['voter' => 'AlwaysTrueVoter', 'params' => []], $default); + $this->assertSame([], $features['testFeature']['params']); } /** - * @throws Exception + * @throws \Exception */ private function createConfiguration(array $config = []): void { diff --git a/Tests/EventListener/ControllerListenerTest.php b/tests/EventListener/ControllerListenerTest.php similarity index 76% rename from Tests/EventListener/ControllerListenerTest.php rename to tests/EventListener/ControllerListenerTest.php index 1916afb..a607211 100644 --- a/Tests/EventListener/ControllerListenerTest.php +++ b/tests/EventListener/ControllerListenerTest.php @@ -1,5 +1,4 @@ -listener = new ControllerListener(new FeatureService([], [], new VoterRegistry())); $this->request = new Request([], [], []); @@ -54,9 +54,10 @@ class_exists(Feature::class); } /** - * Test Annotation Feature at method + * Test Annotation Feature at method. * - * @throws ReflectionException + * @throws \ReflectionException + * @throws Exception */ public function testFeatureAnnotationAtMethod(): void { @@ -64,15 +65,16 @@ public function testFeatureAnnotationAtMethod(): void $controller = new FooControllerFeatureAtMethod(); - $this->event = $this->getControllerEvent([$controller, 'barAction'], $this->request); + $this->event = $this->getControllerEvent($controller->barAction(...), $this->request); $this->listener->onKernelController($this->event); } /** - * Test Annotation Feature at static method + * Test Annotation Feature at static method. * - * @throws ReflectionException + * @throws \ReflectionException + * @throws Exception */ public function testFeatureAnnotationAtStaticMethod(): void { @@ -80,11 +82,14 @@ public function testFeatureAnnotationAtStaticMethod(): void $controller = new FooControllerFeatureAtStaticMethod(); - $this->event = $this->getControllerEvent([$controller, 'barAction'], $this->request); + $this->event = $this->getControllerEvent($controller->barAction(...), $this->request); $this->listener->onKernelController($this->event); } + /** + * @throws Exception + */ protected function getControllerEvent(object|array|string $controller, Request $request): ControllerEvent { /** @var Kernel $mockKernel */ @@ -94,39 +99,42 @@ protected function getControllerEvent(object|array|string $controller, Request $ } /** - * Test Annotation Feature at class + * Test Annotation Feature at class. * - * @throws ReflectionException + * @throws \ReflectionException + * @throws Exception */ public function testFeatureAnnotationAtClass(): void { $this->expectException(NotFoundHttpException::class); $controller = new FooControllerFeatureAtClass(); - $this->event = $this->getControllerEvent([$controller, 'barAction'], $this->request); + $this->event = $this->getControllerEvent($controller->barAction(...), $this->request); $this->listener->onKernelController($this->event); } /** - * Test Annotation Feature at method and class + * Test Annotation Feature at method and class. * - * @throws ReflectionException + * @throws \ReflectionException + * @throws Exception */ public function testFeatureAnnotationAtClassAndMethod(): void { $this->expectException(NotFoundHttpException::class); $controller = new FooControllerFeatureAtClassAndMethod(); - $this->event = $this->getControllerEvent([$controller, 'barAction'], $this->request); + $this->event = $this->getControllerEvent($controller->barAction(...), $this->request); $this->listener->onKernelController($this->event); } /** - * Test Annotation Feature at __invoke method + * Test Annotation Feature at __invoke method. * - * @throws ReflectionException + * @throws \ReflectionException + * @throws Exception */ public function testFeatureAnnotationAtObjectInvoke(): void { @@ -140,9 +148,10 @@ public function testFeatureAnnotationAtObjectInvoke(): void } /** - * Test Proxy extends class with Annotation Feature + * Test Proxy extends class with Annotation Feature. * - * @throws ReflectionException + * @throws \ReflectionException + * @throws Exception */ public function testFeatureProxyExtendsAnnotation(): void { @@ -156,10 +165,10 @@ public function testFeatureProxyExtendsAnnotation(): void } /** - * @throws ReflectionException - * - * @dataProvider callableDataProvider + * @throws \ReflectionException + * @throws Exception */ + #[DataProvider('callableDataProvider')] public function testAvoidClosure(callable $controller): void { /** @var FeatureService&MockObject $featureService */ @@ -176,10 +185,10 @@ public function testAvoidClosure(callable $controller): void $listener->onKernelController($event); } - public function callableDataProvider(): array + public static function callableDataProvider(): array { return [ - [static fn() => 'test'] + [static fn () => 'test'], ]; } } diff --git a/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php b/tests/EventListener/Fixture/FooControllerFeatureAtClass.php similarity index 87% rename from Tests/EventListener/Fixture/FooControllerFeatureAtClass.php rename to tests/EventListener/Fixture/FooControllerFeatureAtClass.php index 56cb245..8895418 100644 --- a/Tests/EventListener/Fixture/FooControllerFeatureAtClass.php +++ b/tests/EventListener/Fixture/FooControllerFeatureAtClass.php @@ -1,4 +1,4 @@ - [ 'voter' => 'AlwaysTrueVoter', - 'params' => [] - ] + 'params' => [], + ], ]; $default = [ 'voter' => null, - 'params' => [] + 'params' => [], ]; // Create service @@ -48,7 +47,7 @@ public function testIfFeatureMatches(): void } /** - * Test Default voter + * Test Default voter. */ public function testDefaultVoter(): void { @@ -56,12 +55,12 @@ public function testDefaultVoter(): void $features = [ 'testFeature' => [ 'voter' => null, - 'params' => [] - ] + 'params' => [], + ], ]; $default = [ 'voter' => 'AlwaysTrueVoter', - 'params' => [] + 'params' => [], ]; // Create service @@ -72,7 +71,7 @@ public function testDefaultVoter(): void } /** - * Test new feature + * Test new feature. */ public function testFeatureUnknownVoter(): void { @@ -83,12 +82,12 @@ public function testFeatureUnknownVoter(): void $features = [ 'testFeature' => [ 'voter' => 'testVoter', - 'params' => [] - ] + 'params' => [], + ], ]; $default = [ 'voter' => null, - 'params' => [] + 'params' => [], ]; // Create service diff --git a/Tests/Twig/FeatureToggleExtensionTest.php b/tests/Twig/FeatureToggleExtensionTest.php similarity index 85% rename from Tests/Twig/FeatureToggleExtensionTest.php rename to tests/Twig/FeatureToggleExtensionTest.php index f64194a..a5b2edf 100644 --- a/Tests/Twig/FeatureToggleExtensionTest.php +++ b/tests/Twig/FeatureToggleExtensionTest.php @@ -1,5 +1,4 @@ -getMockBuilder(FeatureService::class) - ->disableOriginalConstructor() - ->getMock(); + $service = $this->createMock(FeatureService::class); $service ->method('has') @@ -47,7 +48,7 @@ public function testCallable(): void // Check if functions are returned as array // removed because TokenParser is always returned in an array - # $this->assertIsArray($functions); + // $this->assertIsArray($functions); // Check if the function is a twig function $this->assertInstanceOf(TwigFunction::class, $functions[0]); @@ -59,6 +60,5 @@ public function testCallable(): void // Check if callable returns false for an unknown feature $this->assertFalse($callable('unknownFeature')); - } } diff --git a/Tests/Twig/FeatureToggleNodeTest.php b/tests/Twig/FeatureToggleNodeTest.php similarity index 80% rename from Tests/Twig/FeatureToggleNodeTest.php rename to tests/Twig/FeatureToggleNodeTest.php index c48001b..4657b16 100644 --- a/Tests/Twig/FeatureToggleNodeTest.php +++ b/tests/Twig/FeatureToggleNodeTest.php @@ -1,5 +1,4 @@ -expectException(InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); $voter = $this->getRatioVoter(0.5, true, false); $voter->pass(); } /** - * @dataProvider dataProvider - * * @param bool $hasSession */ + #[DataProvider('dataProvider')] public function testHighRatioVoterPass(bool $hasSession): void { $voter = $this->getRatioVoter(0.9, false, $hasSession); @@ -56,42 +54,36 @@ public function testHighRatioVoterPass(bool $hasSession): void } /** - * @dataProvider dataProvider - * * @param bool $hasSession */ + #[DataProvider('dataProvider')] public function testZeroRatioVoterPass(bool $hasSession): void { $voter = $this->getRatioVoter(0, false, $hasSession); $hits = $this->executeTestIteration($voter); - $this->assertEquals(0, $hits); + $this->assertSame(0, $hits); } /** - * @dataProvider dataProvider - * * @param bool $hasSession */ + #[DataProvider('dataProvider')] public function testOneRatioVoterPass(bool $hasSession): void { $voter = $this->getRatioVoter(1, false, $hasSession); $hits = $this->executeTestIteration($voter); - $this->assertEquals(100, $hits); + $this->assertSame(100, $hits); } /** - * Simple data provider for $hasSession - * - * @return array + * Simple data provider for $hasSession. */ - public function dataProvider(): array + public static function dataProvider(): \Iterator { - return [ - [true], - [false], - ]; + yield [true]; + yield [false]; } public function testStickyRatioVoterPass(): void @@ -101,16 +93,16 @@ public function testStickyRatioVoterPass(): void $this->stickyValues = ['_ecn_featuretoggle_ratioTest' => $initialPass]; if ($initialPass) { - $requiredHits = $this->executeTestIteration($voter) === 100; + $requiredHits = 100 === $this->executeTestIteration($voter); } else { - $requiredHits = $this->executeTestIteration($voter) === 0; + $requiredHits = 0 === $this->executeTestIteration($voter); } $this->assertTrue($requiredHits); } /** - * Callback for session stub + * Callback for session stub. * * @param string $key * @@ -122,7 +114,7 @@ public function hasStickyCallback(string $key): bool } /** - * Callback for session stub + * Callback for session stub. * * @param string $key * @@ -137,14 +129,13 @@ protected function getRatioVoter(float $ratio, bool $sticky = false, bool $hasSe { $session = null; - if($hasSession) - { + if ($hasSession) { // Create service stub /** @var Session&MockObject $session */ $session = $this->createMock(SessionInterface::class); - $session->method('get')->willReturnCallback([$this, 'getStickyCallback']); - $session->method('has')->willReturnCallback([$this, 'hasStickyCallback']); + $session->method('get')->willReturnCallback($this->getStickyCallback(...)); + $session->method('has')->willReturnCallback($this->hasStickyCallback(...)); } $params = ['ratio' => $ratio, 'sticky' => $sticky]; @@ -157,7 +148,7 @@ protected function getRatioVoter(float $ratio, bool $sticky = false, bool $hasSe } /** - * Executes the Tests n time returning the number of passes + * Executes the tests n time returning the number of passes. * * @param RatioVoter $ratioVoter * @@ -168,9 +159,9 @@ private function executeTestIteration(RatioVoter $ratioVoter): int $hits = 0; $iterationCount = 100; - for ($i = 1; $i <= $iterationCount; $i++) { + for ($i = 1; $i <= $iterationCount; ++$i) { if ($ratioVoter->pass()) { - $hits++; + ++$hits; } } diff --git a/Tests/Voters/RequestHeaderVoterTest.php b/tests/Voters/RequestHeaderVoterTest.php similarity index 99% rename from Tests/Voters/RequestHeaderVoterTest.php rename to tests/Voters/RequestHeaderVoterTest.php index 7d026ec..44fc2c5 100644 --- a/Tests/Voters/RequestHeaderVoterTest.php +++ b/tests/Voters/RequestHeaderVoterTest.php @@ -1,5 +1,4 @@ -getScheduleVoter((new DateTime())->modify('-1 second')->format(DateTimeInterface::RSS)); + $voter = $this->getScheduleVoter((new \DateTime())->modify('-1 second')->format(\DateTimeInterface::RSS)); $this->assertTrue($voter->pass()); } public function testLaterScheduleIsSet(): void { - $voter = $this->getScheduleVoter((new DateTime())->modify('+1 second')->format(DateTimeInterface::RSS)); + $voter = $this->getScheduleVoter((new \DateTime())->modify('+1 second')->format(\DateTimeInterface::RSS)); $this->assertFalse($voter->pass()); } diff --git a/Tests/Voters/VoterRegistryTest.php b/tests/Voters/VoterRegistryTest.php similarity index 91% rename from Tests/Voters/VoterRegistryTest.php rename to tests/Voters/VoterRegistryTest.php index 593e638..06a15d7 100644 --- a/Tests/Voters/VoterRegistryTest.php +++ b/tests/Voters/VoterRegistryTest.php @@ -1,5 +1,4 @@ - */ class VoterRegistryTest extends TestCase { + /** + * @throws Exception + */ public function testAddVotersToRegistry(): void { // Mock a voter @@ -36,9 +39,11 @@ public function testAddVotersToRegistry(): void // Test for voters $this->assertSame($voterOne, $registry->getVoter('myFirstTestVoter')); $this->assertSame($voterTwo, $registry->getVoter('mySecondTestVoter')); - } + /** + * @throws Exception + */ public function testUnknownVoterException(): void { $this->expectException(VoterNotFoundException::class); diff --git a/Tests/Voters/VoterTraitTest.php b/tests/Voters/VoterTraitTest.php similarity index 96% rename from Tests/Voters/VoterTraitTest.php rename to tests/Voters/VoterTraitTest.php index 9630e9a..5e5dab6 100644 --- a/Tests/Voters/VoterTraitTest.php +++ b/tests/Voters/VoterTraitTest.php @@ -1,5 +1,4 @@ - Date: Tue, 23 May 2023 16:01:46 +0200 Subject: [PATCH 4/4] update composer.json --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index cf52a6e..330c615 100644 --- a/composer.json +++ b/composer.json @@ -18,15 +18,15 @@ "phpunit/phpunit": "^10.1.3", "squizlabs/php_codesniffer": "^3.7.2", "escapestudios/symfony2-coding-standard": "^3.13.0", - "vimeo/psalm": "^5.11.0", + "vimeo/psalm": "^5.12.0", "jetbrains/phpstorm-attributes": "^1.0", - "friendsofphp/php-cs-fixer": "^3.16.0", - "rector/rector": "*", + "friendsofphp/php-cs-fixer": "^3.17.0", + "rector/rector": "^0.16.0", "roave/security-advisories": "dev-master", "twig/twig": "3.6.0" }, "conflict": { - "phpunit/phpunit": "<10.0" + "phpunit/phpunit": "<9.0" }, "suggest": { "twig/twig": "^3.6.0"