Поиск по сайту:

Как использовать перечисления в PHP 8.1


PHP 8.1, наконец, добавит языковую поддержку для перечислений. Перечисления, сокращение от перечислений, представляют собой типы, экземпляры которых могут быть созданы только с определенными значениями. Они обычно встречаются в других объектно-ориентированных языках, но ранее для их реализации в PHP требовались обходные пути в пространстве пользователя.

Базовый синтаксис

Вот как выглядит простое перечисление:

enum PostStatus {
    case Published;
    case InReview;
    case Draft;
}

Ключевое слово case, ранее входившее в операторы switch, используется для обозначения конкретных значений, которые принимает перечисление. На значения ссылаются так же, как и на константы класса:

$published = PostStatus::Published;

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

class BlogPost {
 
    public function __construct(
        public string $Headline,
        public string $Content,
        public PostStatus $Status=PostStatus::Draft) {}
 
}

Вот пример использования класса BlogPost:

// OK
$post = new BlogPost(
    "Example Post",
    "An example",
    PostStatus::Draft
);
 
// TypeError: Argument #3 ($Status) must be of type PostStatus
$post = new BlogPost(
    "Broken Example",
    "A broken example",
    "Submitted"
);

Первый экземпляр работает, потому что его $Status является допустимым значением из перечисления PostStatus. Во втором случае в качестве $Status передается простая строка, что запрещено, поскольку значение должно быть определено в PostStatus.

Случаи перечисления представлены как константы в объекте перечисления. Это означает, что вы можете использовать их как статические значения и как часть постоянных выражений. Конструктор BlogPost показывает случай перечисления, используемый в качестве значения параметра по умолчанию, где $Status автоматически устанавливается в значение Draft, когда вызывающий объект не предоставляет никакого значения.

Вы можете получить доступ ко всем доступным значениям в перечислении, используя его метод cases:

PostStatus::cases();
// [PostStatus::Published, PostStatus::InReview, PostStatus::Draft]

Чистые и резервные перечисления

Перечисление PostStatus выше является чистым перечислением. Он содержит только операторы case без дополнительных данных. PHP также позволяет вам прикреплять значение к случаям перечисления, создавая перечисление backed.

enum PostStatus : string {
    case Published = "S1";
    case InReview = "S2";
    case Draft = "S3";
}

Здесь перечисление PostStatus было изменено для создания поддерживаемого перечисления. Подсказка типа в определении перечисления указывает, что каждому case присвоено строковое значение. В этом примере мы предполагаем, что у каждого именованного статуса сообщения есть связанный с ним короткий идентификатор. Возможно, именно этот идентификатор сохраняется в базе данных при сохранении сообщений.

Вы можете получить доступ к резервным значениям через свойство value в экземплярах case:

class BlogPostRepository {
 
    public function save(BlogPost $Post) : void {
        $this -> insert(
            "blog_posts",
            [
                "headline" => $Post -> Headline,
                "content" => $Post -> Content,
                "status" => $Post -> Status -> value
            ]
        );
    }
 
}
 
$post = new BlogPost("Example", "Demo", PostStatus::Published);
(new BlogPostRepository()) -> save($post);

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

Поддерживаемые перечисления только принимают в качестве значений строки и целые числа. Также нельзя использовать тип объединения string|int. Кроме того, для каждого случая требуется уникальное значение — следующий пример недопустим:

enum PostStatus : string {
 
    case Published = "S1";
    case Draft = "S1";
 
}

PHP предоставляет служебный метод для перечислений для создания экземпляра из поддерживаемого значения:

// fetch the blog post from earlier from the database
// the "status" field = S1
$status = PostStatus::from($record["status"]);

Метод from() будет гидратировать экземпляры из вариантов значений. В этом примере S1 сопоставляется обратно с случаем Published, и ваш код получает экземпляр PostStatus::Published.

from() выдает ValueError, если входное значение неверно; в сценариях, где вы знаете, что значение может быть непригодным для использования, вместо него можно использовать альтернативный метод tryFrom(). Это возвращает null, когда совпадений нет, вместо того, чтобы выдавать ошибку.

Добавление методов в перечисления

Поскольку перечисления основаны на классах, вы также можете добавлять к ним методы!

enum PostStatus {
 
    case Published;
    case Draft;
 
