PHP та перерахування


1149

Я знаю, що в PHP немає рідних перерахувань. Але я звик до них із світу Java. Я хотів би використовувати enums як спосіб надати заздалегідь задані значення, які могли б зрозуміти функції автоматичного завершення IDE.

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

Чи є якісь рішення / обхідні шляхи, якими ви зазвичай користуєтесь? Хтось пригадує, чи мали хлопці PHP якісь думки чи рішення навколо переліків?



1
Я створив функцію обходу, яка перераховує константи як бітові, так і ні. Не помітив , що ви запитали про це раніше, але у мене є краще рішення , ніж змінні класу тут: stackoverflow.com/questions/3836385 / ...
NoodleOfDeath


Ви не хочете поділитися трохи більше про проблему Константи? "Константи роблять фокус, але існує проблема зіткнення простору імен і (або насправді тому, що вони є глобальними". "
XuDing

Відповіді:


1492

Залежно від випадку використання, я зазвичай використовую щось просте, наприклад:

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

Створюючи простий клас enum, який розширює 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

Як бічна примітка, щоразу, коли я використовую відображення хоча б один раз на статичному / const-класі, коли дані не змінюватимуться (наприклад, в перерахунку), я кешую результати цих дзвінків для відображення, оскільки використовую свіжі об'єкти відображення кожного разу врешті-решт матиме помітний вплив на продуктивність (зберігається в асоціативному масиві для кількох перерахунків).

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


70
Я також цим користуюся. Ви також можете розглянути можливість складання класу abstractі final, тому його не можна створити ініціалізувати чи розширювати
ryeguy

21
Ви можете скласти клас abstractі final? Я знаю, що на Java це заборонено. Ви можете це зробити в php?
corsiKa

20
@ryeguy Здається , що ви не можете зробити це , як abstract і final. У такому випадку я б хотів зробити конспект.
Ніколь

45
Про абстрактне або підсумкове; Я роблю їх остаточними і даю їм порожній приватний конструктор
rael_kid

21
Будьте обережні з використанням 0, щоб ви не стикалися з жодними непередбаченими проблемами порівняння помилкових помилок, наприклад, еквівалентністю nullта друзями у switchзаяві. Був там.
yitznewton

185

Є і рідне розширення. The 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 наразі недоступний.


4
Ось приклад із спленом: dreamincode.net/forums/topic/201638-enum-in-php
Nordes

4
Я відкотився, мені це подобається краще, коли я бачу посилання. Це дає мені інформацію про контекст.
markus

5
Я знову відкотився. Я не хочу, щоб ви, хлопці, редагували посилання.
Маркус

6
Будьте обережні, використовуючи це. Типи SPL експериментальні: "Це розширення ЕКСПЕРИМЕНТАЛЬНЕ. Поведінка цього розширення, включаючи назви його функцій та будь-яку іншу документацію, що стосується цього розширення, може змінитися без попереднього повідомлення у майбутньому випуску PHP. Це розширення слід використовувати на власний ризик. "
бзеаман

6
SplEnum не в комплекті з PHP, йому потрібно розширення
SPL_Types

46

Що щодо констант класу?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

$c = new YourClass;
$c->echoConstant();

Я віддаю перевагу такому простому підходу
Девід Лемон

echoConstantможна замінити на __toString. А потім простоecho $c
Юстінас

35

Верхня відповідь вище фантастична. Однак, якщо ти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];
}

2
Мав це саме питання. Брайан або хтось із правами редагування повинен торкнутися його у прийнятій відповіді. Я вирішив це у своєму коді, використовуючи метод 'static ::' замість 'self ::' у функції getConstants () та повторно оголосивши $ constCache у дочірніх перерахунках.
Sp3igel

Це може бути не сексуально, але використання константи інтерфейсу може бути найкращим способом пройти в PHP.
Ентоні Рутлідж

27

Я використовував класи з константами:

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

print Enum::NAME;

27

Я використовую interfaceзамість class:

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

var $today = DaysOfWeek::Sunday;

6
class Foo implements DaysOfWeek { }а потім Foo::Sunday... що?
Ден Лугг

