Enumerations on PHP
Asked Answered
C

40

1317

I know that PHP doesn't yet have native Enumerations. But I have become accustomed to them from the Java world. I would love to use enums as a way to give predefined values which IDEs' auto-completion features could understand.

Constants do the trick, but there's the namespace collision problem and (or actually because) they're global. Arrays don't have the namespace problem, but they're too vague, they can be overwritten at runtime and IDEs rarely know how to autofill their keys without additional static analysis annotations or attributes.

Are there any solutions/workarounds you commonly use? Does anyone recall whether the PHP guys have had any thoughts or decisions around enumerations?

Copyread answered 31/10, 2008 at 18:51 Comment(6)
it.toolbox.com/blogs/macsploitation/…Designedly
I created a work around function that enumerates constants as bitwise or not. Didn't notice you asked this before, but I have a better solution than class variables here: #3836885Quincyquindecagon
github.com/myclabs/php-enumRock
I have recently developed a simple library for PHP Enums: github.com/dnl-blkv/simple-php-enum At the moment of writing this answer, it is still in pre-release stage, but already fully-functional, well-documented and published on Packagist. This might be a handy option if you are looking for easy-to-implement enums similar to those of C/C++.Varion
Enumerations native support in php will be available with version 8.1 expected to be released in November 2021. It looks like the following: enum Status { case started; case stopped; case paused; }Mclaren
Indeed, native enumeration is now possible with release of PHP 8.1, documentation.Dwayne
R
1648

Since PHP 8.1, Enums are supported:

https://www.php.net/manual/en/language.types.enumerations.php

enum DaysOfWeek: int
{
    case Sunday = 0;
    case Monday = 1;
    // etc.
}
$today = DaysOfWeek::Sunday;
var_dump($today->value); // 0
var_dump($today->name); // "Sunday"

PHP 8.0 and earlier

Depending upon use case, I would normally use something simple like the following:

abstract class DaysOfWeek
{
    const Sunday = 0;
    const Monday = 1;
    // etc.
}

$today = DaysOfWeek::Sunday;

However, other use cases may require more validation of constants and values. Based on the comments below about reflection, and a few other notes, here's an expanded example which may better serve a much wider range of cases:

abstract class BasicEnum {
    private static $constCacheArray = NULL;

    private static function getConstants() {
        if (self::$constCacheArray == NULL) {
            self::$constCacheArray = [];
        }
        $calledClass = get_called_class();
        if (!array_key_exists($calledClass, self::$constCacheArray)) {
            $reflect = new ReflectionClass($calledClass);
            self::$constCacheArray[$calledClass] = $reflect->getConstants();
        }
        return self::$constCacheArray[$calledClass];
    }

    public static function isValidName($name, $strict = false) {
        $constants = self::getConstants();

        if ($strict) {
            return array_key_exists($name, $constants);
        }

        $keys = array_map('strtolower', array_keys($constants));
        return in_array(strtolower($name), $keys);
    }

    public static function isValidValue($value, $strict = true) {
        $values = array_values(self::getConstants());
        return in_array($value, $values, $strict);
    }
}

By creating a simple enum class that extends BasicEnum, you now have the ability to use methods thusly for simple input validation:

abstract class DaysOfWeek extends BasicEnum {
    const Sunday = 0;
    const Monday = 1;
    const Tuesday = 2;
    const Wednesday = 3;
    const Thursday = 4;
    const Friday = 5;
    const Saturday = 6;
}

DaysOfWeek::isValidName('Humpday');                  // false
DaysOfWeek::isValidName('Monday');                   // true
DaysOfWeek::isValidName('monday');                   // true
DaysOfWeek::isValidName('monday', $strict = true);   // false
DaysOfWeek::isValidName(0);                          // false

DaysOfWeek::isValidValue(0);                         // true
DaysOfWeek::isValidValue(5);                         // true
DaysOfWeek::isValidValue(7);                         // false
DaysOfWeek::isValidValue('Friday');                  // false

As a side note, any time I use reflection at least once on a static/const class where the data won't change (such as in an enum), I cache the results of those reflection calls, since using fresh reflection objects each time will eventually have a noticeable performance impact (Stored in an assocciative array for multiple enums).

Now that most people have finally upgraded to at least 5.3, and SplEnum is available, that is certainly a viable option as well--as long as you don't mind the traditionally unintuitive notion of having actual enum instantiations throughout your codebase. In the above example, BasicEnum and DaysOfWeek cannot be instantiated at all, nor should they be.

Rotman answered 31/10, 2008 at 18:59 Comment(26)
I use this too. You might also consider making the class abstract and final, so it can't be instantiated or extended.Holmun
You can make a class both abstract and final? I know in Java this is not allowed. You can do that in php?Immature
@Holmun It seems you cannot make it both abstract and final. In that case, I'd go for abstract.Cowardice
Could you do this with namespaces instead of classes in PHP 5.3?Cloninger
Yes, now that namespaces are available, you could alternatively place Sunday/Monday/etc. constants within a DaysOfWeek namespace, referring to them as DaysOfWeek\Sunday and so forth. Choosing between the two methods will ultimately boil down to personal preferences in design and notation, but both work equally as well in 5.3 and beyond.Rotman
About abstract or final; I make them final and give them an empty private constructorBaumbaugh
@BrianCline, is either style any more semantically correct than the other?Melan
@TylerCrompton: No, I don't think so. I still use a final class, but at the same time we have not adopted namespaces to any significant degree yet. I'd say go for the style you prefer most, is most consistent stylistically with your environment and the direction you see it evolving towards in the future.Rotman
This is much better than using constants in an existing class and polluting it's namespaceHare
Improve just extending class DaysOfWeek extends SplEnum => The same than the original post, but with added functionality with zero extra effort.Tybi
I think interfaces are much better than classes as in @Andi T answerBodine
@greg0ire because you could not create an instance of an enum in other languages and you could not create an instance of an interface itself so this fits more natural into the design of an enumBodine
@MaximilianRuta but as an interface, it holds certain polymorphic meaning, i.e. other classes could still implement it, which you can't /wouldn't want to do with enumerations. So that seems kind of misleading to me personally. I think Lex's solution is best.Izard
Be careful with using 0, so you don't run into any unanticipated falsy comparison problems, e.g. equivalence with null and friends in a switch statement. Been there.Steffin
To iterate the names and values for runtime validation: $r = new ReflectionClass('DaysOfWeek'); foreach ($r->getConstants() as $day => $code) { ... }Steinman
@SparK; Yeah, I suppose you could. But that wouldn't solve this problem.Baumbaugh
There is a very slight error in this wonderful code which can lead to some odd results if BasicEnum is extended by more than one class in the same application. I'll put a correction in an answer below.Bacteriology
I'm going to make a Composer package out of this class. I know it's tiny, but I don't like copy-pasting the same thing over and over on projects. @BrianCline : unless you want to do it yourself ?Bushman
@greg0ire: Thank you for packaging this, however please do not GPL license the code I've contributed as my answer. The SO footer states that user contributions are CC BY-SA 3.0 licensed, which requires redistributed/shared copies and derivative works be licensed under the same license; no versions of the GPL are listed by CC as BY-SA 3.0 compatible. Please see the CC BY-SA 3.0 summary or the full license text for details.Rotman
Published that on reddit and gave you some credit in the READMEBushman
Thank you, sir -- glad you have found some use out of this. I'm not sure folks in the Reddit thread noticed that this answer was written almost 5 years ago (with the code being quite a bit older than the answer). I was raised on C and C++, and this was roughly the best that PHP could do at the time since the SPL niceties were still very young (read: young in PHP time). Also if I'm not mistaken SplEnum still requires the separate SPL_Types extension and the docs still mark it as experimental after all this time.Rotman
As mentioned by @NeilTownsend, this code has a serious bug. Here's a demo. codepad.viper-7.com/boTjXT only in php does an answer with an absolutely crippling bug receive 700+ upvotes lol.Annabal
Agree with Neil and goat. My solution was to use 'static::' instead of 'self::' in the getConstants() method and then redeclaring the $constCache in any child enums. Note that late static binding is php5.3+Legend
I tried to replace the consts values with array()s (because it's possible in recent PHP versions) to avoid false-cases in switches while using ints or other classes of enum emulation. I hoped it would create an unique "pointer" that could get compared as fast as an int but unfortunately arrays are compared by value. What the fug, PHPJabber
For me anyday is Humpday, thought I might add that to the discussionPedestrianize
This needs to be updated to reflect php 8.1 enum functionality.Bathypelagic
F
189

There is a native extension, too. The SplEnum

SplEnum gives the ability to emulate and create enumeration objects natively in PHP.

http://www.php.net/manual/en/class.splenum.php

Attention:

https://www.php.net/manual/en/spl-types.installation.php

The PECL extension is not bundled with PHP.

A DLL for this PECL extension is currently unavailable.

Fanti answered 31/10, 2008 at 18:51 Comment(12)
Here's an example with splenum: dreamincode.net/forums/topic/201638-enum-in-phpDrin
I rolled back, I like it better when I can see the link. It gives me context information.Gothic
SplEnum gives the ability to emulate and create enumeration objects natively in PHP.Gothic
What I don't like about it is the integer constants, if you compare the int value with the "enum" it returns true... which is bad imo.Kr
I rolled back again. I don't want you guys to edit out the link.Gothic
But you still have to define a value for each Enum element.Naresh
Installing SplType on Linux PHP is one thing, but how the heck did anyone get it to work (without build tools) on Windows PHP?? Our dev workstations (where we write code, tested on local webserver, before deploying) use the XAMPP stack. How to get php_spl_types.dll installed and activated without errors?Musgrave
Be careful using this. SPL Types are experimental: "This extension is EXPERIMENTAL. The behaviour of this extension including the names of its functions and any other documentation surrounding this extension may change without notice in a future release of PHP. This extension should be used at your own risk."Evalynevan
SplEnum is not bundled with PHP, it needs SPL_Types extentionHabanera
Just use myclabs/php-enum package.. simple. Basically like the SPL extension but not an extension. Does what you want 👍Communicant
The whole SPL_Types has gone from php.net: php.net/manual/en/book.spl-types.php as well as SplEnum. And the package pecl.php.net/package/SPL_Types is not maintained anymore. R.I.P. SPL Types.Begun
Link goes to a 404 page. For some reason this Romanian version works for me php.net/manual/ro/class.splenum.phpCarbonaceous
H
161

From PHP 8.1, you can use native enumerations.

The basic syntax looks like this:

enum TransportMode {
  case Bicycle;
  case Car;
  case Ship;
  case Plane;
  case Feet;
}
function travelCost(TransportMode $mode, int $distance): int
{ /* implementation */ } 

$mode = TransportMode::Boat;

$bikeCost = travelCost(TransportMode::Bicycle, 90);
$boatCost = travelCost($mode, 90);

// this one would fail: (Enums are singletons, not scalars)
$failCost = travelCost('Car', 90);

Values

By default, enumerations are not backed by any kind of scalar. So TransportMode::Bicycle is not 0, and you cannot compare using > or < between enumerations.

But the following works:

$foo = TransportMode::Car;
$bar = TransportMode::Car;
$baz = TransportMode::Bicycle;

$foo === $bar; // true
$bar === $baz; // false

$foo instanceof TransportMode; // true

$foo > $bar || $foo <  $bar; // false either way

Backed Enumerations

You can also have "backed" enums, where each enumeration case is "backed" by either an int or a string.

enum Metal: int {
  case Gold = 1932;
  case Silver = 1049;
  case Lead = 1134;
  case Uranium = 1905;
  case Copper = 894;
}
  • If one case has a backed value, all cases need to have a backed value, there are no auto-generated values.
  • Notice the type of the backed value is declared right after the enumeration name
  • Backed values are read only
  • Scalar values need to be unique
  • Values need to be literals or literal expressions
  • To read the backed value you access the value property: Metal::Gold->value.

Finally, backed enumerations implement a BackedEnum interface internally, which exposes two methods:

  • from(int|string): self
  • tryFrom(int|string): ?self

They are almost equivalent, with the important distinction that the first one will throw an exception if the value is not found, and the second will simply return null.

// usage example:

$metal_1 = Metal::tryFrom(1932); // $metal_1 === Metal::Gold;
$metal_2 = Metal::tryFrom(1000); // $metal_2 === null;

$metal_3 = Metal::from(9999); // throws Exception

Methods

Enumerations may have methods, and thus implement interfaces.

interface TravelCapable
{
    public function travelCost(int $distance): int;
    public function requiresFuel(): bool;
}

enum TransportMode: int implements TravelCapable{
  case Bicycle = 10;
  case Car = 1000 ;
  case Ship = 800 ;
  case Plane = 2000;
  case Feet = 5;
  
  public function travelCost(int $distance): int
  {
    return $this->value * $distance;
  }
  
  public function requiresFuel(): bool {
    return match($this) {
        TransportMode::Car, TransportMode::Ship, TransportMode::Plane => true,
      TransportMode::Bicycle, TransportMode::Feet => false
    }
  }
}

$mode = TransportMode::Car;

$carConsumesFuel = $mode->requiresFuel();   // true
$carTravelCost   = $mode->travelCost(800);  // 800000

Value listing

Both Pure Enums and Backed Enums internally implement the interface UnitEnum, which includes the (static) method UnitEnum::cases(), and allows to retrieve an array of the cases defined in the enumeration:

$modes = TransportMode::cases();

And now $modes is:

[
    TransportMode::Bicycle,
    TransportMode::Car,
    TransportMode::Ship,
    TransportMode::Plane
    TransportMode::Feet
]

Static methods

Enumerations can implement their own static methods, which would generally be used for specialized constructors.


This covers the basics. To get the whole thing, head on to the relevant RFC until the feature is released and published in PHP's documentation.

Heroin answered 15/2, 2021 at 13:21 Comment(4)
In 2022 this answer should be marked as The Answer. All other answers seem to be anti-patterns not using the language features properly.Khalkha
one small addition. to this excellent answer. as ::cases() returns an iteratable type one can use a foreach. For instance something like foreach( TransportMode::cases() as $mode ) { echo $mode->value; }Khalkha
For the UnitEnum::cases() method, how do I convert it to an array, so that I can list those cases in a Select input?Capacitor
"cases() returns a packed array of all defined Cases in the order of declaration."Congeries
O
50

What about class constants?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

    public function echoConstant()
    {
        echo self::SOME_CONSTANT;
    }
}

