fab2s / enumerate
enumerate,为您的枚举类型提供强大支持
Requires
- php: ^8.1
Requires (Dev)
- laravel/pint: ^1.10
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2024-09-21 21:19:40 UTC
README
enumerate
为您的本地枚举类型提供强大支持。本地枚举。
为什么?
PHP 枚举是 PHP 的一大亮点,但它们缺少一些使其在实际应用中完全实用的东西。如果您深入了解,会发现大多数当前限制似乎更多与意识形态有关,而不是实用性。
作为一个软件工艺精神的拥护者,我认为这样的特性应该在其内部承载更多复杂性,以在外部提供更多简洁性。
例如,我实在不理解为什么枚举不能,更糟糕的是,甚至不能被转换为字符串。如果您认为 Unitenum
不应该被转换为字符串,或者您会认为将 IntBackedEnum
转换为 (string)
是背叛,这些都只是 SHOULDs
,它们只是限制了我们如何在现实生活中使用它们,当它们成为 MUST
时。
在实践中,即使是将 IntBackedEnum
转换为字符串,在 HTTP
上下文中(在这种情况下字符串是唯一类型)或写入数据库时也是有意义的。当然,这些都是捷径,但为什么我们被迫走每一条弯路呢?
当然,我更希望有一个接口,允许我们对象进行 scalar
转换,并最终得到枚举的 int
或 string
,但这只是一个梦想 :-)
遗憾的是,我们目前无法自由地将此类行为嵌入我们的枚举中。
我全力支持严格类型,但我发现不得不编写代码只是为了能够读取 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
的另一个有疑问的问题,您将必须声明您的枚举实现了 JsonSerializable
或 EnumerateInterface
接口,因为特性目前不能这样做
use fab2s\Enumerate\EnumerateTrait; use fab2s\Enumerate\EnumerateInterface; enum MyEnum /* :string or : int or nothing */ implements EnumerateInterface // or JsonSerializable { use EnumerateTrait; // ...
那么呢?
从那里开始,您的枚举可以从大多数 Enumerate
辅助方法中受益,直到类型解析方法。
BackedEnum::tryFrom
当前状态是,IntBackedEnum
和 StringBackedEnum
都是 BackedEnum
,但它们在我们允许尝试的类型上并不一致。
这导致了一种情况,即尝试实际上只允许在单个类型(string
或 int
)上,而尝试本身的概念似乎本身就要广泛得多。我的意思是,为什么我们不能尝试 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
Enumerate
将 UnitEnum
视为任何 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
IntBackedEnum
和 StringBackedEnum
工作方式相同。
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
提供了两个方法来断言某些输入是否与情况匹配:equals
和 compares
。
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 许可证 许可。