Skip to content

Commit c3fadbf

Browse files
imorlandStyleCIBotSychO9
authored
[1.x] Conditional extender instantiation (#3898)
* chore: create tests to highlight the conditional instantiation problem * Apply fixes from StyleCI * add callback and invokable class + tests * Apply fixes from StyleCI * address stan issue on php 8.2 * Revert "address stan issue on php 8.2" This reverts commit 1fc2c88. * attempt to make stan happy * Revert "attempt to make stan happy" This reverts commit 1cc327b. * is it really that simple? * Revert "is it really that simple?" This reverts commit 2006755. * let's try this * Update framework/core/src/Extend/Conditional.php Co-authored-by: Sami Mazouz <[email protected]> --------- Co-authored-by: StyleCI Bot <[email protected]> Co-authored-by: Sami Mazouz <[email protected]>
1 parent 82e08e3 commit c3fadbf

File tree

2 files changed

+177
-7
lines changed

2 files changed

+177
-7
lines changed

framework/core/src/Extend/Conditional.php

+41-7
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,49 @@
1313
use Flarum\Extension\ExtensionManager;
1414
use Illuminate\Contracts\Container\Container;
1515

16+
/**
17+
* The Conditional extender allows developers to conditionally apply other extenders
18+
* based on either boolean values or results from callable functions.
19+
*
20+
* This is useful for applying extenders only if certain conditions are met,
21+
* such as the presence of an enabled extension or a specific configuration setting.
22+
*/
1623
class Conditional implements ExtenderInterface
1724
{
1825
/**
19-
* @var array<array{condition: bool|callable, extenders: ExtenderInterface[]}>
26+
* An array of conditions and their associated extenders.
27+
*
28+
* Each entry should have:
29+
* - 'condition': a boolean or callable that should return a boolean.
30+
* - 'extenders': an array of extenders, a callable returning an array of extenders, or an invokable class string.
31+
*
32+
* @var array<array{condition: bool|callable, extenders: ExtenderInterface[]|callable|string}>
2033
*/
2134
protected $conditions = [];
2235

2336
/**
24-
* @param ExtenderInterface[] $extenders
37+
* Apply extenders only if a specific extension is enabled.
38+
*
39+
* @param string $extensionId The ID of the extension.
40+
* @param ExtenderInterface[]|callable|string $extenders An array of extenders, a callable returning an array of extenders, or an invokable class string.
41+
* @return self
2542
*/
26-
public function whenExtensionEnabled(string $extensionId, array $extenders): self
43+
public function whenExtensionEnabled(string $extensionId, $extenders): self
2744
{
2845
return $this->when(function (ExtensionManager $extensions) use ($extensionId) {
2946
return $extensions->isEnabled($extensionId);
3047
}, $extenders);
3148
}
3249

3350
/**
34-
* @param bool|callable $condition
35-
* @param ExtenderInterface[] $extenders
51+
* Apply extenders based on a condition.
52+
*
53+
* @param bool|callable $condition A boolean or callable that should return a boolean.
54+
* If this evaluates to true, the extenders will be applied.
55+
* @param ExtenderInterface[]|callable|string $extenders An array of extenders, a callable returning an array of extenders, or an invokable class string.
56+
* @return self
3657
*/
37-
public function when($condition, array $extenders): self
58+
public function when($condition, $extenders): self
3859
{
3960
$this->conditions[] = [
4061
'condition' => $condition,
@@ -44,6 +65,13 @@ public function when($condition, array $extenders): self
4465
return $this;
4566
}
4667

68+
/**
69+
* Iterates over the conditions and applies the associated extenders if the conditions are met.
70+
*
71+
* @param Container $container
72+
* @param Extension|null $extension
73+
* @return void
74+
*/
4775
public function extend(Container $container, Extension $extension = null)
4876
{
4977
foreach ($this->conditions as $condition) {
@@ -52,7 +80,13 @@ public function extend(Container $container, Extension $extension = null)
5280
}
5381

5482
if ($condition['condition']) {
55-
foreach ($condition['extenders'] as $extender) {
83+
$extenders = $condition['extenders'];
84+
85+
if (is_string($extenders) || is_callable($extenders)) {
86+
$extenders = $container->call($extenders);
87+
}
88+
89+
foreach ($extenders as $extender) {
5690
$extender->extend($container, $extension);
5791
}
5892
}

framework/core/tests/integration/extenders/ConditionalTest.php

+136
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,140 @@ public function conditional_injects_dependencies_to_condition_callable()
159159

160160
$this->app();
161161
}
162+
163+
/** @test */
164+
public function conditional_does_not_instantiate_extender_if_condition_is_false_using_callable()
165+
{
166+
$this->extend(
167+
(new Extend\Conditional())
168+
->when(false, TestExtender::class)
169+
);
170+
171+
$this->app();
172+
173+
$response = $this->send(
174+
$this->request('GET', '/api', [
175+
'authenticatedAs' => 1,
176+
])
177+
);
178+
179+
$payload = json_decode($response->getBody()->getContents(), true);
180+
181+
$this->assertArrayNotHasKey('customConditionalAttribute', $payload['data']['attributes']);
182+
}
183+
184+
/** @test */
185+
public function conditional_does_instantiate_extender_if_condition_is_true_using_callable()
186+
{
187+
$this->extend(
188+
(new Extend\Conditional())
189+
->when(true, TestExtender::class)
190+
);
191+
192+
$this->app();
193+
194+
$response = $this->send(
195+
$this->request('GET', '/api', [
196+
'authenticatedAs' => 1,
197+
])
198+
);
199+
200+
$payload = json_decode($response->getBody()->getContents(), true);
201+
202+
$this->assertArrayHasKey('customConditionalAttribute', $payload['data']['attributes']);
203+
}
204+
205+
/** @test */
206+
public function conditional_does_not_instantiate_extender_if_condition_is_false_using_callback()
207+
{
208+
$this->extend(
209+
(new Extend\Conditional())
210+
->when(false, function (): array {
211+
return [
212+
(new Extend\ApiSerializer(ForumSerializer::class))
213+
->attributes(function () {
214+
return [
215+
'customConditionalAttribute' => true
216+
];
217+
})
218+
];
219+
})
220+
);
221+
222+
$this->app();
223+
224+
$response = $this->send(
225+
$this->request('GET', '/api', [
226+
'authenticatedAs' => 1,
227+
])
228+
);
229+
230+
$payload = json_decode($response->getBody()->getContents(), true);
231+
232+
$this->assertArrayNotHasKey('customConditionalAttribute', $payload['data']['attributes']);
233+
}
234+
235+
/** @test */
236+
public function conditional_does_instantiate_extender_if_condition_is_true_using_callback()
237+
{
238+
$this->extend(
239+
(new Extend\Conditional())
240+
->when(true, function (): array {
241+
return [
242+
(new Extend\ApiSerializer(ForumSerializer::class))
243+
->attributes(function () {
244+
return [
245+
'customConditionalAttribute' => true
246+
];
247+
})
248+
];
249+
})
250+
);
251+
252+
$this->app();
253+
254+
$response = $this->send(
255+
$this->request('GET', '/api', [
256+
'authenticatedAs' => 1,
257+
])
258+
);
259+
260+
$payload = json_decode($response->getBody()->getContents(), true);
261+
262+
$this->assertArrayHasKey('customConditionalAttribute', $payload['data']['attributes']);
263+
}
264+
265+
/** @test */
266+
public function conditional_does_not_work_if_extension_is_disabled()
267+
{
268+
$this->extend(
269+
(new Extend\Conditional())
270+
->whenExtensionEnabled('dummy-extension-id', TestExtender::class)
271+
);
272+
273+
$response = $this->send(
274+
$this->request('GET', '/api', [
275+
'authenticatedAs' => 1,
276+
])
277+
);
278+
279+
$payload = json_decode($response->getBody()->getContents(), true);
280+
281+
$this->assertArrayNotHasKey('customConditionalAttribute', $payload['data']['attributes']);
282+
}
283+
}
284+
285+
class TestExtender
286+
{
287+
public function __invoke(): array
288+
{
289+
return [
290+
(new Extend\ApiSerializer(ForumSerializer::class))
291+
->attributes(function () {
292+
return [
293+
'customConditionalAttribute' => true
294+
];
295+
})
296+
];
297+
}
162298
}

0 commit comments

Comments
 (0)