echo YourClass::SOME_CONSTANT;

$c = new YourClass;
$c->echoConstant();
Optimism answered 31/10, 2008 at 18:57 Comment(1)
echoConstant can be replaced with __toString. And then simply echo $cPunner
S
44

I used classes with constants:

class Enum {
    const NAME       = 'aaaa';
    const SOME_VALUE = 'bbbb';
}

print Enum::NAME;
Settera answered 31/10, 2008 at 18:56 Comment(0)
B
41

The top answer above is fantastic. However, if you extend it in two different ways, then whichever extension is done first results in a call to the functions will create the cache. This cache will then be used by all subsequent calls, no matter whichever extension the calls are initiated by ...

To solve this, replace the variable and first function with:

private static $constCacheArray = null;

private static function getConstants() {
    if (self::$constCacheArray === null) self::$constCacheArray = array();

    $calledClass = get_called_class();
    if (!array_key_exists($calledClass, self::$constCacheArray)) {
        $reflect = new \ReflectionClass($calledClass);
        self::$constCacheArray[$calledClass] = $reflect->getConstants();
    }

    return self::$constCacheArray[$calledClass];
}
Bacteriology answered 3/2, 2014 at 20:14 Comment(2)
Had this very issue. Brian or someone with edit privileges should touch on it in the accepted answer. I solved it in my code using the 'static::' method instead of 'self::' in the getConstants() function and re-declaring the $constCache in the child enums.Legend
It may not be sexy, but using an interface constant may be the best way to go in PHP.Sheriff
K
32

I've commented on some of the other answers here, so I figured I would weigh in too. At the end of the day, since PHP doesn't support typed enumerations, you can go one of two ways: hack out typed enumerations, or live with the fact that they're extremely difficult to hack out effectively.

I prefer to live with the fact, and instead use the const method that other answers here have used in some way or another:

abstract class Enum
{

    const NONE = null;

    final private function __construct()
    {
        throw new NotSupportedException(); // 
    }

    final private function __clone()
    {
        throw new NotSupportedException();
    }

    final public static function toArray()
    {
        return (new ReflectionClass(static::class))->getConstants();
    }

    final public static function isValid($value)
    {
        return in_array($value, static::toArray());
    }

}

An example enumeration:

final class ResponseStatusCode extends Enum
{

    const OK                         = 200;
    const CREATED                    = 201;
    const ACCEPTED                   = 202;
    // ...
    const SERVICE_UNAVAILABLE        = 503;
    const GATEWAY_TIME_OUT           = 504;
    const HTTP_VERSION_NOT_SUPPORTED = 505;

}

Using Enum as a base class from which all other enumerations extend allows for helper methods, such as toArray, isValid, and so on. To me, typed enumerations (and managing their instances) just end up too messy.


Hypothetical

If, there existed a __getStatic magic method (and preferably an __equals magic method too) much of this could be mitigated with a sort of multiton pattern.

(The following is hypothetical; it won't work, though perhaps one day it will)

final class TestEnum
{

    private static $_values = [
        'FOO' => 1,
        'BAR' => 2,
        'QUX' => 3,
    ];
    private static $_instances = [];

    public static function __getStatic($name)
    {
        if (isset(static::$_values[$name]))
        {
            if (empty(static::$_instances[$name]))
            {
                static::$_instances[$name] = new static($name);
            }
            return static::$_instances[$name];
        }
        throw new Exception(sprintf('Invalid enumeration value, "%s"', $name));
    }

    private $_value;

    public function __construct($name)
    {
        $this->_value = static::$_values[$name];
    }

    public function __equals($object)
    {
        if ($object instanceof static)
        {
            return $object->_value === $this->_value;
        }
        return $object === $this->_value;
    }

}

$foo = TestEnum::$FOO; // object(TestEnum)#1 (1) {
                       //   ["_value":"TestEnum":private]=>
                       //   int(1)
                       // }

$zap = TestEnum::$ZAP; // Uncaught exception 'Exception' with message
                       // 'Invalid enumeration member, "ZAP"'

$qux = TestEnum::$QUX;
TestEnum::$QUX == $qux; // true
'hello world!' == $qux; // false
Kwangju answered 11/6, 2013 at 13:17 Comment(1)
I really like the simplicity of this answer. It's the kind of thing you can come back to later and quickly understand how it works without making it look like you did some kind of hacked approach. A shame it doesn't have more up votes.Evidently
A
26

I use interface instead of class:

interface DaysOfWeek
{
    const Sunday = 0;
    const Monday = 1;
    // etc.
}

var $today = DaysOfWeek::Sunday;
Amoeba answered 24/11, 2011 at 14:51 Comment(6)
class Foo implements DaysOfWeek { } and then Foo::Sunday ... what?Kwangju
Author of question asks for a solution for two things: namespace and auto-completion by IDEs. As the top-rated answer suggested, the easiest way is by using class (or interface, which is just a matter of preference).Amoeba
interfaces are used to enforce class implementation integrity, this is outside the scope of an interfaceRadius
@Radius Interfaces can and were/are used in Java to keep constant values. So you are not forced to instantiate a class only to get constant values and any IDE offers code completion on them. Also if you create a class that implements that interface it will inherit all those constants - quite handy sometimes.Rebbecarebbecca
@Radius True, but in PHP, interfaces can have constants. Additionally, these interface constants cannot be overridden by implementing classes, or their children. In effect, this is the best answer in terms of PHP, because anything that can be overridden isn't truly functioning like a constant should. Constant should mean constant, not sometimes (even though polymorphism can be useful at times).Sheriff
However, I would not access the interface constants as depicted here.Sheriff
U
24

Well, for a simple java like enum in php, I use:

class SomeTypeName {
    private static $enum = array(1 => "Read", 2 => "Write");

    public function toOrdinal($name) {
        return array_search($name, self::$enum);
    }

    public function toString($ordinal) {
        return self::$enum[$ordinal];
    }
}

And to call it:

SomeTypeName::toOrdinal("Read");
SomeTypeName::toString(1);

But I'm a PHP beginner, struggling with the syntax so this might not be the best way. I experimented some with Class Constants, using Reflection to get the constant name from it's value, might be neater.

Unfold answered 23/12, 2010 at 20:4 Comment(3)
Good answer, most of the other answers are using classes. You can't have nested classes though.Apricot
This has the benefit of being able to iterate through the values with foreach. And the detriment that an illegal value is not caught.Steinman
No auto completion in the IDE though, so would stimulate guess work. The constants would enable auto completion, sounds better.Bookstall
M
19

Four years later I came across this again. My current approach is this as it allows for code completion in the IDE as well as type safety:

Base class:

abstract class TypedEnum
{
    private static $_instancedValues;

    private $_value;
    private $_name;

    private function __construct($value, $name)
    {
        $this->_value = $value;
        $this->_name = $name;
    }

    private static function _fromGetter($getter, $value)
    {
        $reflectionClass = new ReflectionClass(get_called_class());
        $methods = $reflectionClass->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);    
        $className = get_called_class();

        foreach($methods as $method)
        {
            if ($method->class === $className)
            {
                $enumItem = $method->invoke(null);

                if ($enumItem instanceof $className && $enumItem->$getter() === $value)
                {
                    return $enumItem;
                }
            }
        }

        throw new OutOfRangeException();
    }

    protected static function _create($value)
    {
        if (self::$_instancedValues === null)
        {
            self::$_instancedValues = array();
        }

        $className = get_called_class();

        if (!isset(self::$_instancedValues[$className]))
        {
            self::$_instancedValues[$className] = array();
        }

        if (!isset(self::$_instancedValues[$className][$value]))
        {
            $debugTrace = debug_backtrace();
            $lastCaller = array_shift($debugTrace);

            while ($lastCaller['class'] !== $className && count($debugTrace) > 0)
            {
                $lastCaller = array_shift($debugTrace);
            }

            self::$_instancedValues[$className][$value] = new static($value, $lastCaller['function']);
        }

        return self::$_instancedValues[$className][$value];
    }

    public static function fromValue($value)
    {
        return self::_fromGetter('getValue', $value);
    }

    public static function fromName($value)
    {
        return self::_fromGetter('getName', $value);
    }

    public function getValue()
    {
        return $this->_value;
    }

    public function getName()
    {
        return $this->_name;
    }
}

