Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Питання на співбесіді: 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()) зручно інкапсулюють логіку відображення.

Докладніше в документації: Валідація enum

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.

Докладніше в документації: Eloquent: мутатори та касти

Enum у коді змінюється легко, а от рядки, які вже лежать у базі, - ні. Саме тут зʼявляються помилки після деплою.

Найнебезпечніше - перейменувати кейс:

// було
case Middle = 'middle';

// стало
case Mid = 'mid';

Код збереться, а всі наявні рядки зі значенням middle перестануть кастуватися: ValueError: "middle" is not a valid backing value. Впаде не міграція, а звичайна сторінка.

Правильний порядок для перейменування:

  1. Додати новий кейс, лишивши старий.
  2. Міграцією перевести дані: Vacancy::where('level', 'middle')->update(['level' => 'mid']).
  3. Наступним релізом прибрати старий кейс.

Додати новий кейс - безпечно, якщо колонка varchar. Але коли в базі використано нативний тип enum, потрібна ще й міграція самої колонки, а Schema::table()->change() для нативних enum працює не в усіх драйверах - подекуди доводиться писати DB::statement().

Тому колонку під enum майже завжди роблять string: перелік живе в PHP, база зберігає рядок, і зміни не потребують ALTER на великій таблиці.

Захист від падіння на невідомому значенні:

// null замість винятку, коли в базі щось несподіване
$level = VacancyLevel::tryFrom($vacancy->getRawOriginal('level'));

І ще одне: якщо enum використовується у валідації через Rule::enum(), видалений кейс одразу зробить старі збережені записи невалідними при редагуванні - про це згадують уже після скарг користувачів.

Докладніше в документації: Міграції