eboreum / caster
将任何PHP值转换为有意义的、易读的字符串。非常适合类型安全的输出、异常信息、调试过程中的透明度以及类似的事物。还有助于避免固有的问题,例如打印无限循环的引用对象(无限递归)、限制大型数组和长字符串的输出,并防止(可选)输出敏感字符串,如密码。
Requires
- php: ^8.3
- ext-mbstring: *
- ext-openssl: *
Requires (Dev)
- beberlei/assert: ^3.3
- nikic/php-parser: ^5.0
- phpstan/phpstan: 1.11.5
- phpunit/phpunit: 11.2.5
- slevomat/coding-standard: 8.15.0
- squizlabs/php_codesniffer: 3.10.1
This package is auto-updated.
Last update: 2024-09-10 10:57:35 UTC
README
将任何PHP值转换为有意义的、易读的字符串。非常适合类型安全的输出、异常信息、调试过程中的透明度以及类似的事物。还有助于避免固有的问题,例如打印无限循环的引用对象(无限递归)、限制大型数组和长字符串的输出,并防止(可选)输出敏感字符串,如密码。
为什么使用Eboreum/Caster而不是像XDebug、symfony/var-dumper这样的库呢?
XDebug、symfony/var-dumper等库旨在用于**开发环境**。
Eboreum/Caster适用于任何**所有环境**(开发、测试、预发布、生产)。
使用Eboreum/Caster,您将能够提供有关所有PHP值的详细信息,这在**调试**和**故障场景**中都非常好。您的应用程序中的异常信息是否平淡无奇?使用Eboreum/Caster扩展您的选项和显示值吧!
可以将此包视为扩展的魔术方法__debugInfo
(https://php.ac.cn/manual/en/language.oop5.magic.php#object.debuginfo)。然而,与仅使用实现类的内部结构构建有意义的调试信息不同,__debugInfo
偶尔(通常是令人厌恶的)需要调用其他类的静态方法,而Caster允许有更多的多样性,包括使用适当的依赖注入的自定义格式化程序。
Eboreum/Caster通过选择性地使用自定义格式化程序,**赋予开发者——您——最大的控制权**来处理、解析和呈现输出。
最后,您可以提供一系列敏感文本字符串,如密码、认证令牌、社会保险号等,防止这些字符串在字符串内部输出。您不希望这些信息出现在错误日志、电子邮件中等等。遇到敏感字符串时,将显示静态长度字符串替换(如******
)。
要求
"php": "^8.3", "ext-mbstring": "*", "ext-openssl": "*"
有关更多信息,请参阅composer.json
文件。
安装
通过Composer(https://packagist.org.cn/packages/eboreum/caster)
composer install eboreum/caster
通过GitHub
git clone git@github.com:eboreum/caster.git
基础知识
类型转换到字符串
数据类型将按以下表格所示进行转换。
输出示例
Echo
示例
<?php declare(strict_types=1); use Eboreum\Caster\Caster; $caster = Caster::create(); echo sprintf( "%s\n%s\n%s\n%s", $caster->cast(null), $caster->cast(true), $caster->cast('foo'), $caster->cast(new stdClass()) ); $caster = $caster->withIsPrependingType(true); echo "\n\n"; echo sprintf( "%s\n%s\n%s\n%s", $caster->cast(null), $caster->cast(true), $caster->cast('foo'), $caster->cast(new stdClass()) );
输出
null
true
"foo"
\stdClass
(null) null
(bool) true
(string(3)) "foo"
(object) \stdClass
异常
示例
<?php declare(strict_types=1); use Eboreum\Caster\Caster; /** * @throws InvalidArgumentException */ function foo(mixed $value): void { if (false === is_string($value) && false === is_int($value)) { throw new InvalidArgumentException(sprintf( 'Expects argument $value to be a string or an integer. Found: %s', Caster::create()->castTyped($value), )); } } try { foo(['bar']); } catch (InvalidArgumentException $e) { echo $e->getMessage(); }
输出
Expects argument $value to be a string or an integer. Found: (array(1)) [(int) 0 => (string(3)) "bar"]
用法
\Eboreum\Caster\Caster
是不可变的。这是防止篡改Caster类内部结构的良好保护措施。然而,提供了许多with*
方法,允许克隆被修改。
建议您在自己的应用程序中实现一个 \My\Application\Caster
,它扩展了 \Eboreum\Caster\Caster
并覆盖了 getInstance
方法,从而您可以完全控制自己应用程序的caster实例。
示例
<?php declare(strict_types=1); namespace My\Application; use Eboreum\Caster\Abstraction\Formatter\AbstractArrayFormatter; use Eboreum\Caster\Caster as EboreumCaster; use Eboreum\Caster\CharacterEncoding; use Eboreum\Caster\Collection\Formatter\ArrayFormatterCollection; use Eboreum\Caster\Contract\CasterInterface; use function assert; use function dirname; use function json_encode; use function sprintf; class Caster extends EboreumCaster { private static ?Caster $instance = null; public static function getInstance(): self { if (null === self::$instance) { self::$instance = new self(CharacterEncoding::getInstance()); $instance = self::$instance->withCustomArrayFormatterCollection(new ArrayFormatterCollection([ new class extends AbstractArrayFormatter { public function format(CasterInterface $caster, array $array): ?string { return 'I am an array!'; } public function isHandling(array $array): bool { return true; } }, ])); assert($instance instanceof Caster); self::$instance = $instance; // Do more custom configuring before the instance is forever locked and returned } return self::$instance; } } echo sprintf( 'Instances \\%s::getInstance() !== \\%s::getInstance(): %s', EboreumCaster::class, Caster::class, json_encode(EboreumCaster::getInstance() !== Caster::getInstance()), ) . "\n"; echo sprintf( 'But \\%s::getInstance() === \\%s::getInstance() (same): %s', Caster::class, Caster::class, json_encode(Caster::getInstance() === Caster::getInstance()), ) . "\n";
输出
Instances \Eboreum\Caster\Caster::getInstance() !== \My\Application\Caster::getInstance(): true But \My\Application\Caster::getInstance() === \My\Application\Caster::getInstance() (same): true
标准格式化器
默认情况下,Eboreum\Caster\Caster::create()
每次都返回一个新实例。如果您希望反复使用相同的实例,您有两个选择。
选项 1:将其存储在变量中并使用它。例如
<?php use Eboreum\Caster\Caster; $caster = Caster::create();
选项 2:使用 getInstance
。
为了方便使用,您可以通过调用 \Eboreum\Caster\Caster::getInstance()
来获取相同的实例。如上所述,建议您在自己的应用程序或库中创建自己的 \My\Application\Caster::getInstance()
。
使用实际的依赖注入容器
或者,使用实际的依赖注入容器(DIC)如 Pimple。但是,这意味着您必须将依赖项传递到需要的地方,这在 SOLID 视角下是很好的,但并不总是非常实用。
自定义基本格式化器
您可以根据自己的需求自定义格式化器,例如更改字符串样本大小、数组深度,或提供自定义的数组和/或对象格式化器。
示例
<?php declare(strict_types=1); use Eboreum\Caster\Caster; use Eboreum\Caster\Common\DataType\Integer\PositiveInteger; use Eboreum\Caster\Common\DataType\Integer\UnsignedInteger; use Eboreum\Caster\Common\DataType\String_\Character; $caster = Caster::create(); $caster = $caster->withDepthMaximum(new PositiveInteger(2)); $caster = $caster->withArraySampleSize(new UnsignedInteger(3)); $caster = $caster->withStringSampleSize(new UnsignedInteger(4)); $caster = $caster->withStringQuotingCharacter(new Character('`')); echo '$caster->getDepthMaximum()->toInteger(): ' . $caster->getDepthMaximum()->toInteger() . "\n"; echo '$caster->getArraySampleSize()->toInteger(): ' . $caster->getArraySampleSize()->toInteger() . "\n"; echo '$caster->getStringSampleSize()->toInteger(): ' . $caster->getStringSampleSize()->toInteger() . "\n"; echo '$caster->getStringQuotingCharacter(): ' . $caster->getStringQuotingCharacter() . "\n";
输出
$caster->getDepthMaximum()->toInteger(): 2 $caster->getArraySampleSize()->toInteger(): 3 $caster->getStringSampleSize()->toInteger(): 4 $caster->getStringQuotingCharacter(): `
特定类型的格式化器
以下存在特定类型的格式化器,这可以帮助提供更多信息。特别是对于打印与对象相关的信息非常有用。
使用 with*
方法(返回一个副本)将格式化器(不可变地)添加到 \Eboreum\Caster\Caster
。
可以提供多个自定义格式化器,以便它们各自处理特定情况。顺序很重要。集合中的第一个元素首先处理。您必须在将集合元素传递给 \Eboreum\Caster\Caster
之前对集合元素进行排序。
最终,所有自定义格式化器都会回退到它们各自的标准格式化器。
包含的对象格式化器
以下对象格式化器现成可用。您可以直接使用它们或扩展它们,提供自己的自定义逻辑。一切都是非常开放-关闭原则。
命名空间: \Eboreum\Caster\Formatter\Object_
自定义数组格式化器
示例
<?php declare(strict_types=1); use Eboreum\Caster\Abstraction\Formatter\AbstractArrayFormatter; use Eboreum\Caster\Caster; use Eboreum\Caster\Collection\Formatter\ArrayFormatterCollection; use Eboreum\Caster\Contract\CasterInterface; $caster = Caster::create(); $caster = $caster->withCustomArrayFormatterCollection(new ArrayFormatterCollection([ new class extends AbstractArrayFormatter { public function format(CasterInterface $caster, array $array): ?string { if (false === $this->isHandling($array)) { return null; // Pass on to next formatter or lastly DefaultArrayFormatter } if (1 === count($array)) { /* * /!\ CAUTION /!\ * Do NOT do this in practice! You disable sensitive string masking. */ return print_r($array, true); } if (2 === count($array)) { return 'I am an array!'; } if (3 === count($array)) { $array[0] = 'SURPRISE!'; // Override and use DefaultArrayFormatter for rendering output return $caster->getDefaultArrayFormatter()->format($caster, $array); } return null; // Pass on to next formatter or lastly DefaultArrayFormatter } public function isHandling(array $array): bool { return true; } }, ])); echo $caster->cast(['foo']) . "\n"; echo $caster->cast(['foo', 'bar']) . "\n"; echo $caster->cast(['foo', 'bar', 'baz']) . "\n"; echo $caster->cast(['foo', 'bar', 'baz', 'bim']) . "\n"; echo $caster->castTyped(['foo', 'bar', 'baz', 'bim']) . "\n";
输出
Array ( [0] => foo ) I am an array! [0 => "SURPRISE!", 1 => "bar", 2 => "baz"] [0 => "foo", 1 => "bar", 2 => "baz", ... and 1 more element] (sample) (array(4)) [(int) 0 => (string(3)) "foo", (int) 1 => (string(3)) "bar", (int) 2 => (string(3)) "baz", ... and 1 more element] (sample)
自定义对象格式化器
在这个例子中,使用了 \DateTimeInterface
和 \Throwable
来提供良好的实际用例。
示例
<?php declare(strict_types=1); use Eboreum\Caster\Abstraction\Formatter\AbstractObjectFormatter; use Eboreum\Caster\Caster; use Eboreum\Caster\Collection\Formatter\ObjectFormatterCollection; use Eboreum\Caster\Contract\CasterInterface; $caster = Caster::create(); $caster = $caster->withCustomObjectFormatterCollection(new ObjectFormatterCollection([ new class extends AbstractObjectFormatter { public function format(CasterInterface $caster, object $object): ?string { if (false === $this->isHandling($object)) { return null; // Pass on to next formatter or lastly DefaultObjectFormatter } assert($object instanceof DateTimeInterface); return sprintf( '%s (%s)', Caster::makeNormalizedClassName(new ReflectionObject($object)), $object->format('c'), ); } public function isHandling(object $object): bool { return ($object instanceof DateTimeInterface); } }, new class extends AbstractObjectFormatter { public function format(CasterInterface $caster, object $object): ?string { if (false === $this->isHandling($object)) { return null; // Pass on to next formatter or lastly DefaultObjectFormatter } assert($object instanceof Throwable); return sprintf( '%s {$code = %s, $file = %s, $line = %s, $message = %s}', Caster::makeNormalizedClassName(new ReflectionObject($object)), $caster->cast($object->getCode()), $caster->cast('.../' . basename($object->getFile())), $caster->cast($object->getLine()), $caster->cast($object->getMessage()), ); } public function isHandling(object $object): bool { return ($object instanceof Throwable); } }, ])); echo $caster->cast(new stdClass()) . "\n"; echo $caster->cast(new DateTimeImmutable('2019-01-01T00:00:00+00:00')) . "\n"; echo $caster->cast(new RuntimeException('test', 1)) . "\n";
输出
\stdClass \DateTimeImmutable (2019-01-01T00:00:00+00:00) \RuntimeException {$code = 1, $file = ".../example-custom-object-formatter.php", $line = 68, $message = "test"}
自定义资源格式化器
示例
<?php declare(strict_types=1); use Eboreum\Caster\Abstraction\Formatter\AbstractResourceFormatter; use Eboreum\Caster\Caster; use Eboreum\Caster\Collection\Formatter\ResourceFormatterCollection; use Eboreum\Caster\Common\DataType\Resource_; use Eboreum\Caster\Contract\CasterInterface; $caster = Caster::create(); $caster = $caster->withCustomResourceFormatterCollection(new ResourceFormatterCollection([ new class extends AbstractResourceFormatter { public function format(CasterInterface $caster, Resource_ $resource): ?string { if (false === $this->isHandling($resource)) { return null; // Pass on to next formatter or lastly DefaultResourceFormatter } if ('stream' === get_resource_type($resource->getResource())) { return sprintf( 'opendir/fopen/tmpfile/popen/fsockopen/pfsockopen %s', preg_replace( '/^(Resource id) #\d+$/', '$1 #42', (string)$resource->getResource(), ), ); } return null; // Pass on to next formatter or lastly DefaultResourceFormatter } }, new class extends AbstractResourceFormatter { public function format(CasterInterface $caster, Resource_ $resource): ?string { if (false === $this->isHandling($resource)) { return null; // Pass on to next formatter or lastly DefaultResourceFormatter } if ('xml' === get_resource_type($resource->getResource())) { $identifier = preg_replace( '/^(Resource id) #\d+$/', '$1 #42', (string)$resource->getResource(), ); assert(is_string($identifier)); return sprintf( 'XML %s', $identifier, ); } return null; // Pass on to next formatter or lastly DefaultResourceFormatter } }, ])); echo $caster->cast(fopen(__FILE__, 'r+')) . "\n";
输出
opendir/fopen/tmpfile/popen/fsockopen/pfsockopen Resource id #42
自定义字符串格式化器
示例
<?php declare(strict_types=1); use Eboreum\Caster\Abstraction\Formatter\AbstractStringFormatter; use Eboreum\Caster\Caster; use Eboreum\Caster\Collection\Formatter\StringFormatterCollection; use Eboreum\Caster\Contract\CasterInterface; $caster = Caster::create(); $caster = $caster->withCustomStringFormatterCollection(new StringFormatterCollection([ new class extends AbstractStringFormatter { public function format(CasterInterface $caster, string $string): ?string { if (false === $this->isHandling($string)) { return null; // Pass on to next formatter or lastly DefaultStringFormatter } if ('What do we like?' === (string)$string) { return $caster->cast('CAKE!'); } return null; // Pass on to next formatter or lastly DefaultStringFormatter } public function isHandling(string $string): bool { return true; } }, ])); echo $caster->cast('What do we like?') . "\n"; echo $caster->castTyped('Mmmm, cake') . "\n";
输出
"CAKE!" (string(10)) "Mmmm, cake"
隐藏敏感子串
您可以隐藏敏感字符串,如密码、身份验证令牌、社会保险号等。
在下面的示例中,请注意 "345" 和 "456" 是重叠的,导致这两个字符串的乘积 "3456" 被屏蔽。虽然这可能潜在地揭示了其中一个敏感字符串是另一个字符串的一部分,但这比只屏蔽其中一个字符串并将第二个敏感字符串的其余部分显示在纯文本输出中要小得多。
将按以下顺序屏蔽:最长的敏感字符串到最短的。这意味着对于敏感字符串 "foo"
和 "foobar"
,首先处理 "foobar"
。这个字符串,"foobarbaz"
,将变成 "******baz"
,而在这个例子中,"foo"
永远不会被处理。字符串 "foob foobarbaz"
将变成 "******b ******baz"
,依此类推。
敏感字符串被 加密(IV 和盐在运行时随机生成),这样如果 Eboreum/Caster 内部失败,它将不会在明文中泄露敏感字符串。
当使用 \Eboreum\Caster\Caster->cast(...)
时,任何字符串在传递到字符串格式化器(实现 StringFormatterInterface
的类)之前都会被屏蔽。
示例
<?php declare(strict_types=1); use Eboreum\Caster\Caster; use Eboreum\Caster\Collection\EncryptedStringCollection; use Eboreum\Caster\EncryptedString; $caster = Caster::create(); $caster = $caster->withMaskedEncryptedStringCollection(new EncryptedStringCollection([ new EncryptedString('bar'), new EncryptedString('bim'), new EncryptedString('345'), new EncryptedString('456'), ])); echo $caster->castTyped('foo bar baz bim bum') . "\n"; // Notice: Original string length is not revealed echo "\n\n"; echo $caster->castTyped('0123456789') . "\n"; // Notice: 3456 are masked because 345 and 456 overlap
输出
(string(25)) "foo ****** baz ****** bum" (masked) (string(12)) "012******789" (masked)
测试
测试/开发要求
"beberlei/assert": "^3.3", "nikic/php-parser": "^5.0", "phpstan/phpstan": "1.11.5", "phpunit/phpunit": "11.2.5", "slevomat/coding-standard": "8.15.0", "squizlabs/php_codesniffer": "3.10.1"
运行测试
对于所有单元测试,首先按照以下步骤操作
cd tests
php ../vendor/bin/phpunit
PHPStan
抑制代码
对于一些情况,我们需要抑制 PHPStan 的输出,原因有很多。我们努力避免使用 @phpstan-ignore-line
(以及 @phpstan-ignore-next-line
和类似),但在极少数情况下——主要是在测试中——这是不可能的,因为我们测试的东西正是 PHPStan 不喜欢的。
许可 & 声明
请参阅LICENSE
文件。基本上:使用此库风险自负。
贡献
我们更希望你在https://github.com/eboreum/caster创建一个工单或拉取请求,并在此处讨论功能或错误。
请不要将https://packagist.org.cn/packages/eboreum/exceptional重新集成到本项目。我们不希望有双向依赖,因为eboreum/exceptional使用了eboreum/caster。
致谢
作者
- 卡斯珀·索弗伦 (kafoso)
电子邮箱:soefritz@gmail.com
个人主页:https://github.com/kafoso - 卡尔斯滕·约根森 (corex)
电子邮箱:dev@corex.dk
个人主页:https://github.com/corex
感谢
来源于并替代:https://packagist.org.cn/packages/kafoso/type-formatter (https://github.com/kafoso/type-formatter)。