Example Enum:

final class DaysOfWeek extends TypedEnum
{
    public static function Sunday() { return self::_create(0); }    
    public static function Monday() { return self::_create(1); }
    public static function Tuesday() { return self::_create(2); }   
    public static function Wednesday() { return self::_create(3); }
    public static function Thursday() { return self::_create(4); }  
    public static function Friday() { return self::_create(5); }
    public static function Saturday() { return self::_create(6); }      
}

Example usage:

function saveEvent(DaysOfWeek $weekDay, $comment)
{
    // store week day numeric value and comment:
    $myDatabase->save('myeventtable', 
       array('weekday_id' => $weekDay->getValue()),
       array('comment' => $comment));
}

// call the function, note: DaysOfWeek::Monday() returns an object of type DaysOfWeek
saveEvent(DaysOfWeek::Monday(), 'some comment');

Note that all instances of the same enum entry are the same:

$monday1 = DaysOfWeek::Monday();
$monday2 = DaysOfWeek::Monday();
$monday1 === $monday2; // true

You can also use it inside of a switch statement:

function getGermanWeekDayName(DaysOfWeek $weekDay)
{
    switch ($weekDay)
    {
        case DaysOfWeek::Monday(): return 'Montag';
        case DaysOfWeek::Tuesday(): return 'Dienstag';
        // ...
}

You can also create an enum entry by name or value:

$monday = DaysOfWeek::fromValue(2);
$tuesday = DaysOfWeek::fromName('Tuesday');

Or you can just get the name (i.e. the function name) from an existing enum entry:

$wednesday = DaysOfWeek::Wednesday()
echo $wednesDay->getName(); // Wednesday
Monoatomic answered 27/8, 2014 at 11:52 Comment(3)
+1 for a private constructor. I wouldn't make helper abstract class, just a simple class, private constructor and some of const Monday = DaysOfWeek('Monday');Carport
I wonder about something though. In mysql, 0 for an enum is treated as blank. The valid values always start with 1. Does an extended class with the first value/int as 0 ever cause you issues? Because, I know mySql/Maria will store the int values, but the column string values will always be empty. ('') if you pass a 0; mariadb.com/kb/en/enum dev.mysql.com/doc/refman/8.0/en/enum.htmlHallvard
This is so verbose, you could have used protected const and used __callStatic to create the enum instead, if you want autocompletion, it will need DocComments though, but you could even remove const and just use DocCommentsKosygin
P
10

I found this library on github and I think it provides a very decent alternative to the answers here.

PHP Enum implementation inspired from SplEnum

  • You can type-hint: function setAction(Action $action) {
  • You can enrich the enum with methods (e.g. format, parse, …)
  • You can extend the enum to add new values (make your enum final to prevent it)
  • You can get a list of all the possible values (see below)

Declaration

<?php
use MyCLabs\Enum\Enum;

/**
 * Action enum
 */
class Action extends Enum
{
    const VIEW = 'view';
    const EDIT = 'edit';
}

Usage

<?php
$action = new Action(Action::VIEW);

// or
$action = Action::VIEW();

type-hint enum values:

<?php
function setAction(Action $action) {
    // ...
}
Preclinical answered 11/8, 2013 at 4:20 Comment(2)
This is the correct answer (for now, until enum is added in PHP 7.x) because it allows type hinting.Inwards
Not only does this allow type-hinting, but because of the __toString() magic, it allows you to do what you generally really want to with enums - use them in a switch or if statement, comparing directly with the values of the consts. The best approach short of native enum support, IMO.Nikola
P
8

If you need to use enums that are globally unique (i.e. even when comparing elements between different Enums) and are easy to use, feel free to use the following code. I also added some methods that I find useful. You will find examples in the comments at the very top of the code.

<?php

/**
 * Class Enum
 * 
 * @author Christopher Fox <[email protected]>
 *
 * @version 1.0
 *
 * This class provides the function of an enumeration.
 * The values of Enum elements are unique (even between different Enums)
 * as you would expect them to be.
 *
 * Constructing a new Enum:
 * ========================
 *
 * In the following example we construct an enum called "UserState"
 * with the elements "inactive", "active", "banned" and "deleted".
 * 
 * <code>
 * Enum::Create('UserState', 'inactive', 'active', 'banned', 'deleted');
 * </code>
 *
 * Using Enums:
 * ============
 *
 * The following example demonstrates how to compare two Enum elements
 *
 * <code>
 * var_dump(UserState::inactive == UserState::banned); // result: false
 * var_dump(UserState::active == UserState::active); // result: true
 * </code>
 *
 * Special Enum methods:
 * =====================
 *
 * Get the number of elements in an Enum:
 *
 * <code>
 * echo UserState::CountEntries(); // result: 4
 * </code>
 *
 * Get a list with all elements of the Enum:
 *
 * <code>
 * $allUserStates = UserState::GetEntries();
 * </code>
 *
 * Get a name of an element:
 *
 * <code>
 * echo UserState::GetName(UserState::deleted); // result: deleted
 * </code>
 *
 * Get an integer ID for an element (e.g. to store as a value in a database table):
 * This is simply the index of the element (beginning with 1).
 * Note that this ID is only unique for this Enum but now between different Enums.
 *
 * <code>
 * echo UserState::GetDatabaseID(UserState::active); // result: 2
 * </code>
 */
class Enum
{

    /**
     * @var Enum $instance The only instance of Enum (Singleton)
     */
    private static $instance;

    /**
     * @var array $enums    An array of all enums with Enum names as keys
     *          and arrays of element names as values
     */
    private $enums;

    /**
     * Constructs (the only) Enum instance
     */
    private function __construct()
    {
        $this->enums = array();
    }

    /**
     * Constructs a new enum
     *
     * @param string $name The class name for the enum
     * @param mixed $_ A list of strings to use as names for enum entries
     */
    public static function Create($name, $_)
    {
        // Create (the only) Enum instance if this hasn't happened yet
        if (self::$instance===null)
        {
            self::$instance = new Enum();
        }

        // Fetch the arguments of the function
        $args = func_get_args();
        // Exclude the "name" argument from the array of function arguments,
        // so only the enum element names remain in the array
        array_shift($args);
        self::$instance->add($name, $args);
    }

    /**
     * Creates an enumeration if this hasn't happened yet
     * 
     * @param string $name The class name for the enum
     * @param array $fields The names of the enum elements
     */
    private function add($name, $fields)
    {
        if (!array_key_exists($name, $this->enums))
        {
            $this->enums[$name] = array();

            // Generate the code of the class for this enumeration
            $classDeclaration =     "class " . $name . " {\n"
                        . "private static \$name = '" . $name . "';\n"
                        . $this->getClassConstants($name, $fields)
                        . $this->getFunctionGetEntries($name)
                        . $this->getFunctionCountEntries($name)
                        . $this->getFunctionGetDatabaseID()
                        . $this->getFunctionGetName()
                        . "}";

            // Create the class for this enumeration
            eval($classDeclaration);
        }
    }

    /**
     * Returns the code of the class constants
     * for an enumeration. These are the representations
     * of the elements.
     * 
     * @param string $name The class name for the enum
     * @param array $fields The names of the enum elements
     *
     * @return string The code of the class constants
     */
    private function getClassConstants($name, $fields)
    {
        $constants = '';

        foreach ($fields as $field)
        {
            // Create a unique ID for the Enum element
            // This ID is unique because class and variables
            // names can't contain a semicolon. Therefore we
            // can use the semicolon as a separator here.
            $uniqueID = $name . ";" . $field;
            $constants .=   "const " . $field . " = '". $uniqueID . "';\n";
            // Store the unique ID
            array_push($this->enums[$name], $uniqueID);
        }

        return $constants;
    }

    /**
     * Returns the code of the function "GetEntries()"
     * for an enumeration
     * 
     * @param string $name The class name for the enum
     *
     * @return string The code of the function "GetEntries()"
     */
    private function getFunctionGetEntries($name) 
    {
        $entryList = '';        

        // Put the unique element IDs in single quotes and
        // separate them with commas
        foreach ($this->enums[$name] as $key => $entry)
        {
            if ($key > 0) $entryList .= ',';
            $entryList .= "'" . $entry . "'";
        }

        return  "public static function GetEntries() { \n"
            . " return array(" . $entryList . ");\n"
            . "}\n";
    }

    /**
     * Returns the code of the function "CountEntries()"
     * for an enumeration
     * 
     * @param string $name The class name for the enum
     *
     * @return string The code of the function "CountEntries()"
     */
    private function getFunctionCountEntries($name) 
    {
        // This function will simply return a constant number (e.g. return 5;)
        return  "public static function CountEntries() { \n"
            . " return " . count($this->enums[$name]) . ";\n"
            . "}\n";
    }

    /**
     * Returns the code of the function "GetDatabaseID()"
     * for an enumeration
     * 
     * @return string The code of the function "GetDatabaseID()"
     */
    private function getFunctionGetDatabaseID()
    {
        // Check for the index of this element inside of the array
        // of elements and add +1
        return  "public static function GetDatabaseID(\$entry) { \n"
            . "\$key = array_search(\$entry, self::GetEntries());\n"
            . " return \$key + 1;\n"
            . "}\n";
    }

    /**
     * Returns the code of the function "GetName()"
     * for an enumeration
     *
     * @return string The code of the function "GetName()"
     */
    private function getFunctionGetName()
    {
        // Remove the class name from the unique ID 
        // and return this value (which is the element name)
        return  "public static function GetName(\$entry) { \n"
            . "return substr(\$entry, strlen(self::\$name) + 1 , strlen(\$entry));\n"
            . "}\n";
    }

}


?>
Predisposition answered 21/8, 2010 at 22:45 Comment(2)
I do like this, a lot. However, one of the primary complaints is the ability of the IDE to pick up the values for auto-complete. I'm not sure this would be able to do that without a custom addon for the IDE. Not that it couldn't be done, it just would take some work.Immature
Using eval() just so you can declare new Enums runtime? Eek. I'm not feeling it. How do you prevent other classes from creating an incorrect Enum class before you get to define the proper one? Aren't the Enums known before runtime? And as @Immature implied, no IDE autocompletion. Only benefit I see is lazy coding.Bookstall
R
8

I like enums from java too and for this reason I write my enums in this way, I think this is the most similiar behawior like in Java enums, of course, if some want to use more methods from java should write it here, or in abstract class but core idea is embedded in code below


class FruitsEnum {

    static $APPLE = null;
    static $ORANGE = null;

    private $value = null;

    public static $map;

    public function __construct($value) {
        $this->value = $value;
    }

    public static function init () {
        self::$APPLE  = new FruitsEnum("Apple");
        self::$ORANGE = new FruitsEnum("Orange");
        //static map to get object by name - example Enum::get("INIT") - returns Enum::$INIT object;
        self::$map = array (
            "Apple" => self::$APPLE,
            "Orange" => self::$ORANGE
        );
    }

    public static function get($element) {
        if($element == null)
            return null;
        return self::$map[$element];
    }

    public function getValue() {
        return $this->value;
    }

    public function equals(FruitsEnum $element) {
        return $element->getValue() == $this->getValue();
    }

    public function __toString () {
        return $this->value;
    }
}
FruitsEnum::init();

var_dump(FruitsEnum::$APPLE->equals(FruitsEnum::$APPLE)); //true
var_dump(FruitsEnum::$APPLE->equals(FruitsEnum::$ORANGE)); //false
var_dump(FruitsEnum::$APPLE instanceof FruitsEnum); //true
var_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::$APPLE)); //true - enum from string
var_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::get("Orange"))); //false

