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_STRINGin 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
betweentag 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.