Skip to content

refactor(php-transformer): make layer direction one-way and enforceable - #1564

Merged
chubes4 merged 1 commit into
trunkfrom
refactor/php-transformer-layering
Sep 3, 2026
Merged

refactor(php-transformer): make layer direction one-way and enforceable#1564
chubes4 merged 1 commit into
trunkfrom
refactor/php-transformer-layering

Conversation

@chubes4

@chubes4 chubes4 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Closes #1563

What

Breaks the dependency cycle inside php-transformer and adds a contract test that keeps it broken.

  • Moved the CSS/selector primitives out of HtmlToBlocks/Style/ into a shared Css/ namespace: CssStylesheetTransformer, CssValueSplitter, CssRuleAnalyzer, CssSyntaxScanner, CssSelectorMatcher, CssSelectorMatchCache, CssSelectorTokenizer, AdminBarAccommodation.
  • Moved ShellLandmarkPolicy into Support/.
  • Extracted the emitted-core-block contract map to Contract\EmittedCoreBlockContracts, so WordPress\CoreBlockCapabilityMatrix no longer depends on the HtmlTransformer facade to report capabilities.
  • Added tests/contract/layer-direction.php to composer test.

Result

before:  materialization -> conversion   15 imports
         conversion -> materialization    3 imports   (cycle)

after:   materialization -> conversion    0 imports
         conversion -> materialization    3 imports   (pinned, acyclic)

The layer model the contract now enforces:

  • shared primitives (Css/, Support/, Contract/, AssetAnalysis/) depend on nothing above them
  • WordPress/ is Gutenberg block grammar, which a Gutenberg-targeting translator may legitimately use
  • HtmlToBlocks/ is translation
  • WordPressSitePlan/, StaticSite/ are WordPress materialization and may not import translation
  • ArtifactCompiler/ is the composition root and may import anything, because composing the two halves is its job

Why not split the package

The package boundary is already correct. Static Site Importer imports ArtifactCompiler (27), WordPressSitePlan (6), AssetAnalysis (4), WordPress (2), StaticSite (2), Contract (1), and zero HtmlToBlocks classes — conversion runs inside ArtifactCompiler. No consumer wants one half without the other, so a second package would add a release train and a second version pin while buying no isolation. The defect was internal layering, and that is what this fixes.

Consumer impact

None. No class in a downstream consumer surface moved — Contract\ConversionFindingContract, StaticSite\FontMaterialization\FontMaterializationPlanBuilder, and WordPress\Runtime are all untouched. Every moved class was internal to conversion.

Known remaining edge

HtmlToBlocks still imports StaticSite\FontMaterialization in three places. That class is in SSI's import surface, so relocating it is a coordinated release rather than a move. The contract test pins the exact allowed set and fails if an unpinned materialization dependency is added — or if a pinned one disappears, so the allowance cannot rot.

Verification

  • composer test green, exit 0 — includes 297 parity fixtures, the canonical contract suite, unit suites, and the dist shape contract (267 files).
  • namespace-resolution.php, the repo's existing guard for exactly this class of move, passes: 255 class-likes declared, no unresolvable references. It caught 27 bare references during the work.
  • The new contract was verified to fail by reintroducing a materialization-to-conversion import, then pass once reverted.

AI assistance

OpenAI gpt-5.6-sol running in OpenCode mapped the cycle, performed the moves, wrote the contract test, and ran the verification above. A human directed the work and reviewed the result.

@chubes4
chubes4 merged commit eaa20aa into trunk Sep 3, 2026
10 checks passed
@chubes4
chubes4 deleted the refactor/php-transformer-layering branch September 3, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make php-transformer layer direction one-way and enforceable

1 participant