Rhapsody answered 13/4, 2011 at 9:55 Comment(2)
I'm doing pretty much the same thing, with two small additions though: I've hidden the static values behind static getters. One reason is, that I visually prefer FruitsEnum::Apple() over FruitsEnum::$Apple, but the more important reason is to prevent anyone else from setting $APPLE, thus breaking the enum for the entire application. The other is a simple private static flag $initialized that makes sure that calling init() becomes no-op after calling it for the first time (so no one can mess with that either).Pacian
I did like Martin. .init() is weird, and I don't mind the getter approach.Semicentennial
T
8
abstract class Enumeration
{
    public static function enum() 
    {
        $reflect = new ReflectionClass( get_called_class() );
        return $reflect->getConstants();
    }
}


class Test extends Enumeration
{
    const A = 'a';
    const B = 'b';    
}


foreach (Test::enum() as $key => $value) {
    echo "$key -> $value<br>";
}
Toboggan answered 19/4, 2013 at 10:47 Comment(0)
O
7

It might be as simple as

enum DaysOfWeek {
    Sunday,
    Monday,
    // ...
}

in the future.

PHP RFC: Enumerated Types

Onega answered 28/5, 2015 at 13:26 Comment(1)
FYI as of 7.1 still not hereCadmium
S
7

Finally, a PHP 7.1+ answer with constants that cannot be overridden.

/**
 * An interface that groups HTTP Accept: header Media Types in one place.
 */
interface MediaTypes
{
    /**
    * Now, if you have to use these same constants with another class, you can
    * without creating funky inheritance / is-a relationships.
    * Also, this gets around the single inheritance limitation.
    */

    public const HTML = 'text/html';
    public const JSON = 'application/json';
    public const XML = 'application/xml';
    public const TEXT = 'text/plain';
}

/**
 * An generic request class.
 */
abstract class Request
{
    // Why not put the constants here?
    // 1) The logical reuse issue.
    // 2) Single Inheritance.
    // 3) Overriding is possible.

    // Why put class constants here?
    // 1) The constant value will not be necessary in other class families.
}

/**
 * An incoming / server-side HTTP request class.
 */
class HttpRequest extends Request implements MediaTypes
{
    // This class can implement groups of constants as necessary.
}

If you are using namespaces, code completion should work.

However, in doing this, you lose the ability to hide the constants within the class family (protected) or class alone (private). By definition, everything in an Interface is public.

PHP Manual: Interfaces

Update:

PHP 8.1 now has enumerations.

Sheriff answered 11/9, 2018 at 4:26 Comment(1)
This is not Java. This works in cases where polymorphism / Strategy pattern is not required to override constants in a parent class.Sheriff
W
6

Here is a github library for handling type-safe enumerations in php:

This library handle classes generation, classes caching and it implements the Type Safe Enumeration design pattern, with several helper methods for dealing with enums, like retrieving an ordinal for enums sorting, or retrieving a binary value, for enums combinations.

The generated code use a plain old php template file, which is also configurable, so you can provide your own template.

It is full test covered with phpunit.

php-enums on github (feel free to fork)

Usage: (@see usage.php, or unit tests for more details)

<?php
//require the library
require_once __DIR__ . '/src/Enum.func.php';

//if you don't have a cache directory, create one
@mkdir(__DIR__ . '/cache');
EnumGenerator::setDefaultCachedClassesDir(__DIR__ . '/cache');

//Class definition is evaluated on the fly:
Enum('FruitsEnum', array('apple' , 'orange' , 'rasberry' , 'bannana'));

//Class definition is cached in the cache directory for later usage:
Enum('CachedFruitsEnum', array('apple' , 'orange' , 'rasberry' , 'bannana'), '\my\company\name\space', true);

echo 'FruitsEnum::APPLE() == FruitsEnum::APPLE(): ';
var_dump(FruitsEnum::APPLE() == FruitsEnum::APPLE()) . "\n";

echo 'FruitsEnum::APPLE() == FruitsEnum::ORANGE(): ';
var_dump(FruitsEnum::APPLE() == FruitsEnum::ORANGE()) . "\n";

echo 'FruitsEnum::APPLE() instanceof Enum: ';
var_dump(FruitsEnum::APPLE() instanceof Enum) . "\n";

echo 'FruitsEnum::APPLE() instanceof FruitsEnum: ';
var_dump(FruitsEnum::APPLE() instanceof FruitsEnum) . "\n";

echo "->getName()\n";
foreach (FruitsEnum::iterator() as $enum)
{
  echo "  " . $enum->getName() . "\n";
}

echo "->getValue()\n";
foreach (FruitsEnum::iterator() as $enum)
{
  echo "  " . $enum->getValue() . "\n";
}

echo "->getOrdinal()\n";
foreach (CachedFruitsEnum::iterator() as $enum)
{
  echo "  " . $enum->getOrdinal() . "\n";
}

echo "->getBinary()\n";
foreach (CachedFruitsEnum::iterator() as $enum)
{
  echo "  " . $enum->getBinary() . "\n";
}

Output:

FruitsEnum::APPLE() == FruitsEnum::APPLE(): bool(true)
FruitsEnum::APPLE() == FruitsEnum::ORANGE(): bool(false)
FruitsEnum::APPLE() instanceof Enum: bool(true)
FruitsEnum::APPLE() instanceof FruitsEnum: bool(true)
->getName()
  APPLE
  ORANGE
  RASBERRY
  BANNANA
->getValue()
  apple
  orange
  rasberry
  bannana
->getValue() when values have been specified
  pig
  dog
  cat
  bird
->getOrdinal()
  1
  2
  3
  4
->getBinary()
  1
  2
  4
  8
Withershins answered 21/10, 2010 at 8:36 Comment(0)
B
6

My Enum class definition below is Strongly typed, and very natural to use and define.

Definition:

class Fruit extends Enum {
    static public $APPLE = 1;
    static public $ORANGE = 2;
}
Fruit::initialize(); //Can also be called in autoloader

Switch over Enum

$myFruit = Fruit::$APPLE;

switch ($myFruit) {
    case Fruit::$APPLE  : echo "I like apples\n";  break;
    case Fruit::$ORANGE : echo "I hate oranges\n"; break;
}

>> I like apples

Pass Enum as parameter (Strongly typed)

/** Function only accepts Fruit enums as input**/
function echoFruit(Fruit $fruit) {
    echo $fruit->getName().": ".$fruit->getValue()."\n";
}

/** Call function with each Enum value that Fruit has */
foreach (Fruit::getList() as $fruit) {
    echoFruit($fruit);
}

//Call function with Apple enum
echoFruit(Fruit::$APPLE)

//Will produce an error. This solution is strongly typed
echoFruit(2);

>> APPLE: 1
>> ORANGE: 2
>> APPLE: 1
>> Argument 1 passed to echoFruit() must be an instance of Fruit, integer given

Echo Enum as string

echo "I have an $myFruit\n";

>> I have an APPLE

Get Enum by integer

$myFruit = Fruit::getByValue(2);

echo "Now I have an $myFruit\n";

>> Now I have an ORANGE

Get Enum by Name

$myFruit = Fruit::getByName("APPLE");

echo "But I definitely prefer an $myFruit\n\n";

>> But I definitely prefer an APPLE

The Enum Class:

/**
 * @author Torge Kummerow
 */
class Enum {

    /**
     * Holds the values for each type of Enum
     */
    static private $list = array();

    /**
     * Initializes the enum values by replacing the number with an instance of itself
     * using reflection
     */
    static public function initialize() {
        $className = get_called_class();
        $class = new ReflectionClass($className);
        $staticProperties = $class->getStaticProperties();

        self::$list[$className] = array();

        foreach ($staticProperties as $propertyName => &$value) {
            if ($propertyName == 'list')
                continue;

            $enum = new $className($propertyName, $value);
            $class->setStaticPropertyValue($propertyName, $enum);
            self::$list[$className][$propertyName] = $enum;
        } unset($value);
    }


    /**
     * Gets the enum for the given value
     *
     * @param integer $value
     * @throws Exception
     *
     * @return Enum
     */
    static public function getByValue($value) {
        $className = get_called_class();
        foreach (self::$list[$className] as $propertyName=>&$enum) {
            /* @var $enum Enum */
            if ($enum->value == $value)
                return $enum;
        } unset($enum);

        throw new Exception("No such enum with value=$value of type ".get_called_class());
    }

    /**
     * Gets the enum for the given name
     *
     * @param string $name
     * @throws Exception
     *
     * @return Enum
     */
    static public function getByName($name) {
        $className = get_called_class();
        if (array_key_exists($name, static::$list[$className]))
            return self::$list[$className][$name];

        throw new Exception("No such enum ".get_called_class()."::\$$name");
    }


    /**
     * Returns the list of all enum variants
     * @return Array of Enum
     */
    static public function getList() {
        $className = get_called_class();
        return self::$list[$className];
    }


    private $name;
    private $value;

    public function __construct($name, $value) {
        $this->name = $name;
        $this->value = $value;
    }

    public function __toString() {
        return $this->name;
    }

    public function getValue() {
        return $this->value;
    }

    public function getName() {
        return $this->name;
    }

}

Addition

You can ofcourse also add comments for IDEs

class Fruit extends Enum {

    /**
     * This comment is for autocomplete support in common IDEs
     * @var Fruit A yummy apple
     */
    static public $APPLE = 1;

    /**
     * This comment is for autocomplete support in common IDEs
     * @var Fruit A sour orange
     */
    static public $ORANGE = 2;
}

//This can also go to the autoloader if available.
Fruit::initialize();
Blackfish answered 26/2, 2015 at 16:29 Comment(2)
Pass Enum as parameter (Strongly typed) not working showing Expected type 'SomeEnum'. Found 'int'Euler
What case exactly? Am using this a lot myself in our project and works fine. Your error indicates that you passed an integer instead of the enum and the strong typing works as intendedBlackfish
G
5

The most common solution that I have seen to enum's in PHP has been to create a generic enum class and then extend it. You might take a look at this.

UPDATE: Alternatively, I found this from phpclasses.org.