3
Автор питання просить вирішити дві речі: простір імен та автоматичне заповнення IDE. Як запропонована відповідь з найвищим рейтингом, найпростішим способом є використання class(або interface, що лише питання переваги).
Andi T

4
інтерфейси використовуються для забезпечення цілісності впровадження класу, це виходить за межі інтерфейсу
user3886650

2
@ user3886650 Інтерфейси можуть і використовуються в Java для збереження постійних значень. Таким чином, ви не змушені створювати клас лише для отримання постійних значень, і будь-який IDE пропонує заповнення коду на них. Крім того, якщо ви створили клас, який реалізує цей інтерфейс, він успадкує всі ці константи - іноді досить зручно.
Алекс

@ user3886650 Щоправда, але в PHP інтерфейси можуть мати константи. Крім того, ці константи інтерфейсу не можна змінювати шляхом впровадження класів або їхніх дітей. Насправді, це найкраща відповідь з точки зору PHP, оскільки все, що можна перекрити, насправді не функціонує так, як слід. Констант повинен означати постійне, а не іноді (навіть якщо поліморфізм може бути корисним часом).
Ентоні Ратлідж

25

Я прокоментував деякі інші відповіді тут, тож я зрозумів, що теж зважу. Зрештою, оскільки 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

Мені дуже подобається простота цієї відповіді. Це та річ, до якої можна повернутися пізніше і швидко зрозуміти, як це працює, не змушуючи виглядати, як ти зробив якийсь зламаний підхід. Прикро, що у неї немає більше голосів.
Реакційний

23

Ну а для простого Java, як enum in 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, щоб отримати постійне ім'я від його значення, можливо, буде акуратніше.


Хороша відповідь, більшість інших відповідей використовують класи. Ви ж не можете вкладати заняття.
Кейо

Це має перевагу в тому, що можна повторити ціннісні значення з передбаченням. І шкода, що незаконна цінність не піймається.
Боб Штейн

2
Немає автоматичного заповнення IDE, хоча це стимулюватиме здогадки. Константи дозволяли б автоматичне завершення, звучить краще.
KrekkieD

19

Через чотири роки я знову натрапив на це. Мій сучасний підхід полягає в такому, оскільки він дозволяє заповнити код у 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');

Зауважте, що всі екземпляри одного запису enum однакові:

$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

+1 для приватного конструктора. Я б не робив помічників абстрактним класом, просто простим класом, приватним конструктором та деякими з нихconst Monday = DaysOfWeek('Monday');
Кангур

9

Я знайшов цю бібліотеку на 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();

Значення переліків типу hint:

<?php
function setAction(Action $action) {
    // ...
}

1
Це правильна відповідь (поки що enumне додано в PHP 7.x), оскільки вона дозволяє натякати на тип.
Тобія

1
Це не лише дозволяє натякати на тип, але завдяки __toString()магії дозволяє вам робити те, що ви, як правило, дійсно бажаєте, з перерахунками - використовувати їх у a switchабо ifоператорі, порівнюючи безпосередньо зі значеннями consts. Найкращий підхід, окрім підтримки рідного переліку, IMO.
LinusR

7

Якщо вам потрібно використовувати переліки, які є унікальними у всьому світі (тобто навіть при порівнянні елементів між різними Enums) і прості у використанні, сміливо використовуйте наступний код. Я також додав деякі методи, які вважаю корисними. Ви знайдете приклади в коментарях в самому верху коду.

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

}


?>

1
Мені так подобається, багато. Однак однією з головних скарг є можливість IDE підбирати значення для автоматичного завершення. Я не впевнений, що це вдасться зробити без спеціального додатка для IDE. Не те, щоб цього не можна було зробити, просто знадобилася б якась робота.
corsiKa

2
Використовуючи eval()лише так, щоб ви могли оголосити нову програму Enums? Еек. Я цього не відчуваю. Як ви заважаєте іншим класам створювати неправильний клас Enum, перш ніж визначити належний? Хіба енуми не були відомі до часу виконання? І як випливало з @corsiKa, автоматичного завершення IDE немає. Єдина користь, яку я бачу, - це ліниве кодування.
KrekkieD

7

Мені також подобаються переписки з Java, і тому я так пишу свої переписки, я думаю, що це найбільш подібне поведінка, як у Java enums, звичайно, якщо хтось хоче використовувати більше методів від 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

