refactor: split VariableRef into ControlChars and DynamicContentRules (H2)

- ControlChars::strip()/stripDeep() (was VariableRef::withoutControls/Deep) with
  the S32/S32a/S35 docblock; callers AttrWriter, ShortformWriter, TextReplacer and
  the setting cleaner use it.
- DynamicContentRules (design system + dynamic content map): fieldsFor() (was
  contentFields: meta prefix -> map -> loop_ -> design names -> unknown),
  cleanSetting() (was setting(): raw strip -> raw marker -> field type ->
  final-string check -> meta key check, same order), one private method per
  field family (textSetting, selectPostSetting, scalarSetting), isMetaKey(),
  checkMap(), and the constants DEFAULT_DYNAMIC_CONTENT, META_SETTINGS,
  DESIGN_SETTINGS, META_KEY.
- VariableRef keeps the syntax: const END = ')$' replaces the six literals,
  payloadOf() is shared by typeOf() and canonical(); unknownName() is public for
  the rules. Error messages unchanged.
Tests unchanged apart from a class name in DynamicContentMapTest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 18:05:38 +02:00
co-authored by Claude Opus 5.5
parent e420145cbe
commit 874a92c6be
9 changed files with 403 additions and 316 deletions
@@ -72,7 +72,7 @@ final class TextReplacer
$problems = [];
if (! mb_check_encoding($search, 'UTF-8') || mb_strlen($search) < 2) {
$problems['search'] = 'Suchtext: mindestens 2 Zeichen gültiges UTF-8.';
} elseif (VariableRef::withoutControls($search) !== $search) {
} elseif (ControlChars::strip($search) !== $search) {
$problems['search'] = 'Suchtext darf keine Steuerzeichen enthalten.';
} elseif (VariableRef::contains($search)) {
$problems['search'] = 'Suchtext darf keinen $variable(…)$-Verweis enthalten; dynamische Inhalte werden '
@@ -456,7 +456,7 @@ final class TextReplacer
foreach (explode("\n", $value) as $line) {
$marker = strrpos($line, VariableRef::MARKER);
$end = $marker === false ? false : strrpos($line, ')$');
$end = $marker === false ? false : strrpos($line, VariableRef::END);
if ($marker !== false && ($end === false || $end < $marker)) {
return true;
}
@@ -486,7 +486,7 @@ final class TextReplacer
*/
private static function stripped(string $value): string
{
$value = VariableRef::withoutControls($value);
$value = ControlChars::strip($value);
do {
$before = $value;
$kept = '';