使用属性扩展 PHP 8.1 枚举
发布时间 作者 Rob Fonseca
随着 PHP 8.1 的发布,该语言获得了对枚举的原生支持。枚举是一种类型安全、可读且高效的方式,用于封装数据模型中字段可以采用的少量可能值。使用类而不是数据库枚举提供了更多灵活性,如果你将来需要添加列表。
例如,你有一个用户数据模型,该用户可能具有可以从中选择的特定角色列表。
namespace App\Enums; enum UserRole: string{ case Admin = 'admin'; case TeamAdmin = 'team_admin'; case Support = 'support'; case Basic = 'basic';}
在你的数据模型中,Laravel 还通过将它们转换为值来支持枚举,如果你在 casts 数组中定义了它。
/** * The attributes that should be cast. * * @var array<string,string|class-string> */protected $casts = [ 'role' => UserRole::class,];
添加枚举转换将确保如果我们尝试将未在枚举中定义的角色保存到我们的用户模型,则会抛出异常。
枚举的一个非常实用的用途是为 HTML 中的下拉列表生成值。
<select name=”roles”> @foreach(UserRole::cases() as $role) <option value="{{ $role->value }}">{{ $role->name }}</option> @endforeach</select>
表面上看,上面的示例似乎没有问题,直到你查看下拉列表中每个选项的可见名称。Admin 和 TeamAdmin 是很好的变量名,但Administrator 和 Team Administrator 在 UI 中展示会更好,这样对于管理用户角色的人来说就非常清楚角色是什么。
虽然枚举非常适合简单的名称/值对,但在需要添加第三个属性的情况下,你需要发挥创意。
进入 PHP 属性。借鉴其他语言中注解的概念,它是一种将元数据与属性、方法和类关联起来的方式,这听起来正是我们需要的。
首先,我们需要构建一个 Description 属性。
namespace App\Enums\Attributes; use Attribute; #[Attribute]class Description{ public function __construct( public string $description, ) { }}
现在我们有了 Description 属性,我们可以在我们的枚举中使用它,这样我们就可以定义我们想要的友好的角色名称。
namespace App\Enums; enum UserRole: string{ #[Description('Administrator')] case Admin = 'admin'; #[Description('Team Administrator')] case TeamAdmin = 'team_admin'; case Support = 'support'; case basic = 'basic';}
现在我们需要检索这些属性,这只能通过反射来完成。由于我们可能希望在其他枚举上重复使用此属性,因此我们希望创建一个特质来简化此过程。
namespace App\Enums\Concerns; use Illuminate\Support\Str;use ReflectionClassConstant;use App\Enums\Attributes\Description; trait GetsAttributes{ /** * @param self $enum */ private static function getDescription(self $enum): string { $ref = new ReflectionClassConstant(self::class, $enum->name); $classAttributes = $ref->getAttributes(Description::class); if (count($classAttributes) === 0) { return Str::headline($enum->value); } return $classAttributes[0]->newInstance()->description; }}
如果我们将此方法分解,前两行使用反射来获取枚举的属性。由于并非每个枚举都可能具有 Description 属性,因此我们设置了一个回退,将该枚举的值(或名称)转换为我们的描述。
最后,我们从枚举属性中提取描述的值。我们可以向我们的特质添加另一个方法来处理此问题。
/** * @return array<string,string> */public static function asSelectArray(): array{ /** @var array<string,string> $values */ $values = collect(self::cases()) ->map(function ($enum) { return [ 'name' => self::getDescription($enum), 'value' => $enum->value, ]; })->toArray(); return $values;}
现在,在我们的 HTML 中,我们只需更改我们在枚举类上调用的方法。
<select name=”roles”> @foreach(UserRoles::asSelectArray() as $role) <option value=”{{ $role->value }}”>{{ $role->name }}</option> @endforeach</select>
虽然它们是 PHP 中的相对新手,但枚举和属性是该语言的绝佳补充,并为许多常见用例提供原生支持。