3
Я роблю майже те ж саме, з двома невеликими доповненнями: я приховав статичні значення за статичними геттерами. Одна з причин в тому, що я вважаю за краще візуально FruitsEnum::Apple()більш FruitsEnum::$Apple, але більш важливою причиною є те, щоб НЕ допустити нікого з установки $APPLE, тим самим порушуючи перерахування для всього програми. Інший - це простий приватний статичний прапор, $initializedякий гарантує, що виклик init()стає неоперативним після його першого виклику (тому ніхто також не може зіпсуватись із цим).
Мартін Ендер

Мені подобалося Мартіна. .init()це дивно, і я не заперечую за підходом до геттера.
Себас

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


5

Найпоширенішим рішенням, яке я бачив до перерахунку в PHP, було створення загального класу enum та його розширення. Ви можете поглянути на це .

ОНОВЛЕННЯ: Альтернативно, я знайшов це з phpclasses.org.


1
Хоча реалізація гладка і, ймовірно, зробить цю роботу, недоліком цього є те, що IDE, ймовірно, не знають, як автоматично заповнити перерахунки. Я не міг перевірити його з phpclasses.org, тому що він хотів мене зареєструвати.
Генрік Пол

5

Ось бібліотека github для обробки безпечних для перерахувань типів у php:

Ця бібліотека обробляє генерацію класів, кешування класів, і вона реалізує схему дизайну Type Safe Enumeration, використовуючи кілька допоміжних методів для боротьби з перерахунками, як, наприклад, отримання порядкового порядку для сортування переліків або отримання бінарного значення для комбінацій перерахунків.

Згенерований код використовує звичайний старий файл шаблону php, який також можна налаштувати, тож ви можете надати власний шаблон.

Це повний тест, покритий фпунітом.

php-enums на github (сміливо роздрібнюйся)

Використання: (@ див. 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

4

Я взяв до використання підхід нижче, оскільки він дає мені можливість забезпечити безпеку типу параметрів функції, автоматичне завершення в 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");

Ніщо не заважає вам перевизначити значення перерахунків:DaysOfWeek::$MONDAY = 3;
KrekkieD

@BrianFisher, я знаю, що це вже пізно пізно, але, якщо вам не подобається дзвонити [extended class name]::enumerate();після визначення, чому ви не робите це в конструкції?
Can O 'Spam

4

Наведене нижче визначення мого класу 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

Я розумію, що це дуже-дуже-дуже стара тема, але я подумав про це і хотів знати, що думають люди.

Примітки: я пограв з цим і зрозумів, що якщо я просто змінив __call()функцію, ви можете ще наблизитися до фактичної enums. __call()Функція обробляє всі невідомі виклики функцій. Скажімо, ви хочете зробити три enumsRED_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), збільшує останню змінну значення та повертає ІСТИНА. Якщо встановити значення:

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

?>

3

Я знаю, що це стара нитка, проте жоден із обхідних способів, які я бачив, насправді не виглядав як перерахунки, оскільки майже всі обхідні шляхи вимагають, щоб ви вручну присвоювали значення елементам перерахунків, або це вимагає, щоб ви передали масив ключів перерахунків до функція. Тому я створив для цього власне рішення.

Щоб створити клас enum за допомогою мого рішення, можна просто розширити цей клас Enum нижче, створити купу статичних змінних (не потрібно їх ініціалізувати) та здійснити виклик до yourEnumClass :: init () трохи нижче визначення вашого класу enum .

редагувати: це працює лише у 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

Тепер ви можете використовувати клас SplEnum, щоб створити його спочатку. Відповідно до офіційної документації.

SplEnum надає можливість емуляції та створення об'єктів перерахування в 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;
}
?>

Зауважте, це розширення, яке має бути встановлено, але воно не доступне за замовчуванням. Що підпадає під спеціальні типи, описані на самому веб-сайті php. Наведений вище приклад взято з сайту PHP.


3

Нарешті, відповідь PHP 7.1+ з константами, які неможливо переоцінити.

