ahmedzidan / format
Requires
- php: ^7.0 || ^8.0
- ext-pcre: *
- ahmedzidan/php-core: ^1.0
- ahmedzidan/php-value: ^1.0
Requires (Dev)
- phpunit/phpunit: ^6.0
This package is auto-updated.
Last update: 2024-09-21 02:38:06 UTC
README
格式
format库提供了格式化特殊字符串模式的功能。其实现类似于sprintf
和msgfmt_format_message
,但具有各种独特且非常实用的功能。
安装
打开终端,进入您的项目目录,并执行以下命令以添加此库到依赖项
composer require fleshgrinder/format
此命令需要您全局安装了Composer,如Composer文档中的安装章节所述。
用法
此库提供了一个用于格式化字符串模式的静态方法。格式方法的一些示例:
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('Hello, {}!', ['World']) === 'Hello, World!'); assert(Formatter::format('The number is {}', [1000]) === 'The number is 1,000'); assert(Formatter::format('{}', [[1, 2, 3]]) === '1, 2, 3'); assert(Formatter::format('{value}', ['value' => 42]) === '42'); assert(Formatter::format('{} {}', [1, 2]) === '1 2'); assert(Formatter::format('{.3}', [0.123456789]) === '0.123'); assert(Formatter::format('{.2:and}', [[1, 2, 3]]) === '1.00, 2.00, and 3.00'); assert(Formatter::format('{#b}', [2]) === '0b10'); assert(Formatter::format('{:?}', [tmpfile()]) === 'stream resource');
占位符
每个占位符可以指定它引用的参数值,如果省略,则假定是“下一个参数”。例如,模式{} {} {}
将接受三个参数,它们将以相同的顺序格式化。然而,模式{2} {1} {0}
将按相反的顺序格式化参数。
一旦开始混合两种类型的定位占位符,事情可能会变得有些棘手。可以将“下一个参数”指定符视为参数的迭代器。每次看到“下一个参数”指定符时,迭代器都会前进。这导致以下行为
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{1} {} {0} {}', [1, 2]) === '2 1 1 2');
在看到第一个{}
之前,参数的内部迭代器尚未前进,因此打印第一个参数。然后,在到达第二个{}
时,迭代器已向前移动到第二个参数。本质上,明确指定其参数的占位符不会影响未指定参数占位符的占位符。
占位符不仅限于数字,还可以通过名称访问它们。名称限于A-Za-z0-9_-
字符,这确保了最高的兼容性,并且应该与PHP世界中99%的所有关联数组键匹配
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{placeholder}', ['placeholder' => 'test']) === 'test'); assert(Formatter::format('{placeholder} {}', ['placeholder' => 2, 1]) === '2 1'); assert(Formatter::format('{a} {c} {b}', ['a' => 'a', 'b' => 'b', 'c' => 3]) === 'a 3 b');
必须使用模式使用所有占位符,但不是所有参数。MissingPlaceholderException
会在参数中缺少占位符时抛出
<?php use Fleshgrinder\Core\Formatter; use Fleshgrinder\Core\Formatter\MissingPlaceholderException; try { Formatter::format('{}', []); } catch (MissingPlaceholderException $e) { assert($e->getMessage() === 'Placeholder `0` missing from arguments, got empty array'); }
可以在模式中多次引用相同的参数,包括不同的修饰符(将在以下部分中解释)。
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{a} {a} {a}', ['a' => 'foo']) === 'foo foo foo');
类型修饰符
可以使用类型修饰符来打印参数值的类型,而不是尝试格式化该值
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{:?}', []) === 'void'); assert(Formatter::format('{:?}', [[]]) === 'array'); assert(Formatter::format('{:?}', [true]) === 'boolean'); assert(Formatter::format('{:?}', [false]) === 'boolean'); assert(Formatter::format('{:?}', [1.2]) === 'float'); assert(Formatter::format('{:?}', [1]) === 'integer'); assert(Formatter::format('{:?}', [null]) === 'null'); assert(Formatter::format('{:?}', [new stdClass]) === 'stdClass'); assert(Formatter::format('{:?}', [new DateTime]) === 'DateTime'); assert(Formatter::format('{:?}', [tmpfile()]) === 'stream resource'); assert(Formatter::format('{:?}', ['']) === 'string');
当然可以结合位置和命名占位符
<?php use Fleshgrinder\Core\{Formatter, Value}; assert( Formatter::format( 'Expected argument of type {expected}, got {actual:?}', ['expected' => Value::TYPE_ARRAY, 'actual' => ''] ) === 'Expected argument of type array, got string' );
使用fleshgrinder/value
库来获取值的类型,这意味着实际上类型名称在命名和大小写上是一致的。然而,由于包括资源的类型,资源的输出略有扩展
<?php use Fleshgrinder\Core\{Formatter, Value}; $r = tmpfile(); assert(Value::getType($r) === 'resource'); assert(Formatter::format('{:?}', [$r]) === 'stream resource');
重要
类型修饰符不能与其他修饰符组合,并且总是覆盖其他所有内容!
字符串格式化和修饰符
该方法二进制安全,字符串按原样打印。
从版本1.1.0开始,空字符串和可以转换为字符串的对象将格式化为empty {:?}
,而不是完全不输出。
<?php namespace Fleshgrinder\Examples; use Fleshgrinder\Core\Formatter; class Stringable { function __toString() { return ''; } } assert(Formatter::format('{}', ['']) === 'empty string'); assert(Formatter::format('{}', [new Stringable]) === 'empty Fleshgrinder\Examples\Stringable');
从1.1.0版本起还提供了上标表示法修饰符c
和用于ASCII控制字符的可打印Unicode替换修饰符p
。
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{#c}', ["\0\n"]) === '^@^J'); assert(Formatter::format('{#p}', ["\0\n"]) === '␀␊');
这在构建错误消息时非常有用,其中应包含违反某些约束的字符串,但可能会由于控制字符的性质导致不希望的结果。这两个字符串修饰符都有助于减轻这个问题。请注意,使用上标表示法可能无法重新创建原始字符串,在这些情况下应使用可打印的Unicode替换。然而,这些字符是UTF-8字符,而不是ASCII字符,这可能不被接受。
数字格式化和修饰符
数字使用number_format
及其默认的十进制和千位分隔符进行格式化,这确保了最佳的可读性和一致的格式。默认的小数位数为零,但可以在占位符后面的花括号内插入一个点(.
)来配置自然数。
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{.3}', [1.23456]) === '1.235'); assert(Formatter::format('$ {.2}', [9999.99]) === '$ 9,999.99'); assert(Formatter::format('{0.3}', [1.2345]) === '1.235'); assert(Formatter::format('{a.2}', ['a' => 1]) === '1.00');
可以使用格式修饰符更改数字的输出格式。在占位符后面的花括号内插入一个井号(#
)后跟所需的修饰符来添加格式修饰符。可用的格式修饰符包括
#b
用于二进制数字,#e
用于指数表示法,#o
用于八进制,以及#x
用于十六进制。
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{#b}', [2]) === '0b10'); assert(Formatter::format('{#e}', [0.123456789]) === '1.234568e-1'); assert(Formatter::format('{#o}', [493]) === '0o755'); assert(Formatter::format('{#x}', [42]) === '0x2A');
请注意,没有#E
或#X
修饰符,这是故意的,以强制输出的一致性。
可迭代列表和修饰符
可迭代数据结构以逗号分隔的列表格式化。可以通过在占位符后面的花括号内添加冒号(:
)后跟所需的单词来配置可选的连词。
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{}', [[]]) === 'empty array'); assert(Formatter::format('{}', [new ArrayIterator]) === 'empty ArrayIterator'); assert(Formatter::format('{}', [['a']]) === 'a'); assert(Formatter::format('{}', [['a', 'b']]) === 'a, b'); assert(Formatter::format('{}', [['a', 'b', 'c']]) === 'a, b, c'); assert(Formatter::format('{:and}', [['a', 'b']]) === 'a and b'); assert(Formatter::format('{:or}', [['a', 'b', 'c']]) === 'a, b, or c');
这对于构建错误消息来说非常完美。
<?php use Fleshgrinder\Core\Formatter; try { $expected = ['a', 'b', 'c']; $actual = 'x'; if (in_array($actual, $expected, true) === false) { throw new InvalidArgumentException(Formatter::format( 'Value must be one of {expected:or}, got {actual}', ['expected' => $expected, 'actual' => $actual] )); } } catch (InvalidArgumentException $e) { assert($e->getMessage() === 'Value must be one of a, b, or c, got x'); }
当然,除了类型之外的其他修饰符可以与可迭代列表组合使用。
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{.1:and}', [[0, 1, 2]]) === '0.0, 1.0, and 2.0'); assert(Formatter::format('{#b:and}', [[0, 1, 2]]) === '0b0, 0b1, and 0b10'); assert(Formatter::format('{#o:and}', [[7, 8, 9]]) === '0o7, 0o10, and 0o11'); assert(Formatter::format('{#x:and}', [[9, 10, 11]]) === '0x9, 0xA, and 0xB');
请注意,可迭代数据结构的展开是递归的,这可能会导致意外的输出,但这也可能导致循环引用的数据结构中出现无限循环。请确保您的可迭代参数值是合理的,您已经被警告了。
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('{}', [[0, [1, 2], 3]]) === '0, 1, 2, 3');
可选部分
另一个独特功能是可选部分,只有当标记的参数存在且包含非空值(根据PHP的empty规则)时才会包含。通过括号将一些文本括起来来指定可选部分。
<?php use Fleshgrinder\Core\Formatter; $pattern = 'This is always printed[, and this is printed only if {this_argument}? is non-empty]'; assert( Formatter::format($pattern, ['this_argument' => '']) === 'This is always printed' ); assert( Formatter::format($pattern, ['this_argument' => 'this argument']) === 'This is always printed, and this is printed only if this argument is non-empty' );
占位符通过在占位符闭合花括号后追加一个问号(?
)来标记。为了包含,可选部分中的所有标记占位符都必须非空。请注意,如果可选部分中的标记占位符缺失于参数中,则不会抛出异常,因为这被视为空。另外,请注意,不允许嵌套可选部分,但如果您认为这将是一个更大的特性,您可以提出问题。
重要
只有固定的位置和命名的占位符可以被标记,不考虑到内部参数迭代器。这意味着实际上,以下模式的以下模式永远不会包括可选部分,因为没有固定的位置或命名的占位符存在。
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('[{}?]', ['foobar']) === '');这个限制的原因很简单:可选部分在完全模式评估之前会单独评估。因此,任何内部迭代器都会在可选部分内部开始计数,这会导致难以理解且奇怪的行为;如果支持的话。
转义
特殊字符[
、]
、{
和}
可以通过将它们加倍来转义,分别为[[
、]]
、{{
或}}
。
<?php use Fleshgrinder\Core\Formatter; assert(Formatter::format('[[{.1}, {.1}]]', [0, 1]) === '[0.0, 1.0]'); assert(Formatter::format('{:con}}junction}', [[0, 1]]) === '0 con}junction 1');
错误和异常
被格式化的参数抛出的任何错误或异常都不会被捕获,而是向上传递。该方法本身会抛出已解释的MissingPlaceholderException
异常。如果类型修饰符不存在,并且参数是
- 一个资源——因为无法有意义地格式化它们——或者
- 一个不是
Traversable
的对象,并且没有以下任何一个方法__toString
toFloat
toInt
toString
测试
打开终端,进入项目目录,并执行以下命令以使用您本地安装的PHP可执行文件运行PHPUnit测试。这需要您至少安装了4.0版本的make
make
如果您系统上没有安装或安装版本太旧的make
,您还可以执行以下两个命令
composer install
composer test