Enum в PHP 8.1 — для чего нужен enum, и как реализован в PHP

?v=1

Через несколько дней заканчивается голосование по первой итерации реализации enum в PHP 8.1 . Уже видно, что голосов «за» гораздо больше, так что давайте кратко пройдемся и посмотрим, что же нам приготовили авторы языка.


Зачем нужны enum?

Зачем вообще нужны enum? По сути они служат цели улучшенного описания типов. Давайте рассмотрим пример без енумов и с ними. Допустим, у нас продаются машины трех цветов: красные, черные и белые. Как описать цвет, какой тип выбрать?

class Car {
    private string $color;

    function setColor(string $color): void {
        $this->color = $color;
    }
}

Если мы опишем цвет машины как простой string, то во-первых при вызове $myCar→setColor (…) непонятно, что за строку туда писать. «red» или «RED» или »#ff0000», а во вторых, легко ошибиться, просунув туда случайно что-то не то (пустую строку, к примеру). То же самое будет, если использовать не строки, а числа, например.

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

class Color {
    public const RED   = "red";
    public const BLACK = "black";
    public const WHITE = "white";
}

и задавая цвет, пишут $myCar->setColor(Color::RED);

Уже лучше. Но если мы впервые видим метод $myCar→setColor (…), мы можем и не знать, что где-то есть константы для цветов. И мы всё еще можем сунуть туда любую строку без какого-либо сообщения об ошибке.

Поэтому здесь нужен не класс, а отдельный тип. Этот тип называется enum


Pure enum

В самом простом случае (pure enum), enum описывается так:

enum Color {
    case Red;
    case Black;
    case White;
}

Описав такой тип, мы можем использовать его везде:

class Car {
    private Color $color;

    function setColor(Color $color): void {
        $this->color = $color;
    }
}

Из сигнатуры метода всё сразу видно, какие варианты есть. И метод setColor можно использовать только так: $myCar→setColor (Color: White), никаких строк и доморощенных списков констант. Ничего лишнего не сунешь. Читабельность и поддерживаемость кода стала выше.

Каждый из case-ов (Color: Red, Color: Black, Color: White) является объектом типа Color (можно проверить через instanseof). Т.е. под капотом это не числа 0,1,2 как в некоторых языках, а именно объекты. Их нельзя сравнивать оператором >, например. У каждого такого объекта есть встроенное свойство $name:

print Color::Red->name; // вернет строку "Red”

Enum со скалярами

Но если бы это было всё, то было бы слишком просто. После названия enum можно указать скалярный тип, например string. И у каждого кейса указать скалярное значение. Это может быть полезно для некоторых целей, например для сортировки или записи в базу данных.

enum Color: string {
    case Red = "R";
    case Black = "B";
    case White = "W";
}

Скалярное значение можно получить потом так:

Color::Red->value  //вернет строку "R”

И наоборот, т.е. получить case через значение, тоже можно:

Color::from("R") // вернет объект Color::Red

Помимо полей «case» в enum может быть еще много всего. По сути это разновидность класса. Там могут быть методы, он может реализовывать интерфейсы или использовать трейты.

Пример из RFC.

interface Colorful {
  public function color(): string;
}

trait Rectangle {
  public function shape(): string {
    return "Rectangle";
  }
}

enum Suit implements Colorful {
  use Rectangle;

  case Hearts;
  case Diamonds;
  case Clubs;
  case Spades;

  public function color(): string {
    return match($this) {
      Suit::Hearts, Suit::Diamonds => 'Red',
      Suit::Clubs, Suit::Spaces => 'Black',
    };
  }
}

При этом $this будет тот конкретный объект case, для которого мы вызываем метод.

Кстати, обратите внимание на работу с енамами в конструкции match. Похоже, match затевался именно для них.

Лично я горячо одобряю введение enum в PHP, это очень удобно и читабельно, и в большинстве языков, где есть какие-никакие типы, enum уже давно есть (кроме, разве что Go).


Дальше — больше. Tagged Unions (тип-сумма)

Есть RFC, которые развивают идею enums дальше, чтобы можно было хранить в одном enum значения разных типов. Как в языке Rust, например. Тогда можно будет сделать, допустим, enum Result с двумя case-ами Result: Ok и Result: Err, причем эти объекты будут хранить данные: Ok будет хранить результат, а Err — ошибку, у каждого из этих значений будет свой тип.

И всё это не в Расте или Хаскеле, а в PHP!

Об этом мы расскажем в деталях в ближайших постах телеграм-канала Cross Join, не забудьте подписаться.

© Habrahabr.ru