/**
 * 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.
}

Якщо ви використовуєте простори імен, завершення коду має працювати.

Однак цим ви втрачаєте можливість приховати константи в сімействі класів ( protected) або самому класі ( private). За визначенням, все в а Interfaceє public.

PHP Керівництво: Інтерфейси


Це не Java. Це працює в тих випадках, коли поліморфізм / стратегія не потребує перекриття констант у батьківському класі.
Ентоні Рутлідж

2

Це мій погляд на "динамічний" перерахунок ... так що я можу його назвати змінними, напр. від форми.

подивіться оновлений verison під цим кодовим блоком ...

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

5
Проблема з цією істотою; EnumCategory::$sport = 9;. Ласкаво просимо в музей спорту. const це кращий спосіб зробити це.
Dan Lugg

2

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

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

Деякі хороші рішення тут!

Ось моя версія.

  • Це сильно набрано
  • Він працює з автоматичним доповненням IDE
  • Перерахунки визначаються кодом та описом, де код може бути цілим числом, двійковим значенням, коротким рядком або взагалі будь-чим іншим. Шаблон можна легко поширити на підтримку інших властивостей.
  • Він підтримує значення (==) і посилання (===) і порівнює і працює в операторах переключення.

Я думаю, що головним недоліком є ​​те, що члени 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: Моє перше значення

масив (3) {
[1] =>
об'єкт (EMyEnum) # 1 (2) {
["код": "AbstractEnum": приватний] =>
int (1)
["опис": "AbstractEnum": приватний] =>
рядок (14) "Моє перше значення"
}
[2] =>
об'єкт (EMyEnum) # 2 (2) {
["код": "AbstractEnum": приватний] =>
int (2)
["опис": "AbstractEnum" : private] =>
string (15) "Моє друге значення"
}
[3] =>
об'єкт (EMyEnum) # 3 (2) {
["code": "AbstractEnum": private] =>
int (3)
["опис": "AbstractEnum": приватний] =>
рядок (14) "Моє третє значення"
}
}

Моє друге значення


2
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

Одним із аспектів, яких не вистачає в деяких інших відповідях тут, є спосіб використання переписок із натяком на тип.

Якщо ви визначаєте свій enum як набір констант в абстрактному класі, наприклад

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

тоді ви не можете ввести підказку в параметр функції - для одного, тому що це не миттєво, але також тому, що тип ShirtSize::SMALLє int, не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";

Таким чином, найбільша різниця з точки зору користувача полягає в тому, що вам потрібно застосувати ()ім'я константи.

Одним із мінусів є те, що ===(що порівнює об’єктну рівність) повернеться помилковим, коли ==повернеться true. З цієї причини найкраще надати equalsметод, щоб користувачі не пам’ятали користуватися ==і не використовувати=== порівнювати два значення перерахунку.

EDIT: Кілька існуючих відповідей дуже схожі, зокрема: https://stackoverflow.com/a/25526473/2407870 .


2

Отримавши відповідь на @Brian Cline, я подумав, що можу дати свої 5 копійок

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

я чомусь не можу назвати цю функцію. Це говорить мені, що такі функції не оголошені. Що я роблю неправильно? [базовий клас Enum, розташований в іншому файлі, і я використовую include('enums.php');]. Чомусь він не бачить функцій, оголошених у Enum для дитячих класів ...
Ендрю

Також ... як встановити його з рядка? що таке$currentSeason.set("Spring");
Андрій

1

Моя спроба створити перерахунок за допомогою 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

Вчора я написав цей клас у своєму блозі . Я думаю, це може бути просто у використанні в 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;
}

2
Але це хороший клас і функції, ім'я - рідне. А також translate.google.ru може допомогти.
Arturgspb

2
Використовуйте хромовані хлопці та перекладіть це, якщо ви програмісти, ви читаєте код!
Маркус

8
Взагалі, завжди краще включити код у відповідь, а не посилатися на зовнішній ресурс, який може бути, а може і не знаходитися протягом 'n' місяців / років тощо.
Джон Паркер,

Мій клас такий великий, і я думаю, що читати цю публікацію буде незручно.
Arturgspb

Я думаю, що тут є дві погані речі: це російською мовою (кожен програміст повинен знати англійську мову та користуватися нею, навіть у коментарях) і сюди не входить. Дивіться допомогу, як включити величезний код.
gaRex
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.