Skip to content

Commit 3ae4071

Browse files
committed
feat(config): add support for configuration flags in Context
- Introduced Config and ConfigFlag classes to manage configuration flags. - Added methods to enable and disable flags. - Implemented deprecation warnings for unconfigured flags.
1 parent 01d065b commit 3ae4071

File tree

9 files changed

+370
-0
lines changed

9 files changed

+370
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Features
66

7+
* Add support for configuration flags in `Context`
78
* Allow to specify directory where to output repacked phar
89
* Do not ask confirmation when explicitly using `castor init` command
910
* Allow to pass a callback when using the `run_php` function (similar to the `run` function)

doc/getting-started/context.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,117 @@ if you run castor [in verbose mode](../going-further/interacting-with-castor/log
358358
Additionally, if this command fails when Castor is not in verbose mode, it will
359359
ask you if you want to retry the command with the verbose arguments.
360360

361+
### Configuration flags
362+
363+
Castor allows you to configure optional features through configuration flags.
364+
These flags control behavior changes that may be introduced in future versions
365+
of Castor, allowing you to opt-in or opt-out of specific features.
366+
367+
You can enable or disable flags when creating a context by using the `config`
368+
parameter:
369+
370+
```php
371+
use Castor\Attribute\AsContext;
372+
use Castor\Config;
373+
use Castor\ConfigFlag;
374+
use Castor\Context;
375+
376+
#[AsContext(name: 'my_context')]
377+
function create_context(): Context
378+
{
379+
return new Context(
380+
config: (new Config())
381+
->withEnabled(ConfigFlag::ContextAwareFilesystem)
382+
);
383+
}
384+
```
385+
386+
You can also explicitly disable flags:
387+
388+
```php
389+
use Castor\Attribute\AsContext;
390+
use Castor\Config;
391+
use Castor\ConfigFlag;
392+
use Castor\Context;
393+
394+
#[AsContext(name: 'my_context')]
395+
function create_context(): Context
396+
{
397+
return new Context(
398+
config: (new Config())
399+
->withDisabled(ConfigFlag::ContextAwareFilesystem)
400+
);
401+
}
402+
```
403+
404+
Multiple flags can be configured at once:
405+
406+
```php
407+
use Castor\Config;
408+
use Castor\ConfigFlag;
409+
410+
$config = (new Config())
411+
->withEnabled(ConfigFlag::ContextAwareFilesystem)
412+
->withDisabled(ConfigFlag::SomeOtherFlag);
413+
```
414+
415+
#### Checking flag status
416+
417+
You can check whether a flag is enabled in your tasks:
418+
419+
```php
420+
use Castor\Attribute\AsTask;
421+
422+
use function Castor\context;
423+
use function Castor\io;
424+
425+
#[AsTask()]
426+
function check_flags(): void
427+
{
428+
$context = context();
429+
430+
if ($context->config->isEnabled(ConfigFlag::ContextAwareFilesystem)) {
431+
io()->writeln('ContextAwareFilesystem is enabled.');
432+
}
433+
}
434+
```
435+
436+
#### Available flags
437+
438+
- `ConfigFlag::ContextAwareFilesystem`: When enabled, filesystem operations
439+
will be automatically aware of the context's working directory.
440+
441+
#### Deprecation warnings
442+
443+
Configuration flags support a deprecation system to help you prepare for future
444+
versions of Castor. When a flag is not explicitly configured, or when it's
445+
configured to a value that will change in a future version, Castor will emit
446+
a deprecation warning.
447+
448+
These warnings help you understand:
449+
450+
- Which flags are not configured in your project
451+
- What the current default value is
452+
- What the future default value will be
453+
- In which version the default will change
454+
455+
To avoid these warnings, explicitly configure the flags you use:
456+
457+
```php
458+
use Castor\Config;
459+
use Castor\ConfigFlag;
460+
use Castor\Context;
461+
462+
return new Context(
463+
config: (new Config())
464+
->withEnabled(ConfigFlag::ContextAwareFilesystem)
465+
);
466+
```
467+
468+
> [!TIP]
469+
> Check the [configuration flags example](https://github.com/jolicode/castor/blob/main/examples/basic/context/config.php)
470+
> for a complete working example.
471+
361472
## Advanced usage
362473

363474
See [this documentation](../going-further/interacting-with-castor/advanced-context.md) for more usage about

examples/basic/context/config.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace context;
4+
5+
use Castor\Attribute\AsContext;
6+
use Castor\Attribute\AsTask;
7+
use Castor\Config;
8+
use Castor\ConfigFlag;
9+
use Castor\Context;
10+
11+
use function Castor\context;
12+
use function Castor\io;
13+
14+
#[AsContext(name: 'context_with_config_flags')]
15+
function create_context_with_config_flags(): Context
16+
{
17+
return new Context(
18+
config: (new Config())
19+
->withEnabled(ConfigFlag::ContextAwareFilesystem)
20+
// You can also disable flags explicitly
21+
// ->withDisabled(ConfigFlag::ContextAwareFilesystem)
22+
);
23+
}
24+
25+
#[AsTask]
26+
function use_context_with_config_flags(): void
27+
{
28+
$context = context('context_with_config_flags');
29+
30+
// You can get all flags and their values
31+
$flags = $context->config->getFlags();
32+
foreach ($flags as $flagName => $flagValue) {
33+
io()->writeln("Flag: {$flagName}, Value: " . var_export($flagValue, true));
34+
}
35+
36+
// Or check specific flag if necessary for your logic
37+
if ($context->config->isEnabled(ConfigFlag::ContextAwareFilesystem)) {
38+
io()->writeln('ContextAwareFilesystem is enabled.');
39+
} else {
40+
io()->writeln('ContextAwareFilesystem is disabled.');
41+
}
42+
43+
io()->newLine();
44+
io()->writeln('Disabling ContextAwareFilesystem flag...');
45+
$context->config->withDisabled(ConfigFlag::ContextAwareFilesystem);
46+
47+
if ($context->config->isEnabled(ConfigFlag::ContextAwareFilesystem)) {
48+
io()->writeln('ContextAwareFilesystem is enabled.');
49+
} else {
50+
io()->writeln('ContextAwareFilesystem is disabled.');
51+
}
52+
}

src/Config.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace Castor;
4+
5+
use Symfony\Component\DependencyInjection\Attribute\Exclude;
6+
7+
/**
8+
* Each flag supports three states:
9+
* - true: explicitly enabled
10+
* - false: explicitly disabled
11+
* - null: not configured (will show deprecation warning and use default)
12+
*/
13+
#[Exclude]
14+
class Config
15+
{
16+
/** @var array<string, bool|null> */
17+
private array $flags = [];
18+
19+
/** @var array<string, bool> */
20+
private static array $warnedFlags = [];
21+
22+
public function isEnabled(ConfigFlag $flag): bool
23+
{
24+
$value = $this->get($flag);
25+
26+
$futureDefault = !$flag->defaultValueWhenNull();
27+
28+
if (null === $value || $value !== $futureDefault) {
29+
$this->triggerDeprecationWarning($flag, $value);
30+
}
31+
32+
if (\is_bool($value)) {
33+
return $value;
34+
}
35+
36+
return $flag->defaultValueWhenNull();
37+
}
38+
39+
public function withEnabled(ConfigFlag ...$flags): self
40+
{
41+
foreach ($flags as $flag) {
42+
$this->set($flag, true);
43+
}
44+
45+
return $this;
46+
}
47+
48+
public function withDisabled(ConfigFlag ...$flags): self
49+
{
50+
foreach ($flags as $flag) {
51+
$this->set($flag, false);
52+
}
53+
54+
return $this;
55+
}
56+
57+
/**
58+
* @return array<string, bool|null>
59+
*/
60+
public function getFlags(): array
61+
{
62+
return $this->flags;
63+
}
64+
65+
private function get(ConfigFlag $flag): ?bool
66+
{
67+
return $this->flags[$flag->name] ?? null;
68+
}
69+
70+
private function set(ConfigFlag $flag, ?bool $value): void
71+
{
72+
$this->flags[$flag->name] = $value;
73+
}
74+
75+
private function triggerDeprecationWarning(ConfigFlag $flag, ?bool $value): void
76+
{
77+
// Only warn once per flag per execution
78+
if (isset(self::$warnedFlags[$flag->name])) {
79+
return;
80+
}
81+
82+
self::$warnedFlags[$flag->name] = true;
83+
84+
$currentDefault = $flag->defaultValueWhenNull() ? 'true' : 'false';
85+
$futureDefault = !$flag->defaultValueWhenNull() ? 'true' : 'false';
86+
$futureVersion = $flag->willBeDefaultInVersion();
87+
88+
if (null === $value) {
89+
$message = \sprintf(
90+
'Configuration flag "%s" is not set and defaults to "%s". It will default to "%s" in version %s. ',
91+
$flag->name,
92+
$currentDefault,
93+
$futureDefault,
94+
$futureVersion,
95+
);
96+
} else {
97+
$currentValue = $value ? 'true' : 'false';
98+
$message = \sprintf(
99+
'Configuration flag "%s" is explicitly set to "%s", but will default to "%s" in version %s. ',
100+
$flag->name,
101+
$currentValue,
102+
$futureDefault,
103+
$futureVersion,
104+
);
105+
}
106+
107+
trigger_deprecation('castor/castor', $futureVersion, $message);
108+
}
109+
}

src/ConfigFlag.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Castor;
4+
5+
enum ConfigFlag
6+
{
7+
case ContextAwareFilesystem;
8+
9+
public function description(): string
10+
{
11+
return match ($this) {
12+
self::ContextAwareFilesystem => 'Context-aware filesystem with automatic path resolution',
13+
};
14+
}
15+
16+
public function willBeDefaultInVersion(): string
17+
{
18+
return match ($this) {
19+
self::ContextAwareFilesystem => '2.0',
20+
};
21+
}
22+
23+
public function defaultValueWhenNull(): bool
24+
{
25+
return match ($this) {
26+
self::ContextAwareFilesystem => false,
27+
};
28+
}
29+
}

0 commit comments

Comments
 (0)