shipmonk/input-mapper

支持泛型、数组形状、可选字段等的高性能数组到对象映射器!

0.8.0 2024-09-09 14:02 UTC

README

高性能PHP输入映射器,支持泛型、数组形状和可空类型。对于每个输入类,在运行时生成映射器并在磁盘上缓存。映射器只生成一次,然后在后续请求中重用。生成的映射器高度优化以提高性能,且设计为易于阅读。您可以在测试目录中查看生成的映射器示例

安装

composer require shipmonk/input-mapper

功能

内置映射器

Input Mapper 包含以下类型的内置映射器

  • array, bool, float, int, mixed, string, list
  • positive-int, negative-int, int<TMin, TMax>, non-empty-list
  • array<V>, array<K, V>, list<V>, non-empty-list<V>
  • array{K1: V1, ...}
  • ?T, Optional<T>
  • DateTimeInterface, DateTimeImmutable
  • BackedEnum
  • 最重要的是具有公共构造函数的类

您可以编写自己的映射器或用您自己的映射器替换默认映射器。

内置验证器

Input Mapper 包含一些内置验证器

  • 整数验证器
    • AssertInt16
    • AssertInt32
    • AssertIntRange
    • AssertPositiveInt
    • AssertNegativeInt
    • AssertNonNegativeInt
    • AssertNonPositiveInt
    • AssertIntMultipleOf
  • 浮点数验证器
    • AssertFloatRange
    • AssertPositiveFloat
    • AssertNegativeFloat
    • AssertNonNegativeFloat
    • AssertNonPositiveFloat
    • AssertFloatMultipleOf
  • 字符串验证器
    • AssertStringLength
    • AssertStringMatches
    • AssertUrl
  • 列表验证器
    • AssertListItem
    • AssertListLength
    • AssertUniqueItems (通过 === 比较项目)
  • 日期时间验证器
    • AssertDateTimeRange

如果您需要更多,可以编写自己的验证器。

用法

编写输入类

要使用 Input Mapper,编写一个具有公共构造函数的类,并将本机类型或 PHPDoc 类型添加到所有构造函数参数。

可选字段可以是带有 #[Optional] 属性的(允许您指定默认值),或者如果您需要区分默认值和缺失值,您可以使用 ShipMonk\InputMapper\Runtime\Optional 类将类型包装起来。

use ShipMonk\InputMapper\Compiler\Mapper\Optional;

class Person
{
    public function __construct(
        public readonly string $name,

        public readonly int $age,

        #[Optional]
        public readonly ?string $email,

        /** @var list<string> */
        public readonly array $hobbies,

        /** @var list<self> */
        #[Optional(default: [])]
        public readonly array $friends,
    ) {}
}

默认情况下,不允许任何额外属性。您可以通过在类上添加 #[AllowExtraKeys] 来更改此设置。

映射输入

要映射输入,提供一个可写目录的路径,生成的映射器将存储在该目录中。

在生产中设置 $autoRefresh 为 false 非常重要,以避免在每次请求时重新编译映射器。

$tempDir = sys_get_temp_dir() . '/input-mapper';
$autoRefresh = true; // MUST be set to false in production
$mapperProvider = new ShipMonk\InputMapper\Runtime\MapperProvider($tempDir, $autoRefresh);
$mapper = $mapperProvider->get(Person::class);

try {
    $person = $mapper->map([
        'name' => 'John',
        'age' => 30,
        'hobbies' => ['hiking', 'reading'],
        'friends' => [
            [
                'name' => 'Jane',
                'age' => 28,
                'hobbies' => ['hiking', 'reading'],
            ],
            [
                'name' => 'Jack',
                'age' => 28,
                'hobbies' => ['hiking', 'reading'],
            ],
        ],
    ]);
} catch (\ShipMonk\InputMapper\Runtime\Exception\MappingFailedException $e) {
    // $e->getMessage() // programmer readable error message in English
    // $e->getPath() // path of the problematic field for example ['friends', 0, 'name']
    // ...
}

添加验证规则

您可以通过将属性添加到构造函数参数中来添加验证规则。

例如,为了验证 age 在 18 到 99 之间,您可以将 AssertIntRange 属性添加到构造函数参数中

use ShipMonk\InputMapper\Compiler\Validator\Int\AssertIntRange;

class Person
{
    public function __construct(
        public readonly string $name,

        #[AssertIntRange(gte: 18, lte: 99)]
        public readonly int $age,
    ) {}
}

重命名键

如果输入键与属性名称不匹配,您可以使用 #[SourceKey] 属性来指定键名

use ShipMonk\InputMapper\Compiler\Mapper\Object\SourceKey;

class Person
{
    public function __construct(
        #[SourceKey('full_name')]
        public readonly string $name,
    ) {}
}

解析多态类(具有公共父类的子类型)

如果您需要解析类层次结构,您可以使用 #[Discriminator] 属性。(如果使用 #[AllowExtraKeys],则不需要将区分器字段映射到属性。)

use ShipMonk\InputMapper\Compiler\Mapper\Object\Discriminator;

#[Discriminator(
    key: 'type', // key to use for mapping
    mapping: [
        'car' => Car::class,
        'truck' => Truck::class,
    ]
)]
abstract class Vehicle {
    public function __construct(
        public readonly string $type,
    ) {}
}

class Car extends Vehicle {

    public function __construct(
        string $type,
        public readonly string $color,
    ) {
        parent::__construct($type);
    }

}

class Truck extends Vehicle {

    public function __construct(
        string $type,
        public readonly string $color,
    ) {
        parent::__construct($type);
    }

}

或者,使用枚举

use ShipMonk\InputMapper\Compiler\Mapper\Object\Discriminator;

enum VehicleType: string {
    case Car = 'car';
    case Truck = 'truck';
}

#[Discriminator(
    key: 'type', // key to use for mapping
    mapping: [
        VehicleType::Car->value => Car::class,
        VehicleType::Truck->value => Truck::class,
    ]
)]
abstract class Vehicle {
    public function __construct(
        VehicleType $type,
    ) {}
}

class Car extends Vehicle {

    public function __construct(
        VehicleType $type,
        public readonly string $color,
    ) {
        parent::__construct($type);
    }

}

class Truck extends Vehicle {

    public function __construct(
        VehicleType $type,
        public readonly string $color,
    ) {
        parent::__construct($type);
    }

}

使用自定义映射器

要使用自定义映射器映射类,您需要实现ShipMonk\InputMapper\Runtime\Mapper接口,并将其注册到MapperProvider

class MyCustomMapper implements ShipMonk\InputMapper\Runtime\Mapper
{
    public function map(mixed $data, array $path = []): mixed
    {
        return MyCustomClass::createFrom($data);
    }
}

$mapperProvider->registerFactory(MyCustomClass::class, function () {
    return new MyCustomMapper();
});

自定义从类型推断的默认映射器

要自定义从类型推断默认映射器的方式,您需要实现

  • ShipMonk\InputMapper\Compiler\MapperFactory\MapperCompilerFactory
  • ShipMonk\InputMapper\Compiler\MapperFactory\MapperCompilerFactoryProvider.

然后,将您的工厂提供者注册到MapperProvider

$mapperProvider = new ShipMonk\InputMapper\Runtime\MapperProvider(
    tempDir: $tempDir,
    autoRefresh: $autoRefresh,
    mapperCompilerFactoryProvider: new MyCustomMapperCompilerFactoryProvider(),
);

贡献

  • 使用composer check检查您的代码
  • 使用composer fix:cs自动修复编码风格
  • 所有功能都必须经过测试