fab2s/enumerate

enumerate,为您的枚举类型提供强大支持

dev-main 2024-07-21 21:04 UTC

This package is auto-updated.

Last update: 2024-09-21 21:19:40 UTC


README

QA CI codecov PRs Welcome License

enumerate 为您的本地枚举类型提供强大支持。本地枚举

为什么?

PHP 枚举是 PHP 的一大亮点,但它们缺少一些使其在实际应用中完全实用的东西。如果您深入了解,会发现大多数当前限制似乎更多与意识形态有关,而不是实用性。

作为一个软件工艺精神的拥护者,我认为这样的特性应该在其内部承载更多复杂性,以在外部提供更多简洁性。

例如,我实在不理解为什么枚举不能,更糟糕的是,甚至不能被转换为字符串。如果您认为 Unitenum 不应该被转换为字符串,或者您会认为将 IntBackedEnum 转换为 (string) 是背叛,这些都只是 SHOULDs,它们只是限制了我们如何在现实生活中使用它们,当它们成为 MUST 时。

在实践中,即使是将 IntBackedEnum 转换为字符串,在 HTTP 上下文中(在这种情况下字符串是唯一类型)或写入数据库时也是有意义的。当然,这些都是捷径,但为什么我们被迫走每一条弯路呢?

当然,我更希望有一个接口,允许我们对象进行 scalar 转换,并最终得到枚举的 intstring,但这只是一个梦想 :-)

遗憾的是,我们目前无法自由地将此类行为嵌入我们的枚举中。

我全力支持严格类型,但我发现不得不编写代码只是为了能够读取 HTTP 请求或写入数据库是麻烦的,而这本可以通过一次实现,并以一种完美的方式完成。

还有一件事我不理解,为什么不能扩展枚举。在许多实际情况下,我们希望能够保持 DRY(不要重复自己),例如描述我们应用中的 roles 或甚至 types。这本身就是一个例子,其中引擎可以处理一些额外的复杂性,例如检查继承以保持一致性,并禁止更新案例值,以提供干性和更多的 自由 来发挥创造力。

无论如何,枚举很棒,而这个包的目标是使它们更容易使用。

它提供了一种统一的方式来实例化和使用它们。

此包实现为一个静态辅助类 Enumerate,可以单独使用来以标准化的方式(实例化、JSON 序列化等)操作任何枚举,一个特性,EnumerateTrait,可以用于您的枚举以使它们更容易处理,以及一个接口,EnumerateInterface,它扩展了 JsonSerializable 并可用于 instanceof 您的枚举。

安装

可以使用 composer 安装 Enumerate

composer require "fab2s/enumerate"

用法

要增强 任何 PHP 枚举,请在您的枚举中使用 EnumerateTrait 特性

use fab2s\Enumerate\EnumerateTrait;

enum MyEnum // :string or : int or nothing
{
    use EnumerateTrait;
    // ...

Enumerate 通过 EnumerateInterface 实现了 jsonSerialize(),但,这也是 PHP Traits 的另一个有疑问的问题,您将必须声明您的枚举实现了 JsonSerializableEnumerateInterface 接口,因为特性目前不能这样做

use fab2s\Enumerate\EnumerateTrait;
use fab2s\Enumerate\EnumerateInterface;

enum MyEnum /* :string or : int or nothing */ implements EnumerateInterface // or JsonSerializable
{
    use EnumerateTrait;
    // ...

那么呢?

从那里开始,您的枚举可以从大多数 Enumerate 辅助方法中受益,直到类型解析方法。

BackedEnum::tryFrom

当前状态是,IntBackedEnumStringBackedEnum 都是 BackedEnum,但它们在我们允许尝试的类型上并不一致。

这导致了一种情况,即尝试实际上只允许在单个类型(stringint)上,而尝试本身的概念似乎本身就要广泛得多。我的意思是,为什么我们不能尝试 null 或甚至枚举 实例呢?

只要结果一致,尝试就没有问题。在实践中,这通常会导致在尝试任何事物之前必须实施更多的检查。

Enumerate 通过添加 tryFromAny 方法来解决这个问题

// in EnumerateTrait /  EnumerateInterface
    /**
     * @throws ReflectionException
     */
    public static function tryFromAny(int|string|UnitEnum|null $value, bool $strict = true): ?static

// in Enumerate
    /**
     * @param UnitEnum|class-string<UnitEnum|BackedEnum> $enum
     *
     * @throws ReflectionException
     */
    public static function tryFromAny(UnitEnum|string $enum, int|string|UnitEnum|null $value, bool $strict = true): UnitEnum|BackedEnum|null

所以现在你可以尝试更多类型,这是一种更实用的方法,而不会破坏答案的一致性。

尝试一个 null 将是 null,尝试一个 实例 将在它有意义时给出该 实例 本身,在 StringBackedEnum 上尝试一个 int 将是 null,在 IntBackedEnum 上尝试一个字符串也将是 null。这样做不会破坏任何东西,它只是内部处理了你否则必须在外部处理的复杂性。

没有花哨的东西,只是可用性。

Enumerate 如果你决定放弃 严格性(也就是说,你可以自由选择是否这样做)可以更进一步

// will always be null
$result = MyEnum::tryFromAny(AnotherEnumWithOverlappingCases::someCase); 
// same as 
$result = Enumerate::tryFromAny(MyEnum::class, AnotherEnumWithOverlappingCases::someCase); 

// can return MyEnum::someCase if the case exist in MyEnum
// matched by value or case name for Unitenum's
$result = MyEnum::tryFromAny(AnotherEnumWithOverlappingCases::someCase, false);
// same as, works with enum FQN and instances
$result = Enumerate::tryFromAny(MyEnum::anyCase, AnotherEnumWithOverlappingCases::someCase, false);

BackedEnum::from

同样,Enumerate 提供了 fromAny,它执行与本地 from 方法相同的功能,但在无法从输入创建实例时抛出 InvalidArgumentException 而不是返回 null

tryFromAny 一样,你可以减少 严格性

// throws an InvalidArgumentException if someCase is not present in MyEnum
// either by value for BackedEnum or case name for Unitenum
$result = MyEnum::fromAny('someCase');
// same as
$result = Enumerate::fromAny(MyEnum::class, 'someCase');


// can return MyEnum::someCase if the case exist in MyEnum
$result = MyEnum::fromAny(AnotherEnumWithOverlappingCases::someCase, false);
// same as
$result = Enumerate::fromAny(MyEnum::class, AnotherEnumWithOverlappingCases::someCase, false);

仅仅 Stringable

Enumerate 添加了一个 toValue 方法,这是迄今为止最接近 stringable 的方法。类型被尊重,toValue 将返回:- IntBackedEnum 的整数值 - StringBackedEnum 的字符串值 - Unitenum 的字符串情况名称

所有这些值都可以作为有效输入,使用 tryFromAny / fromAny 创建实例。

// either someCase name for UnitEnum or someCase value for BackedEnum
$result = MyEnum::someCase->toValue();
// same as
$result = Enumerate::toValue(MyEnum::someCase);

UnitEnum

EnumerateUnitEnum 视为任何 Enum,使用 情况名称 匹配逻辑,而不是默认和内置的 情况值 匹配。

这是通过内部使用 fromName / tryFromName 方法完成的,从而完全统一了枚举类型。

这样做当然比值匹配要慢,因为我们必须遍历情况,但它可能在 UnitEnum 存在于现有代码的情况下很有用。

不用说,这样做使得存储和传输 UnitEnum 变得很容易。

use fab2s\Enumerate\Enumerate;
use fab2s\Enumerate\EnumerateTrait;
use fab2s\Enumerate\EnumerateInterface;

enum SomeUnitEnum implements EnumerateInterface
{
    use EnumerateTrait;

