pixaye / puff
Flexible and lightweight template engine for PHP
Requires
- php: ^7.1
- ext-mbstring: *
Requires (Dev)
- phpunit/phpunit: ^8.5
This package is auto-updated.
Last update: 2025-03-10 20:57:16 UTC
README
Puff
Hackable and lightning fast template engine for PHP, which is inspired by Twig.
Contents
- Requirments
- Installation
- Quickstart
- Specification
- Filters
- Extensions system
- Syntax editing
- Escaping tags
Requirments
- PHP 7.1 or higher (for Puff core)
- Mbstring extension (for UpperCaseFilter)
Installation
Install Puff via composer
composer require mkgor/puff
Quickstart
<?php require_once "vendor/autoload.php"; $engine = new \Puff\Engine([ 'modules' => [ new \Puff\Modules\Core\CoreModule() ] ]); echo $engine->render(__DIR__ . '/template.puff.html', [ 'variable' => 'Puff' ]);
<html> <body> Hello, i am [[ variable ]] </body> </html>
Important! Don't forget to initialze CoreModule here if you need all basic statements, such as for, if-else, etc.
Also important! Puff automatically converts '-' and '.' symbols into '_' in variable's name.
Specification
Instructions for executing basic statements, such as if-else, for, import, etc. indicated by combination of characters [% %]
To display variable's value, you should use [[ ]]
For
Working like PHP's foreach cycle
<div class='products'> [% for products in item %] <div class='item'> <b>Id: </b> [[ item.id ]] <b>Name: </b> [[ item.name ]] </div> [% end %] </div>
If-else
Simple if-else implementation
[% if variable == true %] <b>[[ variable ]] is true</b> [% else %] <b>[[ variable ]] is false</b> [% end %]
Import
You can import template into another using import
If you are not specified templates directory path, you should set template path relative to project root
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
[% import src='base.puff.html' %] <body> <div class='content'> ... </body>
Set
Creates/Updates variable
[% set variable = 'test' %] <!-- will display 'test' --> <b>[[ variable ]]</b> [% set variable = 'test2' %] <!-- will display 'test2' --> <b>[[ variable ]]</b>
Extends and Position
Use this tags to define the parent template and load data from the current template into it using the Position tag.
Usage:
Main template
[% position name="title" %] Home page [% endposition %] [% position name="content" %] Hello, [[ name ]] [% endposition %] [% extends src="base.puff.html" %]
Parent template
<html> <head> <title>[% position for="title" %]</title> </head> <body> [% position for="content' %] </body> </html>
Filters system
You can modify some variables before displaying them or if some statement supports filters, you can modify variable before using it in it.
To specify filters for variable, you should specify them by ~ sign.
Example:
[[ variable ~ uppercase ~ transliterate ]]
[[ int_variable ~ round(1) ]]
You also can use filters in for statement
[% for products ~ uppercase in item %]
<!-- uppercase filter recursiely transforms all items of array into uppercase -->
[[ item.name ]]
[% end %]
You can create your own filters. Read how to do it in Extensions system block
Extensions system
Puff is extensible, so you can create your own modules, which can contain your own statements and filters.
To create module, just create class which implements Puff\Modules\ModuleInterface and insert in into modules array of Engine configuration
$engine = new \Puff\Engine([ 'modules' => [ new \Puff\Modules\Core\CoreModule() new \Puff\Modules\NewModule\MyModule() ] ]);
Important! Don't forget to initialze CoreModule here if you need all basic statements, such as for, if-else, etc.
Making new statement (element)
To make new element, you should create class, which should extend Puff\Compilation\Element\AbstractElement
You should specify element's class in your Module class's setUp() method:
... /** * Returns an array of elements and filters which will be initialized * * @return array */ public function setUp(): array { return [ 'elements' => [ 'new_element' => new NewElement(), 'another_new_element' => new AnotherNewElement() ], ... ]; } ...
Now, your element is accessible in template by test_element keyword
Element's process method should return PHP code.
<?php use Puff\Compilation\Element\AbstractElement; /** * Class NewElement */ class NewElement extends AbstractElement { /** * @param array $attributes * @return mixed */ public function process(array $attributes) { return "<?php echo 'Some result of processing'; ?>"; } }
You can provide some attributes handling rules. By default, it is handling like this:
[% element attribute='some_attribute' %]
Process method will get an array:
[
'attribute' => 'some_attribute'
]
But 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
It is getting an array of all elements from tokenizer and should return an array
For example:
[% new_element attribute anotherAttribute ~ 123 %]
handleAttributes() will get an array:
[
'new_element',
'attribute',
'anotherAttribute',
'~',
'123'
]
<?php use Puff\Compilation\Element\AbstractElement; /** * Class NewElement */ class NewElement extends AbstractElement { /** * @param array $attributes * @return mixed */ public function process(array $attributes) { return "<?php echo $attributes['result']; ?>"; } public function handleAttributes(array $tokenAttributes) { if(array_search('attribute', $tokenAttributes)) { return ['result' => 'attribute found']; } else { return ['result' => 'attribute not found']; } } }
Testing for example above:
[% new attribute %]
Will display: attribute found
[% new %]
Will display: attribute not found
Making new filter
To make new element, you should create class, which should extend Puff\Compilation\Element\AbstractElement
You should specify element's class name in Module's class setUp() method:
/** * Returns an array of elements and filters which will be initialized * * @return array */ public function setUp(): array { return [ ... 'filters' => [ 'new_filter' => NewFilter::class ] ]; } ...
Your filter class should implement Puff\Compilation\Filter\FilterInterface
For example, discover UpperCaseFilter code to understand how it works
<?php namespace Puff\Compilation\Filter; /** * Class UpperCaseFilter * @package Puff\Compilation\Filter */ class UpperCaseFilter implements FilterInterface { /** * @param $variable * @param array $args * @return string|array */ public static function handle($variable, ...$args) { mb_internal_encoding('UTF-8'); if(!is_array($variable)) { return mb_strtoupper($variable); } else { array_walk_recursive($variable, function(&$item) { if(!is_array($item)) { $item = mb_strtoupper($item); } }); return $variable; } } }
Value, which handle method returns, will be assigned to variable
Syntax editing
You can configure some elements of syntax, such as symbols which are Puff using in tags, equality symbol, filter separator symbol and etc.
To do this, you should create new class, which can implements Puff\Tokenization\Syntax\SyntaxInterface or extends Puff\Tokenization\Syntax\AbstractSyntax. Let's see how it works with AbstractSyntax
<?php namespace Puff\Tokenization\Syntax; /** * Class NewSyntax * @package Puff\Tokenization\Syntax */ class NewSyntax extends AbstractSyntax { public function getElementTag() : array{ return ["(@", "@)"]; } }
So, we are specified new element tag's symbols. To make it work, you should set it in the configuration array in Engine constructor, or set it in Module's setUp() method.
<?php $engineInstance = new Engine([ 'modules' => [ new \Puff\Modules\Core\CoreModule(), ], 'syntax' => new MySyntax() ]);
Now, all tags should use new syntax, let's see how we should update template
(@ if variable == 1 @) <span>Syntax updated!</span> (@ end @)
Escaping tags
To escape tag, you should set escaping symbols before some tag to tell compiler to ignore it.
Default escaping symbols in Puff is //
, but you can edit it by setting your own Syntax class
[% set variable = 1 %] [[ variable ]] //[[variable]]
Will display:
1
[[ variable ]]