{"id":15296327,"url":"https://github.com/mkgor/puff","last_synced_at":"2026-02-14T09:07:30.945Z","repository":{"id":57018042,"uuid":"227785797","full_name":"mkgor/puff","owner":"mkgor","description":"Hackable and lightning fast template engine for PHP","archived":false,"fork":false,"pushed_at":"2020-04-30T20:37:38.000Z","size":185,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-11T15:53:37.039Z","etag":null,"topics":["composer-project","php7","template-engine","template-language"],"latest_commit_sha":null,"homepage":"https://mkgor.github.io/puff","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mkgor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-12-13T07:52:14.000Z","updated_at":"2022-09-07T11:08:34.000Z","dependencies_parsed_at":"2022-08-24T00:51:23.869Z","dependency_job_id":null,"html_url":"https://github.com/mkgor/puff","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/mkgor/puff","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkgor%2Fpuff","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkgor%2Fpuff/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkgor%2Fpuff/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkgor%2Fpuff/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mkgor","download_url":"https://codeload.github.com/mkgor/puff/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkgor%2Fpuff/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29441168,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T07:24:13.446Z","status":"ssl_error","status_checked_at":"2026-02-14T07:23:58.969Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["composer-project","php7","template-engine","template-language"],"created_at":"2024-09-30T18:10:04.531Z","updated_at":"2026-02-14T09:07:30.929Z","avatar_url":"https://github.com/mkgor.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Coverage](https://img.shields.io/badge/coverage-99.14%25-green)\n![GitHub repo size](https://img.shields.io/github/repo-size/mkgor/puff)\n![Packagist](https://img.shields.io/packagist/l/mkgor/puff)\n![GitHub All Releases](https://img.shields.io/github/downloads/mkgor/puff/total)\n\n# Puff\n![Logo](https://i.imgur.com/6wEUCeH.jpg)\n\nHackable and lightning fast template engine for PHP, which is inspired by Twig. \n\n## Contents\n- [Requirments](https://github.com/mkgor/puff#requirments)\n- [Installation](https://github.com/mkgor/puff#installation)\n- [Quickstart](https://github.com/mkgor/puff#quickstart)\n- [Specification](https://github.com/mkgor/puff#specification) \n    - [For](https://github.com/mkgor/puff#for)\n    - [If-else](https://github.com/mkgor/puff#if-else)\n    - [Import](https://github.com/mkgor/puff#import)\n    - [Set](https://github.com/mkgor/puff#set)\n    - [Extends and Position](https://github.com/mkgor/puff#extends-and-position)\n- [Filters](https://github.com/mkgor/puff#filters-system)\n- [Extensions system](https://github.com/mkgor/puff#extensions-system)\n    - [Making new statement(element)](https://github.com/mkgor/puff#making-new-statement-element)\n    - [Making new filter](https://github.com/mkgor/puff#making-new-filter)\n- [Syntax editing](https://github.com/mkgor/puff#syntax-editing)\n- [Escaping tags](https://github.com/mkgor/puff#escaping-tags)\n\n## Requirments\n\n- PHP 7.1 or higher (for Puff core)\n- Mbstring extension (for UpperCaseFilter)\n\n## Installation\n\nInstall Puff via composer\n\n````bash\ncomposer require mkgor/puff\n````\n\n## Quickstart\n\n````php\n\u003c?php\n\nrequire_once \"vendor/autoload.php\";\n\n$engine = new \\Puff\\Engine([\n    'modules' =\u003e [\n        new \\Puff\\Modules\\Core\\CoreModule()\n    ]\n]);\n\necho $engine-\u003erender(__DIR__ . '/template.puff.html', [\n    'variable' =\u003e 'Puff'\n]);\n````\n\n````html \n\u003chtml\u003e\n\u003cbody\u003e\nHello, i am [[ variable ]]\n\u003c/body\u003e\n\u003c/html\u003e\n````\n\n**Important!** Don't forget to initialze CoreModule here if you need all basic statements, such as **for**, **if-else**, etc.  \n**Also important!** Puff automatically converts '-' and '.' symbols into '_' in variable's name.\n\n## Specification\n\nInstructions for executing basic statements, such as **if-else**, **for**, **import**, etc. indicated by combination of characters ````[% %]````\n\nTo display variable's value, you should use ````[[ ]]````\n\n### For\nWorking like PHP's **foreach** cycle\n\n````html\n\u003cdiv class='products'\u003e\n  [% for products in item %]\n  \u003cdiv class='item'\u003e\n    \u003cb\u003eId: \u003c/b\u003e [[ item.id ]]\n    \u003cb\u003eName: \u003c/b\u003e [[ item.name ]]\n  \u003c/div\u003e\n  [% end %]\n\u003c/div\u003e\n````\n\n### If-else\nSimple if-else implementation\n\n````html\n[% if variable == true %]\n  \u003cb\u003e[[ variable ]] is true\u003c/b\u003e\n[% else %]\n  \u003cb\u003e[[ variable ]] is false\u003c/b\u003e\n[% end %]\n````\n\n### Import\nYou can import template into another using **import**\n\nIf you are not specified templates directory path, you should set template path relative to project root \n\n**Important!** Don't forget, that you are injecting all variables from current template into importing template. If template which you are importing using some variable (for example - it is displaying page title in header), you should specify it in **render** method\n````html\n[% import src='base.puff.html' %]\n\n\u003cbody\u003e\n\u003cdiv class='content'\u003e\n...\n\u003c/body\u003e\n````\n\n### Set\nCreates/Updates variable\n\n````html\n[% set variable = 'test' %]\n\n\u003c!-- will display 'test' --\u003e\n\u003cb\u003e[[ variable ]]\u003c/b\u003e\n\n[% set variable = 'test2' %]\n\n\u003c!-- will display 'test2' --\u003e\n\u003cb\u003e[[ variable ]]\u003c/b\u003e\n````\n\n### Extends and Position\nUse this tags to define the parent template and load data from the current template into it using the Position tag.\n\nUsage:\n\nMain template\n````html\n[% position name=\"title\" %]\nHome page\n[% endposition %]\n\n[% position name=\"content\" %]\nHello, [[ name ]]\n[% endposition %]\n\n[% extends src=\"base.puff.html\" %]\n````\n\nParent template\n````html\n\u003chtml\u003e\n\u003chead\u003e\n\u003ctitle\u003e[% position for=\"title\" %]\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n[% position for=\"content' %]\n\u003c/body\u003e\n\u003c/html\u003e\n````\n\n## Filters system\n\nYou can modify some variables before displaying them or if some statement supports filters, you can modify variable before using it in it.\n\nTo specify filters for variable, you should specify them by **~** sign.\n\nExample:\n\n````html\n[[ variable ~ uppercase ~ transliterate ]]\n````\n\n```` html\n[[ int_variable ~ round(1) ]]\n````\n\nYou also can use filters in **for** statement\n\n````html\n[% for products ~ uppercase in item %]\n  \u003c!-- uppercase filter recursiely transforms all items of array into uppercase --\u003e\n  [[ item.name ]]\n[% end %]\n````\n\nYou can create your own filters. Read how to do it in **Extensions system** block\n\n## Extensions system\nPuff is extensible, so you can create your own modules, which can contain your own statements and filters. \n\nTo create module, just create class which implements **Puff\\Modules\\ModuleInterface** and insert in into **modules** array of **Engine** configuration\n\n````php\n$engine = new \\Puff\\Engine([\n    'modules' =\u003e [\n        new \\Puff\\Modules\\Core\\CoreModule()\n        new \\Puff\\Modules\\NewModule\\MyModule()\n    ]\n]);\n````\n\n**Important!** Don't forget to initialze CoreModule here if you need all basic statements, such as **for**, **if-else**, etc.\n\n### Making new statement (element)\n\nTo make new element, you should create class, which should extend **Puff\\Compilation\\Element\\AbstractElement**\n\nYou should specify element's class in your **Module** class's setUp() method:\n\n````php\n...\n/**\n     * Returns an array of elements and filters which will be initialized\n     *\n     * @return array\n     */\n    public function setUp(): array\n    {\n        return [\n            'elements' =\u003e [\n                'new_element' =\u003e new NewElement(),\n                'another_new_element' =\u003e new AnotherNewElement()\n            ],\n            ...\n        ];\n    }\n    ...\n````\n\nNow, your element is accessible in template by **test_element** keyword\n\nElement's **process** method should return PHP code.\n\n````php\n\u003c?php\n\nuse Puff\\Compilation\\Element\\AbstractElement;\n\n/**\n * Class NewElement\n */\nclass NewElement extends AbstractElement\n{\n    /**\n     * @param array $attributes\n     * @return mixed\n     */\n    public function process(array $attributes)\n    {\n        return \"\u003c?php echo 'Some result of processing'; ?\u003e\";\n    }\n}\n\n````\n\nYou can provide some attributes handling rules. By default, it is handling like this:\n````\n[% element attribute='some_attribute' %]\n\nProcess method will get an array:\n\n[\n  'attribute' =\u003e 'some_attribute'\n]\n````\n\nBut if you want to make statement like **for** (which don't use attributes like \"attribute='attr'\") you can provide your own attributes handling rules by specifying **handleAttributes** method in your element class\n\nIt is getting an array of all elements from tokenizer and **should return an array**\n\nFor example:\n\n````\n[% new_element attribute anotherAttribute ~ 123 %]\n\nhandleAttributes() will get an array:\n\n[\n  'new_element',\n  'attribute',\n  'anotherAttribute',\n  '~',\n  '123'\n]\n````\n\n\n````php\n\n\u003c?php\n\nuse Puff\\Compilation\\Element\\AbstractElement;\n\n/**\n * Class NewElement\n */\nclass NewElement extends AbstractElement\n{\n    /**\n     * @param array $attributes\n     * @return mixed\n     */\n    public function process(array $attributes)\n    {\n        return \"\u003c?php echo $attributes['result']; ?\u003e\";\n    }\n    \n    public function handleAttributes(array $tokenAttributes) \n    {\n      if(array_search('attribute', $tokenAttributes)) {\n        return ['result' =\u003e 'attribute found'];\n      } else {\n        return ['result' =\u003e 'attribute not found'];\n      }\n    }\n}\n````\n\n````\nTesting for example above:\n\n[% new attribute %]\n\nWill display: attribute found\n\n[% new %]\n\nWill display: attribute not found\n````\n\n### Making new filter\n\nTo make new element, you should create class, which should extend **Puff\\Compilation\\Element\\AbstractElement**\n\nYou should specify element's **class name** in **Module's** class setUp() method:\n\n````php\n/**\n     * Returns an array of elements and filters which will be initialized\n     *\n     * @return array\n     */\n    public function setUp(): array\n    {\n        return [\n            ...\n            'filters' =\u003e [\n                'new_filter' =\u003e NewFilter::class\n            ]\n        ];\n    }\n    ...\n````\n\nYour filter class should implement **Puff\\Compilation\\Filter\\FilterInterface**\n\nFor example, discover **UpperCaseFilter** code to understand how it works\n\n````php\n\u003c?php\n\nnamespace Puff\\Compilation\\Filter;\n\n/**\n * Class UpperCaseFilter\n * @package Puff\\Compilation\\Filter\n */\nclass UpperCaseFilter implements FilterInterface\n{\n    /**\n     * @param $variable\n     * @param array $args\n     * @return string|array\n     */\n    public static function handle($variable, ...$args) {\n        mb_internal_encoding('UTF-8');\n\n        if(!is_array($variable)) {\n            return mb_strtoupper($variable);\n        } else {\n            array_walk_recursive($variable, function(\u0026$item) {\n                if(!is_array($item)) {\n                    $item = mb_strtoupper($item);\n                }\n            });\n\n            return $variable;\n        }\n    }\n}\n````\n\nValue, which **handle** method returns, will be assigned to variable\n\n## Syntax editing\n\nYou can configure some elements of syntax, such as symbols which are Puff using in tags,\nequality symbol, filter separator symbol and etc.\n\nTo do this, you should create new class, which can implements **Puff\\Tokenization\\Syntax\\SyntaxInterface** or \nextends **Puff\\Tokenization\\Syntax\\AbstractSyntax**. Let's see how it works with **AbstractSyntax**\n\n````php\n\u003c?php\n\n\nnamespace Puff\\Tokenization\\Syntax;\n\n/**\n * Class NewSyntax\n * @package Puff\\Tokenization\\Syntax\n */\nclass NewSyntax extends AbstractSyntax\n{\n    public function getElementTag() : array{\n        return [\"(@\", \"@)\"];\n    }\n}\n````\n\nSo, we are specified new element tag's symbols. To make it work, you should set it in the configuration array in **Engine**\nconstructor, or set it in **Module**'s setUp() method.\n\n````php\n\u003c?php\n$engineInstance = new Engine([\n    'modules' =\u003e [\n        new \\Puff\\Modules\\Core\\CoreModule(),\n    ],\n    'syntax' =\u003e new MySyntax()\n]);\n````\n\nNow, all tags should use new syntax, let's see how we should update template\n\n````html\n(@ if variable == 1 @)\n    \u003cspan\u003eSyntax updated!\u003c/span\u003e\n(@ end @)\n````\n\n### Escaping tags\n\nTo escape tag, you should set escaping symbols before some tag to tell compiler to ignore it. \n\nDefault escaping symbols in Puff is ``//``, but you can edit it by setting your own Syntax class\n\n````html\n[% set variable = 1 %]\n[[ variable ]]\n\n//[[variable]]\n````\n\nWill display:\n````\n1\n[[ variable ]]\n````\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmkgor%2Fpuff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmkgor%2Fpuff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmkgor%2Fpuff/lists"}