视图解析器
视图解析器可为视图文件中的伪变量执行简单的文本替换。 它能解析简单变量或变量标签对。
伪变量名或控制结构使用大括号包裹,示例如下:
<html>
<head>
<title>{blog_title}</title>
</head>
<body>
<h3>{blog_heading}</h3>
{blog_entries}
<h5>{title}</h5>
<p>{body}</p>
{/blog_entries}
</body>
</html>
这些变量并非实际的 PHP 变量,而是纯文本表示, 用于在模板(视图文件)中消除 PHP 代码。
备注
CodeIgniter 并不强制 使用此类。在视图页面中直接使用原生 PHP(例如 视图渲染器)执行效率稍高。 不过,若需与不熟悉 PHP 的设计师协作,部分开发者可能更倾向于使用模板引擎。
使用视图 Parser 类
加载 Parser 类最简便的方式是通过服务加载:
<?php
$parser = service('parser');
或者,如果未将 Parser 类设为默认渲染器,
也可直接实例化:
<?php
$parser = new \CodeIgniter\View\Parser();
随后可使用其提供的三种标准渲染方法:
render()、setVar() 和
setData()。还能通过 setDelimiters() 方法直接指定分隔符。
重要
使用 Parser 时,视图模板仅由 Parser
自身处理,而非像常规 PHP 视图脚本那样执行。此类脚本中的 PHP 代码
会被 Parser 忽略,仅执行替换操作。
这是设计意图:视图文件不含 PHP。
工作原理
Parser 类用于处理存放在应用视图路径下的“PHP/HTML 脚本”。
此类脚本中不可包含任何 PHP 代码。
每个视图参数(称为“伪变量”)都会根据所提供的数值类型触发相应的替换操作。 伪变量不会提取为 PHP 变量,而是通过在大括号内引用变量名的语法来访问其值。
Parser 类内部使用关联数组来存储伪变量设置,直到调用 render() 方法为止。
因此,伪变量名称必须保持唯一,否则后设置的参数会覆盖先前的配置。
此外,这也会影响脚本内不同上下文环境下的参数转义处理: 必须为每个转义后的数值指定唯一的参数名。
解析器模板
可使用 render() 方法解析(或渲染)简单模板,
示例如下:
<?php
$data = [
'blog_title' => 'My Blog Title',
'blog_heading' => 'My Blog Heading',
];
return $parser->setData($data)->render('blog_template');
视图参数以关联数组形式传递给 setData(),
作为模板中待替换的数据。
上述示例中,模板包含两个变量:{blog_title} 和 {blog_heading}。
render() 的第一个参数为模板名称,
其中 blog_template 是视图模板文件的名称。
重要
如果省略文件扩展名,则视图文件应以 .php 扩展名结尾。
解析器配置选项
可向 render() 或 renderString() 方法传递若干选项。
cache- 保存视图结果的时长(秒);renderString()忽略此项cache_name- 用于保存/获取缓存视图结果的 ID;默认为视图路径;renderString()忽略此项saveData- 是否保留视图数据参数以供后续使用; 默认为 truecascadeData- 嵌套替换时是否继承外部伪变量; 默认为 true
<?php
return $parser->render('blog_template', [
'cache' => HOUR,
'cache_name' => 'something_unique',
]);
替换方式
解析器支持三种替换:简单替换、循环替换和嵌套替换。 替换按伪变量添加的顺序依次执行。
简单替换 是一对一地替换伪变量,对应的数据参数 为标量或字符串值,示例如下:
<?php
$template = '<head><title>{blog_title}</title></head>';
$data = ['blog_title' => 'My ramblings'];
return $parser->setData($data)->renderString($template);
// Result: <head><title>My ramblings</title></head>
Parser 还支持"变量对",
用于嵌套替换或循环,以及一些高级
构造来实现条件替换。
解析器执行时,通常按以下顺序处理:
处理条件替换
处理嵌套/循环替换
处理剩余的单一替换
循环替换
当伪变量的值为嵌套数组(如多行记录的集合)时,将触发 循环替换。
上述示例演示了如何替换简单变量。如果需要重复渲染整个代码块,并在每次迭代中填充新值, 请参考页面顶部的模板示例:
<html>
<head>
<title>{blog_title}</title>
</head>
<body>
<h3>{blog_heading}</h3>
{blog_entries}
<h5>{title}</h5>
<p>{body}</p>
{/blog_entries}
</body>
</html>
上述代码中有一对变量:{blog_entries}
数据... {/blog_entries}。此类情况下,
这对标签之间的整块数据会重复多次,次数对应
参数数组中 "blog_entries" 元素的行数。
解析变量对的方法与解析单一变量相同, 只是需要添加与变量对数据对应的多维数组。示例如下:
<?php
$data = [
'blog_title' => 'My Blog Title',
'blog_heading' => 'My Blog Heading',
'blog_entries' => [
['title' => 'Title 1', 'body' => 'Body 1'],
['title' => 'Title 2', 'body' => 'Body 2'],
['title' => 'Title 3', 'body' => 'Body 3'],
['title' => 'Title 4', 'body' => 'Body 4'],
['title' => 'Title 5', 'body' => 'Body 5'],
],
];
return $parser->setData($data)->render('blog_template');
伪变量 blog_entries 的值是关联数组的数组。
其外层结构并不包含指向各嵌套“行”的关联键。
如果"成对"数据来自数据库结果(本身已是
多维数组),可直接使用数据库的 getResultArray()
方法:
<?php
$query = $db->query('SELECT * FROM blog');
$data = [
'blog_title' => 'My Blog Title',
'blog_heading' => 'My Blog Heading',
'blog_entries' => $query->getResultArray(),
];
return $parser->setData($data)->render('blog_template');
如果要循环的数组包含对象而非数组,
解析器会先在对象上查找 asArray() 方法。如果存在,
则调用该方法,并对返回的数组按上述方式循环。
如果不存在 asArray() 方法,则将对象强制转换为
数组,并将其公共属性提供给解析器使用。
这在实体类中特别实用,实体类具有 asArray() 方法,
返回所有公共和受保护的属性(排除 _options 属性),
并使其可供解析器使用。
嵌套替换
当伪变量的值是关联数组(例如数据库中的记录)时, 会发生嵌套替换:
<?php
$data = [
'blog_title' => 'My Blog Title',
'blog_heading' => 'My Blog Heading',
'blog_entries' => [
[
'title' => 'Title 1',
'body' => 'Body 1',
],
],
];
return $parser->setData($data)->render('blog_template');
伪变量 blog_entries 的值是关联数组。
其中定义的键/值对将在该变量的变量对循环内部暴露。
适用于上述数据的 blog_template.php 示例:
<h1>{blog_title} - {blog_heading}</h1>
{blog_entries}
<div>
<h2>{title}</h2>
<p>{body}</p>
</div>
{/blog_entries}
如果希望在 blog_entries 作用域内也能访问其他伪变量,
请确保 cascadeData 选项设为 true。
注释
可在模板中添加注释,使用 {# #} 符号包裹。
这些注释在解析时会被忽略并移除。
{# 这个注释在解析过程中会被删除。 #}
{blog_entry}
<div>
<h2>{title}</h2>
<p>{body}</p>
</div>
{/blog_entry}
数据级联
嵌套替换和循环替换均可选择将 数据级联到内部替换中。
以下示例不受级联影响:
<?php
$template = '{name} lives in {locations}{city} on {planet}{/locations}.';
$data = [
'name' => 'George',
'locations' => [
['city' => 'Red City', 'planet' => 'Mars'],
],
];
return $parser->setData($data)->renderString($template);
// Result: George lives in Red City on Mars.
以下示例的结果取决于是否级联:
<?php
$template = '{locations}{name} lives in {city} on {planet}{/locations}.';
$data = [
'name' => 'George',
'locations' => [
['city' => 'Red City', 'planet' => 'Mars'],
],
];
return $parser->setData($data)->renderString($template, ['cascadeData' => false]);
// Result: {name} lives in Red City on Mars.
// or
return $parser->setData($data)->renderString($template, ['cascadeData' => true]);
// Result: George lives in Red City on Mars.
禁止解析
可使用 {noparse} {/noparse} 标签指定页面中不解析的部分。
该区域内的内容将保持原样,不会进行变量替换、循环等操作。
{noparse}
<h1>Untouched Code</h1>
{/noparse}
条件逻辑
Parser 类支持一些基本条件语法,用于处理 if、else 和 elseif。
所有 if 块都必须以 endif 标签闭合:
{if $role=='admin'}
<h1>Welcome, Admin!</h1>
{endif}
此简单块在解析时会转换为以下代码:
<?php if ($role === 'admin'): ?>
<h1>Welcome, Admin!</h1>
<?php endif ?>
if 语句中使用的所有变量必须预先定义。除此之外,
其逻辑与标准 PHP 条件表达式完全一致,并遵循所有 PHP 原生规则。
支持各种常用的比较运算符,如 ==、===、!==、<、> 等。
{if $role=='admin'}
<h1>Welcome, Admin</h1>
{elseif $role=='moderator'}
<h1>Welcome, Moderator</h1>
{else}
<h1>Welcome, User</h1>
{endif}
警告
条件语句在后台使用 eval() 进行解析,因此必须注意
条件语句中使用的用户数据,否则可能使应用面临安全风险。
修改条件分隔符
如果模板中包含如下 JavaScript 代码,解析器会抛出语法错误,因为存在可能被解释为条件语句的字符串:
<script type="text/javascript">
var f = function() {
if (hasAlert) {
alert('{message}');
}
}
</script>
此时,可使用 setConditionalDelimiters() 方法修改条件分隔符,避免误判:
<?php
$parser->setConditionalDelimiters('{%', '%}');
此时,模板中的代码应写为:
{% if $role=='admin' %}
<h1>Welcome, Admin</h1>
{% else %}
<h1>Welcome, User</h1>
{% endif %}
数据转义
默认情况下,所有变量替换均会自动转义,以防范 XSS 攻击。
CodeIgniter 的 esc() 方法支持多种上下文环境,例如通用的 html、HTML 属性(attr)及 css 等。
若未明确指定,系统将默认按 HTML 上下文处理数据。可通过 esc() 过滤器指定所需的上下文:
{ user_styles | esc(css) }
<a href="{ user_link | esc(attr) }">{ title }</a>
有时确实需要使用未转义的内容。可在左右大括号中添加感叹号实现:
{! unescaped_var !}
过滤器
每个变量替换均可应用一个或多个过滤器,以改变其展示方式。
过滤器的目的不在于大幅改变输出,而是为了以不同形式重用同一变量。
前文提到的 esc 过滤器便是一例;日期处理也是常见的应用场景,
即在同一页面的不同位置,按需对同一原始数据进行不同的格式化。
过滤器是位于伪变量名之后的命令,使用管道符号 | 分隔:
// -55 显示为 55
{ value|abs }
如果参数需要传入实参,实参必须以逗号分隔并置于括号内:
{ created_at|date(Y-m-d) }
可对值应用多个过滤器,通过管道符号串联。过滤器按从左到右的顺序处理:
{ created_at|date_modify(+5 days)|date(Y-m-d) }
内置过滤器
使用解析器时可用以下过滤器:
过滤器 |
参数 |
说明 |
示例 |
|---|---|---|---|
abs |
显示数字的绝对值。 |
{ v|abs } |
|
capitalize |
将字符串按句首大写形式显示:仅首字母大写, 其余字符均转为小写。 |
{ v|capitalize} |
|
date |
格式 (Y-m-d) |
与 PHP date 兼容的格式化字符串。 |
{ v|date(Y-m-d) } |
date_modify |
要添加/减去 的值 |
与 strtotime 兼容的修改日期的字符串,
如 |
{ v|date_modify(+1 day) } |
default |
默认值 |
如果变量为 empty(),则显示默认值。 |
{ v|default(just in case) } |
esc |
html, attr, css, js |
指定数据转义的上下文。 |
{ v|esc(attr) } |
excerpt |
短语,词数范围 |
返回指定短语前后指定词数范围内的文本。 功能与 excerpt 辅助函数一致。 |
{ v|excerpt(green giant, 20) } |
highlight |
短语 |
使用 '<mark></mark>' 标签高亮文本中的给定短语。 |
{ v|highlight(view parser) } |
highlight_code |
使用 HTML/CSS 高亮代码示例。 |
{ v|highlight_code } |
|
limit_chars |
限制个数 |
将字符数限制为 $limit。 |
{ v|limit_chars(100) } |
limit_words |
限制个数 |
将词数限制为 $limit。 |
{ v|limit_words(20) } |
local_currency |
货币,区域, 小数 |
显示货币的本地化版本。"currency" 值为任意 3 位 ISO 4217 货币代码。 |
{ v|local_currency(EUR,en_US) } |
local_number |
类型,精度, 区域 |
显示数字的本地化版本。"type" 可为:decimal、currency、 percent、scientific、spellout、ordinal、duration。 |
{ v|local_number(decimal,2,en_US) } |
lower |
将字符串转换为小写。 |
{ v|lower } |
|
nl2br |
将所有换行符(n)替换为 HTML <br/> 标签。 |
{ v|nl2br } |
|
number_format |
小数位数 |
封装 PHP number_format 函数,供解析器使用。 |
{ v|number_format(3) } |
prose |
获取正文并使用 auto_typography() 方法 将其转换为更美观、更易读的排版。 |
{ v|prose } |
|
round |
小数位数,类型 |
将数字舍入到指定小数位数。可传入 ceil 和 floor 来使用对应函数。 |
{ v|round(3) } { v|round(ceil) } |
strip_tags |
允许的标签 |
封装 PHP strip_tags。可传递允许的标签字符串。 |
{ v|strip_tags(<br>) } |
title |
将字符串按“标题格式”显示:每个单词首字母大写, 其余部分小写。 |
{ v|title } |
|
upper |
全部大写显示。 |
{ v|upper } |
"local_number" 过滤器的相关细节请参阅 PHP 的 NumberFormatter。
自定义过滤器
可通过编辑 app/Config/View.php 并在 $filters 数组中添加新条目来轻松创建自定义过滤器。
每个键是视图中调用过滤器的名称,值为任何有效的 PHP 可调用对象:
<?php
namespace Config;
use CodeIgniter\Config\View as BaseView;
class View extends BaseView
{
public $filters = [
'foo' => '\Some\Class::methodName',
'str_repeat' => 'str_repeat', // native php function
];
// ...
}
解析器插件
插件可扩展解析器,为每个项目添加自定义功能。可以是任何 PHP 可调用对象,
实现非常简单。在模板中,插件通过 {+ +} 标签指定:
{+ foo +} inner content {+ /foo +}
此示例展示了名为 foo 的插件。可操作开闭标签之间的任何内容。 在此示例中,可处理文本" inner content "。插件在伪变量 替换之前处理。
虽然插件通常由标签对组成(如上所示),但也可是单一标签,无需闭合标签:
{+ foo +}
起始标签还可包含可自定义插件工作方式的参数。参数以键/值对形式表示:
{+ foo bar=2 baz="x y" +}
参数也可是单一值:
{+ include somefile.php +}
内置插件
使用解析器时可用以下插件:
插件 |
参数 |
说明 |
示例 |
|---|---|---|---|
current_url |
current_url 辅助函数的别名。 |
|
|
previous_url |
previous_url 辅助函数的别名。 |
|
|
siteURL |
site_url 辅助函数的别名。 |
|
|
mailto |
email, title, attributes |
mailto 辅助函数的别名。 |
|
safe_mailto |
email, title, attributes |
safe_mailto 辅助函数的别名。 |
|
lang |
语言字符串 |
lang 辅助函数的别名。 |
|
validation_errors |
字段名(可选) |
返回指定字段的错误字符串(如已指定), 或返回所有验证错误。 |
|
route |
路由名称 |
route_to 辅助函数的别名。 |
|
csp_script_nonce |
csp_script_nonce 辅助函数的别名。 |
|
|
csp_style_nonce |
csp_style_nonce 辅助函数的别名。 |
|
注册插件
注册新插件最简便的方式是将其添加到 app/Config/View.php 的 $plugins 数组中。
键为模板文件中使用的插件名称。值为任何有效的 PHP 可调用对象,包括静态类方法:
<?php
namespace Config;
use CodeIgniter\Config\View as BaseView;
class View extends BaseView
{
public $plugins = [
'foo' => '\Some\Class::methodName',
];
// ...
}
也可使用闭包,但只能在配置文件的构造函数中定义:
<?php
namespace Config;
use CodeIgniter\Config\View as BaseView;
class View extends BaseView
{
public $plugins = [];
public function __construct()
{
$this->plugins['bar'] = static fn (array $params = []) => $params[0] ?? '';
parent::__construct();
}
// ...
}
如果可调用对象单独使用,则视为单一标签,而非开/闭标签对。 它将被插件的返回值替换:
<?php
namespace Config;
use CodeIgniter\Config\View as BaseView;
class View extends BaseView
{
public $plugins = [
'foo' => '\Some\Class::methodName',
];
// ...
}
/*
* Tag is replaced by the return value of Some\Class::methodName() static function.
* {+ foo +}
*/
如果可调用对象包裹在数组中,则视为开/闭标签对,可操作标签之间的内容:
<?php
namespace Config;
use CodeIgniter\Config\View as BaseView;
class View extends BaseView
{
public $plugins = [
'foo' => ['\Some\Class::methodName'],
];
// ...
}
// {+ foo +} inner content {+ /foo +}
注意事项
模板中未引用的替换参数将被忽略:
<?php
$template = 'Hello, {firstname} {lastname}';
$data = [
'title' => 'Mr',
'firstname' => 'John',
'lastname' => 'Doe',
];
return $parser->setData($data)->renderString($template);
// Result: Hello, John Doe
若模板中引用的替换参数未定义,结果将直接显示原始伪变量:
<?php
$template = 'Hello, {firstname} {initials} {lastname}';
$data = [
'title' => 'Mr',
'firstname' => 'John',
'lastname' => 'Doe',
];
return $parser->setData($data)->renderString($template);
// Result: Hello, John {initials} Doe
若在预期接收数组(即变量对)时提供了字符串参数, 起始标签会被替换,但闭合标签将无法正确渲染:
<?php
$template = 'Hello, {firstname} {lastname} ({degrees}{degree} {/degrees})';
$data = [
'degrees' => 'Mr',
'firstname' => 'John',
'lastname' => 'Doe',
'titles' => [
['degree' => 'BSc'],
['degree' => 'PhD'],
],
];
return $parser->setData($data)->renderString($template);
// Result: Hello, John Doe (Mr{degree} {/degrees})
视图片段
在视图中实现循环效果并非必须使用变量对。 也可将原本属于变量对内部的内容提取为视图片段, 转而在控制器中控制循环逻辑,而非在视图中处理。
以下示例在视图中控制迭代:
$template = '<ul>{menuitems}
<li><a href="{link}">{title}</a></li>
{/menuitems}</ul>';
$data = [
'menuitems' => [
['title' => 'First Link', 'link' => '/first'],
['title' => 'Second Link', 'link' => '/second'],
]
];
return $parser->setData($data)->renderString($template);
结果:
<ul>
<li><a href="/first">First Link</a></li>
<li><a href="/second">Second Link</a></li>
</ul>
以下示例在控制器中控制迭代, 使用视图片段:
<?php
$temp = '';
$template1 = '<li><a href="{link}">{title}</a></li>';
$data1 = [
['title' => 'First Link', 'link' => '/first'],
['title' => 'Second Link', 'link' => '/second'],
];
foreach ($data1 as $menuItem) {
$temp .= $parser->setData($menuItem)->renderString($template1);
}
$template2 = '<ul>{menuitems}</ul>';
$data = [
'menuitems' => $temp,
];
return $parser->setData($data)->renderString($template2);
结果:
<ul>
<li><a href="/first">First Link</a></li>
<li><a href="/second">Second Link</a></li>
</ul>
类参考
- class CodeIgniter\View\Parser
- render($view[, $options[, $saveData]])
- 参数:
$view (
string) -- 视图源文件的文件名$options (
array) -- 选项数组,以键/值对表示$saveData (
boolean) -- 如果为 true,将保存数据以供其他调用使用;如果为 false,将在渲染视图后清除数据。
- 返回:
所选视图的渲染文本
- 返回类型:
string
基于文件名和已设置的数据构建输出:
<?php return $parser->render('myview');
支持的选项:
cache- 保存视图结果的时长(秒)cache_name- 用于保存/获取缓存视图结果的 ID;默认为视图路径cascadeData- 如果为 true,发生嵌套或循环替换时,生效的数据对将传递到内部saveData- 如果为 true,视图数据参数将保留以供后续使用
首先执行条件替换,然后对每个数据对执行剩余替换。
- renderString($template[, $options[, $saveData]])
- 参数:
$template (
string) -- 以字符串形式提供的视图源$options (
array) -- 选项数组,以键/值对表示$saveData (
boolean) -- 如果为 true,将保存数据以供其他调用使用;如果为 false,将在渲染视图后清除数据。
- 返回:
所选视图的渲染文本
- 返回类型:
string
基于提供的模板源和已设置的数据构建输出:
<?php return $parser->renderString('<ul><li>Item 1</li><li>Item 2</li></ul>');
支持的选项及行为同上。
- setData([$data[, $context = null]])
- 参数:
$data (
array) -- 视图数据数组,以键/值对表示$context (
string) -- 数据转义使用的上下文。
- 返回:
Renderer 实例,用于方法链
- 返回类型:
CodeIgniter\View\RendererInterface
一次性设置多组视图数据:
<?php $parser->setData(['name' => 'George', 'position' => 'Boss']);
支持的转义上下文:html、css、js、url、attr 或 raw。 如果为 'raw',则不进行转义。
- setVar($name[, $value = null[, $context = null]])
- 参数:
$name (
string) -- 视图数据变量名$value (
mixed) -- 此视图数据的值$context (
string) -- 数据转义使用的上下文。
- 返回:
Renderer 实例,用于方法链
- 返回类型:
CodeIgniter\View\RendererInterface
设置单个视图数据:
<?php $parser->setVar('name', 'Joe', 'html');
支持的转义上下文:html、css、js、url、attr 或 raw。 如果为 'raw',则不进行转义。
- setDelimiters($leftDelimiter = '{', $rightDelimiter = '}')
- 参数:
$leftDelimiter (
string) -- 替换字段的左分隔符$rightDelimiter (
string) -- 替换字段的右分隔符
- 返回:
Renderer 实例,用于方法链
- 返回类型:
CodeIgniter\View\RendererInterface
覆盖替换字段的分隔符:
<?php $parser->setDelimiters('[', ']');
- setConditionalDelimiters($leftDelimiter = '{', $rightDelimiter = '}')
- 参数:
$leftDelimiter (
string) -- 条件语句的左分隔符$rightDelimiter (
string) -- 条件语句的右分隔符
- 返回:
Renderer 实例,用于方法链
- 返回类型:
CodeIgniter\View\RendererInterface
覆盖条件分隔符:
<?php $parser->setConditionalDelimiters('{%', '%}');