Перечисления в PHP

Ссылка на оригинал

Перечисляемый тип, коротко «enum» — это тип данных для упорядочивания именных значений. Перечисления могут быть использованы вместо жестко закодированных строк, например, для того, чтобы представить статус поста в блоге более структурированно и типизированно.

В PHP нет встроенного типа enum. Он предоставляет очень простую SPL реализацию, но это ничего не меняет.

Существует популярная библиотека myclabs/php-enum, созданная Матье Наполи. Эту библиотеку я и многие другие используют во многих своих проектах. Она действительно крутая.

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

И последнее: я предполагаю, что вы знаете, что такое перечисления и как использовать их в реальных проектах.

Представьте, если бы:

Мы могли писать что-нибудь такое на PHP…

class Post
{
    public function setStatus(PostStatus $status): void
    {
        $this->status = $status;
    }
}

… и быть уверенными, что значение переменной Post::$status всегда равно одной из трех строк: draft, published и archived.

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

Библиотека myclabs/php-enum позволяет написать нам так:

class PostStatus extends Enum
{
    const DRAFT = 'draft';
    const PUBLISHED = 'published';
    const ARCHIVED = 'archived';
}

Мы можем использовать значения констант напрямую вот так:

class Post
{
    public function setStatus(string $status): void
    {
        $this->status = $status;
    }
}

// …

$post->setStatus(PostStatus::DRAFT);

Но это не дает нам полной проверки, так как любая строка может быть отправлена в Post::setStatus().

Более правильный вариант – это использовать магию, предоставляемую библиотекой:

class PostStatus extends Enum
{
    private const DRAFT = 'draft';
    private const PUBLISHED = 'published';
    private const ARCHIVED = 'archived';
}

$post->setStatus(PostStatus::DRAFT());

Это использование магического метода __callStatic(). В нем создается объект PostStatus со значением “draft” в нем.

Теперь мы можем делать проверку типа на PostStatus и быть уверенными, что то, что было передано в функцию, является одним из значений перечисления.

Здесь появляется проблема с использованием библиотеки myclabs/php-enum: используя метод __callStatic() мы теряем преимущества статического анализа, такие как автодополнение и рефакторинг:

IDE не автодополняет

Как видим, IDE ничего не знает о методе PostStatus::DRAFT().

К счастью, эта проблема решается при помощи докблоков (docblock):

/**
 * @method static self DRAFT()
 * @method static self PUBLISHED()
 * @method static self ARCHIVED()
 */
class PostStatus extends Enum
{
    private const DRAFT = 'draft';
    private const PUBLISHED = 'published';
    private const ARCHIVED = 'archived';
}

$post->setStatus(PostStatus::DRAFT());

Но теперь у нас будут проблемы с тем, если мы захотим поменять что-то в перечислении. Например, переименовать DRAFT в NEW:

Сложности рефактиринга

Таким образом, мы дублируем код: есть константа и подсказка в докблоке.

В этот момент нам нужно остановиться и подумать. В идеальном мире, у нас были бы встроенные перечисления в PHP:

enum PostStatus {
    DRAFT, PUBLISHED, ARCHIVED;
}

Но поскольку это не так, нам приходится сталкиваться с пользовательскими реализациями.

Пользовательское расширение типов в PHP в большинстве своем подразумевает две вещи: магия и рефлексия.

Если мы уже полагаемся на них, то почему бы не сделать нашу жизнь проще, насколько это возможно?

Вот как я пишу перечисления сейчас:

/**
 * @method static self DRAFT()
 * @method static self PUBLISHED()
 * @method static self ARCHIVED()
 */
class PostStatus extends Enum
{
}

Самоуверенно, не правда ли? Меньше кода, больше возможностей.

Я знаю, что это далеко не идеальный вариант. Было бы великолепно когда-нибудь увидеть встроенную поддержку в PHP. Но до тех пор, придется использовать это.

И если вам хочется, то попробуйте мою реализацию тут.

Итак, что вы думаете о перечислениях? Хотели бы увидеть его в ядре PHP?

Ссылка на оригинал

Дата публикации: 28.07.2019