it-blaster/page-layout-bundle
A PHP bundle for configurable page layout management, letting you define layout templates and reusable blocks to compose and render page structure consistently across your application.
The first thing to establish is that this is a Symfony + Sonata Admin bundle, not a Laravel package. There is no service provider, no config/page-layout.php to publish, no Blade views. So "getting started" for a Laravel developer means reading it, not requiring it.
Minimal steps:
composer.json.composer.json first. It answers the only two questions that matter before anything else: which Symfony major, which Sonata Admin major, and whether Gridstack.js is a declared dependency or a vendored compiled asset.README.md, then walk the bundle skeleton in this order:
src/*Bundle.php — what it registers.src/DependencyInjection/*Extension.php + Resources/config/* — the config tree and service wiring; this is where layout configuration is centralised.Resources/views/** — the Twig templates, and the template contract (which block/variables a layout expects).Resources/public/** — the Gridstack JS wiring. This is the only part likely worth copying.Entity/ or Document/ + Admin/ — how a saved layout is persisted and exposed in Sonata.First use case: a read-only renderer in Laravel. Create a page_layouts table with a json column, paste one of the bundle's example layouts into a seeder, and write a Blade component that walks the JSON and emits Bootstrap row/col-* markup. You get the value of the idea in an afternoon without touching Gridstack.
Only if you are genuinely on Symfony/Sonata: composer require, register in config/bundles.php, verify assets are published and built, then exercise it on a staging admin before anything else.
Reference extraction (the recommended Laravel path). Treat the repo as a spec, not a dependency. Copy three things: the Gridstack init options, the change/added/removed event handlers that serialise the grid, and the persisted JSON shape. Rebuild the UI as a Filament custom field or a Livewire component. Reimplement the Twig templates in Blade — the translation is mechanical but every Sonata block() call must be replaced with your own renderer.
Layout/content split as the architectural contract. Persist structure, not markup. A workable shape:
{ "version": 1,
"grid": [ { "id": "a1", "x": 0, "y": 0, "w": 6, "h": 2, "widget": "hero",
"settings": { "headline": "..." } } ] }
The widget key points at your own Blade partial; settings is content. This keeps the editor replaceable and is the single decision that makes the package worth studying.
Renderer-first, editor-second. Build and ship the read-only Blade renderer before the editor exists. Most layout bugs (collapsed columns, empty rows, mobile stacking) surface at render time, and you want them found against a hand-written JSON fixture rather than through a drag-and-drop UI.
Editor wrapper. If you do port the UI, wrap Gridstack in a thin module with two exported functions — mount(el, layout) and serialize() — and keep all Gridstack API calls inside it. Upstream Gridstack has broken its API across majors ($(el).gridstack(...) → GridStack.init(...)); an abstraction layer is the difference between a one-file upgrade and a rewrite.
Draft/publish on layouts. Because the payload is a JSON blob, versioning is cheap: keep draft_json and published_json columns, and let the renderer only ever read published_json. This also gives you an audit trail for free.
Schema versioning. Stamp version on every saved layout and write a migration for every shape change. Without this, an editor change silently orphans stored layouts.
Sonata path (if applicable). Register the admin service, map the form type, and verify the Twig template override path — Symfony resolves bundle view overrides by naming convention, which is your cleanest extension point.
composer require into a Laravel app will not boot and will drag in Sonata's dependency graph. Decide "port the concept vs. adopt the package" before estimating anything.x:0. Force a re-layout when the container becomes visible.y, then x, when emitting markup — or accept the limitation explicitly up front.['hero' => HeroWidget::class]), never as class names — class renames are otherwise a data migration.Resources/config/services.* rather than the classes.How can I help you explore Laravel packages today?