Питання на співбесіді: Enums
Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів
3 питання
Backed enum - перелік зі скалярним значенням, прив'язаним до кожного кейса:
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
}
Інтеграція з Laravel:
// каст у моделі - атрибут стає об'єктом enum
protected $casts = ['status' => Status::class];
// валідація
$request->validate(['status' => [Rule::enum(Status::class)]]);
// Route Model Binding теж резолвить enum з URL
Enum робить «магічні рядки» типобезпечними, а методи на enum (label(), color()) зручно інкапсулюють логіку відображення.
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 у коді змінюється легко, а от рядки, які вже лежать у базі, - ні. Саме тут зʼявляються помилки після деплою.
Найнебезпечніше - перейменувати кейс:
// було
case Middle = 'middle';
// стало
case Mid = 'mid';
Код збереться, а всі наявні рядки зі значенням middle перестануть кастуватися: ValueError: "middle" is not a valid backing value. Впаде не міграція, а звичайна сторінка.
Правильний порядок для перейменування:
- Додати новий кейс, лишивши старий.
- Міграцією перевести дані:
Vacancy::where('level', 'middle')->update(['level' => 'mid']). - Наступним релізом прибрати старий кейс.
Додати новий кейс - безпечно, якщо колонка varchar. Але коли в базі використано нативний тип enum, потрібна ще й міграція самої колонки, а Schema::table()->change() для нативних enum працює не в усіх драйверах - подекуди доводиться писати DB::statement().
Тому колонку під enum майже завжди роблять string: перелік живе в PHP, база зберігає рядок, і зміни не потребують ALTER на великій таблиці.
Захист від падіння на невідомому значенні:
// null замість винятку, коли в базі щось несподіване
$level = VacancyLevel::tryFrom($vacancy->getRawOriginal('level'));
І ще одне: якщо enum використовується у валідації через Rule::enum(), видалений кейс одразу зробить старі збережені записи невалідними при редагуванні - про це згадують уже після скарг користувачів.