markup-carve/symfony-carve
Symfony bundle for Carve, a modern extensible markup language. Parse and render Carve documents in your apps with service container integration, Twig support, configurable options, and cached compilation for fast, flexible markup processing.
carve-php); this bundle is the Symfony/Twig integration layer. Adopting it replaces a bespoke parser, sanitizer, and include-resolver effort with a dependency — the classic "don't build your own transpiler" call, made cheaper by the small surface area (a renderer plus two Twig filters).CarveRenderer::renderFile() / renderFileWithReport() lets authored documents include other documents from a single absolute include_root. This unlocks the patterns product teams actually ask for: shared admonition/legal/disclaimer blocks, per-locale snippet libraries, docs sections assembled from fragments, and email or in-app help content with a common header/footer — without a CMS.renderFileWithReport() returns the resolved and attempted include targets, so we can key our own cache on the exact dependency set of a rendered document. That means correct invalidation when a partial changes, and no guessing at a directory-wide mtime. This is the difference between "we can cache rendered output safely" and "we re-render on every request."render() and the Twig filters still accept anonymous strings only and never touch disk. Content that arrives from users, a database, or an API stays on the string path; only filesystem-authored content goes through renderFile(). We can adopt includes for editorial content without widening the blast radius of any user-content rendering path.carve-php ^0.1.9 and adding psr/log at runtime (for the optional warning logger) is a clean, low-ceremony bump — no framework coupling, no service-provider config sprawl.carve-php plus a thin rendering service we write ourselves; the Twig filters and the Symfony wiring are not reusable. That reframes the decision as "adopt the parser (buy) and own a ~50-line adapter" rather than "adopt this bundle."Consider it when:
renderFile() with a pinned absolute include_root; user-supplied content stays on the string path. If you can state that split in one sentence, adoption is straightforward.Look elsewhere when:
carve-php directly instead.carve-php call is enough; the bundle is surplus.For executives (one paragraph). We can render our documentation and product copy with a small, well-scoped open-source parser instead of maintaining our own markup code. The latest release adds the piece we were missing for composed content: documents can pull in shared fragments from one approved folder, and the renderer tells us exactly which fragments each page used — so caching stays correct and stale pages stop shipping. It is deliberately opt-in: content that comes from users or the database is still rendered as plain text with no file access. The main caveat is maturity — the project is pre-1.0, so we should treat the public API as movable and confine it behind a thin internal service. And because it is a Symfony/Twig integration, in a Laravel codebase the realistic play is adopting the underlying parser and owning a small adapter. Recommendation: pilot on internal docs content, not on the customer-facing critical path.
For engineering (key points).
CarveRenderer::renderFile() and renderFileWithReport(). Requires an absolute include_root; there is no implicit working-directory fallback to reason about.render() and the Twig filters are unchanged and remain string-only — no filesystem access on those paths. Keep user/database-sourced content there and make that a test, not a convention.renderFileWithReport() exposes resolved and attempted include targets. Use that tuple as your cache key; it makes partial-level invalidation exact rather than directory-wide.carve-php ^0.1.9 (required for the include pass) and psr/log added at runtime for the optional warning logger. Pin both; the carve-php floor is not optional.include_root, whether symlinks or traversal can escape it, and whether include depth/cycles are bounded. Treat include_root as a trust boundary and mount it read-only where possible.renderFileWithReport or a change to the report shape is a one-file change. Add a golden-file test over a representative document set to catch rendering drift on upgrade.carve-php directly, bind a CarveRenderer-shaped service in the container, and reimplement the report/cache-key contract ourselves — it is small and keeps us off an unsupported integration path.How can I help you explore Laravel packages today?