toobo/type-checker

一个库,用于检查值是否与PHP类型匹配,是is_a()的强化版。

1.1.0 2024-05-17 09:08 UTC

This package is auto-updated.

Last update: 2024-09-20 17:47:57 UTC


README

Quality Assurance

这是什么

您可以将它视为创建一个强化的is_a()函数的方式。

is_a()仅适用于类、接口和枚举。它不适用于标量(stringint等,包括一元类型如truefalse),也不适用于虚拟类型(iterablecallable)。

此外,它不适用于复杂类型,如联合、交集和DNF。

如果您曾经想要做类似的事情

is_a('iterable|MyThing|(Iterator&Countable)|null', $thing);

那么这个包就是为您准备的。但不仅如此。

is_a()接受类型字符串,但有时人们想要检查与ReflectionType,而这个包也可以做到这一点。

一个使用字符串的例子

use Toobo\TypeChecker\Type;

Type::byString('iterable|MyThing|(Iterator&Countable)|null')->satisfiedBy($thing);

使用ReflectionType

use Toobo\TypeChecker\Type;

function test(iterable|MyThing|(Iterator&Countable)|null $param) {}
$refType = (new ReflectionFunction('test'))->getParameters()[0]->getType();

Type::byReflectionType($refType)->satisfiedBy($thing);

深入了解

命名构造函数

除了通过字符串和通过反射,Type类还可以使用命名构造函数进行实例化,例如Type::string()Type::resource()Type::mixed(),也可以是Type::iterable()Type::callable()甚至Type::null()Type::true()Type::false()

为了完整性,还可以创建将永远不会匹配任何值的类型的实例,如Type::void()Type::never()

匹配类型

Type类旨在表示整个PHP类型系统。并且可以将一个实例与另一个实例进行比较

assert(Type::byString('IteratorAggregate&Countable')->matchedBy('ArrayObject'));
assert(Type::byString('IteratorAggregate&Countable')->matchedBy(Type::byString('ArrayObject')));

assert(Type::byString('ArrayObject')->isA('IteratorAggregate&Countable'));
assert(Type::byString('ArrayObject')->isA(Type::byString('IteratorAggregate&Countable')));

Type::matchedBy()Type::isA()都接受一个字符串或另一个类型实例,并检查类型兼容性。它们是相互的。

Type::matchedBy()的行为可以这样描述:如果函数的参数类型由调用该方法类型的类型表示,它是否会被传递的类型为该参数的类型所满足?

Type::isA()的行为可以这样描述:如果函数的参数类型由传递给参数的类型表示,它是否会被调用该方法类型的实例所满足?

"已检查"与"未检查"实例化

从字符串实例化一个Type实例时,会进行检查以确保字符串代表一个有效的类型(或者至少看起来像是一个有效的类型)。传递一个不包含有效PHP类型定义语法(包括使用保留的PHP关键字)的字符串将导致抛出错误。

另一方面,通过ReflectionType的实例化是"未检查"的,因为我们知道从ReflectionType实例化的是一个有效的PHP类型。从ReflectionType构建的唯一可能的错误是由使用"延迟状态绑定"类型引起的,下面将进一步介绍。

延迟静态绑定类型

PHP支持"延迟状态绑定"(LSB)类型selfparentstatic在仅返回类型声明中。这些类型被称为"延迟"的,因为它们的实际类型是在运行时计算的。

这个库的主要目标是检查类型,没有知道它们被使用的上下文,就无法检查 LSB 类型,而这样的上下文在简单的字符串(如"self")或 "ReflectionType实例"中是缺失的。

因此,这个库不支持它们。尝试从任何引用这些 LSB 类型的类型创建 Type 实例将导致抛出错误。

类型信息

Type 类有几种方法可以获取其表示的 PHP 类型的信息。

  • isStandalone()
  • isUnion()
  • isIntersection()
  • isDnf()

可以判断复合类型是什么类型,或者根本不是复合类型。

还有一个 isNullable() 方法。

类型位置工具

PHP 允许在三个地方声明类型

  • 函数参数类型
  • 函数返回值
  • 属性声明

三个位置支持略有不同的类型集。

例如,voidnever 只能用作返回类型,而 callable 不能用作属性类型。

Type 类有三个方法:Type::isPropertySafe()Type::isArgumentSafe()Type::isReturnSafe(),可以用来确定实例是否代表可以在三个位置使用的类型。

与其他库的比较

还有一些库处理“表示类型的对象”。

  • PHPStan 的 PHPDoc 解析器是从文档块中提取类型的一个惊人的解析器。虽然它与这个包类似,都是从字符串创建“类型对象”,但 PHPStan 包支持比 PHP 原生类型系统更多的功能。然而,它不支持检查一个值是否属于该类型,这是这个包存在的主要原因。

  • PHP 文档器的 TypeResolver是基于上述提到的 PHPStan 包。上面提到的相同差异也适用。

  • Symfony type-info 组件。场景中的“新成员”,仍然是实验性的。它也基于 PHPStan 库进行字符串解析,但类似于这个库也处理反射类型。在撰写本文时,检查一个值是否满足类型的可能性仅限于原生“独立”类型,包括像 iterablecallable 这样的虚拟类型,但不包括复合类型。

总的来说,这个库不依赖任何其他库,只针对 PHP 支持的类型,而不是文档和静态分析中使用的“高级”类型。这种有限的范围使得单个类中的代码更加简单。此外,这个库更注重于检查值的类型,而不是像其他库那样“解析”或“从字符串中提取”类型。