This post is from the professional Laravel developer community, original link: learnku.com/laravel/t/7…

This article aims to provide some better understanding of what enumerations are, when to use them, and how to use them in PHP.

We sometimes use constants to define constant values in our code. They are used to avoid mana. By replacing some mana with a symbolic name, we can give it some meaning. We then reference the symbolic name in our code. Because we defined it once and used it many times, it is easier to search for it and rename or change a value later.

That’s why it’s not uncommon to see code like the one below.

<? php class User { const GENDER_MALE = 0; const GENDER_FEMALE = 1; const STATUS_INACTIVE = 0; const STATUS_ACTIVE = 1; }Copy the code

The constants above represent two sets of attributes, GEDNER_* and STATUS_*. They represent a set of genders and a set of user states. Each group is an enumeration. An enumeration is a collection of elements (also called members), each of which defines a new type. This type, like its value, can contain any element that belongs to the enumeration.

In the example above, enumerations rely on constants, each of which has a value of one member. Note that by doing this, we can only value values in the type contained in the constant. As a result, we write these values with no type hints and no detailed enumeration types.

Let’s look at a short example, but let’s assume there’s more code in the example

<? php interface UserFactory { publicfunction create(
        string $email,
        int $gender,
        int $status
    ): User;
}
$factory->create(
    $email,
    User::STATUS_ACTIVE,
    User::GENDER_FEMALE
);
Copy the code

The code looks good at first glance, but it just happens to work correctly! Because two different enumerators are actually the same value, the create call succeeds because the last two arguments are interchanged without affecting the result. Although we check that the values accepted by the method are valid, the runtime interface does not warn us and the test passes. Someone can spot these bugs correctly, but it’s also likely to go unnoticed. In later cases, such as merge conflicts, if its value changes, it may cause a system exception.

If we use a scalar type, we are constrained by the type and cannot tell whether the two values belong to two different enumerations.

Another problem is that this code doesn’t describe it very well. Imagine that the create method does not reference constants. How difficult will it be for $gender to be seen as an enumeration element? How difficult is it to see where these elements are defined? We’ll read that code later, so we should make it as easy to read and pass as possible.

Can we do better? Sure! This method uses the class instance as the enumeration element, and the class itself defines a new type. Until PHP 7, we could install the SPL class PECL extension and use SplEnum.

<? php class YesNo extends \SplEnum { const __default = self::YES; const NO = 0; const YES = 1; }$no = new YesNo(YesNo::NO);
var_dump($no == YesNo::NO); //true
var_dump(new YesNo(YesNo::NO) == YesNo::NO); //true
Copy the code

We extend SplEnum and define constants that are used to create enumerated elements. Enumeration elements are objects that we manually construct, in this case the constant values themselves. We can compare integers to objects, which may be strange. Also, as the documentation states, this is an emulated enumeration. PHP itself does not support enumerated types, so everything we discuss here is emulated.

What do we get by doing this? We can enter parameters that prompt us and have the PHP engine alert us when an error occurs. We can also include some logic in enumerated classes and use switch statements to simulate polymorphic behavior.

But there are some disadvantages. For example, in most cases, some you can use enumeration elements instead of checking with identifiers. It’s not impossible, we have to be very careful. Since we created the enumerator manually, many members should be the same member, but this is difficult to determine manually.

With SplEnum we solve the problem of enumerated types, but we have to be very careful when checking with symbols. We need a method that limits the number of elements that can be created, such as multiton (Multiple Singleton Objects).

Now we’ll look at two different libraries inspired by Java Enum that implement Multiton.

The first is eloquent/ Enumeration. It creates an instance of a defining class for each element. Note that user emulation of enumerations can never guarantee an enumeration instance without our help, because we limit it to having a method to avoid every step.

This library allows us to try things the wrong way, such as creating an instance with reflection, at which point we can ask ourselves if we did the right thing. It can also help during code reviews, because such an implementation can define several rules that should be followed. It’s easy to spot problems in your code if the rules are simple.

Let’s look at some examples.

<? php final class YesNo extends \Eloquent\Enumeration\AbstractEnumeration { const NO = 0; const YES = 1; } var_dump(YesNo::YES()->key()); // YESCopy the code

