Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Page Bundle Laravel Package

enhavo/page-bundle

Enhavo Page Bundle is a subtree split of the Enhavo CMS, an open-source PHP content management system built on the Symfony framework. It provides page management functionality for building flexible content structures with a clean, usable interface. See the main repository for full documentation.

View on GitHub
Deep Wiki
Context7

Getting Started

For a Laravel developer, "getting started" with enhavo/page-bundle is almost always a reading exercise, not an install. This is a Symfony bundle — a subtree split of the Enhavo CMS monorepo — and it will not boot inside a Laravel application. The realistic first step is to decide which of two paths you are on before writing any code.

Path A — read it as a design reference. You are modeling pages, blocks, and publication state in a Laravel app and want a validated model to borrow from. Start here:

  1. Open the main repository (github.com/enhavo/enhavo), not this split. The split has no docs, no issues, no changelog.
  2. Navigate to the page module: src/Enhavo/Bundle/PageBundle/ in the monorepo.
  3. Read, in this order: the Entity/ directory (Page, Content, Block), Resources/config/ (how pages declare templates and routes), then the admin resource config (how block types are registered and rendered).
  4. Trace one real page render end-to-end: route → controller → page template → block renderers → Twig.

Path B — run Enhavo as a separate service. You keep Laravel as your app and stand up Enhavo in its own Symfony project, then read page content over HTTP or a shared read model. Start with the main Enhavo distribution's install docs (again, the parent repo — not this bundle), get a page rendering in the Enhavo admin, then write a single read-only JSON endpoint exposing page + blocks + slug. That endpoint is your first use case; integrate it into Laravel before you build anything else.

First use case either way: a single page type with one content field and one block type (e.g. a "marketing page" with a "rich text" block). Do not start with routing hierarchies, scheduled publishing, or slug history — those are where the complexity and the framework-mismatch pain both concentrate.

