packagefactory/extractor 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

packagefactory/extractor

Composer 安装命令:

composer require packagefactory/extractor

包简介

A fluent interface that allows to validate primitive PHP data structures while also reading them

README 文档

README

A fluent interface that allows to validate primitive PHP data structures while also reading them

Installation

composer require packagefactory/extractor

Usage

Let's say, you have a PHP-native array structure like this one:

$configuration = [
    'mailer' => [
        'transport' => 'smtp',
        'host' => 'smtp.example.com',
        'port' => 465
    ]
];

It contains configuration for a mailing service. In a lot of PHP projects, configuration comes in this format, usually by being parsed from YAML or JSON sources. While these formats are nicely readable and writable, the result PHP array data structure is completely exempt from type safety.

It is much more desirable to handle the given configuration using a value object like this one:

final class MailerConfiguration
{
    private function __construct(
        public readonly MailerTransport $transport,
        public readonly string $host,
        public readonly int $port
    ) {
    }
}

To convert the array structure into this object, it may be suitable to write a static factory method:

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']
        );
    }
}

Unfortunately, this is a lot of code to write and it would become even more, if we'd actually like to have more helpful error messages.

This is where the Extractor comes in. Using the Extractor API, we can write a static factory method like this:

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()
        );
    }
}

The extractor handles the runtime type checks for us and throws helpful error messages, if the datastructure doesn't follow our assumptions.

To complete the example from the beginning:

$configuration = [
    'mailer' => [
        'transport' => 'smtp',
        'host' => 'smtp.example.com',
        'port' => 465
    ]
];

$mailerConfiguration = MailerConfiguration::fromExtractor(
    Extractor::for($configuration)['mailer']
);

API

Type Guards

bool, tryBool and boolOrNull

Extractor::for(true)->bool(); // returns `true`
Extractor::for(false)->bool(); // returns `false`

Extractor::for(true)->tryBool(); // returns `true`
Extractor::for(false)->tryBool(); // returns `false`
Extractor::for('some string')->tryBool(); // returns `null`

Extractor::for(true)->boolOrNull(); // returns `true`
Extractor::for(false)->boolOrNull(); // returns `false`
Extractor::for(null)->boolOrNull(); // returns `null`

Checks if the data given to the extractor is a boolean and returns it if thats the case. When boolOrNull is used, null will pass as well.

int, tryInt and intOrNull

Extractor::for(42)->int(); // returns `42`

Extractor::for(42)->tryInt(); // returns `42`
Extractor::for('some string')->tryInt(); // returns `null`

Extractor::for(42)->intOrNull(); // returns `42`
Extractor::for(null)->intOrNull(); // returns `null`

Checks if the data given to the extractor is an integer and returns it if thats the case. When intOrNull is used, null will pass as well.

float, tryFloat and floatOrNull

Extractor::for(47.11)->float(); // returns `47.11`

Extractor::for(47.11)->tryFloat(); // returns `47.11`
Extractor::for('some string')->tryFloat(); // returns `null`

Extractor::for(47.11)->floatOrNull(); // returns `47.11`
Extractor::for(null)->floatOrNull(); // returns `null`

Checks if the data given to the extractor is a float and returns it if thats the case. When floatOrNull is used, null will pass as well.

intOrFloat, tryIntOrFloat and intOrFloatOrNull

Extractor::for(42)->intOrFloat(); // returns `42`
Extractor::for(47.11)->intOrFloat(); // returns `47.11`

Extractor::for(42)->tryIntOrFloat(); // returns `42`
Extractor::for(47.11)->tryIntOrFloat(); // returns `47.11`
Extractor::for('some string')->tryIntOrFloat(); // returns `null`

Extractor::for(42)->intOrfloatOrNull(); // returns `42`
Extractor::for(47.11)->intOrfloatOrNull(); // returns `47.11`
Extractor::for(null)->intOrfloatOrNull(); // returns `null`

In JSON there's no distinction between integer and float types. Everything is just a number. These two methods check if the data given to the extractor is a float or an integer (and therefore a number) and returns it if thats the case. When intOrfloatOrNull is used, null will pass as well.

