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

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

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

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

31.10.2008 18:51:14
pbean 5.06.2009 11:25:13
Я создал обходную функцию, которая перечисляет константы как побитовые или нет. Не заметил, что вы спрашивали об этом раньше, но у меня есть лучшее решение, чем переменные класса здесь: stackoverflow.com/questions/3836385/…
NoodleOfDeath 6.04.2013 21:41:26
Matthieu Napoli 5.07.2014 15:07:30
Не могли бы вы рассказать немного больше о проблеме констант? «Константы добиваются цели, но есть проблема столкновения пространства имен и (или на самом деле, потому что) они глобальные».
XuDing 28.10.2017 05:23:56
Evgeniy Tkachenko 17.07.2018 17:59:31
30 ОТВЕТОВ
РЕШЕНИЕ

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

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

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

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

1485
23.05.2017 12:10:44
Я тоже этим пользуюсь. Вы также можете рассмотреть возможность создания класса abstractи final, таким образом, его нельзя создать или расширить.
ryeguy 7.03.2011 18:17:46
Вы можете сделать класс abstractи final? Я знаю, на Java это не разрешено. Вы можете сделать это в php?
corsiKa 13.04.2011 21:46:45
@ryeguy Кажется, вы не можете сделать это одновременно abstract и final. В этом случае я бы пошел на абстракцию.
Nicole 22.06.2011 16:15:39
Об аннотации или окончательной Я делаю их окончательными и даю им пустой приватный конструктор
rael_kid 20.03.2012 11:46:16
Будьте осторожны с использованием 0, чтобы не столкнуться с какими-либо непредвиденными ошибочными сравнениями, например, эквивалентностью с nullи друзьями в switchутверждении. Был там.
yitznewton 28.01.2013 20:43:00

Наиболее распространенным решением, которое я видел для enum в PHP, было создание универсального класса enum, а затем его расширение. Вы могли бы взглянуть на это .

ОБНОВЛЕНИЕ: Кроме того, я нашел это на phpclasses.org.

5
31.10.2008 18:55:51
Несмотря на то, что реализация проста и, вероятно, сработает, недостатком этого является то, что IDE, вероятно, не знают, как автоматически заполнять перечисления. Я не мог проверить один из phpclasses.org, потому что он хотел, чтобы я зарегистрировался.
Henrik Paul 31.10.2008 19:31:49

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

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

print Enum::NAME;
27
28.04.2011 15:26:50

Как насчет классовых констант?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

$c = new YourClass;
$c->echoConstant();
46
31.10.2008 18:57:07
Я предпочитаю этот простой подход
David Lemon 26.10.2018 11:21:07

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

<?php

