Skip to content

Commit 3375f28

Browse files
feat(config): add .env.example generator from ConfigKeys metadata
Replace the hand-maintained .env.example (714 lines) with an auto-generated file derived from ConfigKeys, matching the existing config.dist.php generator approach. Changes: - Extract shared helpers (getComment, sectionTitle, groupFlatEntries, envKey) from ConfigDistGenerator into ConfigKeysMeta - Add EnvExampleGenerator class with render/writeToFile/check/main - Add scripts/generate-env-example.php CLI wrapper - Add composer scripts: env-example:generate, env-example:check - Add CI check in lint-and-analyse-php.yml - Regenerate .env.example from metadata (adds missing keys like LB_ENABLED_LANGUAGES and LB_DEFAULT_PAGE_SIZE, fixes typos)
1 parent e9e6a3f commit 3375f28

8 files changed

Lines changed: 639 additions & 214 deletions

File tree

.env.example

Lines changed: 170 additions & 153 deletions
Large diffs are not rendered by default.

.github/workflows/lint-and-analyse-php.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@ jobs:
126126
exit 1
127127
fi
128128
129+
- name: Check .env.example is up-to-date
130+
if: matrix.php-version == '8.2'
131+
run: |
132+
if ! composer env-example:check; then
133+
echo ""
134+
echo "=========================================="
135+
echo " .env.example is out of date."
136+
echo " To regenerate it locally, run:"
137+
echo ""
138+
echo " composer env-example:generate"
139+
echo ""
140+
echo " Then commit the changes."
141+
echo "=========================================="
142+
exit 1
143+
fi
144+
129145
- name: Cache php-cs-fixer
130146
if: matrix.php-version == '8.2'
131147
uses: actions/cache@v5

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@
5151
"preflight": "php lib/preflight.php",
5252
"test:integration": "./vendor/bin/phpunit --testsuite integration --testdox",
5353
"config-dist:generate": "php scripts/generate-config-dist.php --write",
54-
"config-dist:check": "php scripts/generate-config-dist.php --check"
54+
"config-dist:check": "php scripts/generate-config-dist.php --check",
55+
"env-example:generate": "php scripts/generate-env-example.php --write",
56+
"env-example:check": "php scripts/generate-env-example.php --check"
5557
},
5658
"config": {
5759
"allow-plugins": {

lib/Config/ConfigDistGenerator.php

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public static function render(): string
7070
$lines[] = " 'settings' => [";
7171
$lines[] = '';
7272

73-
foreach (self::groupFlatEntries(flatEntries: $settings['flat']) as $groupTitle => $entries) {
73+
foreach (ConfigKeysMeta::groupFlatEntries(flatEntries: $settings['flat']) as $groupTitle => $entries) {
7474
self::appendSectionHeader(lines: $lines, title: $groupTitle, indent: 2);
7575
$lines[] = '';
7676

@@ -83,7 +83,7 @@ public static function render(): string
8383
}
8484

8585
foreach ($settings['sections'] as $sectionName => $entries) {
86-
$sectionTitle = self::sectionTitle(section: $sectionName);
86+
$sectionTitle = ConfigKeysMeta::sectionTitle(section: $sectionName);
8787
$lines[] = '';
8888
self::appendSectionHeader(lines: $lines, title: $sectionTitle, indent: 2);
8989
$lines[] = '';
@@ -190,25 +190,10 @@ private static function appendSectionHeader(array &$lines, string $title, int $i
190190
$lines[] = "{$pad}{$border}";
191191
}
192192

193-
/**
194-
* Get the comment text for a config entry.
195-
*
196-
* Prefers 'config_file_comment' field, falls back to 'description'.
197-
*/
198-
private static function getComment(array $entry): string
199-
{
200-
$comment = $entry['config_file_comment'] ?? null;
201-
if (is_string($comment) && $comment !== '') {
202-
return $comment;
203-
}
204-
205-
return $entry['description'] ?? '';
206-
}
207-
208193
private static function appendEntry(array &$lines, string $key, array $entry, int $indent): void
209194
{
210195
$pad = str_repeat(string: ' ', times: $indent);
211-
$comment = self::getComment(entry: $entry);
196+
$comment = ConfigKeysMeta::getComment(entry: $entry);
212197
$choices = $entry['choices'] ?? null;
213198
$type = $entry['type'] ?? 'string';
214199

@@ -255,53 +240,11 @@ private static function renderValue(mixed $value, string $type): string
255240
};
256241
}
257242

258-
private static function sectionTitle(string $section): string
259-
{
260-
return ConfigKeysMeta::SECTION_TITLES[$section]
261-
?? ucwords(string: str_replace(search: ['.', '-'], replace: ' ', subject: $section));
262-
}
263-
264243
private static function trimTrailingBlankLine(array &$lines): void
265244
{
266245
if ($lines !== [] && end($lines) === '') {
267246
array_pop($lines);
268247
}
269248
}
270249

271-
/**
272-
* @param array<string, array> $flatEntries
273-
* @return array<string, array<string, array>>
274-
*/
275-
private static function groupFlatEntries(array $flatEntries): array
276-
{
277-
$groups = [];
278-
$assignedKeys = [];
279-
280-
foreach (ConfigKeysMeta::TOP_LEVEL_GROUPS as $groupTitle => $groupKeys) {
281-
$groupEntries = [];
282-
283-
foreach ($groupKeys as $key) {
284-
if (!array_key_exists($key, $flatEntries)) {
285-
throw new LogicException("Top-level group '{$groupTitle}' references unknown flat key '{$key}'");
286-
}
287-
if (isset($assignedKeys[$key])) {
288-
throw new LogicException("Flat key '{$key}' is assigned to multiple top-level groups");
289-
}
290-
291-
$groupEntries[$key] = $flatEntries[$key];
292-
$assignedKeys[$key] = true;
293-
}
294-
295-
$groups[$groupTitle] = $groupEntries;
296-
}
297-
298-
$unassignedKeys = array_diff(array_keys($flatEntries), array_keys($assignedKeys));
299-
if ($unassignedKeys !== []) {
300-
throw new LogicException(
301-
'Top-level groups are missing flat keys: ' . implode(', ', $unassignedKeys)
302-
);
303-
}
304-
305-
return $groups;
306-
}
307250
}

lib/Config/ConfigKeysMeta.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,82 @@
22

33
class ConfigKeysMeta
44
{
5+
/**
6+
* Get the comment text for a config entry.
7+
*
8+
* Prefers 'config_file_comment' field, falls back to 'description'.
9+
*/
10+
public static function getComment(array $entry): string
11+
{
12+
$comment = $entry['config_file_comment'] ?? null;
13+
if (is_string($comment) && $comment !== '') {
14+
return $comment;
15+
}
16+
17+
return $entry['description'] ?? '';
18+
}
19+
20+
/**
21+
* Get the human-readable title for a section.
22+
*/
23+
public static function sectionTitle(string $section): string
24+
{
25+
return self::SECTION_TITLES[$section]
26+
?? ucwords(string: str_replace(search: ['.', '-'], replace: ' ', subject: $section));
27+
}
28+
29+
/**
30+
* Group flat config entries according to TOP_LEVEL_GROUPS ordering.
31+
*
32+
* @param array<string, array> $flatEntries
33+
* @return array<string, array<string, array>>
34+
*
35+
* @throws \LogicException if a group references an unknown key, a key is in multiple groups,
36+
* or flat keys are not covered by any group
37+
*/
38+
public static function groupFlatEntries(array $flatEntries): array
39+
{
40+
$groups = [];
41+
$assignedKeys = [];
42+
43+
foreach (self::TOP_LEVEL_GROUPS as $groupTitle => $groupKeys) {
44+
$groupEntries = [];
45+
46+
foreach ($groupKeys as $key) {
47+
if (!array_key_exists($key, $flatEntries)) {
48+
throw new \LogicException("Top-level group '{$groupTitle}' references unknown flat key '{$key}'");
49+
}
50+
if (isset($assignedKeys[$key])) {
51+
throw new \LogicException("Flat key '{$key}' is assigned to multiple top-level groups");
52+
}
53+
54+
$groupEntries[$key] = $flatEntries[$key];
55+
$assignedKeys[$key] = true;
56+
}
57+
58+
$groups[$groupTitle] = $groupEntries;
59+
}
60+
61+
$unassignedKeys = array_diff(array_keys($flatEntries), array_keys($assignedKeys));
62+
if ($unassignedKeys !== []) {
63+
throw new \LogicException(
64+
'Top-level groups are missing flat keys: ' . implode(', ', $unassignedKeys)
65+
);
66+
}
67+
68+
return $groups;
69+
}
70+
71+
/**
72+
* Derive the environment variable name for a config key.
73+
*
74+
* Matches the logic in AbstractConfigKeys::hasEnv().
75+
*/
76+
public static function envKey(string $configKey): string
77+
{
78+
return strtoupper('LB_' . preg_replace('/[.\-]+/', '_', $configKey));
79+
}
80+
581
public const SECTION_TITLES = [
682
'api' => 'API Configuration',
783
'authentication' => 'Authentication Settings',

0 commit comments

Comments
 (0)