feat: attribute-driven layout system#20
Merged
Conversation
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>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
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)#[Component]classes from all modules viaDiscoveringComponentCollectorLayoutMiddlewareintegrates with the router pipeline{slot}{/slot}custom tag with dual behavior (layout vs component context)'posts_*') — no ambiguity#[After]plugins onComponentCollectorInterface/docs/guides/layout/, package reference at/docs/packages/layout/Test plan
Closes #18
🤖 Generated with Claude Code