Gunstock answered 31/10, 2008 at 18:55 Comment(1)
Although the implementation is slick and would probably do the job, the downside of this is that IDEs probably don't know how to autofill the enums. I couldn't inspect the one from phpclasses.org, because it wanted me to register.Copyread
G
5

I have taken to using the approach below as it gives me the ability to have type safety for function parameters, auto complete in NetBeans and good performance. The one thing I don't like too much is that you have to call [extended class name]::enumerate(); after defining the class.

abstract class Enum {

    private $_value;

    protected function __construct($value) {
        $this->_value = $value;
    }

    public function __toString() {
        return (string) $this->_value;
    }

    public static function enumerate() {
        $class = get_called_class();
        $ref = new ReflectionClass($class);
        $statics = $ref->getStaticProperties();
        foreach ($statics as $name => $value) {
            $ref->setStaticPropertyValue($name, new $class($value));
        }
    }
}

class DaysOfWeek extends Enum {
    public static $MONDAY = 0;
    public static $SUNDAY = 1;
    // etc.
}
DaysOfWeek::enumerate();

function isMonday(DaysOfWeek $d) {
    if ($d == DaysOfWeek::$MONDAY) {
        return true;
    } else {
        return false;
    }
}

$day = DaysOfWeek::$MONDAY;
echo (isMonday($day) ? "bummer it's monday" : "Yay! it's not monday");
Grueling answered 2/12, 2012 at 0:13 Comment(2)
Nothing is preventing you from redefining the enum values: DaysOfWeek::$MONDAY = 3;Bookstall
@BrianFisher, I know it's a nit late now, but, if you don't like calling [extended class name]::enumerate(); after definition, why do you not do it in the construct?Achromatic
K
5

I realize this is a very-very-very old thread but I had a thought about this and wanted to know what people thought.

Notes: I was playing around with this and realized that if I just modified the __call() function that you can get even closer to actual enums. The __call() function handles all unknown function calls. So let's say you want to make three enums RED_LIGHT, YELLOW_LIGHT, and GREEN_LIGHT. You can do so now by just doing the following:

$c->RED_LIGHT();
$c->YELLOW_LIGHT();
$c->GREEN_LIGHT();

Once defined all you have to do is to call them again to get the values:

echo $c->RED_LIGHT();
echo $c->YELLOW_LIGHT();
echo $c->GREEN_LIGHT();

and you should get 0, 1, and 2. Have fun! This is also now up on GitHub.

Update: I've made it so both the __get() and __set() functions are now used. These allow you to not have to call a function unless you want to. Instead, now you can just say:

$c->RED_LIGHT;
$c->YELLOW_LIGHT;
$c->GREEN_LIGHT;

For both the creation and getting of the values. Because the variables haven't been defined initially, the __get() function is called (because there isn't a value specified) which sees that the entry in the array hasn't been made. So it makes the entry, assigns it the last value given plus one(+1), increments the last value variable, and returns TRUE. If you set the value:

$c->RED_LIGHT = 85;

Then the __set() function is called and the last value is then set to the new value plus one (+1). So now we have a fairly good way to do enums and they can be created on the fly.

<?php
################################################################################
#   Class ENUMS
#
#       Original code by Mark Manning.
#       Copyrighted (c) 2015 by Mark Manning.
#       All rights reserved.
#
#       This set of code is hereby placed into the free software universe
#       via the GNU greater license thus placing it under the Copyleft
#       rules and regulations with the following modifications:
#
#       1. You may use this work in any other work.  Commercial or otherwise.
#       2. You may make as much money as you can with it.
#       3. You owe me nothing except to give me a small blurb somewhere in
#           your program or maybe have pity on me and donate a dollar to
#           [email protected].  :-)
#
#   Blurb:
#
#       PHP Class Enums by Mark Manning (markem-AT-sim1-DOT-us).
#       Used with permission.
#
#   Notes:
#
#       VIM formatting.  Set tabs to four(4) spaces.
#
################################################################################
class enums
{
    private $enums;
    private $clear_flag;
    private $last_value;

################################################################################
#   __construct(). Construction function.  Optionally pass in your enums.
################################################################################
function __construct()
{
    $this->enums = array();
    $this->clear_flag = false;
    $this->last_value = 0;

    if( func_num_args() > 0 ){
        return $this->put( func_get_args() );
        }

    return true;
}
################################################################################
#   put(). Insert one or more enums.
################################################################################
function put()
{
    $args = func_get_args();
#
#   Did they send us an array of enums?
#   Ex: $c->put( array( "a"=>0, "b"=>1,...) );
#   OR  $c->put( array( "a", "b", "c",... ) );
#
    if( is_array($args[0]) ){
#
#   Add them all in
#
        foreach( $args[0] as $k=>$v ){
#
#   Don't let them change it once it is set.
#   Remove the IF statement if you want to be able to modify the enums.
#
            if( !isset($this->enums[$k]) ){
#
#   If they sent an array of enums like this: "a","b","c",... then we have to
#   change that to be "A"=>#. Where "#" is the current count of the enums.
#
                if( is_numeric($k) ){
                    $this->enums[$v] = $this->last_value++;
                    }
#
#   Else - they sent "a"=>"A", "b"=>"B", "c"=>"C"...
#
                    else {
                        $this->last_value = $v + 1;
                        $this->enums[$k] = $v;
                        }
                }
            }
        }
#
#   Nope!  Did they just sent us one enum?
#
        else {
#
#   Is this just a default declaration?
#   Ex: $c->put( "a" );
#
            if( count($args) < 2 ){
#
#   Again - remove the IF statement if you want to be able to change the enums.
#
                if( !isset($this->enums[$args[0]]) ){
                    $this->enums[$args[0]] = $this->last_value++;
                    }
#
#   No - they sent us a regular enum
#   Ex: $c->put( "a", "This is the first enum" );
#
                    else {
#
#   Again - remove the IF statement if you want to be able to change the enums.
#
                        if( !isset($this->enums[$args[0]]) ){
                            $this->last_value = $args[1] + 1;
                            $this->enums[$args[0]] = $args[1];
                            }
                        }
                }
            }

    return true;
}
################################################################################
#   get(). Get one or more enums.
################################################################################
function get()
{
    $num = func_num_args();
    $args = func_get_args();
#
#   Is this an array of enums request? (ie: $c->get(array("a","b","c"...)) )
#
    if( is_array($args[0]) ){
        $ary = array();
        foreach( $args[0] as $k=>$v ){
            $ary[$v] = $this->enums[$v];
            }

        return $ary;
        }
#
#   Is it just ONE enum they want? (ie: $c->get("a") )
#
        else if( ($num > 0) && ($num < 2) ){
            return $this->enums[$args[0]];
            }
#
#   Is it a list of enums they want? (ie: $c->get( "a", "b", "c"...) )
#
        else if( $num > 1 ){
            $ary = array();
            foreach( $args as $k=>$v ){
                $ary[$v] = $this->enums[$v];
                }

            return $ary;
            }
#
#   They either sent something funky or nothing at all.
#
    return false;
}
################################################################################
#   clear(). Clear out the enum array.
#       Optional.  Set the flag in the __construct function.
#       After all, ENUMS are supposed to be constant.
################################################################################
function clear()
{
    if( $clear_flag ){
        unset( $this->enums );
        $this->enums = array();
        }

    return true;
}
################################################################################
#   __call().  In case someone tries to blow up the class.
################################################################################
function __call( $name, $arguments )
{
    if( isset($this->enums[$name]) ){ return $this->enums[$name]; }
        else if( !isset($this->enums[$name]) && (count($arguments) > 0) ){
            $this->last_value = $arguments[0] + 1;
            $this->enums[$name] = $arguments[0];
            return true;
            }
        else if( !isset($this->enums[$name]) && (count($arguments) < 1) ){
            $this->enums[$name] = $this->last_value++;
            return true;
            }

    return false;
}
################################################################################
#   __get(). Gets the value.
################################################################################
function __get($name)
{
    if( isset($this->enums[$name]) ){ return $this->enums[$name]; }
        else if( !isset($this->enums[$name]) ){
            $this->enums[$name] = $this->last_value++;
            return true;
            }

    return false;
}
################################################################################
#   __set().  Sets the value.
################################################################################
function __set( $name, $value=null )
{
    if( isset($this->enums[$name]) ){ return false; }
        else if( !isset($this->enums[$name]) && !is_null($value) ){
            $this->last_value = $value + 1;
            $this->enums[$name] = $value;
            return true;
            }
        else if( !isset($this->enums[$name]) && is_null($value) ){
            $this->enums[$name] = $this->last_value++;
            return true;
            }

    return false;
}
################################################################################
#   __destruct().  Deconstruct the class.  Remove the list of enums.
################################################################################
function __destruct()
{
    unset( $this->enums );
    $this->enums = null;

    return true;
}

}
#
#   Test code
#
#   $c = new enums();
#   $c->RED_LIGHT(85);
#   $c->YELLOW_LIGHT = 23;
#   $c->GREEN_LIGHT;
#
#   echo $c->RED_LIGHT . "\n";
#   echo $c->YELLOW_LIGHT . "\n";
#   echo $c->GREEN_LIGHT . "\n";

?>
Karenkarena answered 28/4, 2015 at 16:23 Comment(0)
B
4

I know this is an old thread, however none of the workarounds I've seen really looked like enums, since almost all workarounds requires you to manually assign values to the enum items, or it requires you to pass an array of enum keys to a function. So I created my own solution for this.

To create an enum class using my solution one can simply extend this Enum class below, create a bunch of static variables (no need to initialize them), and make a call to yourEnumClass::init() just below the definition of your enum class.

edit: This only works in php >= 5.3, but it can probably be modified to work in older versions as well

/**
 * A base class for enums. 
 * 
 * This class can be used as a base class for enums. 
 * It can be used to create regular enums (incremental indices), but it can also be used to create binary flag values.
 * To create an enum class you can simply extend this class, and make a call to <yourEnumClass>::init() before you use the enum.
 * Preferably this call is made directly after the class declaration. 
 * Example usages:
 * DaysOfTheWeek.class.php
 * abstract class DaysOfTheWeek extends Enum{
 *      static $MONDAY = 1;
 *      static $TUESDAY;
 *      static $WEDNESDAY;
 *      static $THURSDAY;
 *      static $FRIDAY;
 *      static $SATURDAY;
 *      static $SUNDAY;
 * }
 * DaysOfTheWeek::init();
 * 
 * example.php
 * require_once("DaysOfTheWeek.class.php");
 * $today = date('N');
 * if ($today == DaysOfTheWeek::$SUNDAY || $today == DaysOfTheWeek::$SATURDAY)
 *      echo "It's weekend!";
 * 
 * Flags.class.php
 * abstract class Flags extends Enum{
 *      static $FLAG_1;
 *      static $FLAG_2;
 *      static $FLAG_3;
 * }
 * Flags::init(Enum::$BINARY_FLAG);
 * 
 * example2.php
 * require_once("Flags.class.php");
 * $flags = Flags::$FLAG_1 | Flags::$FLAG_2;
 * if ($flags & Flags::$FLAG_1)
 *      echo "Flag_1 is set";
 * 
 * @author Tiddo Langerak
 */