We define a new class YesNo that inherits Eloquent Enumeration AbstractEnumeration. Next we define a constant that defines the element names and creates the library of objects that represent those elements.

There are also cases where we need to remember to use serialize/deserialize to create custom objects.

You can find more examples and good documentation on the GitHub page.

The second library we’ll show you is zlikavac32/php-enum. Unlike eloquent/ Enumeration, this library is for abstract classes that allow true polymorphic behavior. So instead of using switch’s methods, we can do this by defining an enumeration element for each method. Defining enumerations with strict rules also ensures fairly reliably that there is only one instance per element.

The library is oriented towards abstract classes in order to limit the many instances of each member to one. The idea is that each enumeration must be defined as abstract and enumerate its elements. Note that you can abuse the class by extending it and then constructing an element, but if you do, these will be flagged in the code review process.

With abstract classes, we know that we won’t accidentally have an enumeration of new elements, because it requires a concrete implementation. We can easily detect abuse by following the rules that keep these concrete implementations in the enum itself. Anonymous classes are useful here.

The library enforces abstract enumeration classes, but it does not force the creation of valid elements. This is the responsibility of the users of this library. The library takes care of the rest.

Let’s look at a simple example.

<? php /** * @method static YesNo YES * @method static YesNo NO */ abstract class YesNo extends \Zlikavac32\Enum\Enum { protected staticfunction enumerate(): array
    {
        return [
            'YES'.'NO'
        ];
    }
}
var_dump(YesNo::YES()->name()); // YES
Copy the code

PHPDoc comments define existing static methods that return enumerated elements. This helps in searching and refactoring code. Next, we define the enumeration YesNo as abstract and extend \Zlikavac32\Enum\Enum and define a static method enumerate. Then, in the enumerate method, we list the names of the elements that will be used to represent them.

We talked about polymorphic behavior, so why do we use it? One thing that happens when we try to limit multiple instances of the same enumeration element is that we can’t have circular references. Let’s imagine that we want to have a WorldSide enumeration consisting of NORTH, SOUTH, EAST, and WEST. We also want to have a method opposite() : WorldSide, which returns the element representing the opposite. If we try to inject the opposite element through the constructor, at some point we get a circular reference, which means we need a second instance of the same element. To return a valid reverse world, we had to crack with a proxy object or switch statement.

With polymorphic behavior, all we can do is show us that we can define the WorldSide enumeration we need.

<? php /** * @method static WorldSide NORTH * @method static WorldSide SOUTH * @method static WorldSide EAST * @method static WorldSide WEST */ abstract class WorldSide extends \Zlikavac32\Enum\Enum { protected staticfunction enumerate(): array
    {
        return [
            'NORTH' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    returnWorldSide::SOUTH(); }},'SOUTH' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    returnWorldSide::NORTH(); }},'EAST' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    returnWorldSide::WEST(); }},'WEST' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    returnWorldSide::EAST(); }}]; } abstract publicfunction opposite(): WorldSide;
}
foreach (WorldSide::iterator() as $worldSide) {
    var_dump(sprintf(
        'Opposite of %s is %s', 
        (string) $worldSide, 
        (string) $worldSide->opposite()
    ));
}
Copy the code

In the Enumerate method, we provide an implementation of each enumeration element. Arrays are indexed by enumeration element names. When creating an element manually, we define our element name as the key of the data.

We can use WorldSide::iterator() to get sequential iterators of enumerated elements to define and iterate over them. Each enumerated element has a default __toString(): the string implementation returns the name of the element.

Each enumeration element returns its opposite element.

Just to recall, constants are not enumerations, enumerations are not constants. Each enumeration defines a type. If we have some constant value that is important to us, but the name is not, we should stick with the constant. If we have some constant value that doesn’t matter to us, but is important to be different from everyone else in the same group, use enumerations

Enumerations provide more context for your code, and you can delegate some checking to the engine itself. It would be nice if PHP had a native enumeration support. Syntax changes can make code more readable. The engine can perform checks for us and execute rules that cannot be executed from the user area.

How do you use enumerations, and what are your thoughts on the subject? Please comment below