fi1a / format
PHP 字符串模板格式化
Requires
- php: ^7.3 || ^8
- ext-mbstring: *
- fi1a/collection: ^2.0
- fi1a/tokenizer: ^1.1
Requires (Dev)
- captainhook/captainhook: ^5.4
- phpunit/phpunit: ^9.3
- slevomat/coding-standard: ^6.3
- squizlabs/php_codesniffer: ^3.5
- vimeo/psalm: ^4.3
README
此包提供了使用指定符功能进行字符串模板格式化的能力。
包功能
- 字符串和数字格式化;
- 日期和时间格式化(月份和星期几);
- 将特殊符号转换为HTML实体;
- 将HTML实体转换回相应符号;
- 内存大小格式化;
- 时间格式化;
- 数词后的词语变格;
- 电话号码格式化;
- 价格格式化;
- 条件结构(if, elseif, else, endif);
- 自定义指定符功能;
- 可以为指定符功能设置缩写;
- 可以将指定符功能链式应用于一个值。
安装
可以使用Composer将其作为依赖项安装。
composer require fi1a/format
使用
use Fi1a\Format\Formatter; Formatter::format('[{{user:login}}] - {{user:name}}', ['user' => ['name' => 'John', 'login' => 'john85']]); // [john85] - John Formatter::format('{{0}}, {{1}}',[1, 2]); // 1, 2 Formatter::format('{{}}, {{}}',[1, 2]); // 1, 2 Formatter::format('{{foo}}, {{foo}}, {{foo}}', ['foo' => 'bar',]); // bar, bar, bar
如果没有在值数组中找到键,则抛出异常 \Fi1a\Format\AST\Exception\NotFoundKey
use Fi1a\Format\Formatter; use Fi1a\Format\AST\Exception\NotFoundKey; try { Formatter::format('{{not_exists}}', []); } catch (NotFoundKey $exception) { }
所有值都通过 htmlspecialchars
函数转换为特殊HTML实体。要转换回,请使用指定符 unescape
或将第四个参数传递为 false
以取消自动将特殊符号转换为HTML实体的操作。
use Fi1a\Format\Formatter; Formatter::format('{{value}}', ['value' => '&']); // "&" Formatter::format('{{value|unescape}}', ['value' => '&']); // "&" // или Formatter::format('{{value}}', ['value' => '&'], [], false); // "&"
通过路径访问
字符串模板包含到关联数组中值的路径。
use Fi1a\Format\Formatter; Formatter::format('[{{user:login}}] - {{user:name}}', ['user' => ['name' => 'John', 'login' => 'john85']]); // [john85] - John
通过索引访问
字符串模板包含数组中值的索引。
use Fi1a\Format\Formatter; Formatter::format('{{0}}, {{1}}',[1, 2]); // 1, 2 Formatter::format('{{}}, {{}}',[3, 4]); // 3, 4
特殊符号转义
用于转义特殊符号的是反斜杠符号 "\"。
use Fi1a\Format\Formatter; Formatter::format('\\{{0\\}}', [0 => 'foo']); // {{0}} Formatter::format('{{0}}', [0 => 'foo']); // foo
要转义字符串中的所有特殊符号,可以使用 Fi1a\Format\Safe
类的 escape
函数。
use Fi1a\Format\Formatter; use Fi1a\Format\Safe; Formatter::format(Safe::escape(('{{0}}'), [0 => 'foo']); // {{0}}
要移除字符串中的转义特殊符号,可以使用 Fi1a\Format\Safe
类的 unescape
函数。
use Fi1a\Format\Formatter; use Fi1a\Format\Safe; Formatter::format(Safe::unescape(('\\{{0}}'), [0 => 'foo']); // foo
使用指定符功能
可用的指定符功能
- date - 日期和时间的格式化;
- sprintf - 字符串和数字的格式化;
- escape - 将特殊符号转换为HTML实体;
- unescape - 将特殊的HTML实体转换回相应符号;
- memory - 内存大小的格式化(x.x B, x.x KB, x.x MB, x.x GB, x.x TB, x.x PB);
- time - 时间的格式化(< 1 秒,x 秒,x 分钟,x 小时,x 天);
- declension - 数词后的词语变格;
- phone - 电话号码的格式化(+7(ddd)ddd-dddd);
- price - 价格的格式化。
指定符函数的指示应跟在键之后,用 "|" 分隔。
use Fi1a\Format\Formatter; Formatter::format('{{0|sprintf("04d")}}-{{1|sprintf("02d")}}-{{2|sprintf("02d")}}',[2016, 2, 27,]); // 2016-02-27
可以通过将动态指定符函数的修改器作为第三个参数传递给 format
函数来设置。
use Fi1a\Format\Formatter; Formatter::format('{{value|sprintf(modifier)}}', ['value' => 100.5], ['modifier' => '01.2f']); // 100.50
可以使用 "|" 分隔指定符函数链式指定,这时值将按顺序逐个传递给各个指定符函数。
use Fi1a\Format\Formatter; Formatter::format('{{value|unescape|sprintf|escape}}', ['value' => 'a&b']); // "a&b"
使用 sprintf 格式化 PHP 字符串和数字
sprintf 函数的指定符。指定符函数的指示应跟在键之后,用 "|" 分隔。
字符串和数字的格式化。在 sprintf 函数中使用的指定符。
- b - 将参数视为整数并以二进制表示打印。
- c - 将参数视为整数并以相应的ASCII表中的字符打印。
- d - 将参数视为整数并以带符号的整数打印。
- e - 参数被认为是科学记数法中的数(即 1.2e+2)。精度指定符设置小数点后的数字位数。在较早的版本中,它指定了总的有效数字位数(即小数点后少一个符号)。
- E - 与 e 指定符类似,但使用大写字母(即 1.2E+2)。
- f - 参数被认为是带小数的数(考虑区域设置)。
- F - 参数被认为是带小数的数(不考虑区域设置)。自 PHP 5.0.3 起可用。
- o - 参数被视为整数,并以八进制形式打印。
- s - 参数被视为字符串并打印为字符串。
- u - 参数被视为整数,并以无符号整数形式打印。
- x - 参数被视为整数,并以十六进制形式打印(字母将是小写)。
- X - 参数被视为整数,并以十六进制形式打印(字母将是大写)。
使用填充字符
use Fi1a\Format\Formatter; Formatter::format('{{0|sprintf("\'.9d")}}', [123]); // ......123 Formatter::format('{{0|sprintf("\'.09d")}}', [123]); // 000000123
带有前导零的整数
use Fi1a\Format\Formatter; Formatter::format('{{0|sprintf("04d")}}-{{1|sprintf("02d")}}-{{2|sprintf("02d")}}', [2020, 6, 7]); // 2020-06-07
PHP 日期和时间格式化
date 函数的指定符,日期和时间格式化。指定函数指定符应跟在带有分隔符 "|" 的键之后。
函数中使用的格式化修饰符
天
- d - 月份中的天,两位数字,带前导零(从 01 到 31)。
- D - 俄语星期几的文本表示(从 Пн 到 Вс)。
- j - 月份中的天,不带前导零(从 1 到 31)。
- l - 俄语星期几的完整名称(从 Понедельник 到 Воскресенье)。
- N - 根据 ISO 8601 标准的星期几的顺序号(从 1(星期一)到 7(星期日))。
- S - 英语的序数词后缀,两个字符(st, nd, rd 或 th。与 j 一起使用)。
- w - 星期几的顺序号(从 0(星期日)到 6(星期六))。
- z - 年中的顺序号(从 0 到 365)。
周
- W - 根据 ISO 8601 标准的年中的周顺序号;周从星期一开始,从 0 开始。例如:42(第 42 周)。
月
- F - 俄语月份的完整名称,例如“Января”或“Марта”(从“Января”到“Декабря”)。
- f - 俄语月份的完整名称,例如“Январь”或“Март”(从“Январь”到“Декабрь”)。
- m - 月份的顺序号,带前导零(从 01 到 12)。
- M - 俄语月份的缩写名称,3 个字符(从 “Янв” 到 “Дек”)。
- n - 月份的顺序号,不带前导零(从 1 到 12)。
- t - 指定月份中的天数(从 28 到 31)。
年
- L - 闰年的标志(如果是闰年则为 1,否则为 0。)。
- o - 根据 ISO 8601 标准的年号。它与 Y 有相同的值,除非 ISO 周号(W)属于前一年或后一年;在这种情况下,将使用该周的年份。(例如:1999 或 2003)。
- X - 扩展的完整数字年份表示,至少 4 位数字,对于公元前年份用 - 表示,对于公元年份用 + 表示。(例如:-0055, +0787, +1999, +10191)。
- x - 扩展的完整数字表示,如果需要则使用扩展的完整数字表示,如果可能则使用标准完整的数字表示(例如,Y)。至少 4 位数字。对于公元前年份使用前缀 -。对于公元前(包括)10000 年的年份使用前缀 +。(例如:-0055, 0787, 1999, +10191)。
- Y - 完整的年份表示,至少 4 位数字,对于公元前年份用 - 表示。(例如:-0055, 0787, 1999, 2003, 10191。)
- y - 年号,两位数字(例如:99, 03)。
时间
- a - Ante meridiem(拉丁语“上午”)或 Post meridiem(拉丁语“下午”)的小写形式(am 或 pm)。
- A - Ante meridiem 或 Post meridiem的大写形式(AM 或 PM)。
- B - 互联网时间格式(另一种时间系统)(000 至 999)。
- g - 12小时制时钟(不带前导零)(1 至 12)。
- G - 24小时制时钟(不带前导零)(0 至 23)。
- h - 12小时制时钟(带前导零)(01 至 12)。
- H - 24小时制时钟(带前导零)(00 至 23)。
- i - 带前导零的分钟(00 至 59)。
- s - 带前导零的秒(00 至 59)。
- u - 微秒。例如:654321。
- v - 微秒。例如:654。
- v - 微秒。例如:654。
时区
- e - 时区标识符(例如:UTC,GMT,Atlantic/Azores)。
- I - 夏令时标志(如果日期符合夏令时,则为 1,否则为 0)。
- O - 与格林尼治时间的差值,小时和分钟之间没有冒号(例如:+0200)。
- P - 与格林尼治时间的差值,小时和分钟之间有冒号(例如:+02:00)。
- p - 与 P 相同,但将 +00:00 返回为 Z(从 PHP 8.0.0 开始可用)(例如:+02:00)。
- T - 如果已知,则返回时区的缩写;否则返回格林尼治时间的偏移量。(例如:EST,MDT,+05)。
- Z - 时区偏移量(秒)。对于位于 UTC 西部的时区,返回负数,而对于位于 UTC 东部的时区,返回正数。(从 -43200 到 50400)。
完整日期/时间
- c - ISO 8601 格式的日期(2004-02-12T15:19:21+00:00)。
- r - RFC 222/RFC 5322 格式的日期(例如:Thu, 21 Dec 2000 16:01:07 +0200)。
- U - 自 Unix 纪元(1970 年 1 月 1 日 00:00:00 GMT)以来经过的秒数。(另请参阅 time())。
日期格式化键的俄语
- F - 俄语月份的完整名称,例如“Января”或“Марта”(从“Января”到“Декабря”)。
- f - 俄语月份的完整名称,例如“Январь”或“Март”(从“Январь”到“Декабрь”)。
- M - 俄语月份的缩写名称,3 个字符(从 “Янв” 到 “Дек”)。
- D - 俄语中星期几的文本表示(从“Пн”到“Вс”)。
- l - 俄语中星期几的完整名称(从“Понедельник”到“Воскресенье”)。
use Fi1a\Format\Formatter; Formatter::format('{{foo|date("d.m.Y")}}', ['foo' => time()]); // 28.09.2022 echo Formatter::format('{{|date("d F Y")}}', [time()]); // 18 Октября 2022 echo Formatter::format('{{|date("f")}}', [time()]); // Октябрь
设置默认格式
use Fi1a\Format\Specifier\Date; use Fi1a\Format\Formatter; Formatter::format('{{foo|date}}', ['foo' => time()]); // 28.09.2022 07:06:00 Date::setDefaultFormat('d.m.Y'); Formatter::format('{{foo|date}}', ['foo' => time()]); // 28.09.2022
函数转义指定符
将特殊符号转换为 HTML 实体 {{|escape(flags, encoding, doubleEncode)}}
use Fi1a\Format\Formatter; Formatter::format('{{|escape}}', ['"test"']); // "test"
函数非转义指定符
将特殊的 HTML 实体转换回相应的符号 {{|escape(flags)}}
use Fi1a\Format\Formatter; Formatter::format('{{|unescape}}', ['&quot;test&quot;']); // "test"
PHP 内存大小格式化
函数内存指定符。内存大小的格式化。
use Fi1a\Format\Formatter; Formatter::format('{{|memory}}', [1024]); // 1.0 КБ Formatter::format('{{|memory("B")}}', [1024]); // 1024.0 Б Formatter::format('{{|memory}}', [1024 * 1024]); // 1.0 МБ
PHP 时间格式化
函数时间指定符,时间格式化。
函数指定符的可用参数
- seconds - 格式化结果为秒;
- minutes - 格式化结果为分钟;
- hours - 格式化结果为小时;
- days - 格式化结果为天。
如果指定符的参数未指定,则按最适合的尺寸进行格式化。
use Fi1a\Format\Formatter; Formatter::format('{{|time}}', [60 * 60]); // 1 ч. Formatter::format('{{|time("minutes")}}', [60 * 60]); // 60 мин. Formatter::format('{{|time}}', [2 * 24 * 60 * 60]); // 2 д.
PHP 数词后名词的屈折
使用指定符函数 declension
可以对数字后的名词进行屈折。例如:1 год,2 года,5 лет。
use Fi1a\Format\Formatter; Formatter::format('{{year}} {{year|declension("год", "года", "лет")}}', ['year' => 1]); // 1 год Formatter::format('{{year}} {{year|declension("год", "года", "лет")}}', ['year' => 2]); // 2 года Formatter::format('{{year}} {{year|declension("год", "года", "лет")}}', ['year' => 5]); // 5 лет
指定符屈折函数的修饰符
- 第一个修饰符指定单数值的文本;
- 第二个指定 2-4 个值的;
- 第三个指定所有其他值。
电话号码格式化
使用指定符函数 phone
可以根据给定的掩码(格式)格式化电话号码。
use Fi1a\Format\Formatter; Formatter::format('{{value|phone("+7(ddd)ddd-dddd")}}', ['value' => '+79228223576']); // +7(922)822-3576 Formatter::format('{{value|phone("+7(ddd)ddd-dddd")}}', ['value' => '9228223576']); // +7(922)822-3576 Formatter::format('{{value|phone("(dddd)dd-dd-dd")}}', ['value' => '(6783)44-00-44']); // (6783)44-00-44
数字格式化
使用指定符函数 number
可以格式化数字。
指定符数字格式化函数的修饰符
- int decimals (2) - 小数点后的数字;
- string decimalSeparator ('.') - 小数部分的分隔符;
- string thousandsSeparator ('') - 千位分隔符;
- bool allowZeroDecimal (false) - 允许在分数部分末尾输出 0。
use Fi1a\Format\Formatter; Formatter::format('{{value|number}}', ['value' => 100.00]); // 100 Formatter::format( '{{value|number(decimals, decimalSeparator, thousandsSeparator, allowZeroDecimal)}}', ['value' => 100100.00], [ 'decimals' => 2, 'decimalSeparator' => '.', 'thousandsSeparator' => ' ', 'allowZeroDecimal' => true, ] ); // 100 100.00 Formatter::format( '{{value|number(decimals, decimalSeparator, thousandsSeparator, allowZeroDecimal)}}', ['value' => 100100.00], [ 'decimals' => 2, 'decimalSeparator' => '.', 'thousandsSeparator' => ' ', 'allowZeroDecimal' => false, ] ); // 100 100 Formatter::format( '{{value|number(decimals, decimalSeparator, thousandsSeparator, allowZeroDecimal)}}', ['value' => 100100.12], [ 'decimals' => 2, 'decimalSeparator' => '.', 'thousandsSeparator' => ' ', 'allowZeroDecimal' => false, ] ); // 100 100.12
为了简化输入格式化数字的指定符函数,请使用缩写。
use Fi1a\Format\Formatter; Formatter::addShortcut('number', 'number(2, ".", " ", false)'); Formatter::format('{{value|~number}}', ['value' => 100100.12]); // 100 100.12 Formatter::format('{{value|~number}}', ['value' => 100100]); // 100 100
PHP 格式化价格
通过使用指定符函数 price
可以格式化价格
价格格式化指定符函数的修饰符
- int decimals (2) - 小数点后的数字;
- string decimalSeparator ('.') - 小数部分的分隔符;
- string thousandsSeparator ('') - 千位分隔符;
- bool allowZeroDecimal (false) - 允许在分数部分末尾输出 0。
- int round (null) - 四舍五入价格。传递常量:PHP_ROUND_HALF_UP - 当下一个数字在中间时向上取整,PHP_ROUND_HALF_DOWN - 当下一个数字在中间时向下取整。
- int roundPrecision (0) - 进行四舍五入的十进制位数。
- bool floor (false) - 向下取整(到最接近的整数)。
use Fi1a\Format\Formatter; Formatter::format('{{value|price}} руб.', ['value' => 100.00]); // 100 руб. Formatter::format( '{{value|price(decimals, decimalSeparator, thousandsSeparator, allowZeroDecimal)}} руб.', ['value' => 100100.00], [ 'decimals' => 2, 'decimalSeparator' => '.', 'thousandsSeparator' => ' ', 'allowZeroDecimal' => true, ] ); // 100 100.00 руб. Formatter::format( '{{value|price(decimals, decimalSeparator, thousandsSeparator, allowZeroDecimal)}} руб.', ['value' => 100100.00], [ 'decimals' => 2, 'decimalSeparator' => '.', 'thousandsSeparator' => ' ', 'allowZeroDecimal' => false, ] ); // 100 100 руб. Formatter::format( '{{value|price(decimals, decimalSeparator, thousandsSeparator, allowZeroDecimal)}} руб.', ['value' => 100100.12], [ 'decimals' => 2, 'decimalSeparator' => '.', 'thousandsSeparator' => ' ', 'allowZeroDecimal' => false, ] ); // 100 100.12 руб. Formatter::format( '{{value|price(decimals, decimalSeparator, thousandsSeparator, allowZeroDecimal, round, roundPrecision)}} руб.', ['value' => 100100.5], [ 'decimals' => 2, 'decimalSeparator' => '.', 'thousandsSeparator' => ' ', 'allowZeroDecimal' => false, 'round' => PHP_ROUND_HALF_UP, 'roundPrecision' => 0, ] ); // 100 101 руб. Formatter::format( '{{value|price(decimals, decimalSeparator, thousandsSeparator, allowZeroDecimal, round, roundPrecision)}} руб.', ['value' => 100100.5], [ 'decimals' => 2, 'decimalSeparator' => '.', 'thousandsSeparator' => ' ', 'allowZeroDecimal' => false, 'round' => PHP_ROUND_HALF_DOWN, 'roundPrecision' => 0, ] ); // 100 100 руб. Formatter::format( '{{value|price(decimals, decimalSeparator, thousandsSeparator, allowZeroDecimal, round, roundPrecision, floor)}} руб.', ['value' => 100100.6], [ 'decimals' => 0, 'decimalSeparator' => '.', 'thousandsSeparator' => ' ', 'allowZeroDecimal' => false, 'round' => null, 'roundPrecision' => 0, 'floor' => true, ] ); // 100 100 руб.
为了简化输入价格格式化指定符函数,请使用缩写。
use Fi1a\Format\Formatter; Formatter::addShortcut('price', 'price(0, ".", " ", false, null, 0, true)'); Formatter::format('{{value|~price}} руб.', ['value' => 100100.6]); // 100 100 руб.
条件结构
以下条件结构可用:if, elseif, else, endif
。在数组值中没有键时不会抛出异常。
use Fi1a\Format\Formatter; Formatter::format('{{if(foo)}}{{bar}}{{else}}false{{endif}}', ['foo' => true, 'bar' => 'bar']); // bar Formatter::format('{{if(not_exists)}}{{foo}}{{elseif(bar)}}{{bar}}{{endif}}', ['foo' => 'foo', 'bar' => 'bar']); // bar
在条件结构中可以使用指定符函数
use Fi1a\Format\Formatter; Formatter::format('{{if(value|unescape==="a&b")}}{{value}}{{endif}}', ['value' => 'a&b']); // "a&b"
添加指定符函数
指定符函数的类必须实现接口 \Fi1a\Format\Specifier\ISpecifier
通过方法 Fi1a\Format\Formatter::addSpecifier()
添加新的指定符函数。
use Fi1a\Format\Formatter; Formatter::addSpecifier('spf', Specifier::class); // true Formatter::format('{{foo|spf(true)}}', ['foo' => 'foo']); // foo
缩写
为了简化输入指定符函数,可以使用缩写。使用方法是通过 Formatter::addShortcut
方法添加缩写名称。要使用缩写,请从符号 "~" 开始输入名称。
use Fi1a\Format\Formatter; Formatter::addShortcut('dt', 'date("d.m.Y", "en")'); Formatter::format('{{foo|~dt}}', ['foo' => time()]); // 29.09.2022