abstract class Enum{

    static $BINARY_FLAG = 1;
    /**
     * This function must be called to initialize the enumeration!
     * 
     * @param bool $flags If the USE_BINARY flag is provided, the enum values will be binary flag values. Default: no flags set.
     */ 
    public static function init($flags = 0){
        //First, we want to get a list of all static properties of the enum class. We'll use the ReflectionClass for this.
        $enum = get_called_class();
        $ref = new ReflectionClass($enum);
        $items = $ref->getStaticProperties();
        //Now we can start assigning values to the items. 
        if ($flags & self::$BINARY_FLAG){
            //If we want binary flag values, our first value should be 1.
            $value = 1;
            //Now we can set the values for all items.
            foreach ($items as $key=>$item){
                if (!isset($item)){                 
                    //If no value is set manually, we should set it.
                    $enum::$$key = $value;
                    //And we need to calculate the new value
                    $value *= 2;
                } else {
                    //If there was already a value set, we will continue starting from that value, but only if that was a valid binary flag value.
                    //Otherwise, we will just skip this item.
                    if ($key != 0 && ($key & ($key - 1) == 0))
                        $value = 2 * $item;
                }
            }
        } else {
            //If we want to use regular indices, we'll start with index 0.
            $value = 0;
            //Now we can set the values for all items.
            foreach ($items as $key=>$item){
                if (!isset($item)){
                    //If no value is set manually, we should set it, and increment the value for the next item.
                    $enum::$$key = $value;
                    $value++;
                } else {
                    //If a value was already set, we'll continue from that value.
                    $value = $item+1;
                }
            }
        }
    }
}
Bluecoat answered 28/12, 2011 at 18:23 Comment(0)
D
4
class DayOfWeek {
    static $values = array(
        self::MONDAY,
        self::TUESDAY,
        // ...
    );

    const MONDAY  = 0;
    const TUESDAY = 1;
    // ...
}

$today = DayOfWeek::MONDAY;

// If you want to check if a value is valid
assert( in_array( $today, DayOfWeek::$values ) );

Don't use reflection. It makes it extremely difficult to reason about your code and track down where something is being used, and tends to break static analysis tools (eg what's built into your IDE).

Donate answered 1/3, 2015 at 3:48 Comment(0)
L
4

Now you can use The SplEnum class to build it natively. As per the official documentation.

SplEnum gives the ability to emulate and create enumeration objects natively in PHP.

<?php
class Month extends SplEnum {
    const __default = self::January;

    const January = 1;
    const February = 2;
    const March = 3;
    const April = 4;
    const May = 5;
    const June = 6;
    const July = 7;
    const August = 8;
    const September = 9;
    const October = 10;
    const November = 11;
    const December = 12;
}

echo new Month(Month::June) . PHP_EOL;

try {
    new Month(13);
} catch (UnexpectedValueException $uve) {
    echo $uve->getMessage() . PHP_EOL;
}
?>

Please note it's an extension which has to be installed, but it is not available by default. Which comes under Special Types described on the PHP website itself. The above example is taken from the PHP site.

Lapful answered 26/5, 2018 at 17:39 Comment(0)
W
3

The accepted answer is the way to go and is actually what I am doing for simplicity. Most advantages of enumeration are offered (readable, fast, etc.). One concept is missing, however: type safety. In most languages, enumerations are also used to restrict allowed values. Below is an example of how type safety can also be obtained by using private constructors, static instantiation methods and type checking:

class DaysOfWeek{
 const Sunday = 0;
 const Monday = 1;
 // etc.

 private $intVal;
 private function __construct($intVal){
   $this->intVal = $intVal;
 }

 //static instantiation methods
 public static function MONDAY(){
   return new self(self::Monday);
 }
 //etc.
}

//function using type checking
function printDayOfWeek(DaysOfWeek $d){ //compiler can now use type checking
  // to something with $d...
}

//calling the function is safe!
printDayOfWeek(DaysOfWeek::MONDAY());

We could even go further: using constants in the DaysOfWeek class might lead to misusage: e.g. one might mistakenly use it this way:

printDayOfWeek(DaysOfWeek::Monday); //triggers a compiler error.

which is wrong (calls integer constant). We can prevent this using private static variables instead of constants:

class DaysOfWeeks{

  private static $monday = 1;
  //etc.

  private $intVal;
  //private constructor
  private function __construct($intVal){
    $this->intVal = $intVal;
  }

  //public instantiation methods
  public static function MONDAY(){
    return new self(self::$monday);
  }
  //etc.


  //convert an instance to its integer value
  public function intVal(){
    return $this->intVal;
  }

}

Of course, it is not possible to access integer constants (this was actually the purpose). The intVal method allows to convert a DaysOfWeek object to its integer representation.

Note that we could even go further by implementing a caching mechanism in instantiation methods to save memory in the case enumerations are extensively used...

Hope this will help

Wedged answered 29/11, 2012 at 8:36 Comment(0)
R
2

This is my take on "dynamic" enum... so that i can call it with variables, ex. from a form.

look at updated verison below this codeblock...

$value = "concert";
$Enumvalue = EnumCategory::enum($value);
//$EnumValue = 1

class EnumCategory{
    const concert = 1;
    const festival = 2;
    const sport = 3;
    const nightlife = 4;
    const theatre = 5;
    const musical = 6;
    const cinema = 7;
    const charity = 8;
    const museum = 9;
    const other = 10;

    public function enum($string){
        return constant('EnumCategory::'.$string);
    }
}

UPDATE: Better way of doing it...

class EnumCategory {

    static $concert = 1;
    static $festival = 2;
    static $sport = 3;
    static $nightlife = 4;
    static $theatre = 5;
    static $musical = 6;
    static $cinema = 7;
    static $charity = 8;
    static $museum = 9;
    static $other = 10;

}

Call with

EnumCategory::${$category};
Repast answered 13/4, 2011 at 20:53 Comment(1)
The problem with this being; EnumCategory::$sport = 9;. Welcome to the sports museum. const is the better way of doing it.Kwangju
P
2

Pointed out solution works well. Clean and smooth.

However, if you want strongly typed enumerations, you can use this:

class TestEnum extends Enum
{
    public static $TEST1;
    public static $TEST2;
}
TestEnum::init(); // Automatically initializes enum values

With an Enum class looking like:

class Enum
{
    public static function parse($enum)
    {
        $class = get_called_class();
        $vars = get_class_vars($class);
        if (array_key_exists($enum, $vars)) {
            return $vars[$enum];
        }
        return null;
    }

    public static function init()
    {
        $className = get_called_class();
        $consts = get_class_vars($className);
        foreach ($consts as $constant => $value) {
            if (is_null($className::$$constant)) {
                $constantValue = $constant;
                $constantValueName = $className . '::' . $constant . '_VALUE';
                if (defined($constantValueName)) {
                    $constantValue = constant($constantValueName);
                }
                $className::$$constant = new $className($constantValue);
            }
        }
    }

    public function __construct($value)
    {
        $this->value = $value;
    }
}

This way, enum values are strongly typed and

TestEnum::$TEST1 === TestEnum::parse('TEST1') // true statement

Para answered 15/1, 2013 at 2:11 Comment(0)
N
2

Some good solutions on here!

Here's my version.

  • It's strongly typed
  • It works with IDE auto-completion
  • Enums are defined by a code and a description, where the code can be an integer, a binary value, a short string, or basically anything else you want. The pattern could easily be extended to support orther properties.
  • It asupports value (==) and reference (===) comparisons and works in switch statements.

I think the main disadvantage is that enum members do have to be separately declared and instantiated, due to the descriptions and PHP's inability to construct objects at static member declaration time. I guess a way round this might be to use reflection with parsed doc comments instead.

The abstract enum looks like this:

<?php

abstract class AbstractEnum
{
    /** @var array cache of all enum instances by class name and integer value */
    private static $allEnumMembers = array();

    /** @var mixed */
    private $code;

    /** @var string */
    private $description;

    /**
     * Return an enum instance of the concrete type on which this static method is called, assuming an instance
     * exists for the passed in value.  Otherwise an exception is thrown.
     *
     * @param $code
     * @return AbstractEnum
     * @throws Exception
     */
    public static function getByCode($code)
    {
        $concreteMembers = &self::getConcreteMembers();

        if (array_key_exists($code, $concreteMembers)) {
            return $concreteMembers[$code];
        }

        throw new Exception("Value '$code' does not exist for enum '".get_called_class()."'");
    }

    public static function getAllMembers()
    {
        return self::getConcreteMembers();
    }

    /**
     * Create, cache and return an instance of the concrete enum type for the supplied primitive value.
     *
     * @param mixed $code code to uniquely identify this enum
     * @param string $description
     * @throws Exception
     * @return AbstractEnum
     */
    protected static function enum($code, $description)
    {
        $concreteMembers = &self::getConcreteMembers();

        if (array_key_exists($code, $concreteMembers)) {
            throw new Exception("Value '$code' has already been added to enum '".get_called_class()."'");
        }

        $concreteMembers[$code] = $concreteEnumInstance = new static($code, $description);

        return $concreteEnumInstance;
    }

    /**
     * @return AbstractEnum[]
     */
    private static function &getConcreteMembers() {
        $thisClassName = get_called_class();

        if (!array_key_exists($thisClassName, self::$allEnumMembers)) {
            $concreteMembers = array();
            self::$allEnumMembers[$thisClassName] = $concreteMembers;
        }

        return self::$allEnumMembers[$thisClassName];
    }

    private function __construct($code, $description)
    {
        $this->code = $code;
        $this->description = $description;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function getDescription()
    {
        return $this->description;
    }
}

Here's an example concrete enum:

<?php

require('AbstractEnum.php');

class EMyEnum extends AbstractEnum
{
    /** @var EMyEnum */
    public static $MY_FIRST_VALUE;
    /** @var EMyEnum */
    public static $MY_SECOND_VALUE;
    /** @var EMyEnum */
    public static $MY_THIRD_VALUE;

    public static function _init()
    {
        self::$MY_FIRST_VALUE = self::enum(1, 'My first value');
        self::$MY_SECOND_VALUE = self::enum(2, 'My second value');
        self::$MY_THIRD_VALUE = self::enum(3, 'My third value');
    }
}

EMyEnum::_init();

Which can be used like this:

<?php

require('EMyEnum.php');

echo EMyEnum::$MY_FIRST_VALUE->getCode().' : '.EMyEnum::$MY_FIRST_VALUE->getDescription().PHP_EOL.PHP_EOL;

var_dump(EMyEnum::getAllMembers());

echo PHP_EOL.EMyEnum::getByCode(2)->getDescription().PHP_EOL;

And produces this output:

1 : My first value

array(3) {  
  [1]=>  
  object(EMyEnum)#1 (2) {  
    ["code":"AbstractEnum":private]=>  
    int(1)  
    ["description":"AbstractEnum":private]=>  
    string(14) "My first value"  
  }  
  [2]=>  
  object(EMyEnum)#2 (2) {  
    ["code":"AbstractEnum":private]=>  
    int(2)  
    ["description":"AbstractEnum":private]=>  
    string(15) "My second value"  
  }  
  [3]=>  
  object(EMyEnum)#3 (2) {  
    ["code":"AbstractEnum":private]=>  
    int(3)  
    ["description":"AbstractEnum":private]=>  
    string(14) "My third value"  
  }  
}

