Backed 열거형
Backed 열거형 (Backed enumerations)
기본적으로 열거형 케이스(Enumerated Case)는 스칼라에 대응하는 값이 없어요. 그냥 단일 객체(singleton object)일 뿐이죠. 그런데 열거형 케이스를 데이터베이스나 비슷한 데이터 저장소에 오가며 저장하고 싶은 경우가 많아요. 그래서 내장된 스칼라 값(그래서 쉽게 직렬화할 수 있는 값)을 기본으로 정의해 둘 수 있으면 편리하답니다.
열거형에 스칼라 대응값을 정의하는 문법은 이렇게 써요.
<?php
enum Suit: string
{
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}
이렇게 스칼라 대응값을 가진 케이스를 Backed Case라고 불러요. 더 단순한 값으로 "뒷받침(Backed)"되고 있기 때문이죠. 케이스가 전부 Backed Case로 이루어진 열거형을 **"Backed Enum"**이라고 해요. Backed Enum은 Backed Case만 가질 수 있고, Pure Enum(스칼라 값 없는 열거형)은 Pure Case만 가질 수 있어요.
Backed Enum은 int나 string 타입으로만 뒷받침할 수 있고, 하나의 열거형은 한 번에 한 가지 타입만 지원해요. 즉 int|string처럼 합집합 타입은 쓸 수 없어요. 열거형이 스칼라 대응값을 가진다고 표시하면, 모든 케이스는 고유한 스칼라 대응값을 명시적으로 정의해야 해요. 순차 정수처럼 자동으로 생성되는 스칼라 대응값은 없어요. 또 Backed Case는 전부 고유해야 해요. 두 Backed Enum 케이스가 같은 스칼라 대응값을 가질 수 없다는 뜻이죠. 다만 상수가 케이스를 가리키게 해서 사실상 별칭(alias)을 만들 수는 있어요. 자세한 내용은 열거형 상수 문서를 봐요.
대응값은 상수 스칼라 표현식(constant scalar expression)일 수 있어요. PHP 8.2.0 이전에는 대응값이 리터럴 또는 리터럴 표현식이어야 했어요. 즉 상수나 상수 표현식은 지원되지 않았죠. 1 + 1은 허용됐지만 1 + SOME_CONST는 안 됐다는 뜻이에요.
Backed Case에는 추가적인 읽기 전용 프로퍼티 value가 있어요. 정의에서 지정한 그 값이 들어가지요.
<?php
enum Suit: string
{
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}
print Suit::Clubs->value;
// Prints "C"
value 프로퍼티를 읽기 전용으로 강제하기 위해, 변수를 그 값의 참조로 할당할 수는 없어요. 즉 아래 코드는 오류를 던져요.
<?php
enum Suit: string
{
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}
$suit = Suit::Clubs;
$ref = &$suit->value;
// Fatal Error: Cannot indirectly modify readonly property Suit::$value
Backed Enum은 내부적으로 BackedEnum 인터페이스를 구현하고, 이 인터페이스는 두 개의 추가 메서드를 노출해요.
from(int|string): self— 스칼라 값을 받아 대응하는 열거형 케이스를 반환해요. 찾지 못하면ValueError를 던져요. 입력 스칼라 값을 신뢰할 수 있고, 열거형 값이 없으면 애플리케이션을 멈추는 오류로 봐야 할 때 주로 유용해요.tryFrom(int|string): ?self— 스칼라 값을 받아 대응하는 열거형 케이스를 반환해요. 찾지 못하면null을 반환해요. 입력 스칼라 값을 신뢰할 수 없고 호출자가 자체 오류 처리나 기본값 로직을 구현하고 싶을 때 주로 유용해요.
from()과 tryFrom() 메서드는 표준 weak/strong 타입 규칙을 따라요. weak 타입 모드에서는 정수나 문자열을 넘겨도 되고, 시스템이 값을 그에 맞게 강제 변환(coerce)해요. float를 넘겨도 작동하며 역시 강제 변환돼요. strict 타입 모드에서는 string 기반 열거형의 from()에 정수를 넘기면(또는 그 반대) TypeError가 나요. float도 어떤 상황에서든 마찬가지예요. 그 외의 모든 파라미터 타입은 두 모드 모두에서 TypeError를 던져요.
<?php
enum Suit: string
{
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}
function get_stuff_from_database($id) {
return [
'suit' => 'S',
];
}
$record = get_stuff_from_database(42);
print $record['suit'] . "\n";
$suit = Suit::tryFrom('A') ?? Suit::Spades;
// Invalid data returns null, so Suit::Spades is used instead.
print $suit->value . "\n";
$suit = Suit::from('X');
// Invalid data throws a ValueError: "X" is not a valid backing scalar value for enum Suit
print $suit->value . "\n";
Backed Enum에 from()이나 tryFrom() 메서드를 직접 정의하면 치명적 오류(fatal error)가 나요.