zicht/page-bundle
Zicht PageBundle is a flexible Symfony page management bundle with page entities, routing, template resolution, and admin-friendly tools for building and maintaining hierarchical CMS content without custom glue code.
Install and register
composer require zicht/page-bundle
Register the bundle in config/bundles.php (Symfony 4+) or app/AppKernel.php (Symfony 3). If the package has not been tagged for your Symfony version, check composer.json's require block before installing — this is a zero-star, low-activity bundle and is most likely built against an older Symfony (3.x/4.x era). Pin the version explicitly rather than relying on ^.
Where to look first
Resources/config/routing.xml (or routing.yml) — the bundle ships a catch-all slug route; you almost always need to import it.Entity/Page.php — the core model. Read its fields (slug, title, template, parent, locale/language if present) before touching anything else.Controller/PageController.php — the render action that resolves a slug to a Page and picks a Twig template.Resources/views/ — the default templates the controller falls back to.First use case
Import the bundle routing last in your app routing, create a Page row (via direct DB insert or your admin bundle), set its template to a Twig path you control, and hit /{slug}. If you get a 404, verify the route is loaded (bin/console debug:router) and that the entity table exists (bin/console doctrine:schema:update --dump-sql — the bundle typically ships no migrations).
Catch-all route wiring
The idiomatic pattern is to import the bundle's routing at the bottom of config/routes.yaml:
zicht_page:
resource: "@ZichtPageBundle/Resources/config/routing.xml"
# or: "@ZichtPageBundle/Resources/config/routing.yml"
Then define your specific app routes above it. The bundle route is a {slug} matcher (requirements: { slug: .+ } in most Zicht bundles); anything not matched earlier becomes a page lookup.
Template-per-page
Pages carry a template property naming a Twig file (e.g. pages/about.html.twig). The controller renders that template with the page variable in context. This lets editors, not developers, choose layouts:
{# pages/landing.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>{{ page.title }}</h1>
{{ page.content|raw }}
{% endblock %}
Extending the Page entity
Rather than editing vendor code, subclass Page and point Doctrine at your class via bundle config (zicht_page.page_class or an entity override in config/packages/doctrine.yaml). Run a migration afterward — the bundle provides no schema migrations of its own, so doctrine:migrations:diff is your friend.
Custom controller logic
The cleanest extension point is a thin controller subclass that calls the parent action after injecting data (breadcrumbs, related items, locale-switched content):
class PageController extends \Zicht\Bundle\PageBundle\Controller\PageController
{
public function showAction(Request $request, string $slug)
{
$response = parent::showAction($request, $slug);
// decorate the response or inject via a request attribute
return $response;
}
}
Then override the route's _controller default to point at yours.
Admin integration
Zicht's ecosystem leans on zicht/admin-bundle or Sonata for CRUD. If you use one, register Page as an admin entity and expose template as a choice field listing available Twig templates — this is what makes the bundle pleasant day-to-day.
{slug} route that loads before your API or login routes swallows them. Always import it last, or narrow its requirements regex.content as TEXT/JSON depending on the release).template points at a missing file throws a Twig LoaderError at render time, not at load time. Add a validation step in admin, or a fallback in the controller.bin/console cache:clear; Doctrine metadata caches also need clearing after entity overrides. In dev this bites less, in prod it is silent and confusing.slug (+ locale), duplicates produce non-deterministic renders depending on query result ordering. Add the unique index yourself.debug:router and confirm the bundle route loaded. 500s with a template error: check page.template in the DB. Empty page: check the content field encoding and whether |raw is applied. Entity errors: doctrine:mapping:info and confirm the override class is the one being used.page_class config for entity substitution, the route's _controller default for controller replacement, and Resources/views/ override via templates/bundles/ZichtPageBundle/ (standard Symfony bundle override path). Prefer the template override over forking — it is the only officially-supported customization that survives upgrades.How can I help you explore Laravel packages today?