Skip to content

Commit

Permalink
Add Symfony specific rules
Browse files Browse the repository at this point in the history
  • Loading branch information
antfroger committed Jan 5, 2021
1 parent 5626288 commit 63f28b3
Show file tree
Hide file tree
Showing 15 changed files with 3,999 additions and 912 deletions.
52 changes: 52 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: CI

on: [push, pull_request]

env:
PHPUNIT_FLAGS: "-v"

jobs:
test:
name: ProgressiveBundle (PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }})
runs-on: ${{ matrix.operating-system }}

strategy:
matrix:
operating-system: ['ubuntu-latest', 'windows-latest', 'macos-latest']
php-versions: ['7.3', '7.4', '8.0']

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: composer:v2

- name: Validate composer.json and composer.lock
run: composer validate

- name: "Set composer cache directory"
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"

- name: "Cache composer"
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-${{ matrix.php-version }}-composer-

- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

- name: Install dependencies
run: composer install --prefer-dist --optimize-autoloader

- name: "Run tests"
run: vendor/bin/phpunit ${{ env.PHPUNIT_FLAGS }}



17 changes: 17 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Lint

on: [push, pull_request]

jobs:
php-cs-fixer:
name: PHP-CS-Fixer
runs-on: ubuntu-latest

steps:
- name: "Checkout code"
uses: actions/checkout@v2

- name: PHP-CS-Fixer
uses: docker://oskarstark/php-cs-fixer-ga
with:
args: --diff --dry-run
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/vendor/
.php_cs.cache
.phpunit.xml
.phpunit.result.cache
6 changes: 6 additions & 0 deletions AfProgressiveBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@

namespace Af\ProgressiveBundle;

use Af\ProgressiveBundle\DependencyInjection\Compiler\RulePass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class AfProgressiveBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new RulePass());
}
}
8 changes: 6 additions & 2 deletions DependencyInjection/AfProgressiveExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Af\ProgressiveBundle\DependencyInjection;

use Progressive\Context;
use Progressive\Progressive;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
Expand All @@ -21,12 +23,14 @@ public function load(array $configs, ContainerBuilder $container)
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);

// Add parameters to the Context
if (null !== $config['context']) {
$definition = $container->getDefinition('af.progressive.context');
$definition = $container->getDefinition(Context::class);
$definition->addMethodCall('add', [$config['context']]);
}

$definition = $container->getDefinition('af.progressive');
// Features' configuration
$definition = $container->getDefinition(Progressive::class);
$featuresConfig = Yaml::parseFile($config['config']);
$definition->replaceArgument(0, $featuresConfig);
}
Expand Down
26 changes: 26 additions & 0 deletions DependencyInjection/Compiler/RulePass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Af\ProgressiveBundle\DependencyInjection\Compiler;

use Progressive\Rule\Store;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class RulePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has(Store::class)) {
return;
}

$definition = $container->findDefinition(Store::class);

$taggedServices = $container->findTaggedServiceIds('af_progressive.rule');

foreach ($taggedServices as $id => $tags) {
$definition->addMethodCall('add', [new Reference($id)]);
}
}
}
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Progressive Bundle

