feat: DiviComposition::inLoop() – VariableRef und Placeholders mit Schleifen-Kontext; S31 je Kontext (X3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
6da2c0090f
commit
2f484b77ae
@@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Divi\Pages;
|
||||
use LIT\MCP\Integrations\Divi\Compose\AttrWriter;
|
||||
use LIT\MCP\Integrations\Divi\Compose\Composer;
|
||||
use LIT\MCP\Integrations\Divi\Compose\LinkTargets;
|
||||
use LIT\MCP\Integrations\Divi\Compose\LoopScope;
|
||||
use LIT\MCP\Integrations\Divi\Compose\MediaResolver;
|
||||
use LIT\MCP\Integrations\Divi\Compose\Placeholders;
|
||||
use LIT\MCP\Integrations\Divi\Compose\ShortformWriter;
|
||||
@@ -27,10 +28,11 @@ defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Wires the pure Compose classes with the live catalog, design system, Divi's dynamic content
|
||||
* registry and the WordPress sanitizer/media lookup – once per request and entry. Ruling S31:
|
||||
* ONE VariableRef (carrying the live dynamic content map of the entry, S22), ONE Placeholders
|
||||
* and ONE ValueCleaner instance are shared by AttrWriter, ShortformWriter and Composer, and the
|
||||
* same AttrWriter serves Composer and the abilities.
|
||||
* registry and the WordPress sanitizer/media lookup – once per request, entry and loop scope. Ruling S31
|
||||
* (as reworded by X3): ONE VariableRef per composition (carrying the live dynamic content map of the entry, S22,
|
||||
* plus the loop options of its LoopScope), ONE Placeholders and ONE ValueCleaner instance are shared by
|
||||
* AttrWriter, ShortformWriter and Composer, and the same AttrWriter serves Composer and the abilities. A tool
|
||||
* whose nodes sit in several loop scopes uses one composition – and so one Composer – per scope (inLoop()).
|
||||
*
|
||||
* Presets (D7) reach blocks through ONE PresetAssignment per composition: its book is Divi's stored preset
|
||||
* inventory (DesignSystemWriter::presets()), read lazily and at most once – only when a write names a preset or a
|
||||
@@ -38,8 +40,9 @@ defined('ABSPATH') || exit;
|
||||
* Without a preset source (null, unit tests) there are no presets.
|
||||
*
|
||||
* forPost() binds the composition to the entry being written (DiviWriter::composition() does it
|
||||
* for the abilities); unbound, VariableRef's default map applies. Not final so abilities can be
|
||||
* tested with a mock.
|
||||
* for the abilities); unbound, VariableRef's default map applies. inLoop() binds it to the loop scope of the
|
||||
* nodes it composes (LoopContext); without it the scope is none and every loop_* option is refused. Not final so
|
||||
* abilities can be tested with a mock.
|
||||
*/
|
||||
class DiviComposition
|
||||
{
|
||||
@@ -51,6 +54,10 @@ class DiviComposition
|
||||
private ?AttrWriter $attrWriter = null;
|
||||
private ?Composer $composer = null;
|
||||
private ?PresetAssignment $assignment = null;
|
||||
private LoopScope $loop;
|
||||
|
||||
/** @var array<string, array<string, string>>|null Loop options of $loop for the entry (read once). */
|
||||
private ?array $loopFields = null;
|
||||
|
||||
public function __construct(
|
||||
private DiviModules $modules,
|
||||
@@ -61,35 +68,40 @@ class DiviComposition
|
||||
private LinkTargets $links = new WpLinkTargets(),
|
||||
private ?DesignSystemWriter $presetSource = null
|
||||
) {
|
||||
$this->loop = LoopScope::none();
|
||||
}
|
||||
|
||||
/** A fresh composition for entry $postId (own VariableRef with its live map; TB area for StructureRules). */
|
||||
public function forPost(int $postId, ?string $themeBuilderArea = null): static
|
||||
{
|
||||
$bound = clone $this;
|
||||
$bound = $this->fresh();
|
||||
$bound->postId = $postId;
|
||||
$bound->themeBuilderArea = $themeBuilderArea;
|
||||
$bound->references = null;
|
||||
$bound->placeholders = null;
|
||||
$bound->cleaner = null;
|
||||
$bound->attrWriter = null;
|
||||
$bound->composer = null;
|
||||
$bound->assignment = null;
|
||||
|
||||
return $bound;
|
||||
}
|
||||
|
||||
/**
|
||||
* The one checker of $variable(…)$ values of this composition, with the entry's live map (S22, S31).
|
||||
* A fresh composition of the same entry for nodes inside $scope (ruling X3): its VariableRef and Placeholders
|
||||
* know the loop options of that kind; the abilities take $scope from LoopContext.
|
||||
*/
|
||||
public function inLoop(LoopScope $scope): static
|
||||
{
|
||||
$bound = $this->fresh();
|
||||
$bound->loop = $scope;
|
||||
|
||||
return $bound;
|
||||
}
|
||||
|
||||
/**
|
||||
* The one checker of $variable(…)$ values of this composition: the entry's live map (S22, S31) plus the loop
|
||||
* options of its scope, and the scope itself for the loop messages (X5).
|
||||
*
|
||||
* @internal Public for tests; code uses cleaner() / composer().
|
||||
*/
|
||||
public function references(): VariableRef
|
||||
{
|
||||
return $this->references ??= new VariableRef(
|
||||
$this->design->read(),
|
||||
$this->postId === null ? null : $this->dynamicContent->forPost($this->postId)
|
||||
);
|
||||
return $this->references ??= new VariableRef($this->design->read(), $this->dynamicContentMap(), $this->loop);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +111,7 @@ class DiviComposition
|
||||
*/
|
||||
public function placeholders(): Placeholders
|
||||
{
|
||||
return $this->placeholders ??= new Placeholders($this->design->read(), $this->links);
|
||||
return $this->placeholders ??= new Placeholders($this->design->read(), $this->links, $this->loopFields());
|
||||
}
|
||||
|
||||
/** The one choke point of this composition (ValueCleaner, rulings S1–S3, S7, S13, S28, S32). */
|
||||
@@ -161,4 +173,37 @@ class DiviComposition
|
||||
{
|
||||
return new Warnings($this->design->read(), $this->presets());
|
||||
}
|
||||
|
||||
/** @return array<string, array<string, string>>|null The entry's live map with the scope's loop options. */
|
||||
private function dynamicContentMap(): ?array
|
||||
{
|
||||
$map = $this->postId === null ? null : $this->dynamicContent->forPost($this->postId);
|
||||
|
||||
return $map === null ? null : $map + $this->loopFields();
|
||||
}
|
||||
|
||||
/** @return array<string, array<string, string>> Loop options of the OWN scope's kind only ([] without). */
|
||||
private function loopFields(): array
|
||||
{
|
||||
$kind = $this->loop->kind();
|
||||
|
||||
return $this->loopFields ??= $this->postId === null || $kind === null
|
||||
? []
|
||||
: $this->dynamicContent->loopFields($kind, $this->postId);
|
||||
}
|
||||
|
||||
/** A clone without any cached instance (they hang on the entry and the loop scope). */
|
||||
private function fresh(): static
|
||||
{
|
||||
$bound = clone $this;
|
||||
$bound->references = null;
|
||||
$bound->placeholders = null;
|
||||
$bound->cleaner = null;
|
||||
$bound->attrWriter = null;
|
||||
$bound->composer = null;
|
||||
$bound->assignment = null;
|
||||
$bound->loopFields = null;
|
||||
|
||||
return $bound;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user