thunderer/platenum

PHP 枚举库

v0.4.0 2024-02-03 15:24 UTC

This package is auto-updated.

Last update: 2024-08-30 08:14:24 UTC


README

Build Latest Stable Version Total Downloads Psalm coverage Code Coverage Scrutinizer Code Quality License

Platenum 提供了一个灵活且功能齐全的解决方案,用于在 PHP 中处理枚举(enums),无需任何外部依赖。其名称来源于拉丁语中代表铂金的化学元素。

安装

此库可在 Packagist 上找到,并可以使用 Composer 在支持 PHP 7.1 及以上版本的项目中进行安装。

composer require thunderer/platenum

使用方法

创建一个带有成员定义的新类

<?php
declare(strict_types=1);
namespace X;

use Thunder\Platenum\Enum\ConstantsEnumTrait;

/**
 * @method static static ACTIVE()
 * @method static static INACTIVE()
 * @method static static SUSPENDED()
 * @method static static DISABLED()
 */
final class AccountStatusEnum
{
    private const ACTIVE = 1;
    private const INACTIVE = 2;
    private const SUSPENDED = 3;
    private const DISABLED = 4;

    use ConstantsEnumTrait;
}

提示:为了启用常量方法的自动完成功能,请包含如上所示列表中的 @method 声明

可以使用常量方法、成员名称或其值来创建成员实例。

$active = AccountStatusEnum::ACTIVE();
$alsoActive = AccountStatusEnum::fromMember('ACTIVE');
$stillActive = AccountStatusEnum::fromValue(1);

可以使用严格的 === 操作符或 equals() 方法来比较枚举。

assert($active === $alsoActive);
assert(true === $active->equals($alsoActive));

注意:始终首选严格的 === 比较。松散的 == 比较也将正确工作,但它有很多怪癖

getValue() 方法返回给定实例的原始值。

assert(1 === $active->getValue());

枚举生成器

可以使用内置的 bin/generate 工具自动生成类。它接受三个参数:

  • 其成员的位置(可以是 constantsdocblockstatic),
  • (完全限定)类名,Platenum 会将命名空间与您的自动加载配置相匹配,并将文件放入正确的目录中,
  • 成员名称和可选值(如果支持)。

示例

bin/generate constants Thunder\\Platenum\\YourEnum FOO=1,BAR=3
bin/generate docblock Thunder\\Platenum\\YourEnum FOO,BAR
bin/generate static Thunder\\Platenum\\YourEnum FOO,BAR=3

来源

Platenum 可以从多个来源读取枚举成员。基本 EnumTrait 提供了所有枚举功能,不依赖任何来源,需要在静态 resolve() 方法中定义。每个来源都可以作为使用 EnumTrait 并具有具体 resolve() 方法实现的 trait 以及基于该 traitabstract class。建议使用 trait,因为目标枚举类不应有任何公共类型提示。

在本节中,将使用具有两个成员(ACTIVE=1DISABLED=2)的 StatusEnum 类作为示例。

类常量

final class StatusEnum
{
    use ConstantsEnumTrait;

    private const ACTIVE = 1;
    private const DISABLED = 2;
}
final class StatusEnum extends AbstractConstantsEnum
{
    private const ACTIVE = 1;
    private const DISABLED = 2;
}

类文档块

注意:无法在文档块中指定成员值,因此所有成员名称也是它们的值 - 在这种情况下 ACTIVE='ACTIVE'DISABLED='DISABLED'

/**
 * @method static static ACTIVE()
 * @method static static DISABLED()
 */
final class StatusEnum
{
    use DocblockEnumTrait;
}
/**
 * @method static static ACTIVE()
 * @method static static DISABLED()
 */
final class StatusEnum extends AbstractDocblockEnum {}

属性(PHP 8.0)

通过声明成员来利用 PHP 8.0 的功能。

#[Member('ACTIVE', 1)]
#[Member('DISABLED', 2)]
final class StatusEnum
{
    use AttributeEnumTrait;
}
use Thunder\Platenum\Enum\Member;

#[Member(member: 'ACTIVE',   value: 1)]
#[Member(member: 'DISABLED', value: 2)]
final class StatusEnum extends AbstractAttributeEnum {}

静态属性

final class StatusEnum
{
    use StaticEnumTrait;

    private static $mapping = [
        'ACTIVE' => 1,
        'DISABLED' => 2,
    ];
}
final class StatusEnum extends AbstractStaticEnum
{
    private static $mapping = [
        'ACTIVE' => 1,
        'DISABLED' => 2,
    ];
}

回调

final class Currency
{
    use CallbackEnumTrait;
}
final class Currency extends AbstractCallbackEnum
{
}

与其它类型不同,回调枚举需要在创建成员实例之前进行初始化。为了使其可用,请运行带有返回 member => value 映射(类似于 StaticEnumTrait)的回调的 initialize() 方法。此回调将在创建第一个成员实例之前正好运行一次。

Currency::initialize(fn() => [
    'PLN' => 985,
    'EUR' => 978,
    'USD' => 840,
]);