My second value

Nena answered 21/2, 2013 at 19:26 Comment(0)
C
2

One of the aspects missing from some of the other answers here is a way to use enums with type hinting.

If you define your enum as a set of constants in an abstract class, e.g.

abstract class ShirtSize {
    public const SMALL = 1;
    public const MEDIUM = 2;
    public const LARGE = 3;
}

then you can't type hint it in a function parameter - for one, because it's not instantiable, but also because the type of ShirtSize::SMALL is int, not ShirtSize.

That's why native enums in PHP would be so much better than anything we can come up with. However, we can approximate an enum by keeping a private property which represents the value of the enum, and then restricting the initialization of this property to our predefined constants. To prevent the enum from being instantiated arbitrarily (without the overhead of type-checking a whitelist), we make the constructor private.

class ShirtSize {
    private $size;
    private function __construct ($size) {
        $this->size = $size;
    }
    public function equals (ShirtSize $s) {
        return $this->size === $s->size;
    }
    public static function SMALL () { return new self(1); }
    public static function MEDIUM () { return new self(2); }
    public static function LARGE () { return new self(3); }
}

Then we can use ShirtSize like this:

function sizeIsAvailable ($productId, ShirtSize $size) {
    // business magic
}
if(sizeIsAvailable($_GET["id"], ShirtSize::LARGE())) {
    echo "Available";
} else {
    echo "Out of stock.";
}
$s2 = ShirtSize::SMALL();
$s3 = ShirtSize::MEDIUM();
echo $s2->equals($s3) ? "SMALL == MEDIUM" : "SMALL != MEDIUM";

This way, the biggest difference from the user's perspective is that you have to tack on a () on the constant's name.

One downside though is that === (which compares object equality) will return false when == returns true. For that reason, it's best to provide an equals method, so that users don't have to remember to use == and not === to compare two enum values.

EDIT: A couple of the existing answers are very similar, particularly: https://mcmap.net/q/45389/-enumerations-on-php.

Chane answered 31/3, 2015 at 0:28 Comment(0)
N
2

Stepping on the answer of @Brian Cline I thought I might give my 5 cents

<?php 
/**
 * A class that simulates Enums behaviour
 * <code>
 * class Season extends Enum{
 *    const Spring  = 0;
 *    const Summer = 1;
 *    const Autumn = 2;
 *    const Winter = 3;
 * }
 * 
 * $currentSeason = new Season(Season::Spring);
 * $nextYearSeason = new Season(Season::Spring);
 * $winter = new Season(Season::Winter);
 * $whatever = new Season(-1);               // Throws InvalidArgumentException
 * echo $currentSeason.is(Season::Spring);   // True
 * echo $currentSeason.getName();            // 'Spring'
 * echo $currentSeason.is($nextYearSeason);  // True
 * echo $currentSeason.is(Season::Winter);   // False
 * echo $currentSeason.is(Season::Spring);   // True
 * echo $currentSeason.is($winter);          // False
 * </code>
 * 
 * Class Enum
 * 
 * PHP Version 5.5
 */
abstract class Enum
{
    /**
     * Will contain all the constants of every enum that gets created to 
     * avoid expensive ReflectionClass usage
     * @var array
     */
    private static $_constCacheArray = [];
    /**
     * The value that separates this instance from the rest of the same class
     * @var mixed
     */
    private $_value;
    /**
     * The label of the Enum instance. Will take the string name of the 
     * constant provided, used for logging and human readable messages
     * @var string
     */
    private $_name;
    /**
     * Creates an enum instance, while makes sure that the value given to the 
     * enum is a valid one
     * 
     * @param mixed $value The value of the current
     * 
     * @throws \InvalidArgumentException
     */
    public final function __construct($value)
    {
        $constants = self::_getConstants();
        if (count($constants) !== count(array_unique($constants))) {
            throw new \InvalidArgumentException('Enums cannot contain duplicate constant values');
        }
        if ($name = array_search($value, $constants)) {
            $this->_value = $value;
            $this->_name = $name;
        } else {
            throw new \InvalidArgumentException('Invalid enum value provided');
        }
    }
    /**
     * Returns the constant name of the current enum instance
     * 
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }
    /**
     * Returns the value of the current enum instance
     * 
     * @return mixed
     */
    public function getValue()
    {
        return $this->_value;
    }
    /**
     * Checks whether this enum instance matches with the provided one.
     * This function should be used to compare Enums at all times instead
     * of an identity comparison 
     * <code>
     * // Assuming EnumObject and EnumObject2 both extend the Enum class
     * // and constants with such values are defined
     * $var  = new EnumObject('test'); 
     * $var2 = new EnumObject('test');
     * $var3 = new EnumObject2('test');
     * $var4 = new EnumObject2('test2');
     * echo $var->is($var2);  // true
     * echo $var->is('test'); // true
     * echo $var->is($var3);  // false
     * echo $var3->is($var4); // false
     * </code>
     * 
     * @param mixed|Enum $enum The value we are comparing this enum object against
     *                         If the value is instance of the Enum class makes
     *                         sure they are instances of the same class as well, 
     *                         otherwise just ensures they have the same value
     * 
     * @return bool
     */
    public final function is($enum)
    {
        // If we are comparing enums, just make
        // sure they have the same toString value
        if (is_subclass_of($enum, __CLASS__)) {
            return get_class($this) === get_class($enum) 
                    && $this->getValue() === $enum->getValue();
        } else {
            // Otherwise assume $enum is the value we are comparing against
            // and do an exact comparison
            return $this->getValue() === $enum;   
        }
    }

    /**
     * Returns the constants that are set for the current Enum instance
     * 
     * @return array
     */
    private static function _getConstants()
    {
        if (self::$_constCacheArray == null) {
            self::$_constCacheArray = [];
        }
        $calledClass = get_called_class();
        if (!array_key_exists($calledClass, self::$_constCacheArray)) {
            $reflect = new \ReflectionClass($calledClass);
            self::$_constCacheArray[$calledClass] = $reflect->getConstants();
        }
        return self::$_constCacheArray[$calledClass];
    }
}
Nickelsen answered 9/10, 2015 at 14:55 Comment(2)
for some reason I cannot call this funtions. Its telling me that such functions is not declared. What I'm doing wrong? [basic Enum class located in another file and I'm using include('enums.php'); ]. For some reason it does not see functions declared in Enum for child classes...Beamy
Also... how to set it from string? sth like $currentSeason.set("Spring");Beamy
D
2

Based on this gist, a base class for all enums:

abstract class Enum {
    protected $val;

    protected function __construct($arg) {
        $this->val = $arg;
    }

    public function __toString() {
        return $this->val;
    }

    public function __set($arg1, $arg2) {
        throw new Exception("enum does not have property");
    }

    public function __get($arg1) {
        throw new Exception("enum does not have property");
    }

    // not really needed
    public function __call($arg1, $arg2) {
        throw new Exception("enum does not have method");
    }

    // not really needed
    static public function __callStatic($arg1, $arg2) {
        throw new Exception("enum does not have static method");
    }
}

Your enum:

final class MyEnum extends Enum {
    static public function val1() {
        return new self("val1");
    }

    static public function val2() {
        return new self("val2");
    }

    static public function val3() {
        return new self("val3");
    }
}

Test it:

$a = MyEnum::val1();
echo "1.the enum value is '$a'\n";

function consumeMyEnum(MyEnum $arg) {
    return "2.the return value is '$arg'\n";
}

echo consumeMyEnum($a);
$version = explode(".", PHP_VERSION);
if ($version[0] >= 7) {
    try {
        echo consumeMyEnum("val1");
    } catch (TypeError $e) {
        echo "3.passing argument error happens (PHP 7.0 and above)\n";
    }
}

echo ($a == MyEnum::val1()) ? "4.same\n" : "4.different\n";
echo ($a == MyEnum::val2()) ? "5.same\n" : "5.different\n";

$b = MyEnum::val1();
echo ($a == $b)  ? "6.same\n" : "6.different\n";
echo ($a === $b) ? "7.same\n" : "7.different\n";

$c = MyEnum::val2();
echo ($a == $c)  ? "8.same\n" : "8.different\n";
echo ($a === $c) ? "9.same\n" : "9.different\n";

switch ($c) {
    case MyEnum::val1(): echo "10.case of 1st\n"; break;
    case MyEnum::val2(): echo "11.case of 2nd\n"; break;
    case MyEnum::val3(): echo "12.case of 3rd\n"; break;
}

try {
    $a->prop = 10;
} catch (Exception $e) {
    echo "13.set property error happens\n";
}

try {
    echo $a->prop;
} catch (Exception $e) {
    echo "14.get property error happens\n";
}

try {
    echo $a->meth();
} catch (Exception $e) {
    echo "15.method call error happens\n";
}

try {
    echo MyEnum::meth();
} catch (Exception $e) {
    echo "16.static method call error happens\n";
}

class Ordinary {}
echo $a instanceof MyEnum   ? "17.MyEnum instance\n"   : "17.not MyEnum instance\n";
echo $a instanceof Enum     ? "18.Enum instance\n"     : "18.not Enum instance\n";
echo $a instanceof Ordinary ? "19.Ordinary instance\n" : "19.not Ordinary instance\n";

Try it online: sandbox

Druce answered 5/1, 2020 at 4:59 Comment(0)
I
1

Yesterday I wrote this class on my blog. I think it's maybe be easy for use in php scripts:

final class EnumException extends Exception{}

abstract class Enum
{
    /**
     * @var array ReflectionClass
     */
    protected static $reflectorInstances = array();
    /**
     * Массив конфигурированного объекта-константы enum
     * @var array
     */
    protected static $enumInstances = array();
    /**
     * Массив соответствий значение->ключ используется для проверки - 
     * если ли константа с таким значением
     * @var array
     */
    protected static $foundNameValueLink = array();

    protected $constName;
    protected $constValue;

    /**
     * Реализует паттерн "Одиночка"
     * Возвращает объект константы, но но как объект его использовать не стоит, 
     * т.к. для него реализован "волшебный метод" __toString()
     * Это должно использоваться только для типизачии его как параметра
     * @paradm Node
     */
    final public static function get($value)
    {
        // Это остается здесь для увеличения производительности (по замерам ~10%)
        $name = self::getName($value);
        if ($name === false)
            throw new EnumException("Неизвестая константа");
        $className = get_called_class();    
        if (!isset(self::$enumInstances[$className][$name]))
        {
            $value = constant($className.'::'.$name);
            self::$enumInstances[$className][$name] = new $className($name, $value);
        }

        return self::$enumInstances[$className][$name];
    }

