PHP и перечисления - PullRequest
       131

PHP и перечисления

1078 голосов
/ 31 октября 2008

Я знаю, что в PHP нет нативных перечислений. Но я привык к ним из мира Java. Я хотел бы использовать перечисления как способ дать предопределенные значения, которые могли бы понимать функции автозаполнения IDE.

Константы делают свое дело, но есть проблема столкновения пространства имен и (или на самом деле , потому что ) они глобальные. Массивы не имеют проблем с пространством имен, но они слишком расплывчаты, их можно перезаписать во время выполнения, и IDE редко (никогда?) Знают, как автоматически заполнять свои ключи.

Какие решения / обходные пути вы обычно используете? Кто-нибудь помнит, были ли у ребят из PHP какие-то мысли или решения по поводу перечислений?

Ответы [ 37 ]

1417 голосов
/ 31 октября 2008

В зависимости от варианта использования, я обычно использовал бы что-то простое , например, следующее:

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

$today = DaysOfWeek::Sunday;

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

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);
    }
}

Создав простой перечислимый класс, расширяющий BasicEnum, вы теперь можете использовать методы для простой проверки ввода:

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

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

Теперь, когда у большинства людей наконец обновлен до версии не ниже 5.3, а SplEnum доступно, это, безусловно, приемлемый вариант - если вы не возражаете против традиционно неинтуитивного понятия фактического перечисления экземпляров по всей вашей кодовой базе. В приведенном выше примере, BasicEnum и DaysOfWeek не могут быть созданы вообще, и не должны быть.

168 голосов
/ 07 марта 2011

Также есть собственное расширение. SplEnum

SplEnum дает возможность эмулировать и создавать объекты перечисления изначально в PHP.

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

43 голосов
/ 31 октября 2008

А как насчет констант класса?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

$c = new YourClass;
$c->echoConstant();
34 голосов
/ 04 февраля 2014

Верхний ответ выше фантастический. Однако, если вы extend делаете это двумя разными способами, то, какое бы расширение ни было выполнено первым, в результате вызова функций будет создан кэш. Затем этот кеш будет использоваться всеми последующими вызовами, независимо от того, какое расширение вызовет, инициируя вызовы ...

Чтобы решить эту проблему, замените переменную и первую функцию на:

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];
}
27 голосов
/ 24 ноября 2011

Я использую interface вместо class:

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

var $today = DaysOfWeek::Sunday;
26 голосов
/ 31 октября 2008

Я использовал классы с константами:

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

print Enum::NAME;
23 голосов
/ 11 июня 2013

Я прокомментировал некоторые другие ответы здесь, поэтому я решил, что тоже буду взвешивать. В конце концов, поскольку PHP не поддерживает типизированные перечисления, вы можете пойти одним из двух способов: взломать типизированные перечисления или согласиться с тем фактом, что их чрезвычайно трудно эффективно вывести.

Я предпочитаю жить с фактом, и вместо этого использовать метод const, который другие ответы здесь так или иначе использовали:

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());
    }

}

Пример перечисления:

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;

}

Использование Enum в качестве базового класса, из которого распространяются все другие перечисления, позволяет использовать вспомогательные методы, такие как toArray, isValid и т. Д. Для меня типизированные перечисления ( и управление их экземплярами ) просто оказываются слишком запутанными.


Гипотетический

Если , существовал магический метод __getStatic ( и, предпочтительно, __equals магический метод ), большая часть которого могла бы быть смягчена с помощью своего рода многотонных схем. 1026 *

( Следующее является гипотетическим; оно не будет работать, хотя, возможно, однажды оно будет )

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
22 голосов
/ 23 декабря 2010

Ну, для простого java-подобного enum в php я использую:

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];
    }
}

И назвать это:

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

Но я новичок в PHP, борюсь с синтаксисом, так что это может быть не лучшим способом. Я экспериментировал с константами классов, используя Reflection, чтобы получить имя константы из ее значения, может быть лучше.

15 голосов
/ 27 августа 2014

Четыре года спустя я снова столкнулся с этим. Мой текущий подход заключается в следующем: он допускает завершение кода в IDE и безопасность типов:

Базовый класс:

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;
    }
}

Пример 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); }      
}

Пример использования:

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');

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

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

Вы также можете использовать его внутри оператора switch:

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

Вы также можете создать запись enum по имени или значению:

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

Или вы можете просто получить имя (то есть имя функции) из существующей записи enum:

$wednesday = DaysOfWeek::Wednesday()
echo $wednesDay->getName(); // Wednesday
7 голосов
/ 13 апреля 2011

Мне также нравятся перечисления из java, и поэтому я пишу свои перечисления таким образом, я думаю, что это самое похожее поведение, как в перечислениях Java, конечно, если кто-то хочет использовать больше методов из Java, напишите его здесь или в абстрактном классе, но основная идея встроена в код ниже


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

...