cloudstek/php-enum

PHP 的枚举支持

v1.0.0 2020-03-01 03:36 UTC

This package is auto-updated.

Last update: 2024-09-18 20:50:10 UTC


README

PHP 的枚举支持。

Build Status Coverage Status GitHub tag (latest SemVer) Downloads GitHub GitHub stars

此包为 PHP 添加了对 枚举 的支持,遗憾的是 PHP 本身并没有原生支持。

⚠️ 自 PHP 8.1 以来,PHP 终于原生支持了枚举。如果您需要使用枚举,请考虑升级到 PHP 8.1+ 并迁移离开此包。

仅使用具有常量的简单类无法让您使用类型提示,这意味着您仍然需要进行大量的检查以确定值是否预期。此包允许您以相同的方式定义枚举,但允许对方法参数等使用类型提示。这样,您可以始终确保它包含一组具体的成员和值。

要求

  • PHP 7.1+
  • Composer*

* 由于该包仅由一个类组成,因此可以不使用 Composer 进行安装,但这显然不推荐。

安装

通过 composer 安装该包

composer require cloudstek/php-enum

使用方法

定义

Cloudstek\Enum\Enum 基类负责处理所有幕后工作,因此您只需从该枚举类扩展您的枚举类,并使用属性、常量、方法或这些的组合来定义您的成员。

例如,这个具有三个成员 TODOIN_PROGRESSDONETaskStatus 枚举。在示例中,每个成员都有一个字符串值,但您可以自由分配任何您喜欢的值。

use Cloudstek\Enum\Enum;

/**
 * @method static self TODO()
 * @method static self IN_PROGRESS()
 * @method static self DONE()
 */
class TaskStatus extends Enum
{
    private const TODO = 'todo';
    private const IN_PROGRESS = 'in_progress';
    private const DONE = 'done';
}

文档类型仅用于 IDE 中的自动完成,对于枚举的功能不是必需的。

请确保将您的成员定义为 privateprotected,以避免混淆,直接访问成员值而不是实例,这可能导致在您的代码期望实例而不是值时(如以下示例所示)引发异常。

TaskStatus::TODO !== TaskStatus::TODO()
class Task
{
    /** @var TaskStatus */
    private $status;

    /**
     * Set status
     *
     * @param TaskStatus $status
     */
    public function setStatus(TaskStatus $status)
    {
        $this->status = $status;
    }

    // ..
}

或者,如果您需要更灵活,get 方法将智能地通过名称返回成员,或者如果提供了一个对象,检查它是否是正确的类型。

class Task
{
    /** @var TaskStatus */
    private $status;

    /**
     * Set status
     *
     * @param TaskStatus|string $status
     * 
     * @throws \UnexpectedValueException On unknown status.
     */
    public function setStatus($status)
    {
        $this->status = TaskStatus::get($status);
    }

    // ..
}

有关如何定义您的成员以及如何命名它们的更多信息,请参阅 docs/definition.md

比较

由于枚举每个成员都对应一个单独的实例,因此您可以直接比较它们。

// Compare by instance
TaskStatus::TODO() === TaskStatus::TODO();                 // true
TaskStatus::TODO() === TaskStatus::get('todo');            // true
TaskStatus::get('TODO') === TaskStatus::get('todo');       // true
TaskStatus::TODO() === TaskStatus::get(TaskStatus::TODO()) // true

TaskStatus::TODO() === TaskStatus::DONE();                 // false
TaskStatus::TODO() === TaskStatus::get('done');            // false

// Compare by value
(string) TaskStatus::TODO() === 'todo';                    // true
TaskStatus::TODO()->getValue() === 'todo';                 // true

继承

您应该始终将枚举定义为 final 类,以防止其他类从它继承。如果您希望其他类继承它,请考虑将其定义为 abstract,并编写继承自它的 final 实例类。

如果不将其定义为最终类,则您的代码可能会接受继承的枚举,而实际上您期望的是基类。这可能会导致糟糕的 bug。

例如,考虑以下枚举

use Cloudstek\Enum\Enum;

class FooEnum extends Enum
{
    private const FOO = 'foo';
}

class BarEnum extends FooEnum
{
    private const BAR = 'bar';
}

如果不将 FooEnum 定义为最终类,则您的代码可能会无意中接受 BarEnum,尽管它期望的是 FooEnum

class Foo
{
    public function doSomething(FooEnum $foo)
    {
        // Do something...
    }
}

$foo = new Foo();
$foo->doSomething(FooEnum::FOO()); // Allowed and OK, we were expecting FooEnum
$foo->doSomething(BarEnum::BAR()); // Allowed but not OK, we got BarEnum!

为了防止这种情况,并确保我们始终得到 FooEnum,我们应该将其标记为最终类。这并不意味着它不能继承其他任何内容。

use Cloudstek\Enum\Enum;

abstract class BaseEnum extends Enum
{
    private const HELLO = 'world';
}

final class FooEnum extends BaseEnum
{
    private const FOO = 'foo';
}

final class BarEnum extends BaseEnum
{
    private const BAR = 'bar';
}

现在我们确保只得到 FooEnum 的实例。

class Foo
{
    public function doSomething(FooEnum $foo)
    {
      // Do something...
    }
}

$foo = new Foo();
$foo->doSomething(FooEnum::FOO()); // Allowed and OK, we were expecting FooEnum
$foo->doSomething(BarEnum::BAR()); // Fatal error

但是,如果我们真的不在乎,只要它的基类型是 BaseEnum,我们必须明确将参数类型更改为 BaseEnum,如下所示

class Foo
{
    public function doSomething(BaseEnum $foo)
    {
      // Do something...
    }
}

$foo = new Foo();
$foo->doSomething(FooEnum::FOO()); // OK
$foo->doSomething(BarEnum::BAR()); // OK

存储数据

如果您存储了包含枚举类型的数据,并且希望稍后将其转换回枚举,请确保使用 getName() 存储成员名称,而不是存储其值。如果您只关心值,可以直接使用 getValue() 存储值,或者在可能的情况下将其转换为字符串。

// Update task
$status = TaskStatus::TODO();

$db->update($task, [
    'status' => $status->getName() // 'status' => 'todo'
]);
// Fetch task
$taskRow = $db->tasks->fetchOne(13); // [..., 'status' => 'todo', ...]

$task = new Task();
// ..
$task->setStatus(TaskStatus::get($taskRow['status']));

// or if you call TaskStatus::get() in Task::setStatus()
$task->setStatus($taskRow['status']);

支持

您可以通过贡献到开放问题、提交拉取请求、给这个项目一个 ⭐ 或告诉您的朋友关于它来支持这个项目。

如果您有任何想法或问题,请打开一个问题!

相关项目