卡福索 / 类型格式化器
一个简洁、轻量级的库,用于将PHP数据类型转换为可读的字符串。非常适合安全输出类型、异常消息、调试透明度以及类似的功能。同时有助于避免打印递归对象和大型数组等固有问题的发生。
Requires
- php: >=7.2 <8.4
- ext-mbstring: >=7.2 <8.4
- doctrine/collections: ^1
Requires (Dev)
- ext-xml: *
- doctrine/orm: 2.5.*
- phpunit/phpunit: ^8.5.2
README
一个简洁、轻量级的库,用于将PHP数据类型转换为可读的字符串。非常适合安全输出类型、异常消息、调试透明度以及类似的功能。同时有助于避免打印递归对象和大型数组等固有问题的发生。
需求
"php": ">=7.2", "ext-mbstring": ">=7.2", "doctrine/collections": "^1"
有关更多信息,请参阅 composer.json
文件。
许可证 & 免责声明
请参阅 LICENSE
文件。基本上:使用此库自行承担风险。
安装
通过 Composer (https://packagist.org.cn/packages/kafoso/type-formatter)
composer install kafoso/type-formatter
通过GitHub
git clone git@github.com:kafoso/type-formatter.git
基础
类型转换为字符串
数据类型转换如下表所示。
输出示例
输出
<?php use Kafoso\TypeFormatter\TypeFormatter; $typeFormatter = TypeFormatter::create(); echo sprintf( "%s %s %s %s", $typeFormatter->cast(null), $typeFormatter->cast(true), $typeFormatter->cast("foo"), $typeFormatter->cast(new \stdClass) ); /** * Will output: * null true "foo" \stdClass */
异常
<?php use Kafoso\TypeFormatter\TypeFormatter; /** * @param string|int $value * @throws \InvalidArgumentException */ function foo($value){ 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", TypeFormatter::create()->typeCast($value) )); } }; foo(["bar"]); /** * Exception message will read: * Expects argument $value to be a string or an integer. Found: (array(1)) [(int) 0 => (string(3)) "bar"] */
用法
\Kafoso\TypeFormatter\TypeFormatter
是不可变的。因此,它只能在构造时进行配置。
标准格式化器
默认情况下,Kafoso\TypeFormatter\TypeFormatter::create()
每次都会返回一个新实例。如果您希望反复使用相同的实例,您有两个选择。
选项 1: 将其存储在变量中并使用它。因此
<?php use Kafoso\TypeFormatter\TypeFormatter; $typeFormatter = TypeFormatter::create();
选项 2: 使用依赖注入容器;见下文。
依赖注入容器(默认值 & 变体)
为了方便使用,您可以将格式化器静态存储在 Kafoso\TypeFormatter\TypeFormatter
中。
您可以指定两种类型的依赖关系。
默认
<?php use Kafoso\TypeFormatter\TypeFormatter; $typeFormatter = TypeFormatter::getDefault(); $typeFormatter = $typeFormatter->withArrayDepthMaximum(2); TypeFormatter::setDefault($typeFormatter);
变体
<?php use Kafoso\TypeFormatter\TypeFormatter; $typeFormatter = TypeFormatter::create(); $typeFormatter = $typeFormatter->withArrayDepthMaximum(3); TypeFormatter::setVariation("variation1", $typeFormatter); $typeFormatter = TypeFormatter::getVariation("variation1");
使用真实的依赖注入容器
或者,使用实际的依赖注入容器(如 Pimple)。然而,这意味着您必须将依赖关系传递到需要它们的任何地方,这在 SOLID 视角下是很好的,但并不总是非常实用。
自定义基本格式化器
您可以根据特定需求自定义格式化器,例如更改字符串样本大小、数组深度或提供自定义的数组和/或对象格式化器。之后,您可以将它存储为默认值或变体以供以后重用。
<?php use Kafoso\TypeFormatter\Encoding; use Kafoso\TypeFormatter\TypeFormatter; $customTypeFormatter = TypeFormatter::create(); $customTypeFormatter = $customTypeFormatter->withArrayDepthMaximum(2); $customTypeFormatter = $customTypeFormatter->withArraySampleSize(3); $customTypeFormatter = $customTypeFormatter->withStringSampleSize(4); $customTypeFormatter = $customTypeFormatter->withStringQuotingCharacter("`");
特定类型的格式化器
以下特定类型的格式化器存在,可以帮助提供更多信息。特别是打印与对象相关的相关信息非常有用。
这些格式化器通过使用 with*
方法注入到 \Kafoso\TypeFormatter\TypeFormatter
的所需实例中。请注意,\Kafoso\TypeFormatter\TypeFormatter
是不可变的。
可以提供多个自定义格式化器,以便它们各自处理特定情况。顺序很重要。
最后,所有自定义格式化器都将回退到相应的标准格式化器。
包含的对象格式化器
以下对象格式化器 readily 可用。您可以使用它们“按原样”或扩展它们,提供自己的自定义逻辑。一切都非常符合开放封闭原则。
命名空间: \Kafoso\TypeFormatter\Type\Objects
自定义数组格式化器
<?php use Kafoso\TypeFormatter\Abstraction\Type\AbstractFormatter; use Kafoso\TypeFormatter\Collection\Type\ArrayFormatterCollection; use Kafoso\TypeFormatter\Encoding; use Kafoso\TypeFormatter\Type\ArrayFormatterInterface; use Kafoso\TypeFormatter\TypeFormatter; $customTypeFormatter = TypeFormatter::create(); $customTypeFormatter = $customTypeFormatter->withCustomArrayFormatterCollection(new ArrayFormatterCollection([ new class extends AbstractFormatter implements ArrayFormatterInterface { /** * @inheritDoc */ public function format(array $array): ?string { if (1 == count($array)) { 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 $this->getTypeFormatter()->getDefaultArrayFormatter()->format($array); } return null; // Pass on to next formatter or lastly DefaultArrayFormatter } } ])); echo $customTypeFormatter->cast(["foo"]) . PHP_EOL; /** * Will output: * Array * ( * [0] => foo * ) */ echo $customTypeFormatter->cast(["foo", "bar"]) . PHP_EOL; /** * Will output: * I am an array! */ echo $customTypeFormatter->cast(["foo", "bar", "baz"]) . PHP_EOL; /** * Will output: * [0 => "SURPRISE!", 1 => "bar", 2 => "baz"] */ echo $customTypeFormatter->cast(["foo", "bar", "baz", "bim"]) . PHP_EOL; /** * Will output: * [0 => "foo", 1 => "bar", 2 => "baz", ... and 1 more element] (sample) */ echo $customTypeFormatter->typeCast(["foo", "bar", "baz", "bim"]) . PHP_EOL; /** * Will output: * (array(4)) [(int) 0 => (string(3)) "foo", (int) 1 => (string(3)) "bar", (int) 2 => (string(3)) "baz", ... and 1 more element] (sample) */
自定义对象格式化器
在本例中,使用了 \DateTimeInterface
、\Throwable
以及Doctrine ORM 的 EntityManager 来提供优秀的真实世界用例。
<?php use Doctrine\Common\Persistence\Proxy; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Kafoso\TypeFormatter\Abstraction\Type\AbstractFormatter; use Kafoso\TypeFormatter\Collection\Type\ObjectFormatterCollection; use Kafoso\TypeFormatter\Encoding; use Kafoso\TypeFormatter\Type\DefaultObjectFormatter; use Kafoso\TypeFormatter\Type\ObjectFormatterInterface; use Kafoso\TypeFormatter\TypeFormatter; use PHPUnit\Framework\MockObject\Generator; use PHPUnit\Framework\TestCase; $generator = new Generator; $entityManager = $generator->getMock(EntityManager::class, [], [], '', false); $metadataFactory = $generator->getMock(ClassMetadataFactory::class, [], [], '', false); $metadataFactory ->expects(TestCase::any()) ->method('isTransient') ->withConsecutive(TestCase::equalTo('User'), TestCase::equalTo('stdClass')) ->willReturnOnConsecutiveCalls(TestCase::returnValue(false), TestCase::returnValue(true)); $entityManager ->expects(TestCase::any()) ->method('getMetadataFactory') ->will(TestCase::returnValue($metadataFactory)); $customTypeFormatter = TypeFormatter::create(); $customTypeFormatter = $customTypeFormatter->withCustomObjectFormatterCollection(new ObjectFormatterCollection([ new class ($entityManager) extends AbstractFormatter implements ObjectFormatterInterface { /** * @inheritDoc */ public function format($object): ?string { if (false == is_object($object)) { return null; // Pass on to next formatter or lastly DefaultObjectFormatter } if ($object instanceof \DateTimeInterface) { return sprintf( "\\%s (%s)", DefaultObjectFormatter::getClassName($object), $object->format("c") ); } return null; // Pass on to next formatter or lastly DefaultObjectFormatter } }, new class extends AbstractFormatter implements ObjectFormatterInterface { /** * @inheritDoc */ public function format($object): ?string { if (false == is_object($object)) { return null; // Pass on to next formatter or lastly DefaultObjectFormatter } if ($object instanceof \Throwable) { return sprintf( "\\%s {\$code = %s, \$file = %s, \$line = %s, \$message = %s}", DefaultObjectFormatter::getClassName($object), $this->getTypeFormatter()->cast($object->getCode()), $this->getTypeFormatter()->cast($object->getFile(), false), $this->getTypeFormatter()->cast($object->getLine()), $this->getTypeFormatter()->cast($object->getMessage()) ); } return null; // Pass on to next formatter or lastly DefaultObjectFormatter } }, new class ($entityManager) extends AbstractFormatter implements ObjectFormatterInterface { /** * @var EntityManager */ private $entityManager; public function __construct(EntityManager $entityManager) { $this->entityManager = $entityManager; } /** * @inheritDoc */ public function format($object): ?string { if (false == is_object($object)) { return null; // Pass on to next formatter or lastly DefaultObjectFormatter } $className = ($object instanceof Proxy) ? get_parent_class($object) : DefaultObjectFormatter::getClassName($object); $isEntity = (false == $this->entityManager->getMetadataFactory()->isTransient($className)); $id = null; if ($isEntity && method_exists($object, 'getId')) { // You may of course implement logic, which can extract and present any @ORM\Id columns, even composite IDs. $id = $object->getId(); } if (is_int($id)) { return sprintf( "\\%s {\$id = %d}", $className, $id ); } return null; // Pass on to next formatter or lastly DefaultObjectFormatter } }, ])); echo $customTypeFormatter->cast(new \stdClass) . PHP_EOL; /** * Will output (standard TypeFormatter object-to-string output): * \stdClass */ echo $customTypeFormatter->cast(new \DateTimeImmutable("2019-01-01T00:00:00+00:00")) . PHP_EOL; /** * Will output: * \DateTimeImmutable ("2019-01-01T00:00:00+00:00") */ class User { /** * @ORM\Id * @ORM\Column(type="integer") */ private $id = null; public function getId(): ?int { return $this->id; } } $doctrineEntity = new \User; // Pretend we fetch it from a database $reflectionObject = new \ReflectionObject($doctrineEntity); $reflectionProperty = $reflectionObject->getProperty("id"); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($doctrineEntity, 1); echo $customTypeFormatter->cast($doctrineEntity) . PHP_EOL; /** * Will output: * \User {$id = 1} */ echo $customTypeFormatter->cast(new \RuntimeException("test", 1)) . PHP_EOL; /** * Will output: * \RuntimeException {$code = 1, $file = "<file>", $line = <line>, $message = "test"} * , where: * - <file> is the path this this file. * - <line> is the line number at which the \RuntimeException is instantiated. */
自定义资源格式化器
<?php use Kafoso\TypeFormatter\Abstraction\Type\AbstractFormatter; use Kafoso\TypeFormatter\Collection\Type\ResourceFormatterCollection; use Kafoso\TypeFormatter\Encoding; use Kafoso\TypeFormatter\Type\ResourceFormatterInterface; use Kafoso\TypeFormatter\TypeFormatter; $customTypeFormatter = TypeFormatter::create(); $customTypeFormatter = $customTypeFormatter->withCustomResourceFormatterCollection(new ResourceFormatterCollection([ new class extends AbstractFormatter implements ResourceFormatterInterface { /** * @inheritDoc */ public function format($resource): ?string { if (false == is_resource($resource)) { return null; // Pass on to next formatter or lastly DefaultResourceFormatter } if ("stream" === get_resource_type($resource)) { return "opendir/fopen/tmpfile/popen/fsockopen/pfsockopen {$resource}"; } return null; // Pass on to next formatter or lastly DefaultResourceFormatter } }, new class extends AbstractFormatter implements ResourceFormatterInterface { /** * @inheritDoc */ public function format($resource): ?string { if (false == is_resource($resource)) { return null; // Pass on to next formatter or lastly DefaultResourceFormatter } if ("xml" === get_resource_type($resource)) { return "XML {$resource}"; } return null; // Pass on to next formatter or lastly DefaultResourceFormatter } }, ])); echo $customTypeFormatter->cast(fopen(__FILE__, "r+")) . PHP_EOL; /** * Will output: * opendir/fopen/tmpfile/popen/fsockopen/pfsockopen Resource id #<id> */ echo $customTypeFormatter->cast(\xml_parser_create("UTF-8")) . PHP_EOL; /** * Will output: * XML Resource id #<id> */
自定义字符串格式化器
<?php use Kafoso\TypeFormatter\Abstraction\Type\AbstractFormatter; use Kafoso\TypeFormatter\Collection\Type\StringFormatterCollection; use Kafoso\TypeFormatter\Encoding; use Kafoso\TypeFormatter\Type\StringFormatterInterface; use Kafoso\TypeFormatter\TypeFormatter; $customTypeFormatter = TypeFormatter::create(); $customTypeFormatter = $customTypeFormatter->withCustomStringFormatterCollection(new StringFormatterCollection([ new class extends AbstractFormatter implements StringFormatterInterface { /** * @inheritDoc */ public function format(string $string): ?string { if ("What do we like?" === $string) { return $this->getTypeFormatter()->getDefaultStringFormatter()->format("CAKE!"); } return null; // Pass on to next formatter or lastly DefaultStringFormatter } }, ])); echo $customTypeFormatter->cast("What do we like?") . PHP_EOL; /** * Will output: * "CAKE!" */
测试
单元测试 (tests/tests/Test/Unit)将在符合基本要求的所有环境中运行。
运行测试
对于所有测试,首先遵循以下步骤
单元测试 将在大多数系统上运行。
cd tests
php ../bin/phpunit tests/Test/Unit
鸣谢
作者
- 卡斯珀·索弗雷恩
电子邮件: soefritz@gmail.com
主页: https://github.com/kafoso