Skip to content

Commit 7387490

Browse files
committed
fix: Collect field names in a single namespace
1 parent ca1ff93 commit 7387490

10 files changed

Lines changed: 860 additions & 263 deletions

File tree

src/Blueprint/Blueprint.php

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ class Blueprint
2828

2929
protected AcceptRules|null $acceptRules = null;
3030

31-
protected array $fields = [];
32-
protected array|null $fieldsLower = null;
31+
protected FieldsRegistry $fields;
3332
protected ModelWithContent $model;
3433
protected array $props;
3534
protected Tabs|null $tabs = null;
@@ -174,13 +173,7 @@ public static function factory(
174173
*/
175174
public function field(string $name): array|null
176175
{
177-
if (isset($this->fields[$name]) === true) {
178-
return $this->fields[$name];
179-
}
180-
181-
// field objects use normalized lowercase keys
182-
$this->fieldsLower ??= array_change_key_case($this->fields);
183-
return $this->fieldsLower[Str::lower($name)] ?? null;
176+
return $this->fields->get($name);
184177
}
185178

186179
/**
@@ -213,15 +206,15 @@ public static function fieldProps(array|string $props): array
213206
*/
214207
public function fields(): array
215208
{
216-
return $this->fields;
209+
return $this->fields->toArray();
217210
}
218211

219212
/**
220213
* Normalizes all fields and adds automatic labels,
221214
* types and widths.
222215
* Facade for `Normalizer::normalizeFieldsProps()`
223216
*
224-
* @return array<string, array>
217+
* @return array<string|int, array>
225218
*/
226219
public static function fieldsProps(mixed $fields): array
227220
{

src/Blueprint/FieldsRegistry.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
namespace Kirby\Blueprint;
4+
5+
use Kirby\Toolkit\Str;
6+
7+
/**
8+
* Collects the props of all fields in a blueprint
9+
*
10+
* @copyright Bastian Allgeier
11+
* @license https://getkirby.com/license
12+
* @since 6.0.0
13+
* @unstable
14+
*/
15+
class FieldsRegistry
16+
{
17+
// Props of all fields, keyed by their declared name
18+
protected array $fields = [];
19+
20+
// Declared names of all fields, keyed by their lowercase name
21+
protected array $names = [];
22+
23+
/**
24+
* Claims the given names without touching their props.
25+
* The props can still be any of the shortcuts a blueprint
26+
* allows, as they are only normalized later.
27+
*/
28+
public function __construct(array $fields = [])
29+
{
30+
foreach ($fields as $name => $props) {
31+
$this->fields[$name] = $props;
32+
$this->names[Str::lower((string)$name)] = $name;
33+
}
34+
}
35+
36+
/**
37+
* Adds the props of the given fields under names that are
38+
* not taken yet and returns them, keyed by the names they
39+
* ended up with. A list is keyed by the name in the props.
40+
*/
41+
public function add(array $fields): array
42+
{
43+
$added = [];
44+
45+
foreach ($fields as $name => $props) {
46+
if (is_int($name) === true) {
47+
$name = $props['name'];
48+
}
49+
50+
if ($this->has($name) === true) {
51+
$props = $this->conflict($name, $props);
52+
$name = $props['name'];
53+
}
54+
55+
$this->names[Str::lower((string)$name)] = $name;
56+
$added[$name] = $this->fields[$name] = $props;
57+
}
58+
59+
return $added;
60+
}
61+
62+
/**
63+
* Creates an error field for a name that is already taken.
64+
* The error gets a unique name of its own, so that the field
65+
* which claimed the name first can stay intact.
66+
*/
67+
protected function conflict(string|int $name, array $props): array
68+
{
69+
$count = 1;
70+
71+
while ($this->has($name . '-duplicate-' . $count) === true) {
72+
$count++;
73+
}
74+
75+
return [
76+
'label' => $props['label'] ?? 'Error',
77+
'name' => $name . '-duplicate-' . $count,
78+
'text' => 'The field <strong>"' . $name . '"</strong> already exists in your blueprint',
79+
'theme' => 'negative',
80+
'type' => 'info',
81+
];
82+
}
83+
84+
/**
85+
* Returns the props of a single field. The lookup is
86+
* case-insensitive, just like the namespace.
87+
*/
88+
public function get(string|int $name): array|null
89+
{
90+
$name = $this->names[Str::lower((string)$name)] ?? null;
91+
92+
return $name !== null ? $this->fields[$name] : null;
93+
}
94+
95+
/**
96+
* Checks if a name is already taken
97+
*/
98+
public function has(string|int $name): bool
99+
{
100+
return isset($this->names[Str::lower((string)$name)]) === true;
101+
}
102+
103+
/**
104+
* Returns the props of all fields,
105+
* keyed by their declared name
106+
*/
107+
public function toArray(): array
108+
{
109+
return $this->fields;
110+
}
111+
}

0 commit comments

Comments
 (0)