    public function isPubliclyAccessible() : bool {
        return ($this instanceof self::Published);
    }
 
}

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

Перечисления также могут реализовывать интерфейсы:

enum PostStatus implements PublicAccessGatable {
 
    case Published;
    case Draft;
 
    public function isPubliclyAccessible() : bool {
        return ($this instanceof self::Published);
    }
 
}

Теперь вы можете передать экземпляр PostStatus всему, что принимает PublicAccessGatable:

class UserAuthenticator {
 
    function shouldAllowAccess(PublicAccessGatable $Resource) : bool {
        return ($this -> User -> isAdmin() || $Resource -> isPubliclyAccessible());
    }
 
}
 
$auth = new UserAuthenticator();
 
// get a blog post from the database
if (!$auth -> shouldAllowAccess($post -> Status)) {
    http_response_code(403);
}

Нет никаких ограничений на то, что вы можете делать с методами enum — в конце концов, это обычные методы PHP — но в целом вы ожидаете, что они выполнят какое-то сравнение с экземпляром case, а затем вернут статическое значение. Перечисления могут использовать трейты, поэтому вы также можете использовать существующие методы, которые вы абстрагировали таким образом.

Вы можете использовать методы public, protected и private в перечислениях, хотя protected и private иметь тот же эффект. Перечисления не могут расширять друг друга, поэтому private фактически избыточен. Вы также не можете добавить конструктор или деструктор. Поддерживаются статические методы, которые можно вызывать для класса enum или его экземпляров case.

Константы

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

enum PostStatus {
 
    case Published;
    case Draft;
 
    public const Live = self::Published;
    public const PlainConstant = "foobar";
 
}

Это может привести к путанице, поскольку для доступа к случаям (экземплярам перечисления) и константам используется один и тот же синтаксис:

$published = PostStatus::Published;
$plain = PostStatus::PlainConstant;

Только $published соответствует подсказке типа PostStatus, так как $plain относится к простому скалярному значению.

Когда использовать перечисления?

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

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

В более старых версиях вы могли использовать этот подход:

class PostStatus {
    const Published = 0;
    const Draft = 1;
}
 
class BlogPost {
    public function __construct(
        public string $Headline,
        public int $Status
    ) {}
}
 
$post = new BlogPost("My Headline", PostStatus::Published);

Проблема здесь в том, что $Status на самом деле принимает любое целое число, поэтому следующий вызов будет вполне допустимым:

$post = new BlogPost("My Headline", 9000);

Кроме того, BlogPost и PostStatus полностью отделены друг от друга — никто, читающий BlogPost, не может узнать диапазон значений, которые $Status на самом деле принимает. Хотя эти проблемы можно смягчить, используя соответствующие подсказки типов docblock или сторонние пакеты «фальшивых перечислений», все они добавляют дополнительные уровни вокруг концепции, которую другие языки программирования упрощают.

Добавление нативных перечислений в PHP — это шаг, который помогает завершить систему типов языка. Наконец-то вы можете вводить допустимые значения таким образом, чтобы все оставались на одной странице. Если вы передадите недопустимое значение, вы получите ошибку времени выполнения. Ваша IDE может лучше помочь вам в предоставлении правильных значений, так как она будет знать, что $Status принимает только три параметра вместо «любое целое число».

Заключение

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

Перечисления — это гибкие сущности кодовой базы, которые можно сохранить простыми в чистом виде или расширить за счет резервных значений, реализаций интерфейса и пользовательских методов. В большинстве случаев перечисления ведут себя аналогично обычным объектам и поддерживают функции класса, такие как __call(), __invoke и ::class.

Вы можете исследовать перечисления с помощью новой функции enum_exists() и класса Reflection ReflectionEnum. Кроме того, перечисления реализуют два новых интерфейса: UnitEnum (в случае чистых перечислений) и BackedEnum (для перечислений с резервными значениями). Их можно использовать в универсальном коде фреймворка, который работает с любым перечислением. Интерфейсы не могут быть реализованы вручную с помощью пользовательского кода.

Перечисления появятся в PHP как часть выпуска 8.1 в ноябре 2021 года. Они уже доступны в последних бета-сборках. В PHP 8.1 также будет добавлено несколько других удобных функций, включая свойства только для чтения и типы пересечений.




Все права защищены. © Linux-Console.net • 2019-2024