注意:此类型允许从几乎任何外部位置(数据库、Redis、会话、文件等)加载成员和值映射。对该可调用的唯一要求是它返回适当的 member => value 对。

Currency::initialize(fn() => SomeClass::CONSTANT);
Currency::initialize(fn() => $database->sql('...'));
Currency::initialize(fn() => $redis->hGetAll('...'));
Currency::initialize(fn() => json_decode(file_get_contents('...')));
// etc.

自定义来源

注意:当枚举首次使用时,才会调用 resolve 方法。

final class StatusEnum
{
    use EnumTrait;

    private static function resolve(): array
    {
        return [
            'ACTIVE' => 1,
            'DISABLED' => 2,
        ];
    }
}

异常

该库会在枚举类中发生所有错误时抛出默认的 PlatenumException,并带有针对所有错误的专用消息。某些情况可能需要专门的异常类和消息。要重新定义异常逻辑,覆盖以下所述的一个或多个静态方法。

  • throwInvalidMemberException() 用于枚举在任意方法中接收到无效枚举 成员 时。
  • throwInvalidValueException() 用于枚举在任意方法中接收到无效枚举 时。

注意:如果覆盖的方法不会抛出异常,库中包含一个安全措施,它仍将抛出默认异常。这样,开发中的疏忽就不会隐藏您的应用程序中的错误。

final class AccountStatus
{
    use ConstantsEnumTrait;

    private const ACTIVE = 1;
    private const DISABLED = 2;

    protected static function throwInvalidMemberException(string $name): void
    {
        throw new InvalidAccountStatusException($name);
    }

    protected static function throwInvalidValueException($value): void
    {
        throw new InvalidAccountStatusValueException($value);
    }
}

持久化

枚举通常用于实体并在 ORM 中映射。通过调用专用的 PlatenumDoctrineType 静态方法来注册您自定义的 Doctrine 枚举类型。

PlatenumDoctrineType::registerString('currency', Currency::class);
PlatenumDoctrineType::registerInteger('accountStatus', AccountStatus::class);

提供的别名可以作为 Doctrine 类型使用,如下所示(等效的 XML 和 PHP 映射)。

<entity name="App\Entity" table="app_entity">
    <id name="id" type="bigint" column="id" />
    <field name="currencyCode" type="currency" column="currency_code" />
    <field name="status" type="accountStatus" column="status" />
</entity>
final class Entity
{
    public static function loadMetadata(ClassMetadata $m): void
    {
        $m->setPrimaryTable(['name' => 'doctrine_entity']);

        $m->mapField(['fieldName' => 'id',     'type' => 'bigint',        'id' => true]);
        $m->mapField(['fieldName' => 'code',   'type' => 'currency',      'columnName' => 'code']);
        $m->mapField(['fieldName' => 'status', 'type' => 'accountStatus', 'columnName' => 'status']);
    }
}

原因

PHP 生态系统中已经有一些 enum 库了。为什么还需要另一个?这样做有几个原因。

来源 Platenum 允许枚举成员有多个来源。 EnumTrait 包含所有枚举函数 - 通过扩展您的自定义 resolve() 方法来创建自定义来源。实际上,这个存储库中所有枚举来源都是这样定义的。

特性 Platenum 为枚举成员、值、比较、转换等各种操作提供了完整的特性集。查看 PhpEnumerations 项目,了解在开发此库期间创建的功能矩阵。

继承 现有解决方案使用继承来创建枚举类。

class YourEnum extends LibraryEnum
{
    const ONE = 1;
    const TWO = 2;
}

枚举应表示为不同的类型,而无法互换使用。Platenum 利用 trait 来提供完整的类体,因此 instanceof 比较将失败,正如它应该的那样,也没有可能将泛型 LibraryEnum 类类型提示为允许任何枚举实例。

比较 创建某个枚举值的多个实例不应阻止您像其他任何变量一样严格地比较它们。其他解决方案鼓励使用松散比较(==),因为具有相同值的实例并不是它们类中的相同实例。此库保证相同的枚举值实例始终是相同的实例,可以严格比较。

final class YourEnum
{
    private const ONE = 1;
    private const TWO = 2;

    use EnumTrait;
}

YourEnum::ONE() === YourEnum::ONE()
YourEnum::fromValue(1) === YourEnum::ONE()
YourEnum::fromValue(1) === YourEnum::fromValue(1)

注意:如果您想通过反射或其他 opcache 修改扩展(如 uopz)来证明我是错的,那么请省下这份力气,您是对的,我认输了。

序列化

Platenum 提供正确的(反)序列化解决方案,它保留了其单个成员实例的保证。

唯一违反此保证的情况是当枚举实例在另一个类中 unserialize() 时,因为 PHP 总是在那里创建一个新的对象。这可以通过在 __wakeup() 方法中使用 fromInstance 替换辅助方法来轻松缓解,该方法通过引用接受其参数并自动将其交换到正确的实例。

public function __wakeup()
{
    $this->enum->fromInstance($this->enum);
}

请注意,equals() 方法不受影响,因为它不依赖于相同的对象实例,而是依赖于其类和实际值。

许可证

请参阅此库主目录中的 LICENSE 文件。