Download the PHP package huncwotdev/twigflow without Composer
On this page you can find all versions of the php package huncwotdev/twigflow. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download huncwotdev/twigflow
More information about huncwotdev/twigflow
Files in huncwotdev/twigflow
Package twigflow
Short Description A PHP application with customizable modules and templates, supporting Payload and Sanity CMSes.
License MIT
Informations about the package twigflow
TwigFlow
TwigFlow is a PHP rendering layer that sits between a headless CMS (Sanity or Payload) and the browser. It fetches content from the CMS, runs it through optional PHP processors, and renders it using Twig templates.
Think of it as a "glue" application: the CMS holds all your content, your Twig templates define how pages look, and TwigFlow connects them — handling routing, caching, search, and multi-language support along the way.
How a Page Request Works
Connecting to a CMS
The CMS is selected via the CMS_CLIENT environment variable. Two clients are available out of the box.
Sanity
Set CMS_CLIENT=sanity and provide:
TwigFlow talks to Sanity using GROQ queries. Examples of what it fetches:
| Purpose | GROQ query |
|---|---|
| Single page | *[_type == "page" && slug.current == "about"][0] |
| Collection item | *[_type == "blog" && slug.current == "my-post"][0] |
| References | *[_id in ["id1", "id2"]]{fields…} |
| All documents (scraper) | *[]{_type, slug, _id, _updatedAt} |
| Search index | *[_type == "scraped_documents" && content.en match "query*"] |
What gets processed automatically:
localeString/localeText— multi-language fields; TwigFlow picks the value for the active languageblockContent/localeBlockContent— Sanity's Portable Text; converted to HTMLreference— resolved by fetching the referenced document and substituting it inlinesanity.imageAsset— image URL constructed from the Sanity CDN
Reference resolution is configured in application/reference_fields.json. It tells TwigFlow which fields to fetch when it encounters a reference:
Payload CMS
Set CMS_CLIENT=payload and provide:
TwigFlow fetches pages via the Payload REST API:
Payload uses Lexical for rich text. TwigFlow parses the Lexical JSON format into HTML, handling paragraphs, headings, bold/italic/underline, lists, blockquotes, and links.
Directory Structure
Configuration
All configuration lives in a .env file in the project root.
Required
| Variable | Description |
|---|---|
APP_ENV |
development or production |
CMS_CLIENT |
sanity or payload |
API_URL |
Base URL of your CMS API |
API_KEY |
API token for authentication |
Optional
| Variable | Default | Description |
|---|---|---|
HOMEPAGE_SLUG |
homepage |
CMS slug for the homepage |
SUPPORTED_LANGUAGES |
(none) | Comma-separated codes: en,pl,de |
DEFAULT_LANGUAGE |
first in list | Language used when none is detected |
CACHE_EXPIRE_TIME |
0 |
Cache TTL in seconds (0 = never expire) |
TWIG_CACHE |
false |
Enable compiled template caching |
CACHE_MAX_AGE |
(none) | HTTP Cache-Control: max-age in seconds |
LOG_TO_STDOUT |
false |
Log to stdout instead of rotating files (useful for Docker) |
MEASURE_PERFORMANCE |
false |
Append execution time as HTML comment |
Sanity-specific
| Variable | Description |
|---|---|
API_ID |
Sanity project ID |
API_ENV |
Sanity dataset (e.g., production) |
Database (optional, for scraper queue)
| Variable | Description |
|---|---|
DB_HOST |
MySQL host |
DB_NAME |
Database name |
DB_USERNAME |
MySQL user |
DB_PASSWORD |
MySQL password |
Routing
Routes are defined in src/Config/routes.php. Built-in routes:
| Method | Path | Handler |
|---|---|---|
| GET | / |
Homepage |
| GET | /{slug} |
Any page by slug |
| GET | /api/search |
Full-text search |
| GET | /api/live-search |
Search (autocomplete variant) |
| GET/POST | /api/{endpoint} |
Custom API endpoints |
| GET | /api/clear-cache |
Purge all caches |
| GET | /api/scraper/init |
Build scraper job queue |
| GET | /api/scraper/process |
Process scraper jobs |
| GET | /api/scraper/prune |
Remove stale search entries |
Collection routes are defined in application/routes.json:
This maps /blog/my-post to the blog collection in the CMS, rendered with application/views/pages/blog.twig.
Language routing is automatic. If SUPPORTED_LANGUAGES=en,pl, then /en/about and /pl/about both resolve to the about page with the appropriate language active.
The Module System
Pages in the CMS are built from modules — named blocks of content. TwigFlow processes each module and makes the data available to Twig.
For simple modules (static content), no PHP code is needed. For modules that require extra data (e.g., a "Latest Articles" module that needs to fetch recent posts), you create a processor in application/modules/m_{type}.php:
The fetchData() return values are fired as parallel HTTP requests. Once all requests settle, process() receives the results. This keeps page load times low even when a page has many data-hungry modules.
Global Data
Data you need on every page (navigation, footer, site settings) is defined in application/globals.json:
These queries run alongside module queries and are available in every Twig template as {{ globals.navigation }}.
Templates and the HtmlUpdater
Templates live in application/views/. The default template for all pages is page.twig. Collection-specific templates go in pages/ (e.g., pages/blog.twig).
Every template receives this data:
After Twig renders, HtmlUpdater scans the HTML for special placeholders:
| Placeholder | Result |
|---|---|
[[templates]] |
Injects all files from views/templates/ as <script type="text/template"> tags |
[[svg::name]] |
Inline SVG from views/svg/name.svg |
[[sprite::name]] |
SVG sprite <use> reference |
[[json::name]] |
Contents of src/json/name.json |
[[get::param]] |
Value of GET parameter param |
Search
TwigFlow includes a search system built on top of Sanity's scraped_documents collection.
How content gets indexed
GET /api/scraper/init— Compares all CMS documents against the existing search index and creates a job for each new or updated document.GET /api/scraper/process— Processes pending jobs: renders each page, extracts text from the<article>element (skipping elements with classno-search), and saves the cleaned text to Sanity as ascraped_documentsrecord.GET /api/scraper/prune— Deletes search index entries for documents that no longer exist in the CMS.
Search endpoints
Full search — GET /api/search?q=query
Returns results with highlighted snippets, sorted by relevance (title matches first).
Live search — GET /api/live-search?q=query
Returns fewer results with smaller snippets; designed for autocomplete.
Response format:
To exclude content from search indexing, add class="no-search" to any HTML element in your templates.
Caching
TwigFlow has three caching layers:
| Layer | What it caches | Controlled by |
|---|---|---|
| API response cache | Raw JSON from CMS queries | CACHE_EXPIRE_TIME env var |
| Page cache | Fully rendered HTML per URL + language | CACHE_EXPIRE_TIME env var |
| Twig template cache | Compiled PHP from .twig files |
TWIG_CACHE env var |
Caching is automatically disabled when APP_ENV=development.
To bypass the cache on a single request, add ?disable_cache=true to the URL.
To clear all caches (and optionally pre-warm specific paths), call GET /api/clear-cache. Paths to pre-warm are listed in application/cache_regeneration.json.
Multi-Language Support
Set SUPPORTED_LANGUAGES=en,pl,de to enable multilanguage routing.
- Language is detected from the URL prefix (
/en/,/pl/), then from the session, then from theAccept-Languageheader, then fromDEFAULT_LANGUAGE. - Sanity
localeStringfields are automatically resolved to the active language. - Bots (Googlebot, Bingbot, etc.) receive 301 redirects; regular users get 302 redirects to avoid locking the browser's back button.
- Every page render includes a
pathsvariable with the current page's URL in each supported language, for use in a language switcher.
Custom API Endpoints
Drop a PHP file into application/api/{method}/{endpoint}.php and TwigFlow will route GET/POST /api/{endpoint} to it automatically:
Installation
TwigFlow is a Composer package. The recommended way to start a new project is with the boilerplate, which gives you the application/ directory scaffold and a working index.php entry point:
-
Clone the boilerplate:
-
Install TwigFlow and its dependencies via Composer:
-
Create a
.envfile in the project root and fill in your CMS credentials (see Configuration above). - Point your web server document root at the project root. The included
.htaccessroutes all requests throughindex.php.
Requirements: PHP 8.2+, Composer, a web server with mod_rewrite (Apache) or equivalent.
If you are adding TwigFlow to an existing project rather than using the boilerplate, require the package the same way (
composer require huncwotdigital/twigflow) and then create theapplication/directory structure manually as described in Directory Structure above.
All versions of twigflow with dependencies
slim/slim Version ^4.0
slim/psr7 Version ^1.0
slim/twig-view Version ^3.0
vlucas/phpdotenv Version ^5.6
php-di/php-di Version ^7.0
guzzlehttp/guzzle Version ^7.9
symfony/cache Version ^7.2
symfony/console Version ^5.0
symfony/filesystem Version ^5.0
monolog/monolog Version ^3.8
guzzlehttp/promises Version ^2.0
sanity/sanity-php Version ^1.5