-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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
base: 7.0
Are you sure you want to change the base?
Changes from 1 commit
129043f
48819ec
2f875ea
f2cc75b
13d6176
1d4fb6c
a8a2536
844ae65
0473edc
ba54221
4f03511
416e91d
17fae0a
ad36be4
858f66d
6a356aa
cec1e92
7618497
0ff46e4
19b5c5f
5be52b0
63f256f
1673877
00bed51
c4842ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
Neirda24 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
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]) | ||
; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.