Middle: питання на співбесіді з теми «Enums»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Backed enum кастується в моделі, і колонка починає повертати обʼєкт замість рядка:
enum VacancyLevel: string
{
case Junior = 'junior';
case Middle = 'middle';
case Senior = 'senior';
public function label(): string
{
return match ($this) {
self::Junior => 'Junior',
self::Middle => 'Middle',
self::Senior => 'Senior',
};
}
}
class Vacancy extends Model
{
protected function casts(): array
{
return ['level' => VacancyLevel::class];
}
}
Тепер $vacancy->level - це enum, а не рядок:
$vacancy->level->label();
$vacancy->level === VacancyLevel::Senior;
$vacancy->update(['level' => VacancyLevel::Middle]); // у базу піде 'middle'
Переваги над константами класу:
- Обмежена множина. Значення поза переліком не існує, тоді як константа не заважає передати будь-який рядок.
- Типізація.
function assign(VacancyLevel $level)не прийме випадковий рядок, і редактор підкаже варіанти. - Поведінка поруч зі значенням. Enum має методи, тож підпис, колір чи іконка живуть там само, а не в розкиданих
matchпо шаблонах. matchбезdefaultпідсвітить пропущений випадок, коли додасте новий кейс.
Дві практичні деталі. У валідації є правило Rule::enum(VacancyLevel::class). А tryFrom() повертає null замість винятку - саме він потрібен, коли значення приходить від користувача чи з URL.
Так. Enum у PHP - повноцінний тип: він може мати методи (звичайні й статичні), константи, реалізовувати інтерфейси й використовувати трейти (без властивостей).
interface HasLabel
{
public function label(): string;
}
enum OrderStatus: string implements HasLabel
{
case New = 'new';
case Paid = 'paid';
case Shipped = 'shipped';
public const DEFAULT = self::New;
public function label(): string
{
return match ($this) {
self::New => 'Нове',
self::Paid => 'Оплачене',
self::Shipped => 'Відправлене',
};
}
public function isFinal(): bool
{
return $this === self::Shipped;
}
public static function forSelect(): array
{
return array_combine(
array_column(self::cases(), 'value'),
array_map(fn (self $s) => $s->label(), self::cases()),
);
}
}
$order->status->label(); // 'Оплачене'
OrderStatus::DEFAULT; // OrderStatus::New
Навіщо це: вся поведінка, пов'язана з переліком, живе в одному місці. Замість if ($status === 'paid' || $status === 'shipped'), розкиданих по коду, - $status->isFinal(). Додали новий варіант - match без default змусить оновити кожен метод.
Інтерфейси дозволяють використовувати різні enum однаково: Filament і багато бібліотек розпізнають інтерфейси на кшталт HasLabel, HasColor, HasIcon і самі показують мітку, колір і іконку.
Обмеження:
- Немає властивостей (стану) - лише константи.
- Не можна успадкувати один enum від іншого.
- Трейти - лише без властивостей.
- Магічні методи заборонені, крім
__call,__callStaticі__invoke.
Варіант enum - це об'єкт, а ключем масиву в PHP може бути лише int чи string. Спроба дає помилку:
$limits = [Plan::Free => 3, Plan::Pro => 100];
// TypeError: Cannot access offset of type Plan on array
Варіанти:
->valuebacked enum як ключ:
$limits = [Plan::Free->value => 3, Plan::Pro->value => 100];
$limits[$user->plan->value];
Просто, але втрачається тип: ключ знову рядок, і опечатка в ключі не ловиться.
- Метод на самому enum - найчастіше найкращий варіант:
enum Plan: string
{
case Free = 'free';
case Pro = 'pro';
public function projectLimit(): int
{
return match ($this) {
self::Free => 3,
self::Pro => 100,
};
}
}
Знання про ліміт живе разом із варіантами, а новий варіант без гілки в match не пройде непомітно.
SplObjectStorageчиWeakMap- коли потрібна справжня мапа «об'єкт → значення», наприклад, для підрахунку:$counts[$status] ??= 0зWeakMapпрацює з enum як ключами.
Ще про порівняння: < і > між варіантами не мають сенсу - результат завжди false, бо порядку між об'єктами немає. Якщо порядок потрібен, - метод order(): int чи порівняння ->value для числових backed enum. Рівність перевіряють через ===: варіанти - синглтони.
Докладніше в документації: Відмінності перелічень від об'єктів