☰
Twig `mapping` 测试:判断 PHP 关联数组与对象的模板写法指南
2026/9/26 10:00:37 网站建设 项目流程
  • 后端

【免费下载链接】Twig

Twig, the flexible, fast, and secure template language for PHP

项目地址:https://gitcode.com/gh_mirrors/tw/Twig
点击查看免费下载

导读

本文基于 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 %}

这段代码演示了两个要点:

  1. 用花括号字面量定义映射:{alice: "Alice Dupond", bob: "Bob Smith"}创建了一个以字符串为键、字符串为值的映射。键名alice、bob是"名称(name)"形式,Twig 会将其等价为字符串字面量'alice'、'bob';
  2. 用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); }

逐行解读其判定逻辑:

  1. ArrayObject归一化:如果值是\ArrayObject,先转为普通数组再判断,保证该类型的容器与原生数组行为一致;
  2. 可遍历对象归一化:如果值是\Traversable(如\ArrayIterator、生成器等),用iterator_to_array()转为数组;
  3. 最终判定条件:
    • 是数组且不是列表(!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 mappingis sequence
['a', 'b'](列表)falsetrue
['foo' => 'bar'](关联数组)truefalse
[1 => 'a', 3 => 'b'](键不连续)truefalse
new \stdClass()truefalse
'hello'(字符串)falsefalse

而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

项目地址:https://gitcode.com/gh_mirrors/tw/Twig
点击查看免费下载
上一篇:Skill Seekers实战教程:10步创建React、Godot、Django等流行框架技能
下一篇:EnvFile开源项目使用教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询