001. PHP 8.1 Enums:枚举详解

本视频源码 github.com/LearnKu-LX4/001_005_php...

说明

PHP 8.1 在上周发布,最重磅的功能就是 Enums ,这个视频我演示如何使用。

为了方便演示,将使用这个在线工具 laravelplayground.com/#/?php=81 ,后面的参数是使用 PHP 8.1 版本。

1. 基本使用

<?php

enum Status
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;
}

class Article
{
    public function __construct(
        public Status $status, 
    ) {} 
}

$article = new Article(Status::DRAFT);

// Status {#1854 ▼
//     +name: "DRAFT"
//   }
dd($article->status);

2. Enum 的值

值为整型的 Enum:

enum Status: int
{
    case DRAFT     = 1;
    case PUBLISHED = 2;
    case ARCHIVED  = 3;
}

值为字符串的 Enum:

enum Status: string
{
    case DRAFT     = 'draft';
    case PUBLISHED = 'published';
    case ARCHIVED  = 'archived';
}

Enum 只支持整型和字符串两种类型。

使用其他类型会报错:

enum Status: float
{
    case DRAFT     = 1;
    case PUBLISHED = 2;
    case ARCHIVED  = 3;
}
FatalError
Enum backing type must be int or string, float given
enum Status: string
{
    case DRAFT     = 'draft';
    case PUBLISHED = 'published';
    case ARCHIVED  = 'archived';
}

class Article
{
    public function __construct(
        public Status $status, 
    ) {} 
}

$article = new Article(Status::DRAFT);

dump($article->status);

会打印:

// Status {#1854 ▼
//     +name: "DRAFT"
//     +value: "draft"
//   }

有两个属性,可以使用以下方法获得:

// "draft"
dump($article->status->value);

// "DRAFT"
dump($article->status->name);

Enum 的 cases() 方法可以将所有选项打印出来:

// array:3 [▼
//   0 => Status {#1854 ▼
//     +name: "DRAFT"
//     +value: "draft"
//   }
//   1 => Status {#1882 ▼
//     +name: "PUBLISHED"
//     +value: "published"
//   }
//   2 => Status {#1883 ▼
//     +name: "ARCHIVED"
//     +value: "archived"
//   }
// ]
dump($article->status->cases());

Enum 提供了 from() 方法来通过选项 value 的值来获取对应的选项:

// Status {#1882 ▼
//     +name: "PUBLISHED"
//     +value: "published"
//   }
dump(Status::from('published'));

当获取不存在的 value 时会报错:

// ValueError
// "unknown" is not a valid backing value for enum "Status"
dump(Status::from('unknown'));

如果不想报错来中断程序,可以使用 tryFrom() 方法:

// null
dump(Status::tryFrom('unknown'));

3. Enum 方法

接下来看这个列子:

enum HTTPStatus: int {
    case OK            = 200;
    case ACCESS_DENIED = 403;
    case NOT_FOUND     = 404;
}

// 可以作为传参类型绑定和返回值
function label_for_http_status(HTTPStatus $value) {
    return match ($value) {
        HTTPStatus::OK => '一切正常',
        HTTPStatus::ACCESS_DENIED => '无权限访问',
        HTTPStatus::NOT_FOUND => '页面未找到',
    };
}

class ApiResponse
{
    public function __construct(
        public HTTPStatus $status, 
    ) {} 
}

$response = new ApiResponse(HTTPStatus::NOT_FOUND);

// "页面未找到"
dump(label_for_http_status($response->status));

Enum 可以作为作为传参类型绑定,或者设定返回值的类型。

以上的函数 label_for_http_status() 可以通过在 Enum 里写方法的方式来解决:

enum HTTPStatus: int {
    case OK            = 200;
    case ACCESS_DENIED = 403;
    case NOT_FOUND     = 404;

    public function label(): string {
        return static::getLabel($this);
    }

    public static function getLabel(self $value): string {
        return match ($value) {
            HTTPStatus::OK => '一切正常',
            HTTPStatus::ACCESS_DENIED => '无权限访问',
            HTTPStatus::NOT_FOUND => '页面未找到',
        };
    }
}

class ApiResponse
{
    public function __construct(
        public HTTPStatus $status, 
    ) {} 
}

// "无权限访问"
dump(HTTPStatus::ACCESS_DENIED->label());

// "无权限访问"
dump(HTTPStatus::getLabel(HTTPStatus::ACCESS_DENIED));

// "页面未找到"
$response = new ApiResponse(HTTPStatus::NOT_FOUND);
dump($response->status->label());
exit;

4. 使用 Trait 的 Enum

// 不能包含属性
trait DisplayHelper {
    public function label(): string {
        return static::getLabel($this);
    }