    case ONE;
    case TWO;
    case three;
}

SomeUnitEnum::tryFromName('ONE'); // SomeUnitEnum::ONE
// same as
Enumerate::tryFromName(SomeUnitEnum::class, 'ONE'); // SomeUnitEnum::ONE

// the toValue method is the nearest we can get to stringable
SomeUnitEnum::ONE->tovalue(); // "ONE"
// same as
SomeUnitEnum::ONE->jsonSerialize(); // "ONE"
// same as, except it will take nulls in
Enumerate::toValue(SomeUnitEnum::ONE); // "ONE"
// same as 
json_encode(SomeUnitEnum::ONE); // "ONE"


SomeUnitEnum::fromAny('TWO'); // UnitEnum::TWO
SomeUnitEnum::tryFromAny('TWO'); // UnitEnum::TWO
SomeUnitEnum::tryFromAny(UnitEnum::TWO); // UnitEnum::TWO

BackedEnum

IntBackedEnumStringBackedEnum 工作方式相同。

use fab2s\Enumerate\EnumerateTrait;

enum SomeIntBackedEnum: int implements JsonSerializable
{
    use EnumerateTrait;

    case ONE   = 1;
    case TWO   = 2;
    case three = 3;
}

SomeIntBackedEnum::tryFromAny(1); // SomeIntBackedEnum::ONE
SomeIntBackedEnum::tryFromAny('1'); // null
SomeIntBackedEnum::tryFromAny('ONE'); // null
SomeIntBackedEnum::tryFromName('ONE'); // SomeIntBackedEnum::ONE

比较枚举

Enumerate 提供了两个方法来断言某些输入是否与情况匹配:equalscompares

equals 方法是严格的,只有在至少一个参数可以转换为枚举情况时才会返回 true,而 compares 即使只有匹配来自兼容的 enum 实例(读取另一个重叠当前 enum 情况之一的 enum)也会这样做。

同样,UnitEnum 通过 case name 匹配,这似乎是完全合理的。

// true when someCaseValue is the value of someCase for BackedEnum
// false for UnitEnum, would be true with someCase
MyEnum::someCase->equals('someCaseValue'); 
// same as 
Enumerate::equals(MyEnum::someCase, 'someCaseValue')
// always true
MyEnum::someCase->equals(MyEnum::someCase, null, 'whatever' /*, ...*/); 
// same as 
Enumerate::equals(MyEnum::someCase, MyEnum::someCase, null, 'whatever' /*, ...*/)

// true if we have an equality by value for BackedEnum
// or by name for UnitEnum
MyEnum:::someCase->compares(AnotherEnumWithOverlappingCases::someCase); 
// same as 
Enumerate::compares(MyEnum:::someCase, AnotherEnumWithOverlappingCases::someCase); 

要求

Enumerate 已在 php 8.1、8.2 和 8.3 上进行测试

贡献

欢迎贡献,不要犹豫,提出问题和提交拉取请求。

许可

Enumerate 是开源软件,根据 MIT 许可证 许可。