/**
 * Class Enum
 * 
 * @author Christopher Fox <christopher.fox@gmx.de>
 *
 * @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";
    }

}


?>
7
21.08.2010 22:53:39
Мне это очень нравится. Тем не менее, одной из основных претензий является возможность IDE подобрать значения для автозаполнения. Я не уверен, что это могло бы сделать это без специального дополнения для IDE. Не то чтобы это не могло быть сделано, просто потребовалось бы немного работы.
corsiKa 13.04.2011 21:49:57
Используя eval()только для того, чтобы вы могли объявить новое время выполнения Enums? Ик. Я этого не чувствую. Как вы препятствуете другим классам создавать неправильный класс Enum, прежде чем вы сможете определить правильный? Разве Enums не известны до выполнения? И, как подразумевает @corsiKa, автозаполнение IDE отсутствует. Единственное преимущество, которое я вижу, - это ленивое кодирование.
KrekkieD 15.07.2014 21:10:48

Вот библиотека github для обработки перечислений, безопасных для типов в php:

Эта библиотека обрабатывает генерацию классов, кэширование классов и реализует шаблон проектирования Type Safe Enumeration с несколькими вспомогательными методами для работы с перечислениями, такими как получение порядкового номера для сортировки перечислений или извлечение двоичного значения для комбинаций перечислений.

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

Это полный тест, покрытый phpunit.

php-enums на github (не стесняйтесь форк)

Использование: (@see use.php или юнит-тесты для более подробной информации)

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

Вывод:

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
5
21.10.2010 08:36:53

Ну, для простого 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, чтобы получить имя константы из ее значения, может быть лучше.

23
28.04.2018 09:04:28
Хороший ответ, большинство других ответов используют классы. Вы не можете иметь вложенные классы, хотя.
Keyo 26.09.2011 23:35:13
Преимущество заключается в возможности перебирать значения с помощью foreach. И ущерб от того, что нелегальное значение не пойман.
Bob Stein 16.02.2013 22:03:50
Нет автоматического завершения в IDE, поэтому будет стимулировать работу догадок. Константы включат автозаполнение, звучит лучше.
KrekkieD 15.07.2014 21:15:59

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

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

Использование:

class enumWorkType extends Enum
{
        const FULL = 0;
        const SHORT = 1;
}
1
4.10.2012 13:25:43
Но это хороший класс и имя функции родное. А также translate.google.ru может помочь.
Arturgspb 22.02.2011 13:24:38
Используйте Chrome и переведите его, если вы программисты, вы читаете код!
markus 7.03.2011 18:12:38
В общем, всегда лучше включать код в ответ, а не ссылаться на внешний ресурс, который может быть или не быть в 'n' месяцах / годах и т. Д.
John Parker 7.03.2011 18:15:57
Мой класс такой большой, и я думаю, что читать этот пост будет неудобно.
Arturgspb 8.03.2011 07:49:51
Я думаю, что две плохие вещи здесь: это на русском языке (каждый программист должен знать английский и использовать его, даже в комментариях), и это не включено здесь. Смотрите помощь, как включить огромный код.
gaRex 15.03.2012 15:12:06

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

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

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

Внимание:

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

Расширение PECL не связано с PHP.

DLL для этого расширения PECL в настоящее время недоступна.

185
11.09.2019 08:37:58
Вот пример с splenum: dreamincode.net/forums/topic/201638-enum-in-php
Nordes 6.04.2011 12:00:44
Откатился, мне больше нравится, когда вижу ссылку. Это дает мне контекстную информацию.
markus 26.07.2011 21:58:44
Я снова откатился. Я не хочу, чтобы вы, ребята, отредактировали ссылку.
markus 27.02.2014 20:58:23
Будьте осторожны, используя это. Типы SPL являются экспериментальными: «Это расширение является ЭКСПЕРИМЕНТАЛЬНЫМ. Поведение этого расширения, включая имена его функций и любую другую документацию, касающуюся этого расширения, может измениться без предварительного уведомления в будущем выпуске PHP. Это расширение следует использовать на свой страх и риск. "
bzeaman 17.06.2016 13:05:22
SplEnum не поставляется с PHP, ему нужно расширение
Kwadz 21.12.2016 10:26:01

Мне также нравятся перечисления из 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
7
13.04.2011 09:55:26
Я делаю почти то же самое с двумя небольшими дополнениями: я скрыл статические значения за статическими геттерами. Одна из причин в том, что я предпочитаю визуально FruitsEnum::Apple()более FruitsEnum::$Apple, но более важной причиной является то, чтобы не допустить никого из установки $APPLE, тем самым нарушая перечисление для всего приложения. Другой - это простой закрытый статический флаг, $initializedкоторый гарантирует, что вызов init()становится недоступным после первого вызова (так что никто не может возиться с этим).
Martin Ender 26.09.2013 16:48:42
Мне нравился Мартин. .init()странно, и я не возражаю против подхода получателя.
Sebas 25.12.2015 20:30:40

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

посмотрите обновленную версию ниже этого кодового блока ...

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

ОБНОВЛЕНИЕ: лучший способ сделать это ...

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;

}

Позвонить с

EnumCategory::${$category};
1
5.03.2012 09:28:07
Проблема с этим существом; EnumCategory::$sport = 9;, Добро пожаловать в музей спорта. const это лучший способ сделать это.
Dan Lugg 4.04.2013 10:03:48

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

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

var $today = DaysOfWeek::Sunday;
27
24.11.2011 14:51:56
class Foo implements DaysOfWeek { }а потом Foo::Sunday... что?
Dan Lugg 4.04.2013 10:08:32
Автор вопроса просит найти решение для двух вещей: пространства имен и автозаполнения IDE. Как и предполагал ответ с наивысшим рейтингом, самый простой способ - использовать class(или interface, это просто вопрос предпочтений).
Andi T 11.09.2013 15:02:10
интерфейсы используются для обеспечения целостности реализации класса, это выходит за рамки интерфейса
user3886650 22.12.2015 15:17:47
@ user3886650 Интерфейсы могут и были / были использованы в Java для сохранения постоянных значений. Таким образом, вы не обязаны создавать экземпляр класса только для получения постоянных значений, и любая IDE предлагает для них завершение кода. Также, если вы создадите класс, который реализует этот интерфейс, он унаследует все эти константы - иногда очень удобно.
Alex 16.09.2016 08:26:06
@ user3886650 Правда, но в PHP интерфейсы могут иметь константы. Кроме того, эти интерфейсные константы не могут быть переопределены реализацией классов или их дочерних элементов. По сути, это лучший ответ с точки зрения PHP, потому что все, что может быть переопределено, на самом деле не функционирует как константа. Константа должна означать постоянную, а не иногда (даже если полиморфизм может быть полезен время от времени).
Anthony Rutledge 11.09.2018 03:55:25

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

Чтобы создать класс enum с использованием моего решения, можно просто расширить этот класс Enum ниже, создать группу статических переменных (не нужно их инициализировать) и вызвать ваш yourEnumClass :: init () чуть ниже определения вашего класса enum ,

edit: это работает только в php> = 5.3, но, вероятно, может быть изменено для работы в более старых версиях

/**
 * 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;
                }
            }
        }
    }
}
3
28.12.2011 19:10:47

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

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

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

-3
3.05.2012 08:49:52
Вы упускаете суть полностью. Перечисления Java являются частью их ООП. Вопрос заключался в том, есть ли в PHP альтернатива помимо констант, и ваше решение не использует ни ООП, ни избегает констант.
Sven 19.10.2012 22:19:08

Моя попытка создать enum с помощью PHP ... она чрезвычайно ограничена, поскольку она не поддерживает объекты в качестве значений enum, но все же несколько полезна ...

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();
    }
}
1
27.08.2012 23:03:18
он ограничен во многих направлениях, и существующие ответы предлагают гораздо больше. Я бы сказал, что это не добавляет ничего полезного.
hakre 4.10.2012 13:34:05

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

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

Мы могли бы даже пойти дальше: использование констант в классе DaysOfWeek может привести к неправильному использованию: например, можно ошибочно использовать это следующим образом:

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

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

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

}

Конечно, невозможно получить доступ к целочисленным константам (это и было целью). Метод intVal позволяет преобразовать объект DaysOfWeek в его целочисленное представление.

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

Надеюсь, это поможет

2
29.11.2012 08:36:18

Я воспользовался приведенным ниже подходом, поскольку он дает мне возможность обеспечить безопасность типов для параметров функций, автоматическое заполнение в NetBeans и хорошую производительность. Одна вещь, которая мне не очень нравится, это то, что вам нужно позвонить [extended class name]::enumerate();после определения класса.

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");
4
2.12.2012 00:13:05
Ничто не мешает вам переопределить значения перечисления:DaysOfWeek::$MONDAY = 3;
KrekkieD 15.07.2014 20:50:04
@BrianFisher, я знаю, что сейчас поздно, но если вам не нравится звонить [extended class name]::enumerate();после определения, почему вы не делаете это в конструкции?
Can O' Spam 22.06.2015 11:04:58

Указанное решение работает хорошо. Чисто и гладко.

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

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

С классом Enum, похожим на:

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

Таким образом, значения перечисления строго типизируются и

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

1
15.01.2013 02:11:07

Некоторые хорошие решения здесь!

Вот моя версия.

  • Это сильно напечатано
  • Работает с автозаполнением IDE
  • Перечисления определяются кодом и описанием, где код может быть целым числом, двоичным значением, короткой строкой или, в общем, чем угодно. Шаблон может быть легко расширен для поддержки других свойств.
  • Он поддерживает сравнения значений (==) и ссылок (===) и работает в выражениях switch.

Я думаю, что основным недостатком является то, что члены enum должны быть объявлены и созданы отдельно из-за описаний и неспособности PHP создавать объекты во время статического объявления членов. Я думаю, что можно обойти это, используя вместо этого отражение с проанализированными комментариями к документу.

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

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

Вот пример конкретного перечисления:

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

Который можно использовать так:

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

И производит этот вывод:

1: мое первое значение

array (3) {
[1] =>
object (EMyEnum) # 1 (2) {
["code": "AbstractEnum": private] =>
int (1)
["description": "AbstractEnum": private] =>
string (14) «Мое первое значение»
}
[2] =>
object (EMyEnum) # 2 (2) {
["code": "AbstractEnum": private] =>
int (2)
["description": "AbstractEnum" : private] =>
string (15) «Мое второе значение»
}
[3] =>
object (EMyEnum) # 3 (2) {
["code": "AbstractEnum": private] =>
int (3)
["description": "AbstractEnum": private] =>
string (14) "Мое третье значение"
}
}

Мое второе значение

2
21.02.2013 19:50:14
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>";
}
6
19.04.2013 10:47:59

Я прокомментировал некоторые другие ответы здесь, поэтому я решил, что я тоже буду взвешивать. В конце концов, поскольку 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магический метод тоже ), большая часть этого могла бы быть смягчена с помощью своего рода многотонных схем.

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

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
25
17.04.2015 15:26:49
Мне очень нравится простота этого ответа. Это та вещь, к которой вы можете вернуться позже и быстро понять, как она работает, не создавая впечатление, будто вы использовали какой-то взломанный подход. Жаль, что у него нет больше голосов.
Reactgular 22.08.2016 16:21:34

Я нашел эту библиотеку на github, и я думаю, что она предоставляет очень приличную альтернативу ответам здесь.

Реализация PHP Enum вдохновлена ​​SplEnum

  • Вы можете напечатать подсказку: function setAction(Action $action) {
  • Вы можете обогатить перечисление с методами (например format, parse...)
  • Вы можете расширить перечисление, чтобы добавить новые значения (сделайте ваше перечисление, finalчтобы предотвратить это)
  • Вы можете получить список всех возможных значений (см. Ниже)

декларация

<?php
use MyCLabs\Enum\Enum;

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

Применение

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

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

значения перечисления подсказки типа:

<?php
function setAction(Action $action) {
    // ...
}
9
11.08.2013 04:20:12
Это правильный ответ (пока, пока он enumне добавлен в PHP 7.x), потому что он позволяет подсказки типа.
Tobia 29.06.2016 08:31:37
Это позволяет не только указывать тип, но благодаря __toString()магии позволяет делать то, что вы обычно хотите, с помощью перечислений - использовать их в выражении switchили if, сравнивая непосредственно со значениями констант. Лучший подход, за исключением поддержки родного enum, IMO.
LinusR 8.02.2019 00:10:19

Верхний ответ выше фантастический. Однако, если вы делаете 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];
}
35
28.04.2018 09:20:15
Была эта самая проблема. Брайан или кто-то с правами редактирования должен коснуться этого в принятом ответе. Я решил это в своем коде, используя метод «static ::» вместо «self ::» в функции getConstants () и повторно объявив $ constCache в дочерних перечислениях.
Sp3igel 18.08.2014 23:47:56
Это может быть не очень привлекательно, но использование константы интерфейса может быть лучшим способом для PHP.
Anthony Rutledge 11.09.2018 04:03:33

Четыре года спустя я снова столкнулся с этим. Мой текущий подход заключается в следующем: он допускает завершение кода в 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
19
27.08.2014 12:54:49
+1 для частного конструктора. Я бы не стал делать вспомогательный абстрактный класс, просто простой класс, частный конструктор и некоторые из нихconst Monday = DaysOfWeek('Monday');
Kangur 18.05.2018 16:24:42
// 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");
}
0
14.01.2015 10:46:44

Мое определение класса Enum ниже строго типизировано и очень естественно для использования и определения.

Определение:

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

Переключить 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

Передайте Enum в качестве параметра (строго типизированный)

/** 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 как строка

echo "I have an $myFruit\n";

>> I have an APPLE

Получить Enum по целому

$myFruit = Fruit::getByValue(2);

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

>> Now I have an ORANGE

Получить Enum по имени

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

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

>> But I definitely prefer an APPLE

Класс Enum:

/**
 * @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;
    }

}

прибавление

Вы также можете добавить комментарии для IDE.

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();
4
8.02.2019 10:08:46
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 ) );

Не используйте отражение. Это крайне затрудняет анализ вашего кода и отслеживание того, где что-то используется, и имеет тенденцию ломать инструменты статического анализа (например, то, что встроено в вашу IDE).

2
1.03.2015 03:48:22

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

Если вы определяете свое перечисление как набор констант в абстрактном классе, например,

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

тогда вы не можете напечатать подсказку в параметре функции - например, потому что это не экземпляр, но также потому, что тип ShirtSize::SMALLis int, not ShirtSize.

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

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

Тогда мы можем использовать ShirtSizeтак:

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

Таким образом, самым большим отличием с точки зрения пользователя является то, что вам нужно добавить ()имя константы.

Один недостаток - это то, что ===(который сравнивает равенство объектов) вернет false, когда ==вернет true. По этой причине лучше всего предоставить equalsметод, чтобы пользователям не приходилось запоминать использование ==и не ===сравнивать два перечисляемых значения.

РЕДАКТИРОВАТЬ: несколько существующих ответов очень похожи, в частности: https://stackoverflow.com/a/25526473/2407870 .

2
23.05.2017 12:34:41

Я понимаю, что это очень-очень-очень старая тема, но я подумал об этом и хотел знать, что думают люди.

Примечания: я поиграл с этим и понял, что если я просто изменю __call()функцию, вы сможете стать еще ближе к реальной enums. __call()Функция обрабатывает все неизвестные вызовы функций. enumsДопустим, вы хотите сделать три RED_LIGHT, YELLOW_LIGHT и GREEN_LIGHT. Вы можете сделать это сейчас, просто выполнив следующее:

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

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

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

и вы должны получить 0, 1 и 2. Удачи! Это также сейчас на GitHub.

Обновление: я сделал это так, что теперь используются __get()и __set()функции и. Это позволяет вам не вызывать функцию, если вы этого не хотите. Вместо этого, теперь вы можете просто сказать:

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

Для создания и получения ценностей. Поскольку переменные не были определены изначально, __get()вызывается функция (потому что не указано значение), которая видит, что запись в массиве не была сделана. Таким образом, он делает запись, присваивает ей последнее заданное значение плюс один (+1), увеличивает переменную последнего значения и возвращает TRUE. Если вы установите значение:

$c->RED_LIGHT = 85;

Затем __set()вызывается функция, и последнему значению присваивается новое значение плюс один (+1). Так что теперь у нас есть довольно хороший способ делать перечисления, и их можно создавать на лету.

<?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
#           sim_sales@paypal.com.  :-)
#
#   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";

?>
4
15.04.2019 17:57:29

Это может быть так же просто, как

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

в будущем.

PHP RFC: перечисляемые типы

6
28.05.2015 13:26:25
К вашему сведению, по состоянию на 7.1 все еще нет
toddmo 22.04.2018 20:23:42