{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Cecil - Developers",
  "home_page_url": "https://cecil.app/documentation/developers/",
  "feed_url": "https://cecil.app/documentation/developers/feed.json",
  "description": "Extend Cecil with PHP, use it as a library and understand its architecture.",
  "icon": "https://cecil.app/favicon.7ff4b89eec6dad139a7d2d6561543285.png",
  "favicon": "https://cecil.app/thumbnails/64x/favicon.7ff4b89eec6dad139a7d2d6561543285.png",
  "language": "en",
  "items": [
    {
      "id": "https://cecil.app/documentation/developers/architecture/",
      "url": "https://cecil.app/documentation/developers/architecture/",
      "title": "Architecture",
      "summary": "Diagram of the build pipeline and key components.",
      "date_published": "2026-05-27T00:00:00+00:00",
      "date_modified": "2026-10-02T00:00:00+00:00","content_text": "Architecture\nDiagram\ngraph TD\n    %% Entry\n    CLI[\"bin\/cecil\\n(CLI entry point)\"]\n    APP[\"Application\\n(Symfony Console)\"]\n    CMD[\"Commands\\nbuild \/ serve \/ new:site\\nnew:page \/ clear \/ show:content\"]\n\n    CLI --&gt; APP --&gt; CMD\n\n    %% Orchestrator\n    BUILDER[\"Builder\\n(Orchestrator)\"]\n    CONFIG[\"Configuration\\n(config.yml + default.php\\n+ themes)\"]\n    CMD --&gt; BUILDER\n    CONFIG --&gt; BUILDER\n\n    %% Pipeline\n    subgraph PIPELINE[\"Build pipeline (steps)\"]\n        S1[\"1. Pages\/Load\\n(Finder -&gt; Markdown files)\"]\n        S2[\"2. Data\/Load\\n(YAML files)\"]\n        S3[\"3. StaticFiles\/Load\\n(load static files)\"]\n        S4[\"4. Pages\/Create\\n(create Page objects)\"]\n        S5[\"5. Pages\/Convert\\n(Markdown -&gt; HTML\\nfront matter)\"]\n        S6[\"6. Taxonomies\/Create\\n(create taxonomies)\"]\n        S7[\"7. Pages\/Generate\\n(generators)\"]\n        S8[\"8. Menus\/Create\\n(create menus)\"]\n        S9[\"9. StaticFiles\/Copy\\n(copy static files)\"]\n        S10[\"10. Pages\/Render\\n(Twig rendering)\"]\n        S11[\"11. Pages\/Save\\n(save pages)\"]\n        S12[\"12. Assets\/Save\\n(save assets)\"]\n        S13[\"13. Optimize\/*\\n(optimize HTML\/CSS\/JS\/Images)\"]\n\n        S1 --&gt; S2 --&gt; S3 --&gt; S4 --&gt; S5 --&gt; S6 --&gt; S7 --&gt; S8 --&gt; S9 --&gt; S10 --&gt; S11 --&gt; S12 --&gt; S13\n    end\n\n    BUILDER --&gt; PIPELINE\n\n    %% Subsystems\n    subgraph COLLECTIONS[\"Collections\"]\n        PC[\"PagesCollection\\n(Page objects)\"]\n        TC[\"TaxonomiesCollection\\n(vocabularies\/terms)\"]\n        MC[\"MenusCollection\"]\n    end\n\n    subgraph GENERATORS[\"Generators (virtual pages)\"]\n        GP[\"Pagination\"]\n        GT[\"Taxonomy\"]\n        GS[\"Section\"]\n        GR[\"Redirect\"]\n        GD[\"DefaultPages (home, 404)\"]\n    end\n\n    subgraph RENDERER[\"Twig rendering\"]\n        TW[\"Twig engine\"]\n        EXT[\"Extensions\\n(Core, Content, Collection)\"]\n        PP[\"PostProcessors\\n(metadata, excerpts, links)\"]\n        TH[\"Themes \/ Layouts\"]\n        TW --&gt; EXT\n        TW --&gt; PP\n        TH --&gt; TW\n    end\n\n    subgraph ASSETS[\"Assets\"]\n        AL[\"Asset locator\"]\n        AC[\"Compiler\\n(SCSS -&gt; CSS)\"]\n        AI[\"Image processor\\n(responsive, WebP, AVIF)\"]\n        AO[\"Optimizer\\n(CSS\/JS minification)\"]\n    end\n\n    subgraph OUTPUT[\"Output (_site\/)\"]\n        HTML[\".html pages\"]\n        CSS2[\"CSS\/JS assets\"]\n        IMG[\"images\"]\n        SF[\"static files\"]\n    end\n\n    S4 --&gt; PC\n    S6 --&gt; TC\n    S8 --&gt; MC\n    S7 --&gt; GENERATORS\n    GENERATORS --&gt; PC\n    S10 --&gt; RENDERER\n    S12 --&gt; ASSETS\n\n    PC --&gt; RENDERER\n    TC --&gt; RENDERER\n    MC --&gt; RENDERER\n\n    RENDERER --&gt; S11\n    S11 --&gt; HTML\n    S12 --&gt; CSS2\n    S12 --&gt; IMG\n    S9 --&gt; SF\n\n    %% Inputs\n    subgraph INPUT[\"Sources\"]\n        MD[\"content\/\\n(Markdown + front matter)\"]\n        DATA[\"data\/\\n(YAML)\"]\n        STATIC[\"static\/\"]\n        LAYOUTS[\"layouts\/\\n(Twig templates)\"]\n    end\n\n    MD --&gt; S1\n    DATA --&gt; S2\n    STATIC --&gt; S3\n    LAYOUTS --&gt; TH\nKey Components Legend\n\n\n\nComponent\nRole\n\n\n\n\nBuilder\nCentral orchestrator that executes steps in sequence\n\n\nConfig\nMerges default + theme + project + CLI configuration\n\n\nSteps\nModular pipeline (13 steps), each with init() \/ canProcess() \/ process()\n\n\nCollections\nPages, Taxonomies, Menus: core data structures\n\n\nGenerators\nCreate virtual pages (pagination, tags, redirects, etc.)\n\n\nRenderer (Twig)\nApplies templates + extensions + post-processors\n\n\nAssets\nCompiles SCSS, optimizes images, fingerprints files\n\n\n",
      "content_html": "<h1>Architecture</h1>\n<h2 id=\"diagram\">Diagram</h2>\n<pre><code class=\"language-mermaid\" translate=\"no\">graph TD\n    %% Entry\n    CLI[\"bin/cecil\\n(CLI entry point)\"]\n    APP[\"Application\\n(Symfony Console)\"]\n    CMD[\"Commands\\nbuild / serve / new:site\\nnew:page / clear / show:content\"]\n\n    CLI --&gt; APP --&gt; CMD\n\n    %% Orchestrator\n    BUILDER[\"Builder\\n(Orchestrator)\"]\n    CONFIG[\"Configuration\\n(config.yml + default.php\\n+ themes)\"]\n    CMD --&gt; BUILDER\n    CONFIG --&gt; BUILDER\n\n    %% Pipeline\n    subgraph PIPELINE[\"Build pipeline (steps)\"]\n        S1[\"1. Pages/Load\\n(Finder -&gt; Markdown files)\"]\n        S2[\"2. Data/Load\\n(YAML files)\"]\n        S3[\"3. StaticFiles/Load\\n(load static files)\"]\n        S4[\"4. Pages/Create\\n(create Page objects)\"]\n        S5[\"5. Pages/Convert\\n(Markdown -&gt; HTML\\nfront matter)\"]\n        S6[\"6. Taxonomies/Create\\n(create taxonomies)\"]\n        S7[\"7. Pages/Generate\\n(generators)\"]\n        S8[\"8. Menus/Create\\n(create menus)\"]\n        S9[\"9. StaticFiles/Copy\\n(copy static files)\"]\n        S10[\"10. Pages/Render\\n(Twig rendering)\"]\n        S11[\"11. Pages/Save\\n(save pages)\"]\n        S12[\"12. Assets/Save\\n(save assets)\"]\n        S13[\"13. Optimize/*\\n(optimize HTML/CSS/JS/Images)\"]\n\n        S1 --&gt; S2 --&gt; S3 --&gt; S4 --&gt; S5 --&gt; S6 --&gt; S7 --&gt; S8 --&gt; S9 --&gt; S10 --&gt; S11 --&gt; S12 --&gt; S13\n    end\n\n    BUILDER --&gt; PIPELINE\n\n    %% Subsystems\n    subgraph COLLECTIONS[\"Collections\"]\n        PC[\"PagesCollection\\n(Page objects)\"]\n        TC[\"TaxonomiesCollection\\n(vocabularies/terms)\"]\n        MC[\"MenusCollection\"]\n    end\n\n    subgraph GENERATORS[\"Generators (virtual pages)\"]\n        GP[\"Pagination\"]\n        GT[\"Taxonomy\"]\n        GS[\"Section\"]\n        GR[\"Redirect\"]\n        GD[\"DefaultPages (home, 404)\"]\n    end\n\n    subgraph RENDERER[\"Twig rendering\"]\n        TW[\"Twig engine\"]\n        EXT[\"Extensions\\n(Core, Content, Collection)\"]\n        PP[\"PostProcessors\\n(metadata, excerpts, links)\"]\n        TH[\"Themes / Layouts\"]\n        TW --&gt; EXT\n        TW --&gt; PP\n        TH --&gt; TW\n    end\n\n    subgraph ASSETS[\"Assets\"]\n        AL[\"Asset locator\"]\n        AC[\"Compiler\\n(SCSS -&gt; CSS)\"]\n        AI[\"Image processor\\n(responsive, WebP, AVIF)\"]\n        AO[\"Optimizer\\n(CSS/JS minification)\"]\n    end\n\n    subgraph OUTPUT[\"Output (_site/)\"]\n        HTML[\".html pages\"]\n        CSS2[\"CSS/JS assets\"]\n        IMG[\"images\"]\n        SF[\"static files\"]\n    end\n\n    S4 --&gt; PC\n    S6 --&gt; TC\n    S8 --&gt; MC\n    S7 --&gt; GENERATORS\n    GENERATORS --&gt; PC\n    S10 --&gt; RENDERER\n    S12 --&gt; ASSETS\n\n    PC --&gt; RENDERER\n    TC --&gt; RENDERER\n    MC --&gt; RENDERER\n\n    RENDERER --&gt; S11\n    S11 --&gt; HTML\n    S12 --&gt; CSS2\n    S12 --&gt; IMG\n    S9 --&gt; SF\n\n    %% Inputs\n    subgraph INPUT[\"Sources\"]\n        MD[\"content/\\n(Markdown + front matter)\"]\n        DATA[\"data/\\n(YAML)\"]\n        STATIC[\"static/\"]\n        LAYOUTS[\"layouts/\\n(Twig templates)\"]\n    end\n\n    MD --&gt; S1\n    DATA --&gt; S2\n    STATIC --&gt; S3\n    LAYOUTS --&gt; TH</code></pre>\n<h2 id=\"key-components-legend\">Key Components Legend</h2>\n<table>\n<thead>\n<tr>\n<th>Component</th>\n<th>Role</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Builder</strong></td>\n<td>Central orchestrator that executes steps in sequence</td>\n</tr>\n<tr>\n<td><strong>Config</strong></td>\n<td>Merges default + theme + project + CLI configuration</td>\n</tr>\n<tr>\n<td><strong>Steps</strong></td>\n<td>Modular pipeline (13 steps), each with <code translate=\"no\">init()</code> / <code translate=\"no\">canProcess()</code> / <code translate=\"no\">process()</code></td>\n</tr>\n<tr>\n<td><strong>Collections</strong></td>\n<td>Pages, Taxonomies, Menus: core data structures</td>\n</tr>\n<tr>\n<td><strong>Generators</strong></td>\n<td>Create virtual pages (pagination, tags, redirects, etc.)</td>\n</tr>\n<tr>\n<td><strong>Renderer (Twig)</strong></td>\n<td>Applies templates + extensions + post-processors</td>\n</tr>\n<tr>\n<td><strong>Assets</strong></td>\n<td>Compiles SCSS, optimizes images, fingerprints files</td>\n</tr>\n</tbody>\n</table>",
      "language": "en"
    },
    {
      "id": "https://cecil.app/documentation/developers/library/",
      "url": "https://cecil.app/documentation/developers/library/",
      "title": "Library",
      "summary": "Use Cecil as a PHP library: install it with Composer, then build and diagnose programmatically.",
      "date_published": "2023-12-13T00:00:00+00:00",
      "date_modified": "2026-10-03T00:00:00+00:00","content_text": "Library\nCecil provides a simple PHP API to build your website.\nYou can read the API documentation for more details.\nInstallation\ncomposer require cecil\/cecil\nlibvips support\nTo process images with libvips (optional), install the libvips driver in your project:\ncomposer require intervention\/image-driver-vips\nThis driver requires libvips installed on your system and the PHP FFI extension enabled.\nWithout it, Cecil uses Imagick or GD instead.\nUsage\nBuild\nBuild a new website with a custom configuration:\nrequire_once 'vendor\/autoload.php';\n\nuse Cecil\\Builder;\n\n$config = [\n    'title'   =&gt; \"My website\",\n    'baseurl' =&gt; 'https:\/\/domain.tld\/',\n];\n\nBuilder::create($config)-&gt;build();\n\nexec('php -S localhost:8000 -t _site'); \/\/ preview locally\nThe main parameter of the create method should be a PHP array or a Cecil\\Config instance.\nDiagnostic\nYou can also run doctor checks through dedicated domain services, without using CLI commands.\n&lt;?php\n\nrequire_once 'vendor\/autoload.php';\n\nuse Cecil\\Builder;\nuse Cecil\\Doctor\\SeoDoctor;\nuse Cecil\\Doctor\\SiteDoctor;\n\n$builder = Builder::create(require 'config.php')\n    -&gt;setSourceDir(__DIR__)\n    -&gt;setDestinationDir(__DIR__);\n\n$siteDoctor = new SiteDoctor();\n$diagnosis = $siteDoctor-&gt;diagnose($builder, __DIR__, ['cecil.yml']);\n\n$seoDoctor = new SeoDoctor();\n$seoAudit = $seoDoctor-&gt;audit($builder, [\n    'page' =&gt; '',\n    'include_virtual' =&gt; false,\n]);\n\nvar_dump($diagnosis['errors'], $seoAudit['summary']);",
      "content_html": "<h1>Library</h1>\n<p>Cecil provides a simple PHP API to build your website.</p>\n<p>You can read the <a href=\"https://cecil.app/documentation/library/api/namespaces/cecil.html\">API documentation</a> for more details.</p>\n<h2 id=\"installation\">Installation</h2>\n<pre><code class=\"language-bash hljs bash\" translate=\"no\">composer require cecil/cecil</code></pre>\n<h3 id=\"libvips-support\">libvips support</h3>\n<p>To process images with <a href=\"https://www.libvips.org/\" target=\"_blank\" rel=\"noopener noreferrer\">libvips</a> (optional), install the libvips driver in your project:</p>\n<pre><code class=\"language-bash hljs bash\" translate=\"no\">composer require intervention/image-driver-vips</code></pre>\n<aside class=\"note note-important\"><p>This driver requires <a href=\"https://www.libvips.org/install.html\" target=\"_blank\" rel=\"noopener noreferrer\">libvips</a> installed on your system and the PHP <a href=\"https://www.php.net/manual/book.ffi.php\" target=\"_blank\" rel=\"noopener noreferrer\">FFI</a> extension enabled.<br>\nWithout it, Cecil uses <a href=\"https://www.php.net/manual/book.imagick.php\" target=\"_blank\" rel=\"noopener noreferrer\">Imagick</a> or <a href=\"https://www.php.net/manual/book.image.php\" target=\"_blank\" rel=\"noopener noreferrer\">GD</a> instead.</p></aside>\n<h2 id=\"usage\">Usage</h2>\n<h3 id=\"build\">Build</h3>\n<p>Build a new website with a custom configuration:</p>\n<pre><code class=\"language-php hljs php\" translate=\"no\"><span class=\"hljs-keyword\">require_once</span> <span class=\"hljs-string\">'vendor/autoload.php'</span>;\n\n<span class=\"hljs-keyword\">use</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Builder</span>;\n\n$config = [\n    <span class=\"hljs-string\">'title'</span>   =&gt; <span class=\"hljs-string\">\"My website\"</span>,\n    <span class=\"hljs-string\">'baseurl'</span> =&gt; <span class=\"hljs-string\">'https://domain.tld/'</span>,\n];\n\nBuilder::create($config)-&gt;build();\n\nexec(<span class=\"hljs-string\">'php -S localhost:8000 -t _site'</span>); <span class=\"hljs-comment\">// preview locally</span></code></pre>\n<aside class=\"note note-info\"><p>The main parameter of the <code translate=\"no\">create</code> method should be a PHP <code translate=\"no\">array</code> or a <a href=\"https://github.com/Cecilapp/Cecil/blob/main/src/Config.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code translate=\"no\">Cecil\\Config</code></a> instance.</p></aside>\n<h3 id=\"diagnostic\">Diagnostic</h3>\n<p>You can also run doctor checks through dedicated domain services, without using CLI commands.</p>\n<pre><code class=\"language-php hljs php\" translate=\"no\"><span class=\"hljs-meta\">&lt;?php</span>\n\n<span class=\"hljs-keyword\">require_once</span> <span class=\"hljs-string\">'vendor/autoload.php'</span>;\n\n<span class=\"hljs-keyword\">use</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Builder</span>;\n<span class=\"hljs-keyword\">use</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Doctor</span>\\<span class=\"hljs-title\">SeoDoctor</span>;\n<span class=\"hljs-keyword\">use</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Doctor</span>\\<span class=\"hljs-title\">SiteDoctor</span>;\n\n$builder = Builder::create(<span class=\"hljs-keyword\">require</span> <span class=\"hljs-string\">'config.php'</span>)\n    -&gt;setSourceDir(<span class=\"hljs-keyword\">__DIR__</span>)\n    -&gt;setDestinationDir(<span class=\"hljs-keyword\">__DIR__</span>);\n\n$siteDoctor = <span class=\"hljs-keyword\">new</span> SiteDoctor();\n$diagnosis = $siteDoctor-&gt;diagnose($builder, <span class=\"hljs-keyword\">__DIR__</span>, [<span class=\"hljs-string\">'cecil.yml'</span>]);\n\n$seoDoctor = <span class=\"hljs-keyword\">new</span> SeoDoctor();\n$seoAudit = $seoDoctor-&gt;audit($builder, [\n    <span class=\"hljs-string\">'page'</span> =&gt; <span class=\"hljs-string\">''</span>,\n    <span class=\"hljs-string\">'include_virtual'</span> =&gt; <span class=\"hljs-keyword\">false</span>,\n]);\n\nvar_dump($diagnosis[<span class=\"hljs-string\">'errors'</span>], $seoAudit[<span class=\"hljs-string\">'summary'</span>]);</code></pre>",
      "language": "en"
    },
    {
      "id": "https://cecil.app/documentation/developers/extend/",
      "url": "https://cecil.app/documentation/developers/extend/",
      "title": "Extend",
      "summary": "Create custom pages generators, add Twig extensions and post-process output files.",
      "date_published": "2023-04-17T00:00:00+00:00",
      "date_modified": "2026-10-02T00:00:00+00:00","content_text": "Extend\nBecause Cecil is powered by PHP it's easy to extend its capabilities.\nPages Generator\nA generator helps you create pages without Markdown files (for example, with data from an API or a database) or alter existing pages.\nJust create a new PHP class in the Cecil\\Generator namespace and add the class name to the pages.generators list.\nExample:\n\/extensions\/Cecil\/Generator\/DummyPage.php\n&lt;?php\nnamespace Cecil\\Generator;\n\nuse Cecil\\Collection\\Page\\Page;\nuse Cecil\\Collection\\Page\\Type;\n\nclass DummyPage extends AbstractGenerator implements GeneratorInterface\n{\n    public function generate(): void\n    {\n        \/\/ create a new page $page, then add it to the site collection\n        $page = (new Page('my-page'))\n            -&gt;setType(Type::PAGE-&gt;value)\n            -&gt;setPath('mypage')\n            -&gt;setBodyHtml('&lt;p&gt;My page body&lt;\/p&gt;')\n            -&gt;setVariable('language', 'en')\n            -&gt;setVariable('title', 'My page')\n            -&gt;setVariable('date', now())\n            -&gt;setVariable('menu', ['main' =&gt; ['weight' =&gt; 99]]);\n        $this-&gt;generatedPages-&gt;add($page);\n    }\n}\n\/extensions\/Cecil\/Generator\/Database.php\n&lt;?php\nnamespace Cecil\\Generator;\n\nuse Cecil\\Collection\\Page\\Page;\nuse Cecil\\Collection\\Page\\Type;\n\nclass Database extends AbstractGenerator implements GeneratorInterface\n{\n    public function generate(): void\n    {\n        \/\/ create pages from a SQLite database\n        $db = new SQLite3('database.sqlite');\n        $statement = $db-&gt;prepare('SELECT * FROM blog');\n        $result = $statement-&gt;execute();\n        while ($row = $result-&gt;fetchArray(SQLITE3_ASSOC)) {\n            $page = (new Page($row['page-id']))\n                -&gt;setType(Type::PAGE-&gt;value)\n                -&gt;setPath($row['path'])\n                -&gt;setBodyHtml($row['html'])\n                -&gt;setVariable('title', $row['title'])\n                -&gt;setVariable('date', $row['date']);\n            $this-&gt;generatedPages-&gt;add($page);\n        }\n        $result-&gt;finalize();\n        $db-&gt;close();\n    }\n}\nconfiguration\npages:\n  generators:\n    # priority: class name\n    99: Cecil\\Generator\\DummyPage\n    35: Cecil\\Generator\\Database\nTwig extension\nYou can add custom functions and filters:\n\ncreate a Twig extension in the Cecil\\Renderer\\Extension namespace\nadd the PHP file in the extensions directory\nadd the class name to the configuration\n\nExample:\n\/extensions\/Cecil\/Renderer\/Extension\/MyTwigExtension.php\n&lt;?php\nnamespace Cecil\\Renderer\\Extension;\n\nclass MyTwigExtension extends \\Twig\\Extension\\AbstractExtension\n{\n    public function getFilters()\n    {\n        \/\/ add a new filter named 'md5'\n        return [\n            new \\Twig\\TwigFilter('md5', 'md5'),\n        ];\n    }\n}\nconfiguration\nlayouts:\n  extensions:\n    MyExtension: Cecil\\Renderer\\Extension\\MyTwigExtension\nOutput Post Processor\nYou can post process page output.\nJust create a new PHP class in the Cecil\\Renderer\\PostProcessor namespace and add the class name to the `output.postprocessors list.\nExample:\n\/extensions\/Cecil\/Renderer\/PostProcessor\/MyProcessor.php\n&lt;?php\nnamespace Cecil\\Renderer\\PostProcessor;\n\nuse Cecil\\Collection\\Page\\Page;\n\nclass MyProcessor extends AbstractPostProcessor\n{\n    public function process(Page $page, string $output, string $format): string\n    {\n        \/\/ add a meta tag to the head of the HTML output\n        if ($format == 'html') {\n            if (!preg_match('\/&lt;meta name=\"test\".*\/i', $output)) {\n                $meta = \\sprintf('&lt;meta name=\"test\" content=\"Test\"&gt;');\n                $output = preg_replace_callback('\/([[:blank:]]*)(&lt;\\\/head&gt;)\/i', function ($matches) use ($meta) {\n                    return str_repeat($matches[1] ?: ' ', 2) . $meta . \"\\n\" . $matches[1] . $matches[2];\n                }, $output);\n            }\n        }\n\n        return $output;\n    }\n}\nconfiguration\noutput:\n  postprocessors:\n    MyProcessor: Cecil\\Renderer\\PostProcessor\\MyProcessor",
      "content_html": "<h1>Extend</h1>\n<p>Because Cecil is powered by PHP it's easy to extend its capabilities.</p>\n<h2 id=\"pages-generator\">Pages Generator</h2>\n<p>A generator helps you create pages without Markdown files (for example, with data from an API or a database) or alter existing pages.</p>\n<p>Just create a new PHP class in the <code translate=\"no\">Cecil\\Generator</code> namespace and add the class name to the <a href=\"../configuration/25-pages.md#pages-generators\"><code translate=\"no\">pages.generators</code></a> list.</p>\n<p><strong>Example:</strong></p>\n<p><em>/extensions/Cecil/Generator/DummyPage.php</em></p>\n<pre><code class=\"language-php hljs php\" translate=\"no\"><span class=\"hljs-meta\">&lt;?php</span>\n<span class=\"hljs-keyword\">namespace</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Generator</span>;\n\n<span class=\"hljs-keyword\">use</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Collection</span>\\<span class=\"hljs-title\">Page</span>\\<span class=\"hljs-title\">Page</span>;\n<span class=\"hljs-keyword\">use</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Collection</span>\\<span class=\"hljs-title\">Page</span>\\<span class=\"hljs-title\">Type</span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">DummyPage</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">AbstractGenerator</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">GeneratorInterface</span>\n</span>{\n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">generate</span><span class=\"hljs-params\">()</span>: <span class=\"hljs-title\">void</span>\n    </span>{\n        <span class=\"hljs-comment\">// create a new page $page, then add it to the site collection</span>\n        $page = (<span class=\"hljs-keyword\">new</span> Page(<span class=\"hljs-string\">'my-page'</span>))\n            -&gt;setType(Type::PAGE-&gt;value)\n            -&gt;setPath(<span class=\"hljs-string\">'mypage'</span>)\n            -&gt;setBodyHtml(<span class=\"hljs-string\">'&lt;p&gt;My page body&lt;/p&gt;'</span>)\n            -&gt;setVariable(<span class=\"hljs-string\">'language'</span>, <span class=\"hljs-string\">'en'</span>)\n            -&gt;setVariable(<span class=\"hljs-string\">'title'</span>, <span class=\"hljs-string\">'My page'</span>)\n            -&gt;setVariable(<span class=\"hljs-string\">'date'</span>, now())\n            -&gt;setVariable(<span class=\"hljs-string\">'menu'</span>, [<span class=\"hljs-string\">'main'</span> =&gt; [<span class=\"hljs-string\">'weight'</span> =&gt; <span class=\"hljs-number\">99</span>]]);\n        <span class=\"hljs-keyword\">$this</span>-&gt;generatedPages-&gt;add($page);\n    }\n}</code></pre>\n<p><em>/extensions/Cecil/Generator/Database.php</em></p>\n<pre><code class=\"language-php hljs php\" translate=\"no\"><span class=\"hljs-meta\">&lt;?php</span>\n<span class=\"hljs-keyword\">namespace</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Generator</span>;\n\n<span class=\"hljs-keyword\">use</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Collection</span>\\<span class=\"hljs-title\">Page</span>\\<span class=\"hljs-title\">Page</span>;\n<span class=\"hljs-keyword\">use</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Collection</span>\\<span class=\"hljs-title\">Page</span>\\<span class=\"hljs-title\">Type</span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Database</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">AbstractGenerator</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">GeneratorInterface</span>\n</span>{\n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">generate</span><span class=\"hljs-params\">()</span>: <span class=\"hljs-title\">void</span>\n    </span>{\n        <span class=\"hljs-comment\">// create pages from a SQLite database</span>\n        $db = <span class=\"hljs-keyword\">new</span> SQLite3(<span class=\"hljs-string\">'database.sqlite'</span>);\n        $statement = $db-&gt;prepare(<span class=\"hljs-string\">'SELECT * FROM blog'</span>);\n        $result = $statement-&gt;execute();\n        <span class=\"hljs-keyword\">while</span> ($row = $result-&gt;fetchArray(SQLITE3_ASSOC)) {\n            $page = (<span class=\"hljs-keyword\">new</span> Page($row[<span class=\"hljs-string\">'page-id'</span>]))\n                -&gt;setType(Type::PAGE-&gt;value)\n                -&gt;setPath($row[<span class=\"hljs-string\">'path'</span>])\n                -&gt;setBodyHtml($row[<span class=\"hljs-string\">'html'</span>])\n                -&gt;setVariable(<span class=\"hljs-string\">'title'</span>, $row[<span class=\"hljs-string\">'title'</span>])\n                -&gt;setVariable(<span class=\"hljs-string\">'date'</span>, $row[<span class=\"hljs-string\">'date'</span>]);\n            <span class=\"hljs-keyword\">$this</span>-&gt;generatedPages-&gt;add($page);\n        }\n        $result-&gt;finalize();\n        $db-&gt;close();\n    }\n}</code></pre>\n<p><em>configuration</em></p>\n<pre><code class=\"language-yaml hljs yaml\" translate=\"no\"><span class=\"hljs-attr\">pages:</span>\n  <span class=\"hljs-attr\">generators:</span>\n    <span class=\"hljs-comment\"># priority: class name</span>\n    <span class=\"hljs-attr\">99:</span> <span class=\"hljs-string\">Cecil\\Generator\\DummyPage</span>\n    <span class=\"hljs-attr\">35:</span> <span class=\"hljs-string\">Cecil\\Generator\\Database</span></code></pre>\n<h2 id=\"twig-extension\">Twig extension</h2>\n<p>You can add custom <a href=\"../templates/reference/12-functions.md\">functions</a> and <a href=\"../templates/reference/14-filters.md\">filters</a>:</p>\n<ol>\n<li><a href=\"https://twig.symfony.com/doc/advanced.html#creating-an-extension\" target=\"_blank\" rel=\"noopener noreferrer\">create a Twig extension</a> in the <code translate=\"no\">Cecil\\Renderer\\Extension</code> namespace</li>\n<li>add the PHP file in the <code translate=\"no\">extensions</code> directory</li>\n<li>add the class name to the configuration</li>\n</ol>\n<p><strong>Example:</strong></p>\n<p><em>/extensions/Cecil/Renderer/Extension/MyTwigExtension.php</em></p>\n<pre><code class=\"language-php hljs php\" translate=\"no\"><span class=\"hljs-meta\">&lt;?php</span>\n<span class=\"hljs-keyword\">namespace</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Renderer</span>\\<span class=\"hljs-title\">Extension</span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">MyTwigExtension</span> <span class=\"hljs-keyword\">extends</span> \\<span class=\"hljs-title\">Twig</span>\\<span class=\"hljs-title\">Extension</span>\\<span class=\"hljs-title\">AbstractExtension</span>\n</span>{\n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">getFilters</span><span class=\"hljs-params\">()</span>\n    </span>{\n        <span class=\"hljs-comment\">// add a new filter named 'md5'</span>\n        <span class=\"hljs-keyword\">return</span> [\n            <span class=\"hljs-keyword\">new</span> \\Twig\\TwigFilter(<span class=\"hljs-string\">'md5'</span>, <span class=\"hljs-string\">'md5'</span>),\n        ];\n    }\n}</code></pre>\n<p><em>configuration</em></p>\n<pre><code class=\"language-yaml hljs yaml\" translate=\"no\"><span class=\"hljs-attr\">layouts:</span>\n  <span class=\"hljs-attr\">extensions:</span>\n    <span class=\"hljs-attr\">MyExtension:</span> <span class=\"hljs-string\">Cecil\\Renderer\\Extension\\MyTwigExtension</span></code></pre>\n<h2 id=\"output-post-processor\">Output Post Processor</h2>\n<p>You can post process page output.</p>\n<p>Just create a new PHP class in the <code translate=\"no\">Cecil\\Renderer\\PostProcessor</code> namespace and add the class name to the `output.postprocessors list.</p>\n<p><strong>Example:</strong></p>\n<p><em>/extensions/Cecil/Renderer/PostProcessor/MyProcessor.php</em></p>\n<pre><code class=\"language-php hljs php\" translate=\"no\"><span class=\"hljs-meta\">&lt;?php</span>\n<span class=\"hljs-keyword\">namespace</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Renderer</span>\\<span class=\"hljs-title\">PostProcessor</span>;\n\n<span class=\"hljs-keyword\">use</span> <span class=\"hljs-title\">Cecil</span>\\<span class=\"hljs-title\">Collection</span>\\<span class=\"hljs-title\">Page</span>\\<span class=\"hljs-title\">Page</span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">MyProcessor</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">AbstractPostProcessor</span>\n</span>{\n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">process</span><span class=\"hljs-params\">(Page $page, string $output, string $format)</span>: <span class=\"hljs-title\">string</span>\n    </span>{\n        <span class=\"hljs-comment\">// add a meta tag to the head of the HTML output</span>\n        <span class=\"hljs-keyword\">if</span> ($format == <span class=\"hljs-string\">'html'</span>) {\n            <span class=\"hljs-keyword\">if</span> (!preg_match(<span class=\"hljs-string\">'/&lt;meta name=\"test\".*/i'</span>, $output)) {\n                $meta = \\sprintf(<span class=\"hljs-string\">'&lt;meta name=\"test\" content=\"Test\"&gt;'</span>);\n                $output = preg_replace_callback(<span class=\"hljs-string\">'/([[:blank:]]*)(&lt;\\/head&gt;)/i'</span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-params\">($matches)</span> <span class=\"hljs-title\">use</span> <span class=\"hljs-params\">($meta)</span> </span>{\n                    <span class=\"hljs-keyword\">return</span> str_repeat($matches[<span class=\"hljs-number\">1</span>] ?: <span class=\"hljs-string\">' '</span>, <span class=\"hljs-number\">2</span>) . $meta . <span class=\"hljs-string\">\"\\n\"</span> . $matches[<span class=\"hljs-number\">1</span>] . $matches[<span class=\"hljs-number\">2</span>];\n                }, $output);\n            }\n        }\n\n        <span class=\"hljs-keyword\">return</span> $output;\n    }\n}</code></pre>\n<p><em>configuration</em></p>\n<pre><code class=\"language-yaml hljs yaml\" translate=\"no\"><span class=\"hljs-attr\">output:</span>\n  <span class=\"hljs-attr\">postprocessors:</span>\n    <span class=\"hljs-attr\">MyProcessor:</span> <span class=\"hljs-string\">Cecil\\Renderer\\PostProcessor\\MyProcessor</span></code></pre>",
      "language": "en"
    }
  ]
}