    public static function getLabel(self $value): string {
        return match ($value) {
            HTTPStatus::OK => '一切正常',
            HTTPStatus::ACCESS_DENIED => '无权限访问',
            HTTPStatus::NOT_FOUND => '页面未找到',
        };
    }
}

enum HTTPStatus: int {
    use DisplayHelper;

    case OK            = 200;
    case ACCESS_DENIED = 403;
    case NOT_FOUND     = 404;
}

// "无权限访问"
dump(HTTPStatus::ACCESS_DENIED->label());

// "无权限访问"
dump(HTTPStatus::getLabel(HTTPStatus::ACCESS_DENIED));

注意 Trait 里不能包含属性,只能包含方法。

5. 继承 interface 的 Enum

interface APIStatus
{
    public function label(): string;
}

enum HTTPStatus: int implements APIStatus {
    case OK            = 200;
    case ACCESS_DENIED = 403;
    case NOT_FOUND     = 404;
}

继承 interface ,如果未实现 label 方法的话会报错:

// FatalError
// Class HTTPStatus contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (APIStatus::label)

正确的使用继承:

// 不能有属性
interface APIStatus
{
    public function label(): string;
}

// 继承
enum HTTPStatus: int implements APIStatus {
    case OK            = 200;
    case ACCESS_DENIED = 403;
    case NOT_FOUND     = 404;

    public function label(): string {
        return match ($this) {
            HTTPStatus::OK => '一切正常',
            HTTPStatus::ACCESS_DENIED => '无权限访问',
            HTTPStatus::NOT_FOUND => '页面未找到',
        };
    }
}

class ApiResponse
{
    public function __construct(
        public HTTPStatus $status, 
    ) {} 
}

$response = new ApiResponse(HTTPStatus::NOT_FOUND);

// HTTPStatus {#1854 ▼
//     +name: "NOT_FOUND"
//     +value: 404
//   }
dd($response->status);

注意 interface 里不能有属性。

6. Enum 不是类?

enum HTTPStatus: int {
    case OK            = 200;
    case ACCESS_DENIED = 403;
    case NOT_FOUND     = 404;
}

// true
dump("enum_exists(HTTPStatus::class):");
dump(enum_exists(HTTPStatus::class));

echo ("gettype(HTTPStatus::OK):");
dump(gettype(HTTPStatus::OK));

echo ("is_object(HTTPStatus::NOT_FOUND):");
dump(is_object(HTTPStatus::NOT_FOUND));

echo ("is_a(HTTPStatus::OK, HTTPStatus::class):");
dump(is_a(HTTPStatus::OK, HTTPStatus::class));

echo ("get_class(HTTPStatus::OK):");
dump(get_class(HTTPStatus::OK));

echo ("get_debug_type(HTTPStatus::OK):");
dump(get_debug_type(HTTPStatus::OK));

echo ("HTTPStatus::OK instanceof HTTPStatus:");
dump(HTTPStatus::OK instanceof HTTPStatus);

echo ("HTTPStatus::OK instanceof object:");
dump(HTTPStatus::OK instanceof object);

dump("new stdClass() === new stdClass():");
dump(new stdClass() === new stdClass());

dump("HTTPStatus::ACCESS_DENIED === HTTPStatus::ACCESS_DENIED:");
dump(HTTPStatus::ACCESS_DENIED === HTTPStatus::ACCESS_DENIED);

以上会打印:

"enum_exists(HTTPStatus::class):"
true
gettype(HTTPStatus::OK):
"object"
is_object(HTTPStatus::NOT_FOUND):
true
is_a(HTTPStatus::OK, HTTPStatus::class):
true
get_class(HTTPStatus::OK):
"HTTPStatus"
get_debug_type(HTTPStatus::OK):
"HTTPStatus"
HTTPStatus::OK instanceof HTTPStatus:
true
HTTPStatus::OK instanceof object:
false
"new stdClass() === new stdClass():"
false
"HTTPStatus::ACCESS_DENIED === HTTPStatus::ACCESS_DENIED:"
true

Enum 有不同于类:

enum Bar{}
enum Foo extends Bar {}

不能使用 extends ,以上会报错:

// ParseError
// syntax error, unexpected token "extends", expecting "{"

Enum 和类最大的不同点,是 Enum 不能有状态,也就是说不能使用类属性:

enum Foo {
    public string $value;
}

会报错:

// FatalError
// Enums may not include properties

不过类常量是允许的:

enum UserStatus {
    case NORMAL;
    case BLOCKED;

    public const DEFAULT = self::NORMAL;
}

class User
{
    public function __construct(
        public UserStatus $status, 
    ) {} 
}

$user = new User(UserStatus::DEFAULT);
dd($user->status);

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
贡献者:3
讨论数量: 0
发起讨论 查看所有版本


暂无话题~