From 2c2c0e2300148033f5d01b3146e0762315676636 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Tue, 1 Sep 2026 14:52:12 -0400 Subject: [PATCH] Pass transformer session as typed context --- .../Elements/RuntimeIslandContext.php | 20 ++---- .../Elements/SearchBlockConversionContext.php | 6 +- .../src/HtmlToBlocks/HtmlCompilation.php | 65 +++++-------------- .../Session/HtmlTransformerSession.php | 15 +++-- .../NavigationStyleProjectionContext.php | 33 +++------- .../Style/StyleResolutionContext.php | 28 +++----- .../NavigationToggleSuppressionContext.php | 16 ++--- .../Support/SvgMaterializationContext.php | 26 +++----- .../tests/unit/author-selector-semantics.php | 4 +- .../unit/html-transformer-session-state.php | 15 +++++ .../tests/unit/runtime-island-analyzer.php | 16 ++--- 11 files changed, 93 insertions(+), 151 deletions(-) diff --git a/php-transformer/src/HtmlToBlocks/Elements/RuntimeIslandContext.php b/php-transformer/src/HtmlToBlocks/Elements/RuntimeIslandContext.php index bd90f669..f08d8646 100644 --- a/php-transformer/src/HtmlToBlocks/Elements/RuntimeIslandContext.php +++ b/php-transformer/src/HtmlToBlocks/Elements/RuntimeIslandContext.php @@ -4,6 +4,7 @@ namespace Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Elements; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Diagnostics\FallbackEmitter; +use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\HtmlTransformerSession; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\RuntimeDomState; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\RuntimeSelectorState; use Closure; @@ -12,17 +13,12 @@ /** * Explicit collaborator surface for {@see RuntimeIslandAnalyzer}. * - * The session state objects and the fallback emitter are resolved lazily - * through closures because they are per-transform: the analyzer is constructed - * once with the transformer, but must see the state belonging to the transform - * currently running. + * Per-transform state is read from the compilation's typed session. Closures + * remain only for transformer-owned operations. */ final class RuntimeIslandContext { /** - * @param Closure(): FallbackEmitter $fallbackEmitter - * @param Closure(): RuntimeDomState $runtimeDom - * @param Closure(): RuntimeSelectorState $runtimeSelectors * @param Closure(DOMElement): iterable $descendantElements * @param Closure(DOMElement): array> $requiredScriptsForElement * @param Closure(string): ?DOMElement $preservedHtmlRootElement @@ -31,9 +27,7 @@ final class RuntimeIslandContext * @param Closure(string): bool $isPresentationalAnimationSelector */ public function __construct( - private readonly Closure $fallbackEmitter, - private readonly Closure $runtimeDom, - private readonly Closure $runtimeSelectors, + private readonly HtmlTransformerSession $session, private readonly Closure $descendantElements, private readonly Closure $requiredScriptsForElement, private readonly Closure $preservedHtmlRootElement, @@ -45,17 +39,17 @@ public function __construct( public function fallbackEmitter(): FallbackEmitter { - return ($this->fallbackEmitter)(); + return $this->session->fallbackEmitter(); } public function runtimeDom(): RuntimeDomState { - return ($this->runtimeDom)(); + return $this->session->runtimeDomState(); } public function runtimeSelectors(): RuntimeSelectorState { - return ($this->runtimeSelectors)(); + return $this->session->runtimeSelectorState(); } /** diff --git a/php-transformer/src/HtmlToBlocks/Elements/SearchBlockConversionContext.php b/php-transformer/src/HtmlToBlocks/Elements/SearchBlockConversionContext.php index 240e3877..cff3ac6f 100644 --- a/php-transformer/src/HtmlToBlocks/Elements/SearchBlockConversionContext.php +++ b/php-transformer/src/HtmlToBlocks/Elements/SearchBlockConversionContext.php @@ -3,6 +3,7 @@ namespace Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Elements; +use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\HtmlTransformerSession; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Support\SourceDom; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Classification\FormControlClassifier; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Style\GeneratedSupportStylesheetState; @@ -17,16 +18,15 @@ final class SearchBlockConversionContext * @param Closure(DOMElement): array $presentationDeclarations * @param Closure(string, array, array>, ?DOMElement): array $createBlock * @param Closure(string): string $restoreSvgCasing - * @param Closure(): GeneratedSupportStylesheetState $generatedSupportStyles * @param Closure(DOMElement): bool $isRuntimeDomTarget * @param Closure(DOMElement): array $htmlPreservationBlock */ public function __construct( + private readonly HtmlTransformerSession $session, private readonly Closure $presentationAttributes, private readonly Closure $presentationDeclarations, private readonly Closure $createBlock, private readonly Closure $restoreSvgCasing, - private readonly Closure $generatedSupportStyles, private readonly Closure $isRuntimeDomTarget, private readonly Closure $htmlPreservationBlock ) { @@ -82,7 +82,7 @@ public function restoreSvgCasing(string $html): string public function generatedSupportStyles(): GeneratedSupportStylesheetState { - return ($this->generatedSupportStyles)(); + return $this->session->generatedSupportStylesheetState(); } public function isRuntimeDomTarget(DOMElement $element): bool diff --git a/php-transformer/src/HtmlToBlocks/HtmlCompilation.php b/php-transformer/src/HtmlToBlocks/HtmlCompilation.php index 788579b8..a1beb1e2 100644 --- a/php-transformer/src/HtmlToBlocks/HtmlCompilation.php +++ b/php-transformer/src/HtmlToBlocks/HtmlCompilation.php @@ -845,18 +845,11 @@ private function onSourceMarkupMutated(): void $this->sourceStyles()->invalidateSelectorMatches(); } - /** - * Collaborator surface for {@see StyleResolver}. Per-transform state is - * resolved lazily so the resolver always sees the running transform. - */ + /** Collaborator surface for {@see StyleResolver}. */ private function createStyleResolutionContext(): StyleResolutionContext { return new StyleResolutionContext( - fn (): AuthorStyleAnalysis => $this->authorStyles(), - fn (): SourceStyleResolutionState => $this->sourceStyles(), - fn (): LayoutGeometryState => $this->layoutGeometry(), - fn (): PresentationResolutionCache => $this->presentationResolutionCache(), - fn (): TransformationEvidenceState => $this->transformationEvidence(), + $this->session, fn (DOMElement $element): int => $this->cardLikeChildCount($element), fn (string $value): string => $this->cssComparableValue($value), fn (string $selector): array => $this->parsedCssSelector($selector), @@ -906,40 +899,29 @@ private function materializeStylesheetAsset(array $cssParts, string $source, str )); } - /** - * Collaborator surface for {@see NavigationToggleSuppressor}. Per-transform - * state is resolved lazily so the suppressor always sees the running - * transform. - */ + /** Collaborator surface for {@see NavigationToggleSuppressor}. */ private function createNavigationToggleSuppressionContext(): NavigationToggleSuppressionContext { return new NavigationToggleSuppressionContext( + $this->session, fn (DOMElement $element): bool => $this->sourceElementStartsHidden($element), - fn (): RuntimeSelectorState => $this->runtimeSelectors(), - fn (): NavigationProjectionState => $this->navigationProjection(), fn (): PatternRecognizerRegistry => $this->patternRecognizers, fn (): PatternContext => $this->probePatternContext() ); } - /** - * Collaborator surface for {@see SvgMaterializer}. Per-transform state is - * resolved lazily so the materializer always sees the running transform. - */ + /** Collaborator surface for {@see SvgMaterializer}. */ private function createSvgMaterializationContext(): SvgMaterializationContext { return new SvgMaterializationContext( + $this->session, fn (string $name, array $attrs = array(), array $innerBlocks = array(), ?DOMElement $sourceElement = null, ?DOMElement $logicalSourceElement = null): array => $this->createBlock($name, $attrs, $innerBlocks, $sourceElement, $logicalSourceElement), fn (string $tagName): bool => $this->sourceElementClassifier->isInlineContentElement($tagName), fn (DOMElement $element): bool => $this->sourceElementClassifier->isVisualLayerElement($element), - fn (): LayoutGeometryState => $this->layoutGeometry(), - fn (): AssetMaterializationState => $this->materializedAssets(), fn (DOMElement $element): ?string => $this->reusableComponentFingerprintFor($element), fn (DOMElement $element): string => $this->safeFallbackHtml($element), - fn (DOMElement $element): string => $this->sanitizeInlineSvgMarkup($element), - fn (): TransformationEvidenceState => $this->transformationEvidence(), - fn (): TransformationProvenanceState => $this->transformationProvenance() + fn (DOMElement $element): string => $this->sanitizeInlineSvgMarkup($element) ); } @@ -947,30 +929,21 @@ private function createSvgMaterializationContext(): SvgMaterializationContext private function createSearchBlockConversionContext(): SearchBlockConversionContext { return new SearchBlockConversionContext( + $this->session, fn (DOMElement $element): array => $this->styleResolver->presentationAttributes($element), fn (DOMElement $element): array => $this->styleResolver->presentationDeclarations($element), fn (string $name, array $attributes, array $innerBlocks, ?DOMElement $sourceElement): array => $this->createBlock($name, $attributes, $innerBlocks, $sourceElement), fn (string $html): string => $this->svgMaterializer->restoreSvgCasing($html), - fn (): GeneratedSupportStylesheetState => $this->generatedSupportStyles(), fn (DOMElement $element): bool => $this->runtimeIslands->isRuntimeDomTarget($element), fn (DOMElement $element): array => $this->htmlPreservationBlock($element) ); } - /** - * Collaborator surface for {@see NavigationStyleProjector}. Per-transform - * state is resolved lazily so the projector always sees the running - * transform. - */ + /** Collaborator surface for {@see NavigationStyleProjector}. */ private function createNavigationStyleProjectionContext(): NavigationStyleProjectionContext { return new NavigationStyleProjectionContext( - fn (): AuthorStyleAnalysis => $this->authorStyles(), - fn (): SourceStyleResolutionState => $this->sourceStyles(), - fn (): AuthorSelectorProjectionState => $this->session->authorSelectorProjectionState(), - fn (): GeneratedSupportStylesheetState => $this->generatedSupportStyles(), - fn (): RuntimeBehaviorState => $this->runtimeBehavior(), - fn (): TransformationEvidenceState => $this->transformationEvidence(), + $this->session, fn (string $selector): array => $this->parsedCssSelector($selector), function (array $cssParts, string $source, string $placement, string $pathPrefix, string $target = 'both'): void { $this->materializeStylesheetAsset($cssParts, $source, $placement, $pathPrefix, $target); @@ -1007,16 +980,11 @@ function (DOMElement $element, array &$fallbacks): ?array { ); } - /** - * Collaborator surface for {@see RuntimeIslandAnalyzer}. Session-scoped - * state is resolved lazily so the analyzer always sees the running transform. - */ + /** Collaborator surface for {@see RuntimeIslandAnalyzer}. */ private function createRuntimeIslandContext(): RuntimeIslandContext { return new RuntimeIslandContext( - fn (): FallbackEmitter => $this->fallbackEmitter(), - fn (): RuntimeDomState => $this->runtimeDom(), - fn (): RuntimeSelectorState => $this->runtimeSelectors(), + $this->session, fn (DOMElement $element): iterable => $this->descendantElements($element), fn (DOMElement $element): array => $this->requiredScriptsForElement($element), fn (string $html): ?DOMElement => $this->preservedHtmlRootElement($html), @@ -1111,14 +1079,12 @@ function (DOMElement $element, array &$fallbacks, bool $captureUnsupported): arr private function authorStyles(): AuthorStyleAnalysis { - return $this->session->authorStyleAnalysis() - ?? throw new \LogicException('Author styles have not been prepared for this transform.'); + return $this->session->authorStyleAnalysis(); } private function layoutGeometry(): LayoutGeometryState { - return $this->session->layoutGeometryState() - ?? throw new \LogicException('Layout geometry state has not been prepared for this transform.'); + return $this->session->layoutGeometryState(); } private function transformationProvenance(): TransformationProvenanceState @@ -1154,8 +1120,7 @@ private function generatedBlocks(): GeneratedBlockRegistry private function materializedAssets(): AssetMaterializationState { - return $this->session->assetMaterializationState() - ?? throw new \LogicException('Asset materialization state has not been prepared for this transform.'); + return $this->session->assetMaterializationState(); } private function runtimeDom(): RuntimeDomState diff --git a/php-transformer/src/HtmlToBlocks/Session/HtmlTransformerSession.php b/php-transformer/src/HtmlToBlocks/Session/HtmlTransformerSession.php index c8204f17..2e5b13b1 100644 --- a/php-transformer/src/HtmlToBlocks/Session/HtmlTransformerSession.php +++ b/php-transformer/src/HtmlToBlocks/Session/HtmlTransformerSession.php @@ -65,9 +65,10 @@ public function installAuthorStyleAnalysis(AuthorStyleAnalysis $analysis): void $this->authorSelectorProjectionState->installAuthorStyles($analysis); } - public function authorStyleAnalysis(): ?AuthorStyleAnalysis + public function authorStyleAnalysis(): AuthorStyleAnalysis { - return $this->authorStyleAnalysis; + return $this->authorStyleAnalysis + ?? throw new \LogicException('Author styles have not been prepared for this transform.'); } public function installLayoutGeometryState(LayoutGeometryState $state): void @@ -75,9 +76,10 @@ public function installLayoutGeometryState(LayoutGeometryState $state): void $this->layoutGeometryState = $state; } - public function layoutGeometryState(): ?LayoutGeometryState + public function layoutGeometryState(): LayoutGeometryState { - return $this->layoutGeometryState; + return $this->layoutGeometryState + ?? throw new \LogicException('Layout geometry state has not been prepared for this transform.'); } public function installGeneratedBlockRegistry(GeneratedBlockRegistry $registry): void @@ -95,9 +97,10 @@ public function installAssetMaterializationState(AssetMaterializationState $stat $this->assetMaterializationState = $state; } - public function assetMaterializationState(): ?AssetMaterializationState + public function assetMaterializationState(): AssetMaterializationState { - return $this->assetMaterializationState; + return $this->assetMaterializationState + ?? throw new \LogicException('Asset materialization state has not been prepared for this transform.'); } public function runtimeDomState(): RuntimeDomState diff --git a/php-transformer/src/HtmlToBlocks/Style/NavigationStyleProjectionContext.php b/php-transformer/src/HtmlToBlocks/Style/NavigationStyleProjectionContext.php index f9351f8b..134c3cc5 100644 --- a/php-transformer/src/HtmlToBlocks/Style/NavigationStyleProjectionContext.php +++ b/php-transformer/src/HtmlToBlocks/Style/NavigationStyleProjectionContext.php @@ -3,6 +3,7 @@ namespace Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Style; +use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\HtmlTransformerSession; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\RuntimeBehaviorState; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\TransformationEvidenceState; use Closure; @@ -11,11 +12,8 @@ /** * Explicit collaborator surface for {@see NavigationStyleProjector}. * - * Per-transform state (author styles, source styles, generated support styles, - * materialized assets, runtime behavior, transformation evidence) is resolved - * through closures because the projector is constructed once with the - * transformer but must see the state belonging to the transform currently - * running. + * Per-transform state is read from the compilation's typed session. Closures + * remain only for transformer-owned operations. * * `materializeStylesheetAsset` is a transformer-owned operation rather than a * navigation concern — the transformer uses it for engine-support and author @@ -25,22 +23,11 @@ final class NavigationStyleProjectionContext { /** - * @param Closure(): AuthorStyleAnalysis $authorStyles - * @param Closure(): SourceStyleResolutionState $sourceStyles - * @param Closure(): AuthorSelectorProjectionState $selectorProjections - * @param Closure(): GeneratedSupportStylesheetState $generatedSupportStyles - * @param Closure(): RuntimeBehaviorState $runtimeBehavior - * @param Closure(): TransformationEvidenceState $transformationEvidence * @param Closure(string): array $parsedCssSelector * @param Closure(array, string, string, string, string): void $materializeStylesheetAsset */ public function __construct( - private readonly Closure $authorStyles, - private readonly Closure $sourceStyles, - private readonly Closure $selectorProjections, - private readonly Closure $generatedSupportStyles, - private readonly Closure $runtimeBehavior, - private readonly Closure $transformationEvidence, + private readonly HtmlTransformerSession $session, private readonly Closure $parsedCssSelector, private readonly Closure $materializeStylesheetAsset ) { @@ -48,32 +35,32 @@ public function __construct( public function authorStyles(): AuthorStyleAnalysis { - return ($this->authorStyles)(); + return $this->session->authorStyleAnalysis(); } public function sourceStyles(): SourceStyleResolutionState { - return ($this->sourceStyles)(); + return $this->session->sourceStyleResolutionState(); } public function selectorProjections(): AuthorSelectorProjectionState { - return ($this->selectorProjections)(); + return $this->session->authorSelectorProjectionState(); } public function generatedSupportStyles(): GeneratedSupportStylesheetState { - return ($this->generatedSupportStyles)(); + return $this->session->generatedSupportStylesheetState(); } public function runtimeBehavior(): RuntimeBehaviorState { - return ($this->runtimeBehavior)(); + return $this->session->runtimeBehaviorState(); } public function transformationEvidence(): TransformationEvidenceState { - return ($this->transformationEvidence)(); + return $this->session->transformationEvidenceState(); } /** diff --git a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionContext.php b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionContext.php index f1b74da0..5b6acd89 100644 --- a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionContext.php +++ b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionContext.php @@ -3,6 +3,7 @@ namespace Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Style; +use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\HtmlTransformerSession; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\TransformationEvidenceState; use Closure; use DOMElement; @@ -10,19 +11,12 @@ /** * Explicit collaborator surface for {@see StyleResolver}. * - * Per-transform state (author styles, source styles, layout geometry, the - * presentation cache, transformation evidence) is resolved through closures - * because the resolver is constructed once with the transformer but must see - * the state belonging to the transform currently running. + * Per-transform state is read from the compilation's typed session. Closures + * remain only for transformer-owned operations. */ final class StyleResolutionContext { /** - * @param Closure(): AuthorStyleAnalysis $authorStyles - * @param Closure(): SourceStyleResolutionState $sourceStyles - * @param Closure(): LayoutGeometryState $layoutGeometry - * @param Closure(): PresentationResolutionCache $presentationResolutionCache - * @param Closure(): TransformationEvidenceState $transformationEvidence * @param Closure(DOMElement): int $cardLikeChildCount * @param Closure(string): string $cssComparableValue * @param Closure(string): array $parsedCssSelector @@ -31,11 +25,7 @@ final class StyleResolutionContext * @param Closure(DOMElement): bool $hasRetainedPresentationRuntime */ public function __construct( - private readonly Closure $authorStyles, - private readonly Closure $sourceStyles, - private readonly Closure $layoutGeometry, - private readonly Closure $presentationResolutionCache, - private readonly Closure $transformationEvidence, + private readonly HtmlTransformerSession $session, private readonly Closure $cardLikeChildCount, private readonly Closure $cssComparableValue, private readonly Closure $parsedCssSelector, @@ -47,27 +37,27 @@ public function __construct( public function authorStyles(): AuthorStyleAnalysis { - return ($this->authorStyles)(); + return $this->session->authorStyleAnalysis(); } public function sourceStyles(): SourceStyleResolutionState { - return ($this->sourceStyles)(); + return $this->session->sourceStyleResolutionState(); } public function layoutGeometry(): LayoutGeometryState { - return ($this->layoutGeometry)(); + return $this->session->layoutGeometryState(); } public function presentationResolutionCache(): PresentationResolutionCache { - return ($this->presentationResolutionCache)(); + return $this->session->presentationResolutionCache(); } public function transformationEvidence(): TransformationEvidenceState { - return ($this->transformationEvidence)(); + return $this->session->transformationEvidenceState(); } public function cardLikeChildCount(DOMElement $element): int diff --git a/php-transformer/src/HtmlToBlocks/Support/NavigationToggleSuppressionContext.php b/php-transformer/src/HtmlToBlocks/Support/NavigationToggleSuppressionContext.php index be638fc8..3f053eaa 100644 --- a/php-transformer/src/HtmlToBlocks/Support/NavigationToggleSuppressionContext.php +++ b/php-transformer/src/HtmlToBlocks/Support/NavigationToggleSuppressionContext.php @@ -5,6 +5,7 @@ use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\PatternContext; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\PatternRecognizerRegistry; +use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\HtmlTransformerSession; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\NavigationProjectionState; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\RuntimeSelectorState; use Closure; @@ -13,24 +14,19 @@ /** * Explicit collaborator surface for {@see NavigationToggleSuppressor}. * - * Per-transform state (runtime selectors, navigation projection) and the probe - * pattern context are resolved through closures because the suppressor is constructed once with the - * transformer but must see the state belonging to the transform currently - * running. + * Per-transform state is read from the compilation's typed session. Closures + * remain only for transformer-owned operations. */ final class NavigationToggleSuppressionContext { /** * @param Closure(DOMElement): bool $sourceElementStartsHidden - * @param Closure(): RuntimeSelectorState $runtimeSelectors - * @param Closure(): NavigationProjectionState $navigationProjection * @param Closure(): PatternRecognizerRegistry $patternRecognizers * @param Closure(): PatternContext $probePatternContext */ public function __construct( + private readonly HtmlTransformerSession $session, private readonly Closure $sourceElementStartsHidden, - private readonly Closure $runtimeSelectors, - private readonly Closure $navigationProjection, private readonly Closure $patternRecognizers, private readonly Closure $probePatternContext ) { @@ -43,12 +39,12 @@ public function sourceElementStartsHidden(DOMElement $element): bool public function runtimeSelectors(): RuntimeSelectorState { - return ($this->runtimeSelectors)(); + return $this->session->runtimeSelectorState(); } public function navigationProjection(): NavigationProjectionState { - return ($this->navigationProjection)(); + return $this->session->navigationProjectionState(); } public function patternRecognizers(): PatternRecognizerRegistry diff --git a/php-transformer/src/HtmlToBlocks/Support/SvgMaterializationContext.php b/php-transformer/src/HtmlToBlocks/Support/SvgMaterializationContext.php index 0cf254d3..2d8cbd4f 100644 --- a/php-transformer/src/HtmlToBlocks/Support/SvgMaterializationContext.php +++ b/php-transformer/src/HtmlToBlocks/Support/SvgMaterializationContext.php @@ -4,6 +4,7 @@ namespace Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Support; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\AssetMaterializationState; +use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\HtmlTransformerSession; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\TransformationEvidenceState; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\TransformationProvenanceState; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Style\LayoutGeometryState; @@ -13,10 +14,8 @@ /** * Explicit collaborator surface for {@see SvgMaterializer}. * - * Per-transform state (layout geometry, materialized assets, transformation - * evidence and provenance) is resolved through closures because the materializer - * is constructed once with the transformer but must see the state belonging to - * the transform currently running. + * Per-transform state is read from the compilation's typed session. Closures + * remain only for transformer-owned operations. */ final class SvgMaterializationContext { @@ -24,25 +23,18 @@ final class SvgMaterializationContext * @param Closure(string, array, array>, ?DOMElement, ?DOMElement): array $createBlock * @param Closure(string): bool $isInlineContentElement * @param Closure(DOMElement): bool $isVisualLayerElement - * @param Closure(): LayoutGeometryState $layoutGeometry - * @param Closure(): AssetMaterializationState $materializedAssets * @param Closure(DOMElement): ?string $reusableComponentFingerprintFor * @param Closure(DOMElement): string $safeFallbackHtml * @param Closure(DOMElement): string $sanitizeInlineSvgMarkup - * @param Closure(): TransformationEvidenceState $transformationEvidence - * @param Closure(): TransformationProvenanceState $transformationProvenance */ public function __construct( + private readonly HtmlTransformerSession $session, private readonly Closure $createBlock, private readonly Closure $isInlineContentElement, private readonly Closure $isVisualLayerElement, - private readonly Closure $layoutGeometry, - private readonly Closure $materializedAssets, private readonly Closure $reusableComponentFingerprintFor, private readonly Closure $safeFallbackHtml, - private readonly Closure $sanitizeInlineSvgMarkup, - private readonly Closure $transformationEvidence, - private readonly Closure $transformationProvenance + private readonly Closure $sanitizeInlineSvgMarkup ) { } @@ -73,12 +65,12 @@ public function isVisualLayerElement(DOMElement $element): bool public function layoutGeometry(): LayoutGeometryState { - return ($this->layoutGeometry)(); + return $this->session->layoutGeometryState(); } public function materializedAssets(): AssetMaterializationState { - return ($this->materializedAssets)(); + return $this->session->assetMaterializationState(); } public function reusableComponentFingerprintFor(DOMElement $element): ?string @@ -98,11 +90,11 @@ public function sanitizeInlineSvgMarkup(DOMElement $element): string public function transformationEvidence(): TransformationEvidenceState { - return ($this->transformationEvidence)(); + return $this->session->transformationEvidenceState(); } public function transformationProvenance(): TransformationProvenanceState { - return ($this->transformationProvenance)(); + return $this->session->transformationProvenanceState(); } } diff --git a/php-transformer/tests/unit/author-selector-semantics.php b/php-transformer/tests/unit/author-selector-semantics.php index 810f3d57..8a5a6b4f 100644 --- a/php-transformer/tests/unit/author-selector-semantics.php +++ b/php-transformer/tests/unit/author-selector-semantics.php @@ -620,14 +620,14 @@ $applicabilityCompilation = new HtmlCompilation(); $applicabilityCompilation->transform('
Present
'); $applicabilitySession = (new ReflectionClass($applicabilityCompilation))->getProperty('session')->getValue($applicabilityCompilation); -$applicableRules = $applicabilitySession->authorStyleAnalysis()?->styleRules() ?? array(); +$applicableRules = $applicabilitySession->authorStyleAnalysis()->styleRules(); $applicableSelectors = array_column(array_merge(...array_column($applicableRules, 'selectors')), 'selector'); $assert(array('.present') === $applicableSelectors, 'the installed page-matching graph omits selectors whose required source signals are absent'); $unmatchableCompilation = new HtmlCompilation(); $unmatchableCompilation->transform('

Copy

'); $unmatchableSession = (new ReflectionClass($unmatchableCompilation))->getProperty('session')->getValue($unmatchableCompilation); -$unmatchableIndex = $unmatchableSession->authorStyleAnalysis()?->styleRuleCandidateIndex() ?? array(); +$unmatchableIndex = $unmatchableSession->authorStyleAnalysis()->styleRuleCandidateIndex(); $indexedAuthorSelectors = array(); foreach ( array( 'universal', 'ids', 'classes', 'tags', 'attributes' ) as $bucket ) { $entries = 'universal' === $bucket ? $unmatchableIndex[$bucket] : array_merge(...array_values($unmatchableIndex[$bucket] ?: array(array()))); diff --git a/php-transformer/tests/unit/html-transformer-session-state.php b/php-transformer/tests/unit/html-transformer-session-state.php index 2bfcf84a..c9a22fce 100644 --- a/php-transformer/tests/unit/html-transformer-session-state.php +++ b/php-transformer/tests/unit/html-transformer-session-state.php @@ -5,6 +5,7 @@ use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\HtmlTransformer; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\HtmlTransformerSession; +use Automattic\BlocksEngine\PhpTransformer\WordPress\Runtime; $assert = static function (bool $condition, string $message): void { if ( ! $condition ) { @@ -108,4 +109,18 @@ $assert(! $transformerReflection->hasMethod($magicAccessor), 'HtmlTransformer must not delegate state through ' . $magicAccessor . '.'); } +$unpreparedSession = new HtmlTransformerSession(new Runtime(), static fn (DOMElement $element): array => array()); +foreach ( array( + 'authorStyleAnalysis' => 'Author styles have not been prepared for this transform.', + 'layoutGeometryState' => 'Layout geometry state has not been prepared for this transform.', + 'assetMaterializationState' => 'Asset materialization state has not been prepared for this transform.', +) as $method => $message ) { + try { + $unpreparedSession->{$method}(); + $assert(false, $method . ' must fail before its lifecycle state is installed.'); + } catch ( LogicException $exception ) { + $assert($message === $exception->getMessage(), $method . ' must preserve its lifecycle failure.'); + } +} + fwrite(STDOUT, "HTML transformer session state passed\n"); diff --git a/php-transformer/tests/unit/runtime-island-analyzer.php b/php-transformer/tests/unit/runtime-island-analyzer.php index b344d202..008629e1 100644 --- a/php-transformer/tests/unit/runtime-island-analyzer.php +++ b/php-transformer/tests/unit/runtime-island-analyzer.php @@ -17,8 +17,10 @@ use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Elements\PseudoFormAnalyzer; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Elements\RuntimeIslandAnalyzer; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Elements\RuntimeIslandContext; +use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\HtmlTransformerSession; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\RuntimeDomState; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Session\RuntimeSelectorState; +use Automattic\BlocksEngine\PhpTransformer\WordPress\Runtime; $assertions = 0; $failures = array(); @@ -71,9 +73,6 @@ ); $defaults = array( - 'fallbackEmitter' => static fn () => throw new RuntimeException('fallbackEmitter not expected in this test'), - 'runtimeDom' => static fn (): RuntimeDomState => new RuntimeDomState(), - 'runtimeSelectors' => static fn (): RuntimeSelectorState => $selectors, 'attr' => static fn (DOMElement $e, string $n): string => $e->getAttribute($n), 'descendants' => $descendants, 'islandSelector' => static fn (DOMElement $e): string => strtolower($e->tagName), @@ -86,13 +85,13 @@ 'dedupe' => static fn (array $rows): array => array_values($rows), ); $c = array_merge($defaults, $overrides); + $session = $c['session'] ?? new HtmlTransformerSession(new Runtime(), static fn (DOMElement $element): array => array()); + $session->installRuntimeSelectorState($selectors); $metadataBuilder = new FormControlMetadataBuilder($c['islandSelector']); $pseudoFormAnalyzer = new PseudoFormAnalyzer($metadataBuilder, $c['islandSelector']); return new RuntimeIslandAnalyzer(new RuntimeIslandContext( - $c['fallbackEmitter'], - $c['runtimeDom'], - $c['runtimeSelectors'], + $session, $c['descendants'], $c['requiredScripts'], $c['preservedRoot'], @@ -168,9 +167,10 @@ $assert(! $retain->canRetainRuntimeDomContractNatively($elementFrom('

t

'), 'core/image'), 'unsupported-block-name-does-not-retain'); // Block materialization delegates the complete runtime DOM recording decision. -$runtimeDom = new RuntimeDomState(); +$blockRuntimeSession = new HtmlTransformerSession(new Runtime(), static fn (DOMElement $element): array => array()); +$runtimeDom = $blockRuntimeSession->runtimeDomState(); $blockRuntime = $makeAnalyzer(array('#mount'), array(), array( - 'runtimeDom' => static fn (): RuntimeDomState => $runtimeDom, + 'session' => $blockRuntimeSession, )); $assert($blockRuntime->recordBlockRuntimeDomContract($elementFrom('

text

'), 'core/group'), 'runtime-block-contract-recorded'); $assert(array(array('block_name' => 'core/group', 'tag' => 'section', 'selector' => '#mount')) === $runtimeDom->preservations(), 'runtime-block-native-preservation-recorded');