rfc:enum

PHP RFC: Enumerated Types

Introduction

Often programmers encounter data that is naturally represented as finite set of exclusive values. Currently in PHP there is no convenient type-safe way to represent these data sets. This RFC proposes to add syntax and semantics to facilitate representing this kind of data.

Proposal

Enums are given a name and a finite list of unique, case-sensitive values. Enum types share the same symbol table as classes and interfaces. Names that are invalid for classes are also invalid for enum types. Enum types are implicitly final; you cannot extend an enum. Enums are not editable after definition in any way. Using an enum type that isn't loaded will trigger the autoloader just like a unloaded class.

Syntax

Here is a basic example of an enum type RenewalAction with two values Deny and Approve:

enum RenewalAction {
    Deny,
    Approve
}

To refer to an enum value, you must also include the enum type name: RenewalAction::Deny.

Use in Type Declarations

Enums are strongly typed and can be used as parameter or return types. They can also be used in case statements.

function other(RenewalAction $action): RenewalAction {
    switch ($action) {
        case RenewalAction::Approve:
            return RenewalAction::Deny;
 
        case RenewalAction::Deny:
            return RenewalAction::Approve;
    }
}
other(RenewalAction::Approve);

Equality and Comparisons

An enum value is only equal to itself. Comparing an enum value to anything except itself evaluates to false, except for <=> where it will return 1:

enum Foo { A, B }
 
// int(0)
var_dump(Foo::A <=> Foo::A); 
 
// int(1)
var_dump(Foo::A <=> Foo::B);
var_dump(Foo::B <=> Foo::A);
var_dump(Foo::B <=> 1);
var_dump("A" <=> Foo::A);
var_dump(array() <=> Foo::A);
var_dump(Foo::A <=> new stdclass);

The only time another behavior may occur is if an enum is compared to another object that implements the compare handler. As currently implemented, the handler of the object on the left of the comparison operator will be used.

Methods

Each enum value knows its ordinal, or in other words each enum value knows its position that it was defined in. This can be retrieved with the ordinal() method:

var_dump((RenewalAction::Approve)->ordinal()); // int(1)
var_dump((RenewalAction::Deny)->ordinal()); // int(0)

An array of all values in the enum can be retrieved with the values() method. The order in which they are returned is the same as the order in which they are defined.

foreach (RenewalAction::values() as $value) {
    printf("%d\n", $value->ordinal());
}
/* Result:
0
1
 */

Explanation of Implementation

Here is a basic declaration of an enum:

enum RenewalAction {
        Approve,
        Deny
}

Logically you can think of translating the above to this:

final class RenewalAction {
 
        const Approve = new RenewalAction(0, 'Approve');
        const Deny = new RenewalAction(1, 'Deny');
 
        private $_ordinal;
        private $_name;
        private function __construct(int $ordinal, string $name) {
                $this->_ordinal = $ordinal;
                $this->_name = $name;
        }
        public function ordinal(): int {
                return $this->_ordinal;
        }
        public function name(): string {
                return $this->_name;
        }
        public static function values(): array {
                return [self::Approve, self::Deny];
        }
}

Essentially, for each value in the enum we create a class constant that holds an instance of the enum type. The enum declaration automatically declares the methods ordinal(), name() and values(). Note that the above code is not valid in PHP as it currently stands because constants can't hold objects.

Backwards Compatibility

This RFC adds a new token T_ENUM. This means that if the name enum is used for a property, function, method, class, trait or interface there will now be a parse error instead.

There are no other known backwards compatibility breaks.

Proposed Voting Choices

The vote will be a simple “yes” or “no”. This RFC requires 2/3 of the votes to be “yes” to pass. This RFC targets PHP version 7.1 .

Patches and Tests

A proof of concept implementation can be found on this branch: https://github.com/morrisonlevi/php-src/tree/enum

Future Scope

A few ideas for things that could potentially happen:

  1. Algebraic data types and pattern matching:
    enum Maybe {
        None,
        Some($t)
    }
     
    match ($maybe) {
        case Maybe::None {
            echo "None";
        }
        case Maybe::Some($t) {
            echo "Some($t)";
        }
    }
  2. User defined methods:
    enum Direction {
        North {
            function opposite(): Direction {
                return Direction::South;
            }
        },
        East {
            function opposite(): Direction {
                return Direction::West;
            }
        },
        South {
            function opposite(): Direction {
                return Direction::North;
            }
        },
        West {
            function opposite(): Direction {
                return Direction::East;
            }
        }
    }
  3. Box primitive types, such as what Hack does:
    enum Flags : int {
        a = 1 << 0,
        b = 1 << 1,
        c = 1 << 2,
        d = 1 << 3
    }
rfc/enum.txt · Last modified: 2015/04/21 05:52 by levim