{if user.searching=='template'}This is for you {= user.name }{else}Welcome {= user.name }{/if}
Single-Class PHP5/7 template engine with support for if/loops/filters
- Easy: No compiling or caching - just parse
input string
intooutput string
- Secure: No eval(); no code is generated. No filesystem access needed. Unit-tested.
- Small: No dependencies.
- Features: Nested loops, if/elseif/else, custom filters, auto-escaping
It is aimed to be a small string Template-Engine to meet e-Mailing or small html-Template demands. It is not meant to be a replacement for pre-compiled full featured Template-Engines like Smarty or Twig.
TextTemplate uses Regular-Expressions for text-Parsing. No code is generated or evaluated - so this might be a secure solution to use in no time-critical situations.
Whilst most template-engines rely on eval'ing generated code and filesystem-access, Text-Template uses a
set of regular-expressions to parse the template. Nor any intermediate code is generated nor any
code is eval'ed. So TextTemplate should be more secure than Smarty or Twig by design.
TextTemplate supports infinite-nested loops and sequences.
// 1. Define the Template
$tplStr = <<<EOT
Hello World {= name }
{if name == "Matthias"}
Hallo {= name | capitalize }
{elseif name == "Jan"}
Hi Buddy
{else}
You are not Matthias
{/if}
EOT;
// 2. Define the Data for the Template
$data = [
"name" => "Matthias"
];
// 3. Parse
$tt = new TextTemplate($tplStr);
echo $tt->apply ($data);
I prefer and recommend using composer:
composer require text/template
This documentation refers to the default-tags: Having one bracket {
as start and one }
as
end delimiter. Of course, they are flexible: If you want to change these, see: TextTempate::setOpenCloseTagChars()
Use the value Tag
{= varName}
To inject a value to the Code. Any variables will be htmlspecialchars()
encoded by default. To
output the RAW content use the raw
-Filter: {=htmlCode|raw}
To access array elements or objects use "." to access sub-elements:
{= users.0.name}
You can insert loops:
{for curName in names}
Current Name: {= curName}
{/for}
Inside each loop, there are to magick-values @index0
(index starting with 0) and @index1
for a
index starting with amp1.
{for curName in names}
Line {= @index1 }: {= curName}
{/for}
Inside loops you can {break}
or {continue}
the loop.
You can use if-conditions:
{if someVarName == "SomeValue"}
Hello World
{/if}
Shortcut: Test if a variable is null:
{if someVarName}
someVarName is set!
{/if}
{if !someVarName}
someVarName is not set!
{/if}
Complex logical expressions can be made using && (and), || (or) and brackets.
{if someVarName && otherVarName}
someVarName and otherVarName are set!
{/if}
{if someVarName || otherVarName}
someVarName or otherVarName are set!
{/if}
{if someVarName || (otherVarName && anotherVarName)}
Condition is true!
{/if}
{if someVarName && !(otherVarName && anotherVarName)}
Condition is true!
{/if}
You can use filters on values used in comparsions.
{if someArray|count > otherArray|count}
someArray has more items than otherArray
{/if}
You can add custom operators for use in conditions.
Adding a new Operator:
$tt->addOperator("contains",
function ($operand1, $operand2) {
return strpos($operand1, $operand2) !== false;
}
);
Operator | Description |
---|---|
== | Equal |
!= | Not equal |
> | Greater than |
< | Less than |
>= | Greater than or equal |
<= | Less than or equal |
{if someVarName == "SomeValue"}
Hello World
{else}
Goodbye World
{/if}
Lists of choices:
{if someVarName == "SomeValue"}
Hello World
{elseif someVarName == "OtherValue"}
Hello Moon
{else}
Goodbye World
{/if}
You can register user-defined functions.
$template->addFunction("sayHello",
function ($paramArr, $command, $context, $cmdParam, $self) {
return "Hello " . $paramArr["msg"];
}
);
Call the function and output into template
{sayHello msg="Joe"}
or inject the Result into the context for further processing:
{sayHello msg="Joe" > out}
{=out}
Processing Exceptions:
Use !>
to catch exceptions and redirect them to the scope.
{throw msg="SomeMsg" !> lastErr}
Or use !break
or !continue
to break/continue a loop
Use {# #}
to add comments (will be stripped from output
Template {# Some Comment #}
{# Some
Multiline
Comment #}
You can add custom filters or overwrite own filters. The default filter is html
(htmlspecialchars).
Adding a new Filter:
$tt->addFilter ("currency", function ($input, $decimals=2, $decSeparator=",", $thounsandsSeparator=".") {
return number_format ($input, $decimals, $decSeparator, $thousandsSeparator);
});
Call the filter with parameters (parameter-separator :
):
{= variable | currency:2:,:. }
Use this filter inside your template
{= someVariable | currency }
Name | Description |
---|---|
raw | Display raw data (skip default escaping) |
singleLine | Transform Line-breaks to spaces |
inivalue | like singleLine including addslashes() |
html | htmlspecialchars() |
fixedLength::<pad_char: | Pad / shrink the output to characters |
inflect:tag | Convert to underline tag |
sanitize:hostname | Convert to hostname |
count | Return count of array |
By default and for security reason all values will be escaped using the "DEFAULT"-Filter. (except if "raw" was selected within the filter section)
If you, for some reason, want to disable this functionality or change the escape function you can overwrite the DEFAULT-Filter:
$tt->addFilter ("_DEFAULT_", function ($input) {
return strip_tags ($input);
});
or
$tt->setDefaultFilter("singleLine");
This example will replace the htmlspecialchars() escaper by the strip_tags() function.
Sections are like functions but provide the content they enclose:
{sectionxy name="someName"}
Some Content
{/sectionxy}
{sectionxy name="someName" > out}
Some Content
{/sectionxy}
{= out}
To use sections you must just set the callback:
$textTemplate->addSection("sectionxy", function ($content, $params, $command, $context, $cmdParam, $self) {
return "Content to replace section content with";
});
{strip_empty_lines}
line1
line2
{/strip_empty_lines}
{trim > countElem}{= var | count}{/trim}
{if countElem == "0" }
{/if}
Append output to a variable.
{print >> out}
A
{/print}
{print >> out}
B
{/print}
{= out}
To see all Parameters passed to the template use:
{= __CONTEXT__ | raw}
It will output the structure of the current context.
By default text-template will not throw any exceptions when a template tries to access an undefined variable.
To improve debugging, you can switch this behaviour by setting $softFail
to
false
(Parameter 2 of apply()
function):
try {
$tt = new TextTemplate("{=someUndefinedName}");
echo $tt->apply([], false);
// ^^^^^
} catch (UndefinedVariableExceptions $e) {
echo "UndefinedVariable: {$e->getTriggerVarName()}"
}
will return
UndefinedVariable: someUndefinedName
Sometimes {tag}{\tag}
isn't suitable when parsting other template files. You can change the
opening and closing chars with the function setOpenCloseTagChars()
$textTemplate->setOpenCloseTagChars("{{", "}}");
The above example will listen to {{tag}}{{/tag}}
.
Although the parser is build of pure regular-expressions, I tried to avoid too expensive constructions like read ahead, etc.
And we got quite good results:
Template size | Parsing time[sec] |
---|---|
50kB | 0.002 |
200kB | 0.007 |
If you want to contribute, please send your Pull-Request or open a github issue.
- Bugs & Feature-Request: GitHub Issues
Keeping the tests green: Please see / provide unit-tests. This project uses nette/tester
for unit-testing.
This Project uses kickstart's ready to use development
containers based on docker. Just run ./kickstart.sh
to run this project.
To start the development container
./kickstart.sh
To execute the tests run kick test
inside the container. (See .kick.yml
)
Text-Template was written by Matthias Leuffen http://leuffen.de
Join InfraCAMP