string, tryString and stringOrNull

Extractor::for('string')->string(); // returns `"string"`

Extractor::for('string')->tryString(); // returns `"string"`
Extractor::for(23)->tryString(); // returns `null`

Extractor::for('string')->stringOrNull(); // returns `"string"`
Extractor::for(null)->stringOrNull(); // returns `null`

Checks if the data given to the extractor is a string and returns it if thats the case. When stringOrNull is used, null will pass as well.

array, tryArray and arrayOrNull

Extractor::for([])->array(); // returns `[]`

Extractor::for([])->tryArray(); // returns `[]`
Extractor::for('some string')->tryArray(); // returns `null`

Extractor::for([])->arrayOrNull(); // returns `[]`
Extractor::for(null)->arrayOrNull(); // returns `null`

Checks if the data given to the extractor is an array and returns it if thats the case. When arrayOrNull is used, null will pass as well.

instanceOf, tryInstanceOf and instanceOfOrNull

The several *instanceOf*-methods all take a parameter that specifies the class or interface of which an instance is expected.

Extractor::for(new \DateTimeImmubale())->instanceOf(\DateTimeImmuable::class); // returns the instance that was given
Extractor::for(new \DateTime())->instanceOf(\DateTimeImmuable::class); // fails
Extractor::for(new \DateTime())->instanceOf(\DateTimeInterface::class); // returns the instance that was given

Extractor::for(new \DateTimeImmubale())->tryInstanceOf(\DateTimeImmuable::class); // returns the instance that was given
Extractor::for(new \DateTime())->tryInstanceOf(\DateTimeImmuable::class); // returns null
Extractor::for('some string')->tryInstanceOf(\DateTimeImmuable::class); // returns null

Extractor::for(new \DateTimeImmubale())->instanceOfOrNull(\DateTimeImmuable::class); // returns the instance that was given
Extractor::for(null)->instanceOfOrNull(\DateTimeImmuable::class); // returns `null`

Checks if the data given to the extractor is an array and returns it if thats the case. When arrayOrNull is used, null will pass as well.

Array Access

In order to deal with nested array structures, Extractor implements the \ArrayAccess interface.

Given you have an Extractor that wraps an array, when you access a key, you'll receive the value for that key wrapped in another Extractor instance:

$extractor = Extractor::for([ 'key' => 'value' ]);
$extractor['key']->string(); // returns `"value"`
$extractor['key']->int(); // throws

If you access an unknown key, it'll be treated like Extractor::for(null):

$extractor['unknown key']->stringOrNull(); // returns `null`
$extractor['unknown key']->string(); // throws

If you access a key on something other than an array, Extractor will throw:

$extractor = Extractor::for('This is not an array...');
$extractor['key']; // throws

getPath

Each Extractor instance provides you with the access path by which it has been retrieved:

$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"
// }

Iterable

Extractor implements the \IterableAggregate interface, which allows you to loop over it using foreach:

foreach (Extractor::for([ 'key' => 'value' ]) as $key => $value) {
    $key->string(); // returns `"key"`
    $value->string(); // returns `"value"`

    $key->int(); // throws
}

As you see, both $key and $value are themselves instances of Extractor.

If you try to iterate over an Extractor that wraps something other than an array, the Extractor will throw:

foreach (Extractor::for('This is not an array...') as $key => $value) { // throws
}

Error Handling

Extractor may throw instances of ExtractorException. Each ExtractorException carries the access path by which the throwing Extractor has been retrieved and tries to provide a helpful error message:

$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."
}

Contribution

We will gladly accept contributions. Please send us pull requests.

License

see LICENSE

packagefactory/extractor 适用场景与选型建议

packagefactory/extractor 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 31 次下载、GitHub Stars 达 0, 最近一次更新时间为 2022 年 11 月 25 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 packagefactory/extractor 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 packagefactory/extractor 我们能提供哪些服务?
定制开发 / 二次开发

基于 packagefactory/extractor 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 31
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 11
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: GPL-3.0-or-later
  • 更新时间: 2022-11-25