ThinkPHP Template Engine Built-in Tags Reference

Template Replacement

Before rendering templates, the system performs special string substitutions on the template content. This mechanism enables convenient template definitions with the following default replacement rules:

__ROOT__: Replaced with the current website address (without domain)
__APP__: Replaced with the current application URL (without domain)
__MODULE__: Replaced with the current module URL (without domain)
__CONTROLLER__ (__URL__ for compatibility): Replaced with the current controller URL (without domain)
__ACTION__: Replaced with the current action URL (without domain)
__SELF__: Replaced with the current page URL
__PUBLIC__: Replaced with the website's public directory, typically /Public

By default, template replacement only affects special strings in template files, not dynamic data output.

These special strings are case-sensitive. The replacement rules can be modified or extended by configuring TMPL_PARSE_STRING in the application or module configuration file:

'TMPL_PARSE_STRING' => [
    '__PUBLIC__' => '/Common',
    '__JS__' => '/Public/JS/',
    '__UPLOAD__' => '/Uploads',
]

To output literal __PUBLIC__ string, add a replacement rule:

'TMPL_PARSE_STRING' => [
    '--PUBLIC--' => '__PUBLIC__',
]

Delimiter Configuration

Template files can contain both regular tags and XML tags, each with configurable delimiters.

Regular Tags

The default delimiters for regular tags are { and }. Tags must have the opening delimiter immediately followed by the tag definition. Spaces or line breaks after the opening delimiter are treated as literal text.

Examples: {$name}, {$user.username}, {$data['title']|strtolower}

To change delimiters, configure these parameters:

'TMPL_L_DELIM' => '<{',
'TMPL_R_DELIM' => '}>',

With this configuration, you must use <{$name}> instead of {$name}.

XML Tags

XML tags support variable output, file inclusion, conditional logic, and iteration. They can be configured separately:

'TAGLIB_BEGIN' => '[',
'TAGLIB_END' => ']',

Original:

<eq name="role" value="admin">Administrator</eq>

After changing to square brackets:

[eq name="role" value="admin"]Administrator[/eq]

Warning: XML tag and regular tag delimiters must not conflict, otherwise parsing errors will occur.


Variable Output

Basic Variables

Assign variables in the controller and display in templates:

$author = 'ThinkPHP';
$this->assign('author', $author);
$this->display();

Template:

Hello, {$author}!

Compiled output:

Hello, <?php echo($author); ?>!

