{"id":28528857,"url":"https://github.com/rgamba/tonic","last_synced_at":"2025-10-24T03:51:33.549Z","repository":{"id":28957873,"uuid":"32483998","full_name":"rgamba/tonic","owner":"rgamba","description":"Super fast and powerful PHP template engine","archived":false,"fork":false,"pushed_at":"2020-07-16T12:10:33.000Z","size":74,"stargazers_count":49,"open_issues_count":2,"forks_count":6,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-06-09T13:12:57.104Z","etag":null,"topics":["php","template-engine"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rgamba.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-18T20:58:19.000Z","updated_at":"2022-08-08T05:02:33.000Z","dependencies_parsed_at":"2022-08-17T17:40:55.327Z","dependency_job_id":null,"html_url":"https://github.com/rgamba/tonic","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/rgamba/tonic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgamba%2Ftonic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgamba%2Ftonic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgamba%2Ftonic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgamba%2Ftonic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rgamba","download_url":"https://codeload.github.com/rgamba/tonic/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgamba%2Ftonic/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263988193,"owners_count":23540139,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["php","template-engine"],"created_at":"2025-06-09T13:13:00.929Z","updated_at":"2025-10-24T03:51:33.478Z","avatar_url":"https://github.com/rgamba.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tonic\n\n[![Build Status](https://travis-ci.org/rgamba/tonic.svg?branch=master)](https://travis-ci.org/rgamba/tonic)\n\nSuper fast and powerful template engine. Pure PHP, zero dependencies.\n\n## Usage\nUsing Tonic is pretty straight forward.\n```php\nuse Tonic\\Tonic;\n$tpl = new Tonic(\"demo.html\");\n$tpl-\u003euser_role = \"member\";\necho $tpl-\u003erender();\n```\nIt's also very flexible. The above code can also be written like:\n```php\n$tpl = new Tonic();\necho $tpl-\u003eload(\"demo.html\")-\u003eassign(\"user_role\",\"member\")-\u003erender();\n```\n## Show me the syntax\nUsing Tonic\n```html\n\u003cbody\u003e\n\u003ch1\u003eWelcome {$user.name.capitalize().truncate(50)}\u003c/h1\u003e\nUser role: {$role.lower().if(\"admin\",\"administrator\").capitalize()}\n\u003c/body\u003e\n```\nvs. writting all in PHP\n```html\n\u003cbody\u003e\n\u003ch1\u003eWelcome \u003c?php echo (strlen($user[\"name\"]) \u003e 50 ? substr(ucwords($user[\"name\"]),0,50).\"...\" : ucwords($user[\"name\"])) ?\u003e\u003c/h1\u003e\nUser role: \u003c?php if(strtolower($role) == \"admin\") { echo \"Administrator\" } else { echo ucwords($role) } ?\u003e\n\u003c/body\u003e\n```\n## Installation\nInstall using composer\n```\n$ composer require rgamba/tonic\n```\n## Caching\nAll tonic templates are compiled back to native PHP code. It's highly recommended that you use the caching functionality so that the same template doesn't need to be compiled over and over again increasing the CPU usage on server side.\n```php\n$tpl = new Tonic();\n$tpl-\u003ecache_dir = \"./cache/\"; // Be sure this directory exists and has writing permissions\n$tpl-\u003eenable_content_cache = true; // Importante to set this to true!\n```\n## Modifiers\nModifiers are functions that modify the output variable in various ways. All modifiers must be preceded by a variable and can be chained with other modifiers. Example:\n```html\n{$variable.modifier1().modifier2().modifier3()}\n```\nWe can also use modifiers in the same way when using associative arrays:\n```html\n{$my_array.item.sub_item.modifier1().modifier2().modifier3()}\n```\n## Working with dates\nIt's easy to handle and format dates inside a Tonic template.\n```php\nTonic::$local_tz = 'America/New_york'; // Optionaly set the user's local tz\n$tpl = new Tonic();\n$tpl-\u003emy_date = date_create();\n```\nAnd the template\n```html\n\u003cp\u003eToday is {$my_date.date(\"Y-m-d h:i a\")}\u003c/p\u003e\n```\nWorking with timezones\n```html\n\u003cp\u003eThe local date is {$my_date.toLocal().date(\"Y-m-d h:i a\")}\u003c/p\u003e\n```\nWhich will render `$my_date` to the timezone configured in ` Tonic::$local_tz`\n### Custom timezone\n```html\n\u003cp\u003eThe local date is {$my_date.toTz(\"America/Mexico_city\").date(\"Y-m-d h:i a\")}\u003c/p\u003e\n```\n### List of modifiers\n\nName | Description\n--- | ---\n`upper()` | Uppercase\n`lower()` | Lowercase\n`capitalize()` | Capitalize words (ucwords)\n`abs()` | Absolute value\n`truncate(len)` | Truncate and add \"...\" if string is larger than \"len\"\n`count()` | Alias to count()\n`length()` | alias to count()\n`date(format)` | Format date like date(format)\n`nl2br()` | Alias to nl2br\n`stripSlashes()` | Alias to stripSlashes()\n`sum(value)` | Sums value to the current variable\n`substract(value)` | Substracts value to the current variable\n`multiply(value)` | Multiply values\n`divide(value)` | Divide values\n`addSlashes()` | Alias of addSlashes()\n`encodeTags()` | Encode the htmls tags inside the variable\n`decodeTags()` | Decode the tags inside the variable\n`stripTags()` | Alias of strip_tags()\n`urldecode()` | Alias of urldecode()\n`trim()` | Alias of trim()\n`sha1()` | Returns the sha1() of the variable\n`numberFormat(decimals)` | Alias of number_format()\n`lastIndex()` | Returns the last array's index of the variable\n`lastValue()` | Returns the array's last element\n`jsonEncode()` | Alias of json_encode()\n`replace(find,replace)` | Alias of str_replace()\n`default(value)` | In case variable is empty, assign it value\n`ifEmpty(value [,else_value])` | If variable is empty assign it value, else if else_value is set, set it to else_value\n`if(value, then_value [,else_value [,comparisson_operator]] )` | Conditionally set the variable's value. All arguments can be variables\n`preventTagEncode()` | If ESCAPE_TAGS_IN_VARS = true, this prevents the variable's value to be encoded\n\n### Creating custom modifiers\nIf you need a custom modifier you can extend the list and create your own.\n```php\n// This function will only prepend and append some text to the variable\nTonic::extendModifier(\"myFunction\",function($input, $prepend, $append = \"\"){\n    // $input will hold the current variable value, it's mandatory that your lambda\n    // function has an input receiver, all other arguments are optional\n    // We can perform input validations\n    if(empty($prepend)) {\n        throw new \\Exception(\"prepend is required\");\n    }\n    return $prepend . $input . $append;\n});\n```\nAnd you can easily use this modifier:\n```html\n\u003cp\u003e{$name.myFunction(\"hello \",\" goodbye\")}\u003c/p\u003e\n```\n### Anonymous modifiers\nSometimes you just need to call functions directly from inside the template whose return value is constantly changing and therefore it can't be linked to a static variable. Also, it's value is not dependant on any variable. In those cases you can use anonymous modifiers.\nTo do that, you need to create a custom modifier, IGNORE the `$input` parameter in case you need to use other parameters.\n```php\nTonic::extendModifier(\"imagesDir\", function($input){\n    // Note that $input will always be empty when called this modifier anonymously\n    return \"/var/www/\" . $_SESSION[\"theme\"] . \"/images\";\n});\n```\nThen you can call it directly from the template\n```html\n\u003cimg src=\"{$.imagesDir()}/pic.jpg\" /\u003e\n```\n## Context awareness\nTonic prevents you from escaping variables in your app that could led to possible attacks. Each variable that's going to be displayed to the user should be carefully escaped, and it sould be done acoardingly to it's context.\nFor example, a variable in a href attr of a link should be escaped in a different way from some variable in a javascript tag or a `\u003ch1\u003e` tag.\nThe good news is that tonic does all this work for you.\n```php\n$tonic-\u003eassign(\"array\",array(\"Name\" =\u003e \"Ricardo\", \"LastName\", \"Gamba\"));\n$tonic-\u003eassign(\"ilegal_js\",\"javascript: alert('Hello');\");\n```\nAnd the HTML\n```html\n\u003ca href=\"{$ilegal_js}\"\u003eClick me\u003c/a\u003e\n\u003c!-- Will render: \u003ca href=\"javascript%3A+alert%28%27Hello%27%29%3B\"\u003eClick me\u003c/a\u003e --\u003e\n\u003cp\u003eThe ilegal js is: {$ilegal_js}\u003c/p\u003e\n\u003c!-- Will render: \u003cp\u003e The ilegal js is: javascript: alert(\u0026#039;Hello\u0026#039;);\u003c/p\u003e --\u003e\n\u003ca href=\"?{$array}\"\u003eValid link generated\u003c/a\u003e\n\u003c!-- Will render: \u003ca href=\"?Name=Ricardo\u0026LastName=Gamba\"\u003eValid link generated\u003c/a\u003e --\u003e\n\u003cp\u003e We can also ignore the context awareness: {$ilegal_js.ignoreContext()}\u003c/p\u003e\n```\n## Include templates\nYou can include a template inside another template\n```html\n{include footer.html}\n```\nWe can also fetch and external page and load it into our current template\n```html\n{include http://mypage.com/static.html}\n```\nTemplates includes support nested calls, but beware that infinite loop can happen in including a template \"A\" inside \"A\" template.\n## Control structures\n### If / else\nMaking conditionals is very easy\n```html\n{if $user.role eq \"admin\"}\n\u003ch1\u003eHello admin\u003c/h1\u003e\n{elseif $user.role.upper() eq \"MEMBER\" or $user.role.upper() eq \"CLIENT\"}\n\u003ch1\u003eHello member\u003c/h1\u003e\n{else}\n\u003ch1\u003eHello guest\u003c/h1\u003e\n{endif}\n```\nYou can use regular logic operators (==, !=, \u003e, \u003c, \u003e=, \u003c=, ||, \u0026\u0026) or you can use the following\n\nOperator | Equivalent\n--- | ---\neq | ==\nneq | !=\ngt | \u003e\nlt | \u003c\ngte | \u003e=\nlte | \u003c=\n\n### Loops\n```html\n\u003cul\u003e\n{loop $user in $users}\n\u003cli\u003e{$user.name.capitalize()}\u003c/h1\u003e\n{endloop}\n\u003c/ul\u003e\n```\nOr if the array key is needed\n```html\n\u003cul\u003e\n{loop $i,$user in $users}\n\u003cli\u003e{$i} - {$user.name.capitalize()}\u003c/h1\u003e\n{endloop}\n\u003c/ul\u003e\n```\n### Working with macros\nBoth if structures and loop constructs can be written in a more HTML-friendly way so your code can be more readable. Here's an example:\n```html\n\u003cul tn-if=\"$users\"\u003e\n    \u003cli tn-loop=\"$user in $users\"\u003eHello {$user}\u003c/li\u003e\n\u003c/ul\u003e\n```\nWhich is exactly the same as:\n```html\n{if $users}\n\u003cul\u003e\n    {loop $user in $users}\n    \u003cli\u003eHello {$user}\u003c/li\u003e\n    {endloop}\n\u003c/ul\u003e\n{endif}\n```\n\n## Template inheritance\nTonic supports single template inheritance. The idea behind this is to keep things nice and simple. Multiple inheritance can lead to complicated views difficult to maintain.\n\nIn Tonic, template inheritance is based on `blocks`. Suppose we have the following base template:\n\n**base.html**\n```html\n\u003chtml\u003e\n\u003chead\u003e\n\u003ctitle\u003eTonic\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003csection tn-block=\"header\"\u003e\n    \u003ch1\u003eDefault welcome message!\u003c/h1\u003e\n\u003c/section\u003e\n\u003csection\u003e\n    \u003cdiv tn-block=\"content\"\u003e\n        \u003cp\u003eThis is the default content.\u003c/p\u003e\n    \u003c/div\u003e\n\u003c/section\u003e\n\u003csection tn-block=\"footer\"\u003eTonic 2016\u003c/section\u003e\n\u003c/body\u003e\n```\n\nThen you have several partial templates or views and you would like to reuse the main `base.html` as a \"skeleton\". To do that, we work with `blocks`.\nEach block is defined by the tag `{block name}{endblock}` and/or by the html attribute `tn-block=\"name\"` which effectively encloses the HTML element with the attibute as the block with the name __name__.\n\n**inner.html**\n```html\n{ extends base.html }\n\u003csection tn-block=\"header\" class=\"myheader\"\u003e\n    \u003ch1\u003eWelcome to my inner page!\u003c/h1\u003e\n\u003c/section\u003e\n\u003cp\u003eThis content WON´T be rendered at all!\u003c/p\u003e\n\u003cdiv tn-block=\"content\"\u003e\n    \u003cp\u003eThis is the content of my inner view.\n\u003c/div\u003e\n```\n\nAs a result we will have the following view:\n```html\n\u003chtml\u003e\n\u003chead\u003e\n\u003ctitle\u003eTonic\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003csection class=\"myheader\"\u003e\n    \u003ch1\u003eWelcome to my inner page!\u003c/h1\u003e\n\u003c/section\u003e\n\u003csection\u003e\n    \u003cdiv\u003e\n        \u003cp\u003eThis is the content of my inner view.\n    \u003c/div\u003e\n\u003c/section\u003e\n\u003csection\u003eTonic 2016\u003c/section\u003e\n\u003c/body\u003e\n```\n\nThere are several keys here:\n* The `{ extend }` tag. Which first and only argument should be the template file relative to `Tonic::$root` (by default `./`).\n* The `tn-block=\"header\"` html attribute that defines the block and is enclosed by the closing matching tag of the HTML element. \n* All the blocks found in the child template (inner.html) will effectively replace the matching blocks on the parent template (base.html). If there is a block in the child template that is not defined in the parent template, **that block won´t be rendered at all**.\n* Block names must only by alphanumerical with and must not contain `$` or any special characters or spaces.\n* The parent template (base.html) inherits the context (scope, variables) of the child template.\n* You can only extend 1 template.\n\n**NOTE** It is also possible to define blocks using the `{block header}{endblock}` notation. We prefer to use HTML attributes as it is cleaner. \nExample:\n```html\n{block myBlock}\u003cdiv\u003e\u003ch1\u003eWelcome\u003c/h1\u003e\u003c/div\u003e{endblock}\n```\nis exactly the same as:\n```html\n\u003cdiv tn-block=\"myBlock\"\u003e\u003ch1\u003eWelcome\u003c/h1\u003e\u003c/div\u003e\n```\n\n\n## Changelog\n* 11-10-2016 - 3.1.0 - Added support for template inheritance\n* 25-03-2015 - 3.0.0 - Added Context Awareness and Maco Syntax for ifs and loops\n* 23-03-2015 - 2.2.0 - Added namespace support and added modifier exceptions\n* 20-03-2015 - 2.1.0 - Added the option to extend modifiers.\n* 19-03-2015 - 2.0.0 - IMPORTANT update. The syntax of most structures has changed slightly, it's not backwards compatible with previous versions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frgamba%2Ftonic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frgamba%2Ftonic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frgamba%2Ftonic/lists"}