Content Pipeline
How Markdown, drivers, caching, and theming turn slugs into rendered pages.
High-level flow #
- Routing resolves
/about→PageController(see HTTP docs) which callsPageService::load('about'). - Content driver (file/db/hybrid/auto) fetches the
Pagemodel’s metadata + Markdown. - MarkdownService parses Markdown → HTML and caches it (default 10 minutes).
PageServicefires lifecycle events (page.loading,page.loaded, etc.) and caches hydrated pages viaCacheDriver+CacheTagManager.ThemeServicehands thePageto your layout, injecting helper functions (asset(),url(),partial()), and builds the final HTML response.
PageService responsibilities #
VelvetCMS\Services\PageService is the orchestrator:
- Uses the configured
ContentDriverfor storage. The driver implementation is injected via the container, so swapping drivers is instant. - Wraps expensive operations in
CacheDriver::remember()with per-page keys (page:about) and invalidates the cache onsave/delete. - Exposes helpers like
list(),published(),drafts(), andexists()which all go through the driver. - Publishes lifecycle events that modules can hook:
page.loadingbefore hitting storage.page.loadedonce aPageobject exists.page.saving/page.saved,page.deleting/page.deletedfor write operations.
- Collaborates with
CacheTagManagerto tag aggregated queries (e.g.,pages:list) so any write flushes the relevant tag.
Page model #
VelvetCMS\Models\Page is a lightweight data class:
- Public promoted properties (
slug,title,content,status,layout,meta, timestamps). Page::fromArray()builds instances from driver payloads.setHtml()stores the rendered HTML alongside raw Markdown.- Convenience helpers:
html(),getExcerpt(),publish(),isPublished(), etc.
MarkdownService #
VelvetCMS\Services\MarkdownService wraps league/commonmark with YAML frontmatter parsing and caching:
- Reads parser config + extensions from
config/content.php(content.markdown). parse()accepts Markdown, an optional TTL, and toggles caching withcacheboolean.- Emits events
markdown.parsingandmarkdown.parsedso modules can rewrite content or inject shortcodes. parseFrontmatter()splits YAML frontmatter and Markdown body. Drivers reuse it to hydrate metadata.
TemplateEngine & ThemeService #
This stage just hands pages to the theming layer. For directory layout, helpers, and template syntax, see Theme Basics and Template Engine. The TL;DR: ThemeService loads themes/<name>, injects helpers like $asset()/$url(), and caches compiled .velvet.php templates under storage/cache/templates. Modules can call addPath() to contribute their own layouts or partials.
Content drivers #
All drivers conform to VelvetCMS\Contracts\ContentDriver which defines CRUD + list() + count().
| Driver | Best for | Storage | Highlights |
|---|---|---|---|
file |
Git-centric sites, docs, small marketing sites | Markdown files in content/pages |
YAML frontmatter parsed via MarkdownService. Maintains an index in storage/cache/file-driver-index.json for speedy listings and automatic Markdown → HTML caching. |
db |
Content-heavy CRUD, dashboards | pages table via VelvetCMS\Database\Connection |
Stores Markdown directly in the DB, JSON-encodes meta, supports filters/order/pagination server-side. |
hybrid |
Large catalogs with editorial workflows | Metadata in DB + Markdown files | Keeps content files for Git diffing but adds indexed metadata for fast queries. Automatically JSON-encodes meta, expects file + DB to stay in sync. |
auto |
Progressive scaling without manual switches | Delegates to file or hybrid | Reads current page count (DB or file fallback) and flips when content.drivers.auto.threshold is exceeded; re-evaluates after writes. |
Configure the driver via CONTENT_DRIVER env or config/content.php. All drivers share validation rules (slug/title required, status must be draft/published).
Caching strategy #
- Per-page cache:
CacheDriver::remember("page:$slug", 300, ...)stores hydratedPageobjects. - Collection cache:
CacheTagManagertags the aggregated list results (pages:list). When any page is saved/deleted, tags are flushed, ensuring fresh listings. - Markdown cache:
MarkdownServiceoptionally caches rendered HTML keyed by the Markdown hash. TTL defaults tocontent.markdown.cache_ttl.
Backends for CacheDriver can be file, Redis, etc. Pick them in config/cache.php; the interface requires get/set/has/delete/remember.
Events worth subscribing to #
page.loading/page.loaded– instrumentation, audit, on-the-fly metadata.page.saving/page.saved– validations, queueing background tasks.markdown.parsing/markdown.parsed– custom syntax, embed processing.page.rendering/page.rendered– theme-related injections, analytics beacons.
Register listeners inside a module boot() or via a service provider binding the dispatcher.
CLI + automation touch points #
velvet page:list,page:create,page:publish, etc., are thin wrappers aroundPageService; they ensure the same driver + validation as the HTTP layer.velvet cache:clear --tag=pages:listflushes the tagged list cache, great for CI/CD hooks.velvet diagnosechecks that the selected driver can read/write (file permissions, DB tables, etc.) and reports Markdown cache health.
Extending the pipeline #
- Custom drivers: implement
ContentDriver, bind it with$app->bind(ContentDriver::class, MyDriver::class)in a module’sregister(). - Shortcodes/embeds: listen to
markdown.parsing(raw Markdown) ormarkdown.parsed(HTML) to run DOM transforms. - Theme variables: call
$theme->set('featureFlags', [...])in middleware or controllers, and read via{{ $featureFlags['beta'] }}. - Headless use: skip
ThemeServiceand returnPage::toArray()orPage::html()from an API controller—nothing in the pipeline forces server-side rendering.
With these building blocks you can own the entire content lifecycle, from Markdown authoring to cached HTML responses and module-level overrides.