packagefactory / extractor
一个流畅的接口,允许在读取的同时验证原始PHP数据结构
Requires
- php: >=8.1
Requires (Dev)
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^11.3
- squizlabs/php_codesniffer: ^3.10
This package is auto-updated.
Last update: 2024-09-16 16:01:43 UTC
README
一个流畅的接口,允许在读取的同时验证原始PHP数据结构
安装
composer require packagefactory/extractor
使用
例如,您有一个类似这样的PHP原生数组结构
$configuration = [
'mailer' => [
'transport' => 'smtp',
'host' => 'smtp.example.com',
'port' => 465
]
];
它包含邮件服务的配置。在许多PHP项目中,配置通常以这种格式出现,通常是通过解析YAML或JSON源来实现的。虽然这些格式易于阅读和写入,但生成的PHP数组数据结构完全缺乏类型安全。
使用此类值对象处理给定配置更为理想:
final class MailerConfiguration
{
private function __construct(
public readonly MailerTransport $transport,
public readonly string $host,
public readonly int $port
) {
}
}
要将数组结构转换为该对象,可能适合编写一个静态工厂方法
final class MailerConfiguration
{
/* ... */
public static function fromArray(array $array): self
{
if (!isset($array['transport']) || !is_string($array['transport'])) {
throw new \Exception('Transport must be a string!');
}
if (!isset($array['host']) || !is_string($array['host'])) {
throw new \Exception('Host must be a string!');
}
if (!isset($array['port']) || !is_int($array['port'])) {
throw new \Exception('Port must be an integer!');
}
return new self(
transport: MailerTransport::from($array['transport']),
host: $array['host'],
port: $array['port']
);
}
}
遗憾的是,这需要编写大量代码,如果我们还想获得更多有用的错误信息,代码将更加冗长。
这就是Extractor发挥作用的地方。使用Extractor API,我们可以编写如下静态工厂方法:
final class MailerConfiguration
{
/* ... */
public static function fromExtractor(Extractor $extractor): self
{
return new self(
transport: MailerTransport::from($extractor['transport']->string()),
host: $extractor['host']->string(),
port: $extractor['port']->int()
);
}
}
Extractor为我们处理运行时类型检查,并在数据结构不符合我们的假设时抛出有用的错误信息。
为了完成从开始时的示例
$configuration = [
'mailer' => [
'transport' => 'smtp',
'host' => 'smtp.example.com',
'port' => 465
]
];
$mailerConfiguration = MailerConfiguration::fromExtractor(
Extractor::for($configuration)['mailer']
);
API
类型守卫
bool
和 boolOrNull
Extractor::for(true)->bool(); // returns `true`
Extractor::for(false)->bool(); // returns `false`
Extractor::for(true)->boolOrNull(); // returns `true`
Extractor::for(false)->boolOrNull(); // returns `false`
Extractor::for(null)->boolOrNull(); // returns `null`
检查提供给Extractor的数据是否为布尔值,并在是的情况下返回它。当使用 boolOrNull
时,null
也会通过。
int
和 intOrNull
Extractor::for(42)->int(); // returns `42`
Extractor::for(42)->intOrNull(); // returns `42`
Extractor::for(null)->intOrNull(); // returns `null`
检查提供给Extractor的数据是否为整数,并在是的情况下返回它。当使用 intOrNull
时,null
也会通过。
float
和 floatOrNull
Extractor::for(47.11)->float(); // returns `47.11`
Extractor::for(47.11)->floatOrNull(); // returns `47.11`
Extractor::for(null)->floatOrNull(); // returns `null`
检查提供给Extractor的数据是否为浮点数,并在是的情况下返回它。当使用 floatOrNull
时,null
也会通过。
intOrFloat
和 intOrFloatOrNull
Extractor::for(42)->intOrFloat(); // returns `42`
Extractor::for(47.11)->intOrFloat(); // returns `47.11`
Extractor::for(42)->intOrfloatOrNull(); // returns `42`
Extractor::for(47.11)->intOrfloatOrNull(); // returns `47.11`
Extractor::for(null)->intOrfloatOrNull(); // returns `null`
在 JSON
中,整数和浮点数类型之间没有区别。一切都是 number
。这两个方法检查提供给Extractor的数据是否为浮点数或整数(因此是 number
),并在是的情况下返回它。当使用 intOrfloatOrNull
时,null
也会通过。
string
和 stringOrNull
Extractor::for('string')->string(); // returns `"string"`
Extractor::for('string')->stringOrNull(); // returns `"string"`
Extractor::for(null)->stringOrNull(); // returns `null`
检查提供给Extractor的数据是否为字符串,并在是的情况下返回它。当使用 stringOrNull
时,null
也会通过。
array
和 arrayOrNull
Extractor::for([])->array(); // returns `[]`
Extractor::for([])->arrayOrNull(); // returns `[]`
Extractor::for(null)->arrayOrNull(); // returns `null`
检查提供给Extractor的数据是否为数组,并在是的情况下返回它。当使用 arrayOrNull
时,null
也会通过。
数组访问
为了处理嵌套数组结构,Extractor实现了\ArrayAccess
接口。
如果您有一个包含数组的Extractor,当您访问一个键时,您将收到另一个Extractor实例包裹的该键的值
$extractor = Extractor::for([ 'key' => 'value' ]);
$extractor['key']->string(); // returns `"value"`
$extractor['key']->int(); // throws
如果您访问一个未知键,它将被处理为Extractor::for(null)
$extractor['unknown key']->stringOrNull(); // returns `null`
$extractor['unknown key']->string(); // throws
如果您在不是数组的东西上访问键,Extractor将抛出异常
$extractor = Extractor::for('This is not an array...');
$extractor['key']; // throws
getPath
每个Extractor实例都提供了它被检索的访问路径
$extractor = Extractor::for([
'some' => [
'deep' => [
'path' => '1234'
]
]
]);
$nested = $extractor['some']['deep']['path'];
var_dump($nested->getPath());
// Output:
// array(3) {
// [0] =>
// string(4) "some"
// [1] =>
// string(4) "deep"
// [2] =>
// string(4) "path"
// }
可迭代
Extractor
实现了\IterableAggregate
接口,这使得您可以使用foreach
遍历它
foreach (Extractor::for([ 'key' => 'value' ]) as $key => $value) {
$key->string(); // returns `"key"`
$value->string(); // returns `"value"`
$key->int(); // throws
}
如您所见,$key
和 $value
本身也是 Extractor
的实例。
如果您尝试迭代一个包装的不是数组的 Extractor
,则 Extractor
将会抛出
foreach (Extractor::for('This is not an array...') as $key => $value) { // throws
}
错误处理
Extractor
可能会抛出 ExtractorException
的实例。每个 ExtractorException
都携带了抛出异常的 Extractor
的访问路径,并尝试提供有帮助的错误信息
$extractor = Extractor::for([
'some' => [
'deep' => [
'path' => '1234'
]
]
]);
try {
$extractor['some']['deep']['path']->int();
} catch (ExtractorException $e) {
var_dump($e->getPath());
// Output:
// array(3) {
// [0] =>
// string(4) "some"
// [1] =>
// string(4) "deep"
// [2] =>
// string(4) "path"
// }
var_dump($e->getMessage());
// Output:
// string(65) "Value was expected to be of type int, got string("1234") instead."
}
贡献
我们乐意接受贡献。请发送给我们拉取请求。
许可协议
请参阅 LICENSE