twig自定义extension比filter更适合货币/日期格式化,因其可注入intldateformatter等服务并缓存多locale的numberformatter实例,避免setlocale进程级副作用和全局变量状态污染,支持运行时动态时区与语言切换。

Twig 自定义 Extension 为什么比 filter 更适合货币/日期格式化?
因为 Twig_SimpleFilter 只能做纯函数式转换,而货币和日期格式化往往依赖运行时上下文(比如当前语言、时区、货币符号位置),必须通过 Twig_Extension 注入服务或配置。直接写 date 或 money_format 这类 filter 容易硬编码 locale,一换环境就出错。
- Twig filter 无法访问容器(如 Symfony 的
IntlDateFormatter或CurrencyConverter) - 自定义 Extension 可在构造时传入
IntlDateFormatter实例,复用已配置的时区与 locale - 避免在每个模板里重复调用
setlocale(LC_MONETARY, ...)—— PHP 的 setlocale 是进程级副作用,多线程下不安全
如何注册一个支持多 locale 的 currency_filter?
别用 number_format 硬拼小数点和千分位,它不认 locale。正确做法是用 NumberFormatter,并在 Extension 构造函数中缓存 formatter 实例:
class CurrencyExtension extends Twig_Extension
{
private $formatters = [];
public function __construct(array $locales = ['en_US', 'zh_CN'])
{
foreach ($locales as $locale) {
$this->formatters[$locale] = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
}
}
public function getFilters()
{
return [
new \Twig_SimpleFilter('currency', [$this, 'formatCurrency'], ['is_safe' => ['html']]),
];
}
public function formatCurrency($amount, $currency = 'USD', $locale = 'en_US')
{
if (!isset($this->formatters[$locale])) {
$locale = 'en_US';
}
return $this->formatters[$locale]->formatCurrency($amount, $currency);
}
}
注意:formatCurrency 返回的是带 HTML 实体的字符串(如 ¥),所以必须加 ['is_safe' => ['html']],否则 Twig 会自动转义。
date_filter 怎么避免时区错乱?
常见错误是直接对 DateTime 对象调用 format() —— 它永远按对象自带时区输出,而模板渲染时可能需要统一显示为用户本地时间或服务器时间。正确做法是强制 normalize 到目标时区:
- Extension 构造时传入默认时区(如
new \DateTimeZone('Asia/Shanghai')) - filter 参数支持可选
$timezone,优先用它,否则 fallback 到默认 - 不要用
date_modify()改原始对象,要用clone+setTimezone()
示例片段:
public function formatDate(\DateTimeInterface $date, string $format = 'Y-m-d H:i', ?\DateTimeZone $timezone = null)
{
$tz = $timezone ?: $this->defaultTimezone;
$normalized = clone $date;
$normalized->setTimezone($tz);
return $normalized->format($format);
}
如果传入的是字符串(如 '2023-01-01'),先用 new \DateTimeImmutable($input, new \DateTimeZone('UTC')) 初始化,再 set timezone —— 否则 PHP 会按本地时区解析,结果不可控。
为什么不能把 formatter 实例存在 Twig 全局变量里?
因为 Twig 全局变量是静态的,所有模板共享同一个引用。一旦某个模板调用 setLocale() 或 setAttribute() 改了 formatter 状态,后续渲染全乱套。真实踩坑场景:
- 用户 A 页面用
zh_CN格式化金额 → formatter 内部 locale 被设为zh_CN - 用户 B 页面紧接着用
en_US→ 仍沿用上次的 locale,输出 ¥100.00 而非 $100.00 - 并发请求下更危险:PHP-FPM worker 复用导致 formatter 状态污染
所以必须在 Extension 构造阶段就为每个 locale 创建独立 NumberFormatter 实例,并只读使用 —— 它们是 immutable 的,不会被外部修改。











