Skip to content

Commit

Permalink
Add Symfony bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
trompette committed Dec 17, 2019
1 parent 139b807 commit 6a0a914
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 1 deletion.
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "*",
Expand Down
13 changes: 13 additions & 0 deletions sources/Bundle/FeatureTogglesBundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Trompette\FeatureToggles\Bundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class FeatureTogglesBundle extends Bundle
{
public function getContainerExtensionClass()
{
return FeatureTogglesExtension::class;
}
}
48 changes: 48 additions & 0 deletions sources/Bundle/FeatureTogglesConfiguration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Trompette\FeatureToggles\Bundle;

use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class FeatureTogglesConfiguration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new UnifiedTreeBuilder('feature_toggles');
$treeBuilder
->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;
}
}
101 changes: 101 additions & 0 deletions sources/Bundle/FeatureTogglesExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace Trompette\FeatureToggles\Bundle;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Trompette\FeatureToggles\Console\ConfigureFeatureCommand;
use Trompette\FeatureToggles\Console\MigrateDBALSchemaCommand;
use Trompette\FeatureToggles\DBAL\OnOffStrategyConfigurationRepository;
use Trompette\FeatureToggles\DBAL\PercentageStrategyConfigurationRepository;
use Trompette\FeatureToggles\DBAL\WhitelistStrategyConfigurationRepository;
use Trompette\FeatureToggles\FeatureRegistry;
use Trompette\FeatureToggles\OnOffStrategy\OnOff;
use Trompette\FeatureToggles\PercentageStrategy\Percentage;
use Trompette\FeatureToggles\ToggleRouter;
use Trompette\FeatureToggles\WhitelistStrategy\Whitelist;

class FeatureTogglesExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$config = $this->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')
;
}
}
62 changes: 62 additions & 0 deletions tests/Bundle/FeatureTogglesBundleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Test\Trompette\FeatureToggles\Bundle;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
use Trompette\FeatureToggles\Bundle\FeatureTogglesBundle;
use Trompette\FeatureToggles\ToggleRouter;

class FeatureTogglesBundleTest extends TestCase
{
public function testAppKernelCanBootWithBundleRegistered()
{
$kernel = new AppKernel('test', true);
$kernel->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';
}
}

0 comments on commit 6a0a914

Please sign in to comment.