Skip to content

[FeatureFlags] Propose a new component #51649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: 7.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
129043f
[FeatureToggle] Import component from private sources
Neirda24 Jul 28, 2023
48819ec
[UPDATE] Fix twig function must be snake_case + gitattributes for bundle
Neirda24 Jul 30, 2023
2f875ea
cleanup before RFC (#1)
Jean-Beru Sep 7, 2023
f2cc75b
[UPDATE] Removed the Random Strategy & Provider
Neirda24 Sep 7, 2023
13d6176
[UPDATE] According to comments
Neirda24 Sep 13, 2023
1d4fb6c
[FabBot] Apply some patches
Neirda24 Sep 13, 2023
a8a2536
[UPDATE] Remove Outer*Interface's
Neirda24 Sep 21, 2023
844ae65
[UPDATE] Use Traversable instead of ArrayIterator
Neirda24 Sep 21, 2023
0473edc
[UPDATE] Remove forgottent Outer*Interface's
Neirda24 Sep 21, 2023
ba54221
[UPDATE] Add StrategyInterface on previous Outer*Strategy's
Neirda24 Sep 21, 2023
4f03511
[FEATURE] Add header, query strategies through request stack and make…
Neirda24 Sep 21, 2023
416e91d
[UPDATE] Add UnanimousStrategy
Neirda24 Sep 21, 2023
17fae0a
[UPDATE] Prefixed some return phpdoc tag with phpstan-
Neirda24 Sep 21, 2023
ad36be4
[UPDATE] Rename to
Neirda24 Sep 21, 2023
858f66d
[UPDATE] Fix phpunit covers annotation
Neirda24 Sep 21, 2023
6a356aa
[UPDATE][fabbot] Coding standard
Neirda24 Sep 22, 2023
cec1e92
[UPDATE][fabbot] usage of void in tests
Neirda24 Sep 22, 2023
7618497
[UPDATE] Improve performance on FeatureCollection to stop at first found
Neirda24 Sep 22, 2023
0ff46e4
[Update] Move Debug files
Jean-Beru Sep 25, 2023
19b5c5f
Rework of providers
Neirda24 Sep 29, 2023
5be52b0
[UPDATE] Fix coding standards according to fabbot
Neirda24 Sep 29, 2023
63f256f
rename to FeatureFlags (#6)
Jean-Beru Sep 29, 2023
1673877
Feature/add debug command
Neirda24 Oct 5, 2023
00bed51
[UPDATE] Add coding standards
Neirda24 Oct 5, 2023
c4842ba
move to FrameworkBundle (#9)
Jean-Beru Oct 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[FeatureToggle] Import component from private sources
  • Loading branch information
Neirda24 authored and Jean-Beru committed Oct 16, 2023
commit 129043f4e2fb97506ba21a1353d16daf4e4d785c
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"symfony/error-handler": "self.version",
"symfony/event-dispatcher": "self.version",
"symfony/expression-language": "self.version",
"symfony/feature-toggle": "self.version",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"symfony/feature-toggle": "self.version",
"symfony/feature-flags": "self.version",

"symfony/filesystem": "self.version",
"symfony/finder": "self.version",
"symfony/form": "self.version",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FeatureToggleBundle\DataCollector;

use Closure;
use Symfony\Component\FeatureToggle\Feature;
use Symfony\Component\FeatureToggle\FeatureCollection;
use Symfony\Component\FeatureToggle\Strategy\StrategyInterface;
use Symfony\Component\FeatureToggle\StrategyResult;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Symfony\Component\VarDumper\Caster\ClassStub;
use Symfony\Component\VarDumper\Cloner\Data;

/**
* @phpstan-type FeatureType array{
* default: bool,
* description: string,
* strategy: StrategyInterface,
* }
* @phpstan-type ToggleType array{
* feature: string,
* result: bool|null,
* computes: array<string, ComputeType>,
* }
* @phpstan-type ComputeType array{
* strategyId: string,
* strategyClass: string,
* level: int,
* result: StrategyResult|null,
* }
*
* @property Data|array{
* features: array<string, FeatureType>,
* toggles: array<string, ToggleType>,
* } $data
*/
final class FeatureCheckerDataCollector extends DataCollector implements LateDataCollectorInterface
{
/** @var \SplStack<string> */
private \SplStack $currentToggle;

/** @var \SplStack<string> */
private \SplStack $currentCompute;

public function __construct(
private readonly FeatureCollection $featureCollection,
) {
$this->data = ['features' => [], 'toggles' => []];
$this->currentToggle = new \SplStack();
$this->currentCompute = new \SplStack();
}

public function collect(Request $request, Response $response, \Throwable $exception = null): void
{
foreach ($this->featureCollection as $feature) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we really iterate over all registered features to collect them ? Those don't represent what happens during that request (and this will force to instantiate them all).

To me, this looks like a use case for a debug:feature-toggles command allowing to show the configuration of toggles (or maybe debug:container --tag="feature_toggle.feature" would already be enough)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a dedicated command could be indeed the solution. To me the debug is not enough as it doesn't show each inner configuration and nested strategies.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Features are not listed anymore in profiler. This will be done in a dedicated command.

$strategy = (Closure::bind(fn(): StrategyInterface => $this->strategy, $feature, Feature::class))();
$default = (Closure::bind(fn(): bool => $this->default, $feature, Feature::class))();

$this->data['features'][$feature->getName()] = [
'default' => $default,
'description' => $feature->getDescription(),
'strategy' => $strategy,
];
}
}

public function collectIsEnabledStart(string $featureName): void
{
$toggleId = uniqid();

$this->data['toggles'][$toggleId] = [
'feature' => $featureName,
'computes' => [],
'result' => null,
];
$this->currentToggle->push($toggleId);
}

/**
* @param class-string $strategyClass
*/
public function collectComputeStart(string $strategyId, string $strategyClass): void
{
$toggleId = $this->currentToggle->top();
$computeId = uniqid();
$level = $this->currentCompute->count();

$this->data['toggles'][$toggleId]['computes'][$computeId] = [
'strategyId' => $strategyId,
'strategyClass' => new ClassStub($strategyClass),
'level' => $level,
'result' => null,
];
$this->currentCompute->push($computeId);
}

public function collectComputeStop(StrategyResult $result): void
{
$toggleId = $this->currentToggle->top();
$computeId = $this->currentCompute->pop();

$this->data['toggles'][$toggleId]['computes'][$computeId]['result'] = $result;
}

public function collectIsEnabledStop(bool $result): void
{
$toggleId = $this->currentToggle->pop();

$this->data['toggles'][$toggleId]['result'] = $result;
}

public function getName(): string
{
return 'feature_toggle';
}

public function reset(): void
{
$this->data = [
'features' => [],
'toggles' => [],
];
}

public function lateCollect(): void
{
$this->data = $this->cloneVar($this->data);
}

/**
* @return list<FeatureType>|Data
*/
public function getFeatures(): array|Data
{
return $this->data['features'];
}

/**
* @return list<ToggleType>|Data
*/
public function getToggles(): array|Data
{
return $this->data['toggles'];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FeatureToggleBundle\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\FeatureToggle\Debug\TraceableFeatureChecker;
use Symfony\Component\FeatureToggle\Debug\TraceableStrategy;

final class DebugPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has('feature_toggle.data_collector')) {
return;
}

$container->register('debug.toggle_feature.feature_checker', TraceableFeatureChecker::class)
->setDecoratedService('toggle_feature.feature_checker')
->setArguments([
'$featureChecker' => new Reference('.inner'),
'$dataCollector' => new Reference('feature_toggle.data_collector'),
])
;

foreach ($container->findTaggedServiceIds('feature_toggle.feature_strategy') as $serviceId => $tags) {
$container->register('debug.'.$serviceId, TraceableStrategy::class)
->setDecoratedService($serviceId)
->setArguments([
'$strategy' => new Reference('.inner'),
'$strategyId' => $serviceId,
'$dataCollector' => new Reference('feature_toggle.data_collector'),
])
;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FeatureToggleBundle\DependencyInjection\CompilerPass;

use Closure;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\FeatureToggle\Provider\ProviderInterface;

final class FeatureCollectionPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;

public function process(ContainerBuilder $container): void
{
$container->registerForAutoconfiguration(ProviderInterface::class)->addTag('feature_toggle.feature_provider');

$collection = $container->getDefinition('toggle_feature.feature_collection');

foreach ($this->findAndSortTaggedServices('feature_toggle.feature_provider', $container) as $provider) {
$collectionDefinition = (new Definition(Closure::class))
->setFactory([Closure::class, 'fromCallable'])
->setArguments([[$provider, 'provide']])
;

$collection
->addMethodCall('withFeatures', [$collectionDefinition])
;
}
}
}
Loading