001. PHP 8.1 Enums:枚举详解
说明
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);