CompositeComponentRegistry
One {@see ComponentRegistryInterface} over ordered, LABELLED layers — the host's own components, then each plugin's — so ONE live endpoint serves every plugin's components and a cross-component effect resolves across them (greenhouse decisions/0211). Resolution is first-layer-wins, but shadowing is never silent: at construction every name that appears in two layers is checked, and a name bound to DIFFERENT definitions (a different class, or a different instance of a class that carries state) throws {@see ComponentNameConflictException} naming the component and both layers. The same instance registered in two layers is fine, and so are two instances of a STATELESS class (no instance property at all) — with nothing to differ in, they are one definition. The check is identity or statelessness, never a structural compare: two instances of a stateful class are a conflict even when their state would compare equal (a deep `==` over a component that holds a collaborator pointing back at it is a fatal, not a verdict). The shipped components hold a dispatcher, so two `new TextareaComponent()` under one name in two layers ARE a conflict: a plugin reuses the host's instance or names its own component. Conflict detection and {@see names()} cover the layers that implement {@see ListsComponents}; an opaque layer still resolves but cannot be enumerated, so it takes no part in either. Writes go to the one layer named writable (a plugin registering late, a host adopting a section); with no writable layer the composite is read-only and {@see register()} throws.
CompositeComponentRegistry::__construct()
public function __construct(array $layers, ?string $writable = null):Parameters
| Name | Type | Description |
|---|---|---|
| $layers | array<string, ComponentRegistryInterface> | label => registry, in resolution order (first wins) |
| $writable | (string | null) | the label of the layer {@see register()} writes to, or null for a read-only composite |
Throws
\InvalidArgumentException if there are no layers, or `$writable` names no layer
ComponentNameConflictException if two layers bind one name to different definitions
CompositeComponentRegistry::has()
public function has(string $name): boolTrue when any layer has a component under `$name`.
Parameters
| Name | Type | Description |
|---|---|---|
| $name | string |
CompositeComponentRegistry::get()
public function get(string $name): Milpa\Live\Contracts\Component\ComponentDefinitionInterfaceThe component under `$name` from the first layer that has it.
Parameters
| Name | Type | Description |
|---|---|---|
| $name | string |
Throws
\RuntimeException If no layer has a component under `$name`.
CompositeComponentRegistry::register()
public function register(string $name, Milpa\Live\Contracts\Component\ComponentDefinitionInterface $component): voidRegisters `$component` in the writable layer.
Parameters
| Name | Type | Description |
|---|---|---|
| $name | string | |
| $component | Milpa\Live\Contracts\Component\ComponentDefinitionInterface |
Throws
\LogicException if the composite has no writable layer
ComponentNameConflictException if another layer already binds `$name` to a different definition
CompositeComponentRegistry::names()
public function names(): arrayThe union of every listing layer's names, in layer order then registration order, each name once.
CompositeComponentRegistry::labels()
public function labels(): arrayThe layer labels, in resolution order.