ahmedzidan/format

字符串格式化库。

1.1.1 2022-03-20 21:13 UTC

This package is auto-updated.

Last update: 2024-09-21 02:38:06 UTC


README

Latest Stable Version License Travis CI build status AppVeyor CI build status

Dependency Status Coveralls branch Scrutinizer Code Climate: GPA Total Downloads

格式

format库提供了格式化特殊字符串模式的功能。其实现类似于sprintfmsgfmt_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