Stabilize modular school API scaffold

This commit is contained in:
root
2026-05-29 17:49:39 -04:00
parent e362f68d8d
commit f55213a3df
18 changed files with 822 additions and 76 deletions
+76 -23
View File
@@ -1,40 +1,93 @@
<?php
declare(strict_types=1);
namespace App\Support\Release;
final class FeatureFlagRegistry
{
/** @return FeatureFlagDefinition[] */
public function all(): array
/**
* Return the raw modular release feature flag config.
*
* This class is intentionally safe to use from plain PHPUnit tests where
* Laravel's application container has not been booted yet.
*
* @return array<string, array<string, mixed>>
*/
public static function all(): array
{
$flags = config('modular_release.flags', []);
$config = self::loadConfig();
return array_map(
fn (string $key, array $flag): FeatureFlagDefinition => new FeatureFlagDefinition(
key: $key,
owner: (string) ($flag['owner'] ?? 'unowned'),
default: (bool) ($flag['default'] ?? false),
removalTarget: $flag['removal_target'] ?? null,
dimensions: $flag['dimensions'] ?? ['global', 'environment', 'school_id', 'domain_profile']
),
array_keys($flags),
array_values($flags)
);
return $config['feature_flags'] ?? $config['flags'] ?? [];
}
public function find(string $key): ?FeatureFlagDefinition
/**
* Export a normalized list of flags for release checks and generated docs.
*
* @return array<int, array<string, mixed>>
*/
public function export(): array
{
foreach ($this->all() as $flag) {
if ($flag->key === $key) {
return $flag;
$flags = [];
foreach (self::all() as $key => $definition) {
$flags[] = [
'key' => (string) $key,
'owner' => (string) ($definition['owner'] ?? ''),
'default' => (bool) ($definition['default'] ?? false),
'removal_target' => $definition['removal_target'] ?? null,
'dimensions' => $definition['dimensions'] ?? [],
];
}
return $flags;
}
/**
* @return array<int, array<string, mixed>>
*/
public static function highRisk(): array
{
return array_values(array_filter(
(new self())->export(),
fn (array $flag): bool => ($flag['risk'] ?? null) === 'high'
));
}
/**
* @return array<string, mixed>
*/
private static function loadConfig(): array
{
if (class_exists(\Illuminate\Foundation\Application::class)
&& class_exists(\Illuminate\Container\Container::class)
) {
$container = \Illuminate\Container\Container::getInstance();
if ($container instanceof \Illuminate\Foundation\Application && $container->bound('config')) {
return ['flags' => $container['config']->get('modular_release.flags', [])];
}
}
return null;
}
$path = dirname(__DIR__, 3).'/config/modular_release.php';
public function export(): array
{
return array_map(fn (FeatureFlagDefinition $flag): array => $flag->toArray(), $this->all());
if (! is_file($path)) {
return [];
}
$contents = file_get_contents($path);
if ($contents === false) {
return [];
}
if (! preg_match("/'flags'\s*=>\s*\[(.*?)\n\s*\],\s*\n\s*'rollout_tracks'/s", $contents, $matches)) {
return [];
}
/** @var array<string, array<string, mixed>> $flags */
$flags = eval('return ['.$matches[1].'];');
return ['flags' => is_array($flags) ? $flags : []];
}
}