Important: No spacces allowed between { and $ in tags.

Array Variables

$user['username'] = 'ThinkPHP';
$user['email'] = 'framework@tp.com';
$this->assign('user', $user);

Template usage:

Username: {$user.username}
Email: {$user.email}

Or using bracket notation:

Username: {$user['username']}
Email: {$user['email']}

Bracket notation is required for multi-dimensional arrays.

Object Variables

$user = new User();
$user->username = 'ThinkPHP';
$this->assign('user', $user);

Template:

Username: {$user:username}
Email: {$user->email}

Ternary Operator

Templates support ternary operations:

{$status ? 'Active' : 'Inactive'}
{$info.status ? $info.message : $info.error}

Note: Dot syntax is not supported within ternary operators.


Arithmetic Operators

Operator Example
+ {$x + $y}
- {$a - $b}
* {$m * $n}
/ {$p / $q}
% {$i % $j}
++ {$counter++} or {++$counter}
-- {$count--} or {--$count}
Combined {$a + $b * 2 + $c}

Dot syntax is not supported in expressions. Correct usage:

<!-- Incorrect -->
{$user.score + 10}

<!-- Correct -->
{$user['score'] + 10}
{$user['price'] * $user['quantity']}

Default Values

Provide fallback values for variables:

{$user.nickname|default="No description provided"}

Works with system variables:

{$Think.get.username|default="Guest"}

Default values and functions can be combined:

{$Think.get.name|default="Anonymous"|getName}

Function Filters

Apply functions to template output using the pipe operator:

{$data.name|md5}
<!-- Compiled to: -->
<?php echo (md5($data['name'])); ?>

For multi-parameter functions, use ### to mark varible position:

{$created_at|date="Y-m-d H:i:s",###}
<!-- Compiled to: -->
<?php echo (date("Y-m-d H:i:s", $created_at)); ?>

When the variable is the first parameter:

{$fullname|substr=0,10}
<!-- Outputs: -->
<?php echo (substr($fullname, 0, 10)); ?>

<!-- Equivalent: -->
{$fullname|substr=###,0,10}

Chain multiple functions:

{$password|md5|strtoupper|substr=0,8}
<!-- Compiled to: -->
<?php echo (substr(strtoupper(md5($password)), 0, 8)); ?>

Functions execute from left to right. Alternative inline syntax:

{:substr(strtoupper(md5($password)), 0, 8)}

Both built-in PHP functions and custom functions (including static methods) are supported.


Conditional Tags

IF Tag

<if condition="($role eq 1) OR ($level gt 5)">
    Premium Access
<elseif condition="$role eq 2"/>
    Standard Access
<else />
    Guest Access
</if>

Condition expressions include eq, neq, gt, lt, etc. Direct comparison operators like < and > are not supported in condition attributes. Instead, use alternative syntax or PHP code:

<if condition="strtoupper($user['role']) neq 'ADMIN'">
    Non-admin user
<else />
    Admin user
</if>

Point syntax is supported:

<if condition="$account.balance neq 0">
    Balance available
<else />
    Zero balance
</if>

For objects:

<if condition="$account:balance neq 0">
    Balance available
</if>

SWITCH Tag

<switch name="user.level">
    <case value="1">Administrator</case>
    <case value="2">Editor</case>
    <case value="3">Viewer</case>
    <default />Guest
</switch>

Functions and system variables work with the name attribute:

<switch name="Think.get.status|abs">
    <case value="1">Active</case>
    <default />Inactive
</switch>

Multiple values per case using |:

<switch name="Think.get.fileType">
    <case value="jpg|png|gif|bmp">Image File</case>
    <default />Other File Type
</switch>

The break attribute controls case termination (default is 1):

<switch name="Think.get.category">
    <case value="1" break="0">Category One</case>
    <case value="2">Category Two</case>
    <default />Default Category
</switch>

Variables in case values:

<switch name="User.userId">
    <case value="$adminId">Administrator</case>
    <case value="$moderatorId">Moderator</case>
    <default />Member
</switch>

Range Tags

IN and NOT IN

<!-- Controller -->
$category = 2;
$this->assign('category', $category);

<!-- Template -->
<in name="category" value="1,2,3">
    Valid category
<else/>
    Invalid category
</in>

System variables:

<in name="Think.get.tag" value="featured,popular,trending">
    Special content
</in>

Variables as values:

<in name="category" value="$allowedCategories">
    Allowed
</in>

BETWEEN and NOT BETWEEN

<!-- Within range -->
<between name="age" value="18,60">Adult rate applies</between>

<!-- Outside range -->
<notbetween name="age" value="18,60">Age requirement not met</notbetween>

<!-- Combined with else -->
<between name="score" value="0,100">
    Valid score
<else/>
    Invalid score
</between>

The between tag uses the first two values only. The third value is ignored.

Range tag provides an alternative syntax:

<range name="category" value="1,2,3" type="in">In range</range>

Supported types: in, notin, between, notbetween.


Loop Tags

Volist Tag

Used for iterating over query results (two-dimensional arrays):

$User = M('User');
$users = $User->limit(20)->select();
$this->assign('users', $users);
<volist name="users" id="item">
    ID: {$item.id} - Name: {$item.username}<br/>
</volist>

Custom iteration variable:

<volist name="users" id="record">
    {$record.id}: {$record.username}
</volist>

Slice data with offset and length:

<volist name="users" id="item" offset="5" length="10">
    {$item.username}
</volist>

Odd/even row detection with mod:

<volist name="users" id="item" mod="2">
    <eq name="mod" value="1">{$item.username}</eq>
</volist>

Control row breaks every N items:

<volist name="users" id="item" mod="4">
    {$item.username}
    <eq name="mod" value="3"><br/></eq>
</volist>

Empty state handling:

<volist name="users" id="item" empty="No users found">
    {$item.username}
</volist>

Pass empty message as variable:

$this->assign('emptyMsg', '<span class="empty">No records</span>');
<volist name="users" id="item" empty="$emptyMsg">
    {$item.username}
</volist>

Loop counter with key:

<volist name="users" id="item" key="k">
    {$k}. {$item.username}
</volist>

Default counter variable is i, or access array keys directly:

<volist name="users" id="item">
    {$key}. {$item.username}
</volist>

Inline data without controller assignment:

<volist name=":getUsersByRole('editor')" id="item">
    {$item.username}
</volist>

Foreach Tag

Simpler alternative to volist:

<foreach name="users" item="usr">
    {$usr.id}: {$usr.username}
</foreach>

Display index:

<foreach name="users" item="usr">
    {$key} - {$usr.username}
</foreach>

Custom index variable:

<foreach name="users" item="usr" key="idx">
    {$idx}. {$usr.username}
</foreach>

For Tag

<for start="1" end="50" comparison="lt" step="2" name="counter">
    {$counter}
</for>

Compiled output:

for ($counter=1; $counter<50; $counter+=2) {
    echo $counter;
}

Defaults: comparison is lt, name is i, step is 1.


Template Inclusion

Using Template Expressions

Format: Module@Theme/Controller/Action

<include file="Public/header" />
<include file="Public/sidebar" />
<include file="Dark/Public/footer" />

Include multiple templates:

<include file="Public/header,Public/navigation,Public/footer" />

Included templates do not execute controller methods. Variables must be assigned in the calling action.

Using Direct File Paths

<include file="./Application/Home/View/default/Public/header.html" />

Passing Parameters

<include file="Public/header" title="My Website" keywords="PHP Framework" />

In the included header.html:

<html>
<head>
    <title>[title]</title>
    <meta name="keywords" content="[keywords]" />
</head>

In production mode, template cache must be cleared after modifying included files for changes to take effect.

Tags: ThinkPHP template engine PHP Framework web development

Posted on Mon, 29 Jun 2026 17:40:40 +0000 by zero816