toobo / type-checker
一个库,用于检查值是否与PHP类型匹配,是is_a()的强化版。
Requires
- php: >=8.1 < 8.4
Requires (Dev)
- inpsyde/php-coding-standards: ^2
- phpunit/phpunit: ^10.5.20
- vimeo/psalm: ^5.24.0
README
这是什么
您可以将它视为创建一个强化的is_a()函数的方式。
is_a()仅适用于类、接口和枚举。它不适用于标量(string、int等,包括一元类型如true和false),也不适用于虚拟类型(iterable、callable)。
此外,它不适用于复杂类型,如联合、交集和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)类型self、parent和static在仅返回类型声明中。这些类型被称为"延迟"的,因为它们的实际类型是在运行时计算的。
这个库的主要目标是检查类型,没有知道它们被使用的上下文,就无法检查 LSB 类型,而这样的上下文在简单的字符串(如"self")或 "ReflectionType实例"中是缺失的。
因此,这个库不支持它们。尝试从任何引用这些 LSB 类型的类型创建 Type 实例将导致抛出错误。
类型信息
Type 类有几种方法可以获取其表示的 PHP 类型的信息。
isStandalone()isUnion()isIntersection()isDnf()
可以判断复合类型是什么类型,或者根本不是复合类型。
还有一个 isNullable() 方法。
类型位置工具
PHP 允许在三个地方声明类型
- 函数参数类型
- 函数返回值
- 属性声明
三个位置支持略有不同的类型集。
例如,void 和 never 只能用作返回类型,而 callable 不能用作属性类型。
Type 类有三个方法:Type::isPropertySafe()、Type::isArgumentSafe() 和 Type::isReturnSafe(),可以用来确定实例是否代表可以在三个位置使用的类型。
与其他库的比较
还有一些库处理“表示类型的对象”。
-
PHPStan 的 PHPDoc 解析器是从文档块中提取类型的一个惊人的解析器。虽然它与这个包类似,都是从字符串创建“类型对象”,但 PHPStan 包支持比 PHP 原生类型系统更多的功能。然而,它不支持检查一个值是否属于该类型,这是这个包存在的主要原因。
-
PHP 文档器的 TypeResolver是基于上述提到的 PHPStan 包。上面提到的相同差异也适用。
-
Symfony type-info 组件。场景中的“新成员”,仍然是实验性的。它也基于 PHPStan 库进行字符串解析,但类似于这个库也处理反射类型。在撰写本文时,检查一个值是否满足类型的可能性仅限于原生“独立”类型,包括像
iterable和callable这样的虚拟类型,但不包括复合类型。
总的来说,这个库不依赖任何其他库,只针对 PHP 支持的类型,而不是文档和静态分析中使用的“高级”类型。这种有限的范围使得单个类中的代码更加简单。此外,这个库更注重于检查值的类型,而不是像其他库那样“解析”或“从字符串中提取”类型。