**Symfony integration for the feature-flag library [Progressive](https://github.com/antfroger/progressive)**
Symfony integration for the feature-flag library [Progressive](https://github.com/antfroger/progressive)

[![Build Status](https://github.com/antfroger/progressive-bundle/workflows/CI/badge.svg)](https://github.com/antfroger/progressive-bundle)

## Installation

Expand Down Expand Up @@ -55,3 +57,19 @@ features:
env: ['dev', 'preprod']
roles: ['ROLE_ADMIN', 'ROLE_DEV']
```
Look at the [Progressive documentation](https://github.com/antfroger/progressive#usage) to know more about the feature's configuration.
## Usage
You can use Progressive in a controller:
```php
public function info(Progressive $progressive): Response
{
if ($progressive->isEnabled('call-center')) {
// Do what you want when the feature `call-center` is enabled
}
}
```

20 changes: 16 additions & 4 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,24 @@
https://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="Progressive\Progressive" alias="af.progressive"/>
<service id="af.progressive" class="Progressive\Progressive">
<service id="Progressive\Progressive">
<argument type="collection" /><!-- configuration will be dynamically filled in -->
<argument type="service" id="af.progressive.context"/>
<argument type="service" id="Progressive\Context"/>
<argument type="service" id="Progressive\Rule\Store"/>
</service>

<service id="af.progressive.context" class="Progressive\Context" />
<service id="Progressive\Context"/>
<service id="Progressive\Rule\Store"/>

<service id="Af\ProgressiveBundle\Rule\Environments">
<argument>%kernel.environment%</argument>
</service>
<service id="Af\ProgressiveBundle\Rule\Roles">
<argument type="service" id="security.helper"/>
</service>

<instanceof id="Progressive\Rule\RuleInterface" autowire="true">
<tag name="af_progressive.rule"/>
</instanceof>
</services>
</container>
33 changes: 33 additions & 0 deletions Rule/Environments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Af\ProgressiveBundle\Rule;

use Progressive\ParameterBagInterface;
use Progressive\Rule\RuleInterface;

class Environments implements RuleInterface
{
/** @var string */
private $env;

public function __construct($env)
{
$this->env = $env;
}

/**
* {@inheritdoc}
*/
public function decide(ParameterBagInterface $bag, array $envs = []): bool
{
return in_array($this->env, $envs);
}

/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'env';
}
}
45 changes: 45 additions & 0 deletions Rule/Roles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Af\ProgressiveBundle\Rule;

use Progressive\ParameterBagInterface;
use Progressive\Rule\RuleInterface;
use Symfony\Component\Security\Core\Security;

class Roles implements RuleInterface
{
/** @var Security */
private $security;

public function __construct(Security $security)
{
$this->security = $security;
}

/**
* {@inheritdoc}
*/
public function decide(ParameterBagInterface $bag, array $roles = []): bool
{
if (null === $user = $this->security->getUser()) {
return false;
}

$userRoles = $user->getRoles();
foreach ($userRoles as $role) {
if (in_array($role, $roles, true)) {
return true;
}
}

return false;
}

/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'roles';
}
}
34 changes: 34 additions & 0 deletions Tests/Rule/EnvironmentsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Af\ProgressiveBundle\Tests\Rule;

use Af\ProgressiveBundle\Rule\Environments;
use PHPUnit\Framework\TestCase;
use Progressive\ParameterBagInterface;

class EnvironmentsTest extends TestCase
{
/**
* @dataProvider valueProvider
*/
public function testDecide(string $env, array $envs, bool $expected)
{
$context = $this->createMock(ParameterBagInterface::class);

$rule = new Environments($env);
$response = $rule->decide($context, $envs);

$this->assertSame($response, $expected);
}

public function valueProvider(): array
{
return [
['dev', ['dev', 'test', 'prod'], true],
['dev', ['preprod', 'dev'], true],
['test', ['test'], true],
['test', [], false],
['prod', ['dev', 'test'], false],
];
}
}
57 changes: 57 additions & 0 deletions Tests/Rule/RolesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Af\ProgressiveBundle\Tests\Rule;

use Af\ProgressiveBundle\Rule\Roles;
use PHPUnit\Framework\TestCase;
use Progressive\ParameterBagInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;

class RolesTest extends TestCase
{
public function testNotAuthenticated()
{
$security = $this->createMock(Security::class);
$context = $this->createMock(ParameterBagInterface::class);

$rule = new Roles($security);
$this->assertFalse($rule->decide($context, []));
}

/**
* @dataProvider rolesProvider
*/
public function testAuthenticatedUser(array $roles, bool $expected)
{
$user = $this->createMock(UserInterface::class);
$user->expects($this->once())
->method('getRoles')
->willReturn(['ROLE_DEV', 'ROLE_ADMIN'])
;

$security = $this->createMock(Security::class);
$security->expects($this->once())
->method('getUser')
->willReturn($user)
;
$context = $this->createMock(ParameterBagInterface::class);

$rule = new Roles($security);
$response = $rule->decide($context, $roles);

$this->assertSame($response, $expected);
}

public function rolesProvider(): array
{
return [
[[], false],
[['ROLE_SUPERADMIN', 'ROLE_TRANSLATOR'], false],
[['ROLE_ADMIN', 'ROLE_DEV', 'ROLE_SUPERADMIN'], true],
[['ROLE_DEV', 'ROLE_ADMIN'], true],
[['ROLE_ADMIN'], true],
[['ROLE_DEV'], true],
];
}
}
10 changes: 8 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@
}
],
"require": {
"antfroger/progressive": "^1.0"
"antfroger/progressive": "^1.0",
"symfony/config": "^5.2",
"symfony/dependency-injection": "^5.2",
"symfony/http-kernel": "^5.2",
"symfony/security-core": "^5.2",
"symfony/yaml": "^5.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.17"
"friendsofphp/php-cs-fixer": "^2.17",
"phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": { "Af\\ProgressiveBundle\\": "" }
Expand Down
Loading

0 comments on commit 63f28b3

Please sign in to comment.