Skip to content

feat: attribute-driven layout system#20

Merged
markshust merged 5 commits intodevelopfrom
feature/layout-system
Apr 10, 2026
Merged

feat: attribute-driven layout system#20
markshust merged 5 commits intodevelopfrom
feature/layout-system

Conversation

@markshust
Copy link
Copy Markdown
Collaborator

Summary

  • Introduces marko/layout — a PHP attribute-driven layout system where everything is a component
  • #[Component] attribute with template, handle, slot, nested slots, sortOrder, before/after support
  • #[Layout] attribute for controllers (class and method level)
  • Auto-discovery of #[Component] classes from all modules via DiscoveringComponentCollector
  • Multi-pass rendering with nested slot support (dot-notation) and circular reference detection
  • LayoutMiddleware integrates with the router pipeline
  • Latte {slot}{/slot} custom tag with dual behavior (layout vs component context)
  • Explicit wildcard handle matching ('posts_*') — no ambiguity
  • Cross-module component manipulation via #[After] plugins on ComponentCollectorInterface
  • Full docs: guide at /docs/guides/layout/, package reference at /docs/packages/layout/

Test plan

  • All 5192 tests passing (112 new layout package tests)
  • HandleResolver: generate, wildcard prefix, exact match, default
  • ComponentCollection: add/remove/move/get, sortOrder, before/after constraints, ambiguity detection
  • ComponentCollector: attribute scanning, handle matching, class-reference resolution, missing package handling
  • LayoutProcessor: full render pipeline, slot validation, nested multi-pass rendering, circular detection
  • LayoutMiddleware: delegates to processor, preserves non-layout behavior, invokes controller for side effects
  • SlotExtension: renders slots in layout templates, outputs placeholders in component templates
  • DiscoveringComponentCollector: module scanning, merging, graceful failure
  • Module bindings and singleton registration verified
  • Global middleware registration verified (SessionMiddleware + LayoutMiddleware)
  • RouteMatcherInterface registered in container

Closes #18

🤖 Generated with Claude Code

markshust and others added 4 commits April 10, 2026 15:36
Introduces marko/layout — a PHP attribute-driven layout system where
everything is a component. Layouts, page sections, and widgets are
composed via #[Component] attributes with cross-module extensibility
via the existing Plugin/Preference system.

- Package scaffolding: composer.json, module.php, config, 6 exceptions
- #[Component] and #[Layout] attributes with full metadata support
- HandleResolver with auto-generation and prefix matching
- ComponentDefinition, ComponentCollection with before/after sorting
- ComponentCollector and ComponentCollectorInterface for discovery
- LayoutResolver reads #[Layout] from controllers (method overrides class)
- ComponentDataResolver injects route params into data() methods
- LayoutProcessor orchestrates full render pipeline with multi-pass nested slots
- Circular slot reference detection via DFS traversal
- LayoutMiddleware integrates with router (readonly class workaround)
- Latte {slot}{/slot} custom tag in view-latte package
- RouteMatcherInterface extracted from RouteMatcher for testability
- Docs page at docs/src/content/docs/packages/layout.md
- README.md for the package

Closes #18

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…from modules

Adds DiscoveringComponentCollector that wraps ComponentCollector and
auto-discovers #[Component] classes from all module src/ directories
using ClassFileParser, following the RoutingBootstrapper pattern.

- DiscoveringComponentCollector scans modules via ModuleRepositoryInterface
- Binds ComponentCollectorInterface → DiscoveringComponentCollector in module.php
- Binds LayoutProcessorInterface → LayoutProcessor in module.php
- Registers HandleResolver and LayoutResolver as singletons
- LayoutProcessor now gets discovered components automatically (no empty array)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LayoutMiddleware depends on RouteMatcherInterface via constructor
injection, but it was never registered. Register it in
RoutingBootstrapper::boot() using the same RouteCollection instance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In component templates with sub-slots, $slots is not passed — only the
layout template receives it. The {slot} tag now outputs the literal
{slot name}{/slot} placeholder text when $slots is not set, allowing
LayoutProcessor to find and replace it during multi-pass rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the enhancement New feature or request label Apr 10, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@markshust markshust merged commit a6c9746 into develop Apr 10, 2026
@markshust markshust deleted the feature/layout-system branch April 10, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create a properly-architected layout architecture for the view layer

1 participant