diff --git a/composer.json b/composer.json index 067571e..2404954 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,11 @@ "ext-pdo": "*", "beberlei/assert": "^3.2", "doctrine/dbal": "^2.10", + "symfony/config": "^3.4 || ^4.3 || ^5.0", "symfony/console": "^3.4 || ^4.3 || ^5.0", - "symfony/expression-language": "^3.4 || ^4.3 || ^5.0" + "symfony/dependency-injection": "^3.4 || ^4.3 || ^5.0", + "symfony/expression-language": "^3.4 || ^4.3 || ^5.0", + "symfony/http-kernel": "^3.4 || ^4.3 || ^5.0" }, "require-dev": { "ext-sqlite3": "*", diff --git a/sources/Bundle/FeatureTogglesBundle.php b/sources/Bundle/FeatureTogglesBundle.php new file mode 100644 index 0000000..19c8fbe --- /dev/null +++ b/sources/Bundle/FeatureTogglesBundle.php @@ -0,0 +1,13 @@ +getRootNode() + ->children() + ->scalarNode('doctrine_dbal_connection')->defaultValue('doctrine.dbal.default_connection')->end() + ->arrayNode('declared_features') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('description')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('strategy')->isRequired()->cannotBeEmpty()->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + + return $treeBuilder; + } +} + +class UnifiedTreeBuilder extends TreeBuilder +{ + public function __construct(string $name, string $type = 'array', NodeBuilder $builder = null) + { + $builder = $builder ?: new NodeBuilder(); + $this->root = $builder->node($name, $type)->setParent($this); + } + + public function getRootNode(): NodeDefinition + { + return $this->root; + } +} diff --git a/sources/Bundle/FeatureTogglesExtension.php b/sources/Bundle/FeatureTogglesExtension.php new file mode 100644 index 0000000..e204ca2 --- /dev/null +++ b/sources/Bundle/FeatureTogglesExtension.php @@ -0,0 +1,101 @@ +processConfiguration($this->getConfiguration([], $container), $configs); + + $this->defineFeatureRegistry($config['declared_features'], $container); + $this->defineTogglingStrategies($config['doctrine_dbal_connection'], $container); + $this->defineToggleRouter($container); + $this->defineConsoleCommands($container); + } + + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new FeatureTogglesConfiguration(); + } + + private function defineFeatureRegistry(array $declaredFeatures, ContainerBuilder $container) + { + $featureRegistry = $container->register(FeatureRegistry::class, FeatureRegistry::class); + + foreach ($declaredFeatures as $name => $declaredFeature) { + $featureRegistry->addMethodCall( + 'register', + [$name, $declaredFeature['description'], $declaredFeature['strategy']] + ); + } + } + + private function defineTogglingStrategies(string $doctrineDBALConnection, ContainerBuilder $container) + { + $configurationRepositories = [ + 'onoff' => OnOffStrategyConfigurationRepository::class, + 'whitelist' => WhitelistStrategyConfigurationRepository::class, + 'percentage' => PercentageStrategyConfigurationRepository::class, + ]; + + foreach ($configurationRepositories as $key => $class) { + $container->register($class, $class)->addArgument(new Reference($doctrineDBALConnection)); + } + + $togglingStrategies = [ + 'onoff' => OnOff::class, + 'whitelist' => Whitelist::class, + 'percentage' => Percentage::class, + ]; + + foreach ($togglingStrategies as $key => $class) { + $container->register($class, $class)->addArgument(new Reference($configurationRepositories[$key])); + } + } + + private function defineToggleRouter(ContainerBuilder $container) + { + $container + ->register(ToggleRouter::class, ToggleRouter::class) + ->setPublic(true) + ->addArgument(new Reference(FeatureRegistry::class)) + ->addArgument([ + 'onoff' => new Reference(OnOff::class), + 'whitelist' => new Reference(Whitelist::class), + 'percentage' => new Reference(Percentage::class), + ]) + ; + } + + private function defineConsoleCommands(ContainerBuilder $container) + { + $container + ->register(MigrateDBALSchemaCommand::class, MigrateDBALSchemaCommand::class) + ->addArgument(new Reference(OnOffStrategyConfigurationRepository::class)) + ->addArgument(new Reference(WhitelistStrategyConfigurationRepository::class)) + ->addArgument(new Reference(PercentageStrategyConfigurationRepository::class)) + ->addTag('console.command') + ; + + $container + ->register(ConfigureFeatureCommand::class, ConfigureFeatureCommand::class) + ->addArgument(new Reference(ToggleRouter::class)) + ->addTag('console.command') + ; + } +} diff --git a/tests/Bundle/FeatureTogglesBundleTest.php b/tests/Bundle/FeatureTogglesBundleTest.php new file mode 100644 index 0000000..7cab050 --- /dev/null +++ b/tests/Bundle/FeatureTogglesBundleTest.php @@ -0,0 +1,62 @@ +boot(); + + $this->assertTrue($kernel->getContainer()->has(ToggleRouter::class)); + } +} + +class AppKernel extends Kernel +{ + public function registerBundles() + { + return [new FeatureTogglesBundle()]; + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + $loader->load(function (ContainerBuilder $container) { + $container + ->register('my_doctrine_dbal_connection', Connection::class) + ->setFactory([DriverManager::class, 'getConnection']) + ->setArguments([['url' => 'sqlite:///:memory:']]) + ; + + $container->loadFromExtension('feature_toggles', [ + 'doctrine_dbal_connection' => 'my_doctrine_dbal_connection', + 'declared_features' => [ + 'feature' => [ + 'description' => 'awesome feature', + 'strategy' => 'onoff or whitelist or percentage', + ] + ] + ]); + }); + } + + public function getCacheDir() + { + return sys_get_temp_dir().'/php-feature-toggles/'.Kernel::VERSION.'/cache'; + } + + public function getLogDir() + { + return sys_get_temp_dir().'/php-feature-toggles/'.Kernel::VERSION.'/logs'; + } +}