    /**
     * Возвращает массив констант пар ключ-значение всего перечисления
     * @return array 
     */
    final public static function toArray()
    {
        $classConstantsArray = self::getReflectorInstance()->getConstants();
        foreach ($classConstantsArray as $k => $v)
            $classConstantsArray[$k] = (string)$v;
        return $classConstantsArray;
    }

    /**
     * Для последующего использования в toArray для получения массива констант ключ->значение 
     * @return ReflectionClass
     */
    final private static function getReflectorInstance()
    {
        $className = get_called_class();
        if (!isset(self::$reflectorInstances[$className]))
        {
            self::$reflectorInstances[$className] = new ReflectionClass($className);
        }
        return self::$reflectorInstances[$className];
    }

    /**
     * Получает имя константы по её значению
     * @param string $value
     */
    final public static function getName($value)
    {
        $className = (string)get_called_class();

        $value = (string)$value;
        if (!isset(self::$foundNameValueLink[$className][$value]))
        {
            $constantName = array_search($value, self::toArray(), true);
            self::$foundNameValueLink[$className][$value] = $constantName;
        }
        return self::$foundNameValueLink[$className][$value];
    }

    /**
     * Используется ли такое имя константы в перечислении
     * @param string $name
     */
    final public static function isExistName($name)
    {
        $constArray = self::toArray();
        return isset($constArray[$name]);
    }

    /**
     * Используется ли такое значение константы в перечислении
     * @param string $value
     */
    final public static function isExistValue($value)
    {
        return self::getName($value) === false ? false : true;
    }   


    final private function __clone(){}

    final private function __construct($name, $value)
    {
        $this->constName = $name;
        $this->constValue = $value;
    }

    final public function __toString()
    {
        return (string)$this->constValue;
    }
}

Usage:

class enumWorkType extends Enum
{
        const FULL = 0;
        const SHORT = 1;
}
Incoordination answered 19/2, 2011 at 15:57 Comment(6)
But it's good class and functions name's is native. And also translate.google.ru maybe help.Incoordination
Use chrome guys and translate it, if you're programmers, you read code!Gothic
In general, it's always better to include the code within the answer, rather than linking to an external resource that may or may not be there in 'n' months/years, etc.Anorthic
My class so big and I think that reading this post will be inconvenient.Incoordination
I think two bad things here: it's in Russian (every programmer must know english and use it, even in comments) & it's not included here. See help how to include huge code.Wellington
@Arturgspb: Code fits perfectly on site, let us know if something is missing.Maldon
F
1

My attempt to create an enum with PHP...it's extremely limited since it doesn't support objects as the enum values but still somewhat useful...

class ProtocolsEnum {

    const HTTP = '1';
    const HTTPS = '2';
    const FTP = '3';

    /**
     * Retrieve an enum value
     * @param string $name
     * @return string
     */
    public static function getValueByName($name) {
        return constant('self::'. $name);
    } 

    /**
     * Retrieve an enum key name
     * @param string $code
     * @return string
     */
    public static function getNameByValue($code) {
        foreach(get_class_constants() as $key => $val) {
            if($val == $code) {
                return $key;
            }
        }
    }

    /**
     * Retrieve associate array of all constants (used for creating droplist options)
     * @return multitype:
     */
    public static function toArray() {      
        return array_flip(self::get_class_constants());
    }

    private static function get_class_constants()
    {
        $reflect = new ReflectionClass(__CLASS__);
        return $reflect->getConstants();
    }
}
Fischer answered 27/8, 2012 at 23:3 Comment(1)
it's limited in many directions and the existing answers offer far more over it. I'd say this is not really adding anything useful.Maldon
H
0
// My Enumeration Class
class Enum
{
    protected $m_actions = array();

    public function __construct($actions)
    {
        $this->init($actions);
    }

    public function init($actions)
    {
        $this->m_actions = array();
        for($i = 0; $i < count($actions); ++$i)
        {
            $this->m_actions[$actions[$i]] = ($i + 1); 
            define($actions[$i], ($i + 1));
        }
    }

    public function toString($index)
    {
        $keys = array_keys($this->m_actions);
        for($i = 0; $i < count($keys); ++$i)
        {
            if($this->m_actions[$keys[$i]] == $index)
            {
                return $keys[$i];
            }
        }

        return "undefined";
    }

    public function fromString($str)
    {
        return $this->m_actions[$str];
    }
}

// Enumeration creation
$actions = new Enum(array("CREATE", "READ", "UPDATE", "DELETE"));

// Examples
print($action_objects->toString(DELETE));
print($action_objects->fromString("DELETE"));

if($action_objects->fromString($_POST["myAction"]) == CREATE)
{
    print("CREATE");
}
Heger answered 14/1, 2015 at 10:46 Comment(0)
M
0

A simpler and lighter version that doesn't use reflection:

abstract class enum {
    private function __construct() {}
    static function has($const) {
        $name = get_called_class();
        return defined("$name::$const");
    }
    static function value($const) {
        $name = get_called_class();
        return defined("$name::$const")? constant("$name::$const") : false;
    }
}

Usage:

class requestFormat  extends enum { const HTML = 1; const JSON = 2; const XML  = 3; const FORM = 4; }

echo requestFormat::value('JSON'); // 2
echo requestFormat::has('JSON');   // true

This gives the advantage of constants and also allows for checking the validity of them but it lacks other fancy functionality provided by more complex solutions given is this question, the more obvious the inability of checking the reverse of a value (in the example above, you can't check if '2' is a valid value)

Monition answered 6/5, 2017 at 21:27 Comment(0)
B
0

If you want type safety and a bunch of constants that match that type, one way to go is to have an abstract class for your enum and then extend that class with a locked constructor, like so:

abstract class DaysOfWeekEnum{
    public function __construct(string $value){
        $this->value = $value; 
    }
    public function __toString(){
        return $this->value;
    }

}
class Monday extends DaysOfWeekEnum{
    public function __construct(){
        parent::__construct("Monday");
    }
}

class Tuesday extends DaysOfWeekEnum{
    public function __construct(){
        parent::__construct("Tuesday");
    }
}

Then you can have your methods take an instance of DaysOfWeek and pass it an instance of Monday, Tuesday, etc... The only downside is having to 'new-up' an instance every time you want to use your enum, but I find it worth it.

function printWeekDay(DaysOfWeek $day){
    echo "Today is $day.";
}

printWeekDay(new Monday());
Butene answered 23/1, 2018 at 12:57 Comment(0)
G
0

Another approach would be to use the magic method __set and make the enum private.

e.g.

class Human{
    private $gender;

    public function __set($key, $value){
        if($key == 'day' && !in_array($value, array('Man', 'Woman')){
            new Exception('Wrong value for '.__CLASS__.'->'.$key);
        }
        else{
            $this->$key = $value;
        }
        ...
    }
}

This magic method is called whenever code outside the class itself try to set up the class property. This works from PHP5 - 8.

Goldman answered 6/8, 2021 at 14:50 Comment(0)
F
0

I just created a library that I hope does the job. It can be used stand-alone on any PHP project and has some Laravel goodies to make life easier. I use them on production projects.

https://github.com/Kwaadpepper/enum

Don't hesitate to provide feedbacks if you like it or not. It can be printed, and serialized to JSON. Its definition is pretty simple.

Usage is pretty simple:

$enum = BasicEnum::someValue();
echo $enum->equals(BasicEnum::someValue()) ? 'true' : 'false'; // true
echo $enum->value; // 'someValue' or the value you have defined
echo $enum->label; // 'someValue' or the label you have defined
echo $enum; // 'someValue' or the value you have defined
echo json_encode($enum); // {"label": "someValue", "value: "someValue" }

An enum definition is pretty simple (values and labels methods are optional)

/**
 * @method static self post()
 * @method static self about()
 * @method static self contact()
 */
class PostType extends BaseEnum
{
    protected static function values(): array
    {
        return [
            'post' => 0,
            'about' => 1,
            'contact' => 2
        ];
    }

    protected static function labels(): array
    {
        return [
            'post' => 'Regular posts',
            'about' => 'The about page',
            'contact' => 'The contact page'
        ];
    }
}
Farmergeneral answered 12/9, 2021 at 22:12 Comment(2)
I suggest renaming the library to something less ambiguous than "Enum".Bridgeport
I am all opened to suggestions. I could have named it enumerations, but I'm not so inspired right now.Farmergeneral
T
0

PHP8-like Enum implementation for PHP7.4

Base class:

<?php
declare(strict_types=1);

namespace App\Enum;

abstract class Enum {

    protected const CASES = [];

    public readonly string $value;

    private function __construct(string $value) {
        $this->value = $value;
    }

    /**
     * @return static[]
     */
    public static function from(string $value): Enum {
        $Instance = static::tryFrom($value);
        if (null === $Instance) {
            throw new \ValueError(sprintf('"%s" is not a valid backing value for enum "%s', $value, static::class));
        }

        return $Instance;
    }

    /**
     * @return static[]
     */
    public static function tryFrom(string $value): ?Enum {
        if (!in_array($value, static::CASES, true)) {
            return null;
        }

        static $instances = [];

        $key = static::class . '_' . $value;
        $Instance = &$instances[$key];
        if (null === $Instance) {
            $Instance = new static($value);
        }

        return $Instance;
    }

    /**
     * @return static[]
     */
    public static function cases(): array {
        $result = [];
        foreach (static::CASES as $value) {
            $result[$value] = static::from($value);
        }

        return $result;
    }
}

Child class:

<?php
declare(strict_types=1);

namespace App\Enum;

class Level extends Enum {

    public const Easy = 'easy';
    public const Medium = 'medium';
    public const Hard = 'hard';

    protected const CASES = [
        self::Easy,
        self::Medium,
        self::Hard,
    ];

    public function getTitle(): string {
        return match ($this->value) {
            static::Easy => __('Easy'),
            static::Medium => __('Medium'),
            static::Hard => __('Hard'),
        };
    }

    public static function values(): array {
        return array_column(static::cases(), 'value');
    }

    public static function getOptions(): array {
        $result = [];
        foreach (static::cases() as $Case) {
            $result[$Case->value] = $Case->getTitle();
        }

        return $result;
    }
}

Usage:

$RequestedLevel = Level::tryFrom($_GET['level']);
$Level = Level::from(Level::Medium);
if ($RequestedLevel === $Level) {
    echo $Level->getTitle();
}
Taitaichung answered 7/11, 2023 at 19:33 Comment(0)
M
-3

I use a construction like the following for simple enums. Typically you can use them for switch statements.

<?php 
  define("OPTION_1", "1");
  define("OPTION_2", OPTION_1 + 1);
  define("OPTION_3", OPTION_2 + 1);

  // Some function...
   switch($Val){
    case OPTION_1:{ Perform_1();}break;
    case OPTION_2:{ Perform_2();}break;
    ...
  }
?>

It is not as conviniet as a native enum like in C++ but it seems to work and requires less maintenance if you later would like to add an option in between.

Melodee answered 3/5, 2012 at 8:49 Comment(1)
You are missing the point entirely. Java Enumerations are part of their OOP. The question was whether there is an alternativ in PHP besides constants, and your solution does neither use OOP, nor avoid constants.Tracietracing

© 2022 - 2024 — McMap. All rights reserved.