Do not attempt composer require enhavo/page-bundle into a Laravel project. The dependency graph (Doctrine ORM/DBAL, Symfony HttpKernel, Form, Twig, plus sibling enhavo/* bundles) collides with Laravel's own Symfony component pins, Eloquent, Blade, and its service container. Composer failing to resolve is the expected outcome, not a bug to fight.

Implementation Patterns

Pattern 1 — Reimplement the page model in Laravel (most common). This is what most Laravel teams actually do after reading the bundle. The Enhavo model maps cleanly to a Laravel schema:

  • pages table: id, slug, template, status (draft/published/scheduled), published_at, expires_at, timestamps.
  • page_blocks table (or a JSON blocks column): polymorphic — page_id, type, position, data (JSON). Reuse Eloquent polymorphic relations or a single JSON column with a type discriminator cast to a block class.
  • slugs / page_slug_history table for redirects: page_id, slug, created_at. On slug change, write history and 301 old URLs. This is the one behavior teams most often skip and later regret.

Blade rendering maps one-to-one to Enhavo's Twig block renderers: one Blade component or partial per block type, driven by a match on the discriminator. Publication state becomes two Eloquent scopes: ->published() checking published_at <= now() and expires_at null-or-future. Wire a scheduled command or ->where() in queries — do not rely on a background job to flip a status column; that produces stale draft/published mismatches.

Pattern 2 — CMS-as-a-service. Enhavo runs as its own Symfony app; Laravel calls it. The practical shape:

  • Enhavo exposes GET /api/pages/{slug} returning { slug, template, blocks: [{type, data}], meta }.
  • Laravel has a PageClient service with a tagged cache (cache()->tags(['page', $slug])->remember(...)) and a null-object fallback so a Laravel page renders an empty/404 shell rather than a 500 when Enhavo is down.
  • Cache invalidation: Enhavo should POST a webhook to Laravel on publish, which flushes the tagged cache. Without this you will be chasing stale marketing pages in production.

Pattern 3 — Hybrid spike. Borrow the rendering contract (block types, template names) from Enhavo but own the data and admin UI in Laravel (Filament is the natural fit for the admin CRUD). This gets you the content model without the framework mismatch, and it's the option most teams land on.

Integration tips across all patterns:

  • Keep the content contract narrow. Every field you expose from Enhavo (in Pattern 2) or copy from it (in Pattern 1) is a coupling point. Agree the fields first, in a doc, before writing code.
  • Model slug history from day one. It's cheap on day one and expensive to retrofit once you have published URLs.
  • Treat "publish" as a state transition, not a boolean. Enhavo's draft/scheduled model implies at minimum draft | scheduled | published | expired; a single is_published flag will not survive contact with a marketing team.
  • If you go the service route, do the cache + fallback before the admin UI. The admin UI is the most visible work and the least risky; caching and failure-mode handling is where Laravel-side bugs live.

Gotchas and Tips

The framework mismatch is not negotiable. This is a Symfony bundle, not a Laravel package. Laravel uses Symfony components (HttpFoundation, HttpKernel, Console, etc.) — it does not run Symfony bundles. A bundle expects config/bundles.php, the FrameworkBundle extension, container compilation passes, Symfony routing/Form/Twig wiring, and Doctrine. Hand-wiring a kernel shim inside Laravel to boot a bundle means running two frameworks with overlapping responsibility for routing, sessions, security, and caching. Do not spend a sprint trying this; the result is a frankenstein you cannot upgrade.

Do not file issues or PRs against this repository. It is an explicitly read-only subtree split, auto-generated from the Enhavo monorepo. Anything filed here is lost on the next sync. Issues, docs, and release notes belong upstream. Same for version-pinning: the split is tagged in lockstep with the monorepo, so pinning enhavo/page-bundle implicitly pins a large slice of Enhavo. If you ever need a patch, it must be a real fork of the monorepo or a Composer patch (cweagans/composer-patches), not a local edit — local edits get overwritten.

Zero dependents means zero reference implementations. There is no third-party example, no Stack Overflow corpus, no peer-reviewed integration pattern for this bundle. Every question becomes a question to the Enhavo maintainers. Budget for reading source. This is also why the honest day-to-day answer for a Laravel dev is "read it, don't install it" — there is no ecosystem to lean on.

No docs at the package level. The README is a logo and "see main repository." All real documentation — install steps, required bundle set, entity fields, extension points, upgrade notes — is upstream. The maturity rating ("readme") is literal. Do not infer API stability, test coverage, or versioning from this repo; it isn't there.

Publication state edge cases to design for in any reimplementation. Enhavo's draft/scheduled/published model implies behaviors that are easy to get subtly wrong:

  • Scheduled → published must be evaluated at read time, not by a job that might not have run. Query scopes are safer than status-flip jobs.
  • Timezone — published_at comparisons are wrong more often than they're right. Store UTC, compare in the app's display timezone.
  • Slug changes on a live page — write old slug to a history table, 301 redirect, and scope the lookup so history slugs don't shadow live ones.
  • Blocks with no data — a page with an unpublished/removed block should render the page without that block, not error.

Debugging. You cannot php artisan tinker into this bundle, and you cannot use Laravel Telescope or Ray on it. If you're in Path B (service), debug at the HTTP boundary: log the JSON body and status in Laravel's client, and watch Enhavo's own Symfony profiler on the Enhavo side. Do not expect Laravel's tooling to reach into the Symfony process.

Extension points, if you ever run Enhavo for real. You extend via documented bundle extension config and by registering page templates/block types in the admin resource config — not by editing this bundle. Contributions should flow upstream. Any local change is overwritten. This is the fork-vs-extend decision, and it's a resourcing commitment: extend upstream-first, or fork the monorepo and own maintenance forever.

Where to look first instead. If the goal is page/content management in a Laravel app, evaluate Laravel-native options before touching Enhavo: Statamic (content model + admin UI), a headless CMS behind a contract, a Filament-based admin with a custom content model, or just the bounded reimplementation (Pattern 1). For Symfony-native comparison, look at Sulu, Symfony CMF, or Ibexa. This bundle only makes sense if the broader decision is "run Enhavo as our CMS" — in which case you consume it transitively via the parent distribution and treat this repo as a build artifact, not a dependency.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin