PHP Enum: Руководство для начинающих

Источник: «Enums in PHP: A Beginner's Guide to Safer Coding»
Давайте улучшим ваш код с помощью более безопасного способа программирования с использованием перечислений PHP, или enum. В этой статье вы узнаете о них всё, что нужно.

Будучи разработчиком, вы наверняка сталкивались с ситуациями, когда переменная может принимать только одно из небольшого набора возможных значений. Например, переменная, отражающая статус пользователя, может иметь только значения Active, Inactive или Suspended.

Вы можете представить эти состояния с помощью отдельных логических переменных или присвоить им определённые строковые или целочисленные значения, но в этом случае все становится запутанным и чреватым ошибками. Разве не было бы здорово иметь способ объявить этот ограниченный набор возможных значений в ясной, самодокументирующейся манере? Именно здесь на помощь приходят перечисления, или, как их часто называют, Enum.

Перечисления уже много лет являются частью многих языков программирования. Они позволяют определить тип, ограниченный определённым набором значений, что повышает ясность и безопасность.

Для разработчиков PHP отличной новостью является то, что начиная с версии PHP 8.1, перечисления стали частью ядра PHP. Да, вы не ослышались! PHP теперь обеспечивает встроенную поддержку перечислений.

Краткая история перечислений (enum) в PHP

До версии PHP 8.1 в PHP не было встроенной поддержки перечислений. Хотя отсутствие перечислений не мешало разработчикам писать код, это означало, что в PHP отсутствовал инструмент, присутствующий во многих других языках, который может сделать кодирование более безопасным и эффективным.

Без встроенных перечислений разработчикам приходилось использовать другие конструкции для представления набора возможных значений, часто прибегая к константам классов, массивам, а иногда просто к строкам или целым числам.

Однако это могло привести к потенциальным ошибкам и часто приводило к тому, что код был не таким понятным, как мог бы быть.

Введение перечислений в PHP 8.1 стало значительным дополнением к языку.

Реализация перечислений в PHP является более мощной, чем во многих других языках, поскольку перечисления в PHP — это не просто целочисленные или строковые значения.

В PHP перечисление — это особый вид объекта, причём case Enum является единственным объектом данного класса.

Это означает, что вы можете использовать перечисления везде, где могли бы использовать объект, что делает их чрезвычайно универсальными.

Добавление перечислений в PHP означает постоянную эволюцию языка в сторону внедрения более современных концепций и парадигм программирования, повышая его эффективность и позволяя разработчикам писать более безопасный и чистый код.

В следующем разделе мы разберём основы перечислений в PHP.

Мы объясним, что такое перечисления, как они объявляются, и как вы можете использовать их в своём коде. Оставайтесь с нами, чтобы увидеть некоторые практические действия!

Понимание основ перечислений (enum) в PHP

Чтобы раскрыть основы использования перечислений в PHP, давайте перенесёмся в волшебный мир Гарри Поттера.

Представьте, что мы пишем код для школы чародейства и волшебства Хогвартс, где каждый новый ученик должен быть отсортирован по домам Сортировочной шляпой.

В Хогвартсе есть только четыре дома: Гриффиндор, Хаффлпафф, Рейвенкло и Слизерин. Таким образом, мы можем представить это как объект Enum в PHP.

Вот как объявить перечисление:

<?php

enum House
{
case Gryffindor;
case Hufflepuff;
case Ravenclaw;
case Slytherin;
}

В этом примере мы создали Enum с именем House, который может иметь четыре возможных значения — Gryffindor, Hufflepuff, Ravenclaw и Slytherin.

Давайте создадим класс Student, в котором каждый ученик будет обладать свойством $house.

class Student
{
public ?House $house = null;
}

У нас также есть класс SortingHat с методом сортировки.

Этот метод либо принимает предложение о выборе дома, либо случайным образом назначает дом ученику.

Как и в серии книг о Гарри Поттере, Сортировочная шляпа приняла во внимание выбор Гарри!

class SortingHat
{
public function sort(Student $student, ?House $suggestion = null)
{
if ($suggestion) {
$student->house = $suggestion;

return;
}

$houses = [
House::Gryffindor,
House::Hufflepuff,
House::Ravenclaw,
House::Slytherin,
];

$index = array_rand($houses);

$student->house = $houses[$index];
}
}

Как вы видите, мы ограничили значения свойств $suggestion и $house только одним из четырёх домов Хогвартса с помощью перечисления House.

Если бы вы попытались отсортировать ученика по несуществующему дому, перечисление PHP поймает это, и процесс не будет запущен.

В этом и заключается магия перечисления!

В этом сценарии дома Хогвартса без связанных данных называются Pure Cases, а перечисление, содержащее только чистые кейсы, как наше перечисление House — Pure Enum. Позже мы поговорим об их альтернативе, Backed Enums.

Список значений перечисления (enum)

В предыдущем примере вы видели, что без $suggestion от студента дом назначается случайным образом.

Однако в примере, который я привёл, меня беспокоит то, что мы вручную перечислили возможные значения перечисления House для построения массива.

К счастью, у всех перечислений есть метод cases(), способный сделать код более гибким!

class SortingHat
{
public function sort(Student $student, ?House $suggestion = null)
{
if ($suggestion) {
$student->house = $suggestion;

return;
}

- $houses = [
- House::Gryffindor,
- House::Hufflepuff,
- House::Ravenclaw,
- House::Slytherin,
- ];
+ $houses = House::cases();

$index = array_rand($houses);

$student->house = $houses[$index];
}
}

Довольно аккуратно, правда?

Глубокое погружение в перечисления (enum) PHP и их сравнение с классами

В нашем путешествии с перечислениями PHP вы, возможно, заметили, что они похожи на классы. Они могут быть разграничены в пространстве имён, реализовывать интерфейсы, использовать трейты и точно так же могут быть автоматически загружаемыми. Но, конечно, есть и некоторые существенные различия между классами PHP и перечислениями.

Давайте вернёмся к нашему волшебному примеру из Гарри Поттера, чтобы понять эти различия.

Когда ученик распределяется по домам Сортировочной шляпой, мы знаем, что дом всегда будет одним из четырёх предопределённых вариантов — Гриффиндор, Хаффлпафф, Рейвенкло или Слизерин.

Других возможностей в контексте Хогвартса не существует. Этот сценарий идеально подходит для перечислений.

Перечисление, как и House, — это уникальный тип данных, состоящий из набора предопределённых констант.

Он означает, что переменная может иметь только одну из этих предопределённых констант и ничего больше.

Например, свойство $house класса Student может содержать только один из четырёх вариантов домов, определённых в перечислении House.

class Student
{
public ?House $house = null;
}

В отличие от этого, классы в PHP, например, наш класс Student, могут содержать множество различных свойств и могут быть созданы несколько раз с различными значениями свойств.

Перечисления, с другой стороны, не могут быть изменены и используются для определения фиксированного, ограниченного набора экземпляров.

Ещё одно различие заключается в способе сравнения классов и перечислений. В PHP перечисления сравниваются по их идентичности, а не по их значениям.

Давайте рассмотрим пример:

$a = House::Gryffindor;
$b = House::Gryffindor;

var_dump($a === $b); // bool(true)

В данном примере $a и $b являются одним и тем же экземпляром перечисления House, поэтому $a === $b будет истинным.

Сравнение с использованием операторов меньше < или больше > не имеет смысла для объектов перечисления и всегда будет возвращать false.

Кейсы перечислений в PHP имеют специальное свойство name, которое представляет собой имя самого кейса с учётом регистра. Это может быть полезно, когда вы хотите напечатать имя кейса перечисления.

echo House::Gryffindor->name; // Выводит "Gryffindor".

Работа с методами перечисления (enum)

Теперь, когда мы рассмотрели, как можно сравнивать перечисления и их отличия от классов, пришло время погрузиться глубже и изучить методы перечисления в PHP Enum.

Как и классы, перечисления в PHP могут содержать методы.

Давайте посмотрим, как мы можем использовать эту возможность на примере нашего сценария сортировки Гарри Поттера.

enum House
{
case Gryffindor;
case Hufflepuff;
case Ravenclaw;
case Slytherin;

public function getHouseColors() : array
{
return match($this) {
House::Gryffindor => ['Red', 'Gold'],
House::Hufflepuff => ['Yellow', 'Black'],
House::Ravenclaw => ['Blue', 'Bronze'],
House::Slytherin => ['Green', 'Silver'],
};
}
}

// array(2) {
// [0]=>
// string(3) "Red"
// [1]=>
// string(4) "Gold"
// }
var_dump(House::Gryffindor->getHouseColors());

В волшебном мире Хогвартса каждый дом имеет свои цвета. В нашем примере выше мы добавили метод getHouseColor() к нашему перечислению House, чтобы вернуть цвет каждого дома.

Когда метод определяется внутри перечисления, определяется переменная $this, которая ссылается на экземпляр перечисления.

Перечисления (enum) могут использовать трейты и реализовывать интерфейсы

Как и классы, перечисления могут использовать трейты. Это удобно, когда методов много, и вы хотите разделить их на несколько файлов, чтобы сделать код более аккуратным.

Однако здесь есть некоторые ограничения:

trait Colors
{
public function getHouseColors() : array
{
return match($this) {
House::Gryffindor => ['Red', 'Gold'],
House::Hufflepuff => ['Yellow', 'Black'],
House::Ravenclaw => ['Blue', 'Bronze'],
House::Slytherin => ['Green', 'Silver'],
};
}
}

enum House
{
use Colors;

case Gryffindor;
case Hufflepuff;
case Ravenclaw;
case Slytherin;
}

То же самое с интерфейсами. Это работает так же, как и в классах, как вы можете видеть:

interface HasColors
{
public function getHouseColors() : array;
}

enum House implements HasColors
{
case Gryffindor;
case Hufflepuff;
case Ravenclaw;
case Slytherin;

public function getHouseColors() : array
{
return match($this) {
House::Gryffindor => ['Red', 'Gold'],
House::Hufflepuff => ['Yellow', 'Black'],
House::Ravenclaw => ['Blue', 'Bronze'],
House::Slytherin => ['Green', 'Silver'],
};
}
}

Дополнительные материалы

Предыдущая Статья

Вложенность CSS

Следующая Статья

Больше контроля над выбором :nth-child() с синтаксисом of S