Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit e542f2e

Browse files
authored
Allow passing variadic args into Unleash->isFeatureEnabled()/Unleash->isFeatureDisabled() (#22)
This change allows for more complex strategies to be employed that rely on runtime data not available in the request.
1 parent b2f8e9c commit e542f2e

File tree

6 files changed

+227
-8
lines changed

6 files changed

+227
-8
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@ $feature = Feature::get('myAwesomeFeature');
7979
$allFeatures = Feature::all();
8080
```
8181

82+
### Dynamic Arguments
83+
84+
If your strategy relies on dynamic data at runtime, you can pass additional arguments to the feature check functions:
85+
86+
```php
87+
use \MikeFrancis\LaravelUnleash\Unleash;
88+
use Config;
89+
90+
$unleash = app(Unleash::class);
91+
92+
$allowList = config('app.allow_list');
93+
94+
if ($unleash->isFeatureEnabled('myAwesomeFeature', $allowList)) {
95+
// Congratulations, you can see this awesome feature!
96+
}
97+
98+
if ($unleash->isFeatureDisabled('myAwesomeFeature', $allowList)) {
99+
// Check back later for more features!
100+
}
101+
```
102+
82103
### Blade
83104

84105
Blade directive for checking if a feature is **enabled**:
@@ -96,3 +117,5 @@ Or if a feature is **disabled**:
96117
Check back later for more features!
97118
@endfeatureDisabled
98119
```
120+
121+
You cannot currently use dynamic strategy arguments with Blade template directives.

config/unleash.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
// Mapping of strategies used to guard features on Unleash. The default strategies are already
2020
// mapped below, and more strategies can be added - they just need to implement the
21-
// `\MikeFrancis\LaravelUnleash\Strategies\Strategy` interface. If you would like to disable
21+
// `\MikeFrancis\LaravelUnleash\Strategies\Strategy` or
22+
// `\MikeFrancis\LaravelUnleash\Strategies\DynamicStrategy` interface. If you would like to disable
2223
// a built-in strategy, please comment it out or remove it below.
2324
'strategies' => [
2425
'applicationHostname' => \MikeFrancis\LaravelUnleash\Strategies\ApplicationHostnameStrategy::class,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace MikeFrancis\LaravelUnleash\Strategies\Contracts;
4+
5+
use Illuminate\Http\Request;
6+
7+
interface DynamicStrategy
8+
{
9+
/**
10+
* @param array $params Strategy Configuration from Unleash
11+
* @param Request $request Current Request
12+
* @param mixed $args An arbitrary number of arguments passed to isFeatureEnabled/Disabled
13+
* @return bool
14+
*/
15+
public function isEnabled(array $params, Request $request, ...$args): bool;
16+
}

src/Strategies/Contracts/Strategy.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,10 @@
66

77
interface Strategy
88
{
9+
/**
10+
* @param array $params Strategy Configuration from Unleash
11+
* @param Request $request Current Request
12+
* @return bool
13+
*/
914
public function isEnabled(array $params, Request $request): bool;
1015
}

src/Unleash.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Illuminate\Contracts\Config\Repository as Config;
1010
use Illuminate\Http\Request;
1111
use Illuminate\Support\Arr;
12+
use MikeFrancis\LaravelUnleash\Strategies\Contracts\DynamicStrategy;
1213
use MikeFrancis\LaravelUnleash\Strategies\Contracts\Strategy;
1314

1415
class Unleash
@@ -64,7 +65,7 @@ function (array $unleashFeature) use ($name) {
6465
);
6566
}
6667

67-
public function isFeatureEnabled(string $name): bool
68+
public function isFeatureEnabled(string $name, ...$args): bool
6869
{
6970
$feature = $this->getFeature($name);
7071
$isEnabled = Arr::get($feature, 'enabled', false);
@@ -83,25 +84,29 @@ public function isFeatureEnabled(string $name): bool
8384
return false;
8485
}
8586

86-
$strategy = new $allStrategies[$className];
87+
if (is_callable($allStrategies[$className])) {
88+
$strategy = $allStrategies[$className]();
89+
} else {
90+
$strategy = new $allStrategies[$className];
91+
}
8792

88-
if (!$strategy instanceof Strategy) {
89-
throw new \Exception("${$className} does not implement base Strategy.");
93+
if (!$strategy instanceof Strategy && !$strategy instanceof DynamicStrategy) {
94+
throw new \Exception("${$className} does not implement base Strategy/DynamicStrategy.");
9095
}
9196

9297
$params = Arr::get($strategyData, 'parameters', []);
9398

94-
if (!$strategy->isEnabled($params, $this->request)) {
99+
if (!$strategy->isEnabled($params, $this->request, ...$args)) {
95100
return false;
96101
}
97102
}
98103

99104
return $isEnabled;
100105
}
101106

102-
public function isFeatureDisabled(string $name): bool
107+
public function isFeatureDisabled(string $name, ...$args): bool
103108
{
104-
return !$this->isFeatureEnabled($name);
109+
return !$this->isFeatureEnabled($name, ...$args);
105110
}
106111

107112
protected function fetchFeatures(): array
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<?php
2+
3+
namespace MikeFrancis\LaravelUnleash\Tests\Strategies;
4+
5+
use GuzzleHttp\Client;
6+
use GuzzleHttp\Handler\MockHandler;
7+
use GuzzleHttp\Psr7\Response;
8+
use Illuminate\Contracts\Cache\Repository as Cache;
9+
use Illuminate\Contracts\Config\Repository as Config;
10+
use Illuminate\Http\Request;
11+
use MikeFrancis\LaravelUnleash\Tests\Stubs\ImplementedStrategy;
12+
use MikeFrancis\LaravelUnleash\Unleash;
13+
use PHPUnit\Framework\TestCase;
14+
15+
class DynamicStrategyTest extends TestCase
16+
{
17+
protected $mockHandler;
18+
19+
protected $client;
20+
21+
public function testWithoutArgs()
22+
{
23+
$featureName = 'someFeature';
24+
25+
$this->setMockHandler($featureName);
26+
27+
$cache = $this->createMock(Cache::class);
28+
29+
$request = $this->createMock(Request::class);
30+
31+
$strategy = $this->createMock(ImplementedStrategy::class);
32+
$strategy->expects($this->exactly(2))->method('isEnabled')
33+
->with([], $request)
34+
->willReturn(true);
35+
36+
$config = $this->getMockConfig($strategy);
37+
38+
$unleash = new Unleash($this->client, $cache, $config, $request);
39+
40+
$this->assertTrue($unleash->isFeatureEnabled($featureName));
41+
$this->assertFalse($unleash->isFeatureDisabled($featureName));
42+
}
43+
44+
public function testWithArg()
45+
{
46+
$featureName = 'someFeature';
47+
48+
$this->setMockHandler($featureName);
49+
50+
$cache = $this->createMock(Cache::class);
51+
52+
$request = $this->createMock(Request::class);
53+
54+
$strategy = $this->createMock(ImplementedStrategy::class);
55+
$strategy->expects($this->exactly(2))->method('isEnabled')
56+
->with([], $request, true)
57+
->willReturn(true);
58+
59+
$config = $this->getMockConfig($strategy);
60+
61+
$unleash = new Unleash($this->client, $cache, $config, $request);
62+
63+
$this->assertTrue($unleash->isFeatureEnabled($featureName, true));
64+
$this->assertFalse($unleash->isFeatureDisabled($featureName, true));
65+
}
66+
67+
public function testWithArgs()
68+
{
69+
$featureName = 'someFeature';
70+
71+
$this->setMockHandler($featureName);
72+
73+
$cache = $this->createMock(Cache::class);
74+
75+
$request = $this->createMock(Request::class);
76+
77+
$strategy = $this->createMock(ImplementedStrategy::class);
78+
$strategy->expects($this->exactly(2))->method('isEnabled')
79+
->with([], $request, 'foo', 'bar', 'baz')
80+
->willReturn(true);
81+
82+
$config = $this->getMockConfig($strategy);
83+
84+
$unleash = new Unleash($this->client, $cache, $config, $request);
85+
86+
$this->assertTrue($unleash->isFeatureEnabled($featureName, 'foo', 'bar', 'baz'));
87+
$this->assertFalse($unleash->isFeatureDisabled($featureName, 'foo', 'bar', 'baz'));
88+
}
89+
90+
/**
91+
* @param \PHPUnit\Framework\MockObject\MockObject $strategy
92+
* @return Config|\PHPUnit\Framework\MockObject\MockObject
93+
*/
94+
protected function getMockConfig(\PHPUnit\Framework\MockObject\MockObject $strategy)
95+
{
96+
$config = $this->createMock(Config::class);
97+
98+
$config->expects($this->at(0))
99+
->method('get')
100+
->with('unleash.isEnabled')
101+
->willReturn(true);
102+
$config->expects($this->at(1))
103+
->method('get')
104+
->with('unleash.cache.isEnabled')
105+
->willReturn(false);
106+
$config->expects($this->at(2))
107+
->method('get')
108+
->with('unleash.strategies')
109+
->willReturn(
110+
[
111+
'testStrategy' => function () use ($strategy) {
112+
return $strategy;
113+
},
114+
]
115+
);
116+
$config->expects($this->at(3))
117+
->method('get')
118+
->with('unleash.strategies')
119+
->willReturn(
120+
[
121+
'testStrategy' => function () use ($strategy) {
122+
return $strategy;
123+
},
124+
]
125+
);
126+
return $config;
127+
}
128+
129+
/**
130+
* @param string $featureName
131+
*/
132+
protected function setMockHandler(string $featureName): void
133+
{
134+
$this->mockHandler->append(
135+
new Response(
136+
200,
137+
[],
138+
json_encode(
139+
[
140+
'features' => [
141+
[
142+
'name' => $featureName,
143+
'enabled' => true,
144+
'strategies' => [
145+
[
146+
'name' => 'testStrategy',
147+
],
148+
],
149+
],
150+
],
151+
]
152+
)
153+
)
154+
);
155+
}
156+
157+
protected function setUp(): void
158+
{
159+
parent::setUp();
160+
161+
$this->mockHandler = new MockHandler();
162+
163+
$this->client = new Client(
164+
[
165+
'handler' => $this->mockHandler,
166+
]
167+
);
168+
}
169+
}

0 commit comments

Comments
 (0)