- 后端
【免费下载链接】Twig
Twig, the flexible, fast, and secure template language for PHP
导读
本文基于 Twig 模板引擎官方文档中关于mapping测试的说明(doc/tests/mapping.rst),深入讲解如何在 Twig 模板中判断一个变量是否为"映射(mapping)",并结合仓库源码中的核心实现(CoreExtension.php)与测试用例(tests/Fixtures/tests/mapping.test)进行底层原理剖析。读完本文,你将掌握mapping测试的语法、与sequence/iterable测试的边界差异,以及它在for循环等常见场景中的实战用法。
什么是mapping测试
Twig 提供了丰富的"测试(test)"运算符,用于在模板中对变量进行类型与结构判断。其中mapping测试用于检查一个变量是否为映射(mapping)。
在 Twig 语境下:
- 序列(sequence):键为连续整数(0、1、2……)的数组,即 PHP 中的"列表(list)";
- 映射(mapping):键为字符串或非连续整数的数组,即 PHP 中的"关联数组",以及任意对象(object)。
mapping测试在模板中的语法为variable is mapping,返回布尔值,可直接用于{% if %}、{% set %}、三元表达式等场景。
基本语法与官方示例
mapping测试的官方文档(doc/tests/mapping.rst)给出了如下示例:
{% set users = {alice: "Alice Dupond", bob: "Bob Smith"} %} {# evaluates to true if the users variable is a mapping #} {% if users is mapping %} {% for key, user in users %} {{ key }}: {{ user }}; {% endfor %} {% endif %}这段代码演示了两个要点:
- 用花括号字面量定义映射:
{alice: "Alice Dupond", bob: "Bob Smith"}创建了一个以字符串为键、字符串为值的映射。键名alice、bob是"名称(name)"形式,Twig 会将其等价为字符串字面量'alice'、'bob'; - 用
is mapping判断后安全遍历:只有当users确实是映射时才进入循环,配合{% for key, user in users %}可以同时取出键与值,输出alice: Alice Dupond; bob: Bob Smith;。
映射字面量的写法细节
从源码 LiteralExpressionParser.php 可以看出,Twig 解析映射字面量时支持四种键的写法:
- 数字:
{1: "one"}; - 字符串:
{'a': "x"}; - 名称(等价于字符串):
{alice: "x"}; - 括号包裹的任意表达式:
{(1 + 2): "x"}。
另外,{a}是{a: a}的简写(即键与值引用同名变量),映射支持尾随逗号,还支持...展开运算符,例如{firstName: 'Ryan', ...morePersonalDetails}(见 spread_mapping_operator.test)。解析错误时(如键未加引号且不是合法名称/数字/括号表达式),会抛出语法错误提示。
底层实现:testMapping到底判断了什么
mapping测试在核心扩展中注册,见 CoreExtension.php:
new TwigTest('mapping', [self::class, 'testMapping'], ['always_allowed_in_sandbox' => true]),其实现为(src/Extension/CoreExtension.php#L1503-L1524):
public static function testMapping($value): bool { if ($value instanceof \ArrayObject) { $value = $value->getArrayCopy(); } if ($value instanceof \Traversable) { $value = iterator_to_array($value); } return (\is_array($value) && !array_is_list($value)) || \is_object($value); }逐行解读其判定逻辑:
ArrayObject归一化:如果值是\ArrayObject,先转为普通数组再判断,保证该类型的容器与原生数组行为一致;- 可遍历对象归一化:如果值是
\Traversable(如\ArrayIterator、生成器等),用iterator_to_array()转为数组; - 最终判定条件:
- 是数组且不是列表(
!array_is_list($value))——即关联数组判定为映射; - 或者是对象——任意 PHP 对象都判定为映射。
- 是数组且不是列表(
需要注意:array_is_list()是 PHP 8.1+ 的函数,判定数组的键是否为从 0 开始的连续整数。因此['foo', 'bar']是序列而非映射,而['foo' => 'bar']、[1 => 'a', 3 => 'b'](键不连续)都被判定为映射。
与sequence测试的对比
mapping与同族的sequence测试恰好互补(sequence.rst、CoreExtension.php#L1490-L1501):
| 变量 | is mapping | is sequence |
|---|---|---|
['a', 'b'](列表) | false | true |
['foo' => 'bar'](关联数组) | true | false |
[1 => 'a', 3 => 'b'](键不连续) | true | false |
new \stdClass() | true | false |
'hello'(字符串) | false | false |
而iterable测试(iterable.rst)则更宽泛,只要变量是数组或可遍历对象即为true,无论其是序列还是映射。
边界行为:空数组与各种 PHP 类型
仓库自带的集成测试 tests/Fixtures/tests/mapping.test 覆盖了完整边界情况:
{{ empty is mapping ? 'ok' : 'ko' }} {{ sequence is mapping ? 'ok' : 'ko' }} {{ empty_array_obj is mapping ? 'ok' : 'ko' }} {{ sequence_array_obj is mapping ? 'ok' : 'ko' }} {{ mapping_array_obj is mapping ? 'ok' : 'ko' }} {{ obj is mapping ? 'ok' : 'ko' }} {{ mapping is mapping ? 'ok' : 'ko' }} {{ string is mapping ? 'ok' : 'ko' }}测试数据与期望输出如下:
| 变量(PHP 值) | is mapping结果 |
|---|---|
[](空数组) | ko(false,空数组是空列表) |
['foo', 'bar', 'baz'](列表) | ko |
new \ArrayObject()(空) | ko |
new \ArrayObject(['foo', 'bar'])(列表型) | ko |
new \ArrayObject(['foo' => 'bar'])(关联型) | ok |
new \stdClass() | ok |
['foo' => 'bar', 'bar' => 'foo'](关联数组) | ok |
'test'(字符串) | ko |
从中可以总结出几条实用结论:
- 空数组
[]不是映射(它被当作空序列),判断空容器时应配合empty测试使用; ArrayObject的表现取决于其内部内容:关联内容为映射,列表内容为序列;- 任何非
Traversable的对象都是映射(如stdClass、实体对象等); - 字符串、数字、布尔值等标量都不是映射。
实战场景:在模板中安全地遍历映射
mapping测试最常见的价值在于让模板对不同数据结构给出不同的渲染策略,避免在非映射值上盲目调用键值遍历。典型写法:
{% if user is mapping %} {# 关联数组 / 对象:按 键 => 值 遍历 #} {% for key, value in user %} <dt>{{ key }}</dt> <dd>{{ value }}</dd> {% endfor %} {% else %} {# 标量或其他类型:直接输出 #} <p>{{ user }}</p> {% endif %}结合{% set %}与字面量,可以构造"可配置"的模板逻辑:
{% set defaults = {theme: "light", locale: "en"} %} {% set settings = settings is defined and settings is mapping ? defaults|merge(settings) : defaults %} {% for key, value in settings %} {{ key }} = {{ value }} {% endfor %}此外,由于mapping测试被标记为always_allowed_in_sandbox(CoreExtension.php#L327),在 Twig 沙箱(sandbox)模式下也可无条件使用,不受安全策略白名单限制,适合在渲染不可信模板的场景(如render_sandboxed)中做防御性判断。
小结
mapping测试的语法为variable is mapping,用于判断变量是否为关联数组或对象;- 判定逻辑定义在 src/Extension/CoreExtension.php#L1503-L1524:
ArrayObject/Traversable先归一化,再以array_is_list()区分关联与列表,对象恒为映射; - 边界行为经 tests/Fixtures/tests/mapping.test 充分验证:空数组、列表、字符串均非映射;关联数组、关联型
ArrayObject、stdClass均为映射; - 与
sequence测试互补、比iterable测试更精确,是处理键值数据、实现差异化渲染的可靠工具。
- 后端
【免费下载链接】Twig
Twig, the flexible, fast, and secure template language for PHP
相关推荐
Hugo 模板函数 reflect.IsSite 详解:判断值是否为 Site 对象
Hugo 模板函数 reflect.IsSite 详解:判断值是否为 Site 对象 导读 reflect.IsSite 是 Hugo 在 v0.154.0 中
开发工具前端CLIHugo 页面方法 Eq:判断两个 Page 对象是否相等的方法与模板实践
Hugo 页面方法 Eq:判断两个 Page 对象是否相等的方法与模板实践 导读 Page.Eq 是 Hugo 模板系统中用于判断"两个 Page 对象是否指向
开发工具前端CLIRawTherapee工作流程优化:从导入到导出的高效照片处理方案
RawTherapee工作流程优化:从导入到导出的高效照片处理方案 RawTherapee是一款功能强大的跨平台原始照片处理程序,能够帮助摄影爱好者和专业人士实
桌面应用图形学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考