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 支持的类型,而不是文档和静态分析中使用的“高级”类型。这种有限的范围使得单个类中的代码更加简单。此外,这个库更注重于检查值的类型,而不是像其他库那样“解析”或“从字符串中提取”类型。