PHP i wyliczenia


1149

Wiem, że PHP nie ma natywnych wyliczeń. Ale przyzwyczaiłem się do nich ze świata Java. Chciałbym używać wyliczeń jako sposobu na podanie predefiniowanych wartości, które funkcje automatycznego uzupełniania IDE mogłyby zrozumieć.

Stałe załatwiają sprawę, ale istnieje problem kolizji przestrzeni nazw i (a właściwie dlatego, że) są globalne. Tablice nie mają problemu z przestrzenią nazw, ale są zbyt niejasne, można je nadpisać w czasie wykonywania, a IDE rzadko (nigdy?) Nie wiedzą, jak automatycznie wypełnić swoje klucze.

Czy są jakieś rozwiązania / obejścia, których często używasz? Czy ktoś pamięta, czy faceci PHP mieli jakieś przemyślenia lub decyzje dotyczące enum?



1
Stworzyłem funkcję obejścia, która wylicza stałe jako bitowe lub nie. Nie zauważyłem, że o to pytałeś wcześniej, ale mam tutaj lepsze rozwiązanie niż zmienne klasowe: stackoverflow.com/questions/3836385/…
NoodleOfDeath


Czy masz coś więcej do powiedzenia na temat problemu ze stałymi? „Stałe załatwiają sprawę, ale istnieje problem kolizji przestrzeni nazw i (a właściwie dlatego, że) są globalne”.
XuDing,

Odpowiedzi:


1492

W zależności od przypadku użycia zwykle użyłbym czegoś prostego, takiego jak:

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

$today = DaysOfWeek::Sunday;

Jednak inne przypadki użycia mogą wymagać większej weryfikacji stałych i wartości. W oparciu o poniższe komentarze na temat refleksji i kilka innych uwag , oto rozszerzony przykład, który może lepiej służyć w znacznie szerszym zakresie przypadków:

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

Tworząc prostą klasę enum, która rozszerza BasicEnum, możesz teraz w ten sposób używać metod do prostej weryfikacji danych wejściowych:

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

Na marginesie: za każdym razem, gdy przynajmniej raz użyję odbicia w klasie statycznej / stałej, w której dane się nie zmienią (np. W wyliczeniu), buforuję wyniki tych wywołań odbicia, ponieważ za każdym razem używam świeżych obiektów odbicia ostatecznie będzie miało zauważalny wpływ na wydajność (Przechowywane w tablicy asocjacyjnej dla wielu wyliczeń).

Teraz, gdy większość ludzi w końcu dokonała aktualizacji do wersji co najmniej 5.3 i SplEnumjest dostępna, jest to z pewnością realna opcja - pod warunkiem, że nie masz nic przeciwko tradycyjnie nieintuicyjnemu wyobrażeniu o tworzeniu instancji w enum w całej bazie kodu. W powyższym przykładzie, BasicEnumi DaysOfWeeknie może być instancja w ogóle, ani nie powinny być.


70
Ja też tego używam. Można również rozważyć klasę abstracti finaltak nie może być instancja lub przekształcone.
ryeguy

21
Możesz zrobić zajęcia zarówno, jak abstracti final? Wiem, że w Javie jest to niedozwolone. Możesz to zrobić w php?
corsiKa

20
@ryeguy Wydaje się, że nie może go zarówno abstract i final. W takim razie wybrałbym streszczenie.
Nicole

45
Informacje abstrakcyjne lub końcowe; Robię je ostateczne i daję pustego prywatnego konstruktora
rael_kid

21
Zachowaj ostrożność przy użyciu 0, aby nie napotkać żadnych nieprzewidzianych problemów z porównywaniem falsy, np. Równoważności z nulli przyjaciółmi w switchwypowiedzi. Byłem tam
yitznewton

185

Istnieje również rozszerzenie natywne. SplEnum

SplEnum daje możliwość emulacji i tworzenia obiektów wyliczeniowych natywnie w PHP.

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

Uwaga:

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

Rozszerzenie PECL nie jest dołączone do PHP.

Biblioteka DLL dla tego rozszerzenia PECL jest obecnie niedostępna.


4
Oto przykład ze śledzioną: dreamincode.net/forums/topic/201638-enum-in-php
Nordes

4
Cofnąłem się, bardziej mi się podoba, kiedy widzę link. Daje mi informacje kontekstowe.
markus

5
Przetoczyłem się ponownie. Nie chcę, żebyście edytowali link.
markus

6
Ostrożnie korzystaj z tego. Typy SPL są eksperymentalne: „To rozszerzenie jest DOŚWIADCZALNE. Zachowanie tego rozszerzenia, w tym nazwy jego funkcji i wszelka inna dokumentacja otaczająca to rozszerzenie, mogą ulec zmianie bez powiadomienia w przyszłej wersji PHP. Z tego rozszerzenia należy korzystać na własne ryzyko. „
bzeaman

6
SplEnum nie jest dołączony do PHP, wymaga rozszerzenia
SPL_Types

46

Co ze stałymi klasowymi?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

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

Wolę to proste podejście
David Lemon

echoConstantmożna zastąpić __toString. A potem po prostuecho $c
Justinas

35

Górna odpowiedź powyżej jest fantastyczna. Jeśli jednak zrobisz extendto na dwa różne sposoby, w zależności od tego, które rozszerzenie zostanie wykonane jako pierwsze, wywołanie funkcji utworzy pamięć podręczną. Ta pamięć podręczna będzie następnie używana przez wszystkie kolejne połączenia, niezależnie od rozszerzenia, które połączenia są inicjowane przez ...

Aby rozwiązać ten problem, zamień zmienną i pierwszą funkcję na:

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
Miałem właśnie ten problem. Brian lub ktoś z uprawnieniami do edycji powinien dotknąć go w zaakceptowanej odpowiedzi. Rozwiązałem go w moim kodzie przy użyciu metody „static ::” zamiast „self ::” w funkcji getConstants () i ponownie deklarując $ constCache w wyliczeniach potomnych.
Sp3igel,

To może nie być seksowne, ale użycie stałej interfejsu może być najlepszym sposobem na przejście w PHP.
Anthony Rutledge,

27

Użyłem klas ze stałymi:

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

print Enum::NAME;

27

Używam interfacezamiast class:

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

var $today = DaysOfWeek::Sunday;

6
class Foo implements DaysOfWeek { }a potem Foo::Sunday... co?
Dan Lugg

3
Autor pytania pyta o rozwiązanie dwóch rzeczy: przestrzeni nazw i automatycznego uzupełniania przez IDE. Jak sugeruje najwyżej oceniana odpowiedź, najłatwiejszym sposobem jest użycie class(lub interface, co jest tylko kwestią preferencji).
Andi T

4
interfejsy są używane do wymuszania integralności implementacji klas, jest to poza zakresem interfejsu
użytkownik3886650

2
@ user3886650 Interfejsy mogą i były / są używane w Javie w celu utrzymania stałych wartości. Nie musisz więc tworzyć instancji klasy tylko w celu uzyskania stałych wartości, a każde IDE oferuje na nich uzupełnienie kodu. Również jeśli utworzysz klasę, która implementuje ten interfejs, odziedziczy wszystkie te stałe - czasem całkiem przydatne.
Alex

@ user3886650 To prawda, ale w PHP interfejsy mogą mieć stałe. Ponadto tych stałych interfejsu nie można zastąpić przez wdrożenie klas lub ich dzieci. W rzeczywistości jest to najlepsza odpowiedź pod względem PHP, ponieważ wszystko, co można zastąpić, nie działa tak jak powinno. Stała powinna oznaczać stałą, niekiedy (choć czasem polimorfizm może być przydatny).
Anthony Rutledge,

25

Skomentowałem niektóre inne odpowiedzi tutaj, więc pomyślałem, że też waży. Ostatecznie, ponieważ PHP nie obsługuje wyliczeń wpisywanych na klawiaturze, możesz przejść na jeden z dwóch sposobów: zhakować wyliczenia wpisane na maszynie lub żyć z faktem, że niezwykle trudno jest je skutecznie wyhackować.

Wolę żyć z tym faktem i zamiast tego użyć constmetody, którą w inny sposób zastosowały inne odpowiedzi tutaj:

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

}

Przykładowe wyliczenie:

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;

}

Użycie Enumjako klasy podstawowej, z której rozciągają się wszystkie inne wyliczenia, pozwala na zastosowanie metod pomocniczych, takich jak toArray, isValiditd. Dla mnie wpisane wyliczenia ( i zarządzanie ich instancjami ) są po prostu zbyt niechlujne.


Hipotetyczny

Jeśli istniała __getStaticmetoda magiczna ( najlepiej też __equalsmagiczna) ), wiele z nich można by złagodzić za pomocą pewnego rodzaju wzoru wielotonowego.

( Poniższe jest hipotetyczne; nie zadziała, choć może kiedyś to nastąpi )

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

Bardzo podoba mi się prostota tej odpowiedzi. Jest to coś, do czego możesz wrócić później i szybko zrozumieć, jak to działa, nie sprawiając, że wyglądało to tak, jakbyś zrobił jakieś zhakowane podejście. Szkoda, że ​​nie ma więcej głosów.
Reactgular

23

Cóż, dla prostego java takiego jak enum w php używam:

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

I nazwać to:

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

Ale jestem początkującym PHP i mam problemy ze składnią, więc może to nie być najlepszy sposób. Poeksperymentowałem ze stałymi klasowymi, użycie Odbicia, aby uzyskać stałą nazwę z jej wartości, może być fajniejsze.


Dobra odpowiedź, większość innych odpowiedzi używa klas. Nie możesz jednak zagnieżdżać klas.
Keyo

Ma to tę zaletę, że można iterować wartości z foreach. I szkoda, że ​​nielegalna wartość nie zostanie złapana.
Bob Stein

2
Brak automatycznego uzupełniania w IDE, więc stymulowałoby zgadywanie. Stałe umożliwią automatyczne uzupełnianie, brzmi lepiej.
KrekkieD

19

Cztery lata później znów się z tym spotkałem. Moje obecne podejście jest takie, ponieważ pozwala na uzupełnianie kodu w IDE, a także na bezpieczeństwo typu:

Klasa podstawowa:

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

Przykład wyliczenia:

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

Przykładowe użycie:

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

Zauważ, że wszystkie wystąpienia tego samego wpisu wyliczeniowego są takie same:

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

Możesz również użyć go w instrukcji switch:

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

Możesz także utworzyć pozycję enum według nazwy lub wartości:

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

Lub możesz po prostu uzyskać nazwę (tj. Nazwę funkcji) z istniejącego wpisu wyliczeniowego:

$wednesday = DaysOfWeek::Wednesday()
echo $wednesDay->getName(); // Wednesday

+1 dla prywatnego konstruktora. Nie stworzyłbym klasy abstrakcyjnej pomocnika, zwykłej klasy, prywatnego konstruktora i niektórychconst Monday = DaysOfWeek('Monday');
Kangur

9

Znalazłem tę bibliotekę na githubie i myślę, że stanowi ona bardzo przyzwoitą alternatywę dla odpowiedzi tutaj.

Implementacja PHP Enum inspirowana SplEnum

  • Możesz wpisać podpowiedź: function setAction(Action $action) {
  • Można wzbogacić enum z metod (np format,parse ...)
  • Możesz rozszerzyć wyliczenie, aby dodać nowe wartości (uczyń wyliczenie, finalaby temu zapobiec)
  • Możesz uzyskać listę wszystkich możliwych wartości (patrz poniżej)

Deklaracja

<?php
use MyCLabs\Enum\Enum;

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

Stosowanie

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

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

wartości wyliczenia podpowiedzi typu:

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

1
To poprawna odpowiedź (na razie, dopóki nie enumzostanie dodana w PHP 7.x), ponieważ pozwala na podpowiedzi typu.
Tobia,

1
Pozwala to nie tylko na podpowiedzi typu, ale ze względu na __toString()magię pozwala ci robić to, co na ogół naprawdę chcesz z wyliczeniami - używaj ich w instrukcji switchlub if, porównując bezpośrednio z wartościami const. Najlepsze podejście poza natywną obsługą enum, IMO.
LinusR

7

Jeśli potrzebujesz użyć wyliczeń, które są globalnie unikalne (tj. Nawet przy porównywaniu elementów między różnymi wyliczeniami) i są łatwe w użyciu, możesz użyć następującego kodu. Dodałem także kilka metod, które uważam za przydatne. Przykłady znajdziesz w komentarzach na samej górze kodu.

<?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
Bardzo to lubię. Jednak jedną z głównych skarg jest zdolność IDE do zbierania wartości dla autouzupełniania. Nie jestem pewien, czy byłoby to możliwe bez niestandardowego dodatku do IDE. Nie, że nie można tego zrobić, to po prostu zajmie trochę pracy.
corsiKa,

2
Używasz eval()tylko w celu zadeklarowania nowego środowiska uruchomieniowego Enums? Eek. Nie czuje tego Jak zapobiec tworzeniu przez inne klasy niepoprawnej klasy Enum przed zdefiniowaniem właściwej? Czy Enums nie są znane przed uruchomieniem? I jak sugeruje @corsiKa, brak autouzupełniania IDE. Jedyną korzyścią, jaką widzę, jest leniwe kodowanie.
KrekkieD

7

Lubię też wyliczenia z java iz tego powodu piszę wyliczenia w ten sposób, myślę, że jest to najbardziej podobne zachowanie jak w wyliczeniach Java, oczywiście, jeśli ktoś chce użyć więcej metod z java, powinien napisać to tutaj, lub w klasa abstrakcyjna, ale podstawowa idea jest osadzona w kodzie poniżej


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
Robię prawie to samo, z dwoma małymi dodatkami: ukryłem wartości statyczne za statycznymi modułami pobierającymi. Jednym z powodów jest to, że ja wolę wizualnie FruitsEnum::Apple()ponad FruitsEnum::$Apple, ale ważniejszy powód jest zapobieganie nikogo innego z ustawieniem $APPLE, łamiąc w ten sposób wyliczenia dla całej aplikacji. Drugi to prosta prywatna flaga statyczna $initialized, która init()gwarantuje , że wywołanie nie będzie działać po pierwszym wywołaniu (więc nikt też nie może z tym zadzierać).
Martin Ender

Lubiłem Martina. .init()jest dziwne i nie przeszkadza mi podejście gettera.
Sebas,

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

Najczęstszym rozwiązaniem, które widziałem dla enum w PHP, było utworzenie ogólnej klasy enum, a następnie jej rozszerzenie. Można spojrzeć na to .

AKTUALIZACJA: Alternatywnie znalazłem to z phpclasses.org.


1
Chociaż implementacja jest łatwa i prawdopodobnie wykona zadanie, wadą tego jest to, że IDE prawdopodobnie nie wiedzą, jak automatycznie wypełniać wyliczenia. Nie mogłem sprawdzić tego z phpclasses.org, ponieważ chciałem się zarejestrować.
Henrik Paul

5

Oto biblioteka github do obsługi wyliczeń bezpiecznych dla typu w php:

Ta biblioteka obsługuje generowanie klas, buforowanie klas i implementuje wzorzec projektowy Bezpieczne wyliczanie typów, z kilkoma metodami pomocniczymi do radzenia sobie z wyliczeniami, takimi jak wyszukiwanie porządków do sortowania wyliczeń lub pobieranie wartości binarnych dla kombinacji wyliczeń.

Wygenerowany kod używa zwykłego starego pliku szablonu php, który można również konfigurować, dzięki czemu możesz podać własny szablon.

Jest to pełny test pokryty phpunitem.

php-enums na github (nie krępuj się rozwidlać)

Użycie: (@see use.php lub testy jednostkowe, aby uzyskać więcej informacji)

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

Wynik:

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

Zastosowałem poniższe podejście, ponieważ daje mi ono możliwość zapewnienia bezpieczeństwa typu dla parametrów funkcji, automatycznego uzupełniania w NetBeans i dobrej wydajności. Jedyne, co mi się nie podoba, to to, że musisz zadzwonić [extended class name]::enumerate();po zdefiniowaniu klasy.

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

Nic nie stoi na przeszkodzie, aby przedefiniować wartości wyliczeniowe:DaysOfWeek::$MONDAY = 3;
KrekkieD

@BrianFisher, wiem, że jest już późno, ale jeśli nie lubisz dzwonić [extended class name]::enumerate();po definicji, dlaczego nie robisz tego w konstrukcie?
Can O 'Spam

4

Moja definicja klasy Enum poniżej jest silnie wpisana i bardzo naturalna w użyciu i zdefiniowaniu.

Definicja:

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

Przełącz 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

Przekaż Enum jako parametr (silnie wpisany)

/** 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 jako ciąg

echo "I have an $myFruit\n";

>> I have an APPLE

Uzyskaj Enum przez liczbę całkowitą

$myFruit = Fruit::getByValue(2);

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

>> Now I have an ORANGE

Uzyskaj Enum według nazwy

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

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

>> But I definitely prefer an APPLE

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

}

Dodanie

Możesz oczywiście dodawać komentarze do 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

Zdaję sobie sprawę, że to bardzo, bardzo, bardzo stary wątek, ale pomyślałem o tym i chciałem wiedzieć, co myślą ludzie.

Uwagi: bawiłem się tym i zdałem sobie sprawę, że jeśli po prostu zmodyfikuję __call()funkcję, możesz zbliżyć się do rzeczywistości enums. __call()Funkcja obsługuje wszystkie nieznane połączenia funkcji. Powiedzmy, że chcesz zrobić trzy enumsRED_LIGHT, YELLOW_LIGHT i GREEN_LIGHT. Możesz to zrobić teraz, wykonując następujące czynności:

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

Po zdefiniowaniu wystarczy zadzwonić do nich ponownie, aby uzyskać wartości:

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

i powinieneś dostać 0, 1 i 2. Baw się dobrze! To jest teraz także dostępne w GitHub.

Aktualizacja: Zrobiłem to, więc teraz są używane funkcje __get()i __set(). Pozwalają ci one nie musieć wywoływać funkcji, chyba że chcesz. Zamiast tego możesz teraz powiedzieć:

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

Zarówno do tworzenia, jak i uzyskiwania wartości. Ponieważ zmienne nie zostały początkowo zdefiniowane, __get()funkcja jest wywoływana (ponieważ nie określono wartości), co oznacza, że ​​wpis w tablicy nie został wprowadzony. Czyni zatem wpis, przypisuje mu ostatnią podaną wartość plus jeden (+1), zwiększa ostatnią zmienną wartości i zwraca PRAWDA. Jeśli ustawisz wartość:

$c->RED_LIGHT = 85;

Następnie __set()wywoływana jest funkcja, a ostatnia wartość jest ustawiana na nową wartość plus jeden (+1). Więc teraz mamy dość dobry sposób na wyliczanie i można je tworzyć w locie.

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

Wiem, że jest to stary wątek, jednak żadne z obejść, które widziałem, nie wyglądało naprawdę jak wyliczenia, ponieważ prawie wszystkie obejścia wymagają ręcznego przypisania wartości do elementów wyliczeniowych, lub wymagają przekazania tablicy kluczy wyliczeniowych do funkcjonować. Więc stworzyłem własne rozwiązanie.

Aby utworzyć klasę enum przy użyciu mojego rozwiązania, wystarczy po prostu rozszerzyć tę klasę Enum poniżej, utworzyć wiązkę zmiennych statycznych (nie trzeba ich inicjować) i wywołać metodę yourEnumClass :: init () tuż poniżej definicji klasy enum .

edit: Działa to tylko w php> = 5.3, ale prawdopodobnie można go zmodyfikować tak, aby działał również w starszych wersjach

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

Teraz możesz użyć klasy SplEnum, aby zbudować ją natywnie. Zgodnie z oficjalną dokumentacją.

SplEnum daje możliwość emulacji i tworzenia obiektów wyliczeniowych natywnie w 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;
}
?>

Uwaga: jest to rozszerzenie, które musi być zainstalowane, ale domyślnie niedostępne. Który znajduje się w sekcji Typy specjalne opisane w samej witrynie php. Powyższy przykład pochodzi ze strony PHP.


3

Na koniec odpowiedź PHP 7.1+ ze stałymi, których nie można zastąpić.

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

Jeśli używasz przestrzeni nazw, uzupełnianie kodu powinno działać.

Jednak robiąc to, tracisz możliwość ukrywania stałych w rodzinie klas ( protected) lub samej klasie ( private). Z definicji wszystko Interfacejest w public.

Podręcznik PHP: Interfejsy


To nie jest Java. Działa to w przypadkach, gdy polimorfizm / wzorzec strategii nie jest wymagany do zastąpienia stałych w klasie nadrzędnej.
Anthony Rutledge

2

To jest moje zdanie na temat „dynamicznego” wyliczenia ... abym mógł go nazwać zmiennymi, np. z formularza.

spójrz na zaktualizowaną wersję poniżej tego kodu ...

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

AKTUALIZACJA: Lepszy sposób na zrobienie tego ...

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;

}

Zadzwoń z

EnumCategory::${$category};

5
Problem z tą istotą; EnumCategory::$sport = 9;. Witamy w muzeum sportu. const jest lepszym sposobem na zrobienie tego.
Dan Lugg,

2

Przyjęta odpowiedź jest właściwą drogą i tak naprawdę robię to dla uproszczenia. Oferowane są większość zalet wyliczenia (czytelne, szybkie itp.). Brakuje jednak jednej koncepcji: bezpieczeństwo typu. W większości języków wyliczenia są również używane do ograniczenia dozwolonych wartości. Poniżej znajduje się przykład, w jaki sposób można uzyskać bezpieczeństwo typu przy użyciu prywatnych konstruktorów, metod tworzenia instancji statycznych i sprawdzania typu:

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

Możemy nawet pójść dalej: użycie stałych w klasie DaysOfWeek może prowadzić do niewłaściwego użycia: np. Można błędnie użyć tego w ten sposób:

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

co jest złe (wywołuje stałą całkowitą). Możemy temu zapobiec za pomocą prywatnych zmiennych statycznych zamiast stałych:

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

}

Oczywiście nie jest możliwy dostęp do stałych całkowitych (taki był właśnie cel). Metoda intVal pozwala na konwersję obiektu DaysOfWeek na jego całkowitą reprezentację.

Zauważ, że moglibyśmy nawet pójść dalej, wdrażając mechanizm buforowania w metodach tworzenia instancji w celu zaoszczędzenia pamięci w przypadku, gdy wyliczenia są szeroko stosowane ...

Mam nadzieję, że to pomoże


2

Kilka dobrych rozwiązań tutaj!

Oto moja wersja.

  • Jest mocno napisany
  • Działa z autouzupełnianiem IDE
  • Wyliczenia są definiowane przez kod i opis, gdzie kod może być liczbą całkowitą, wartością binarną, krótkim łańcuchem lub w zasadzie czymkolwiek innym. Wzorzec można łatwo rozszerzyć w celu obsługi innych właściwości.
  • Obsługuje porównania wartości (==) i odniesień (===) i działa w instrukcjach switch.

Myślę, że główną wadą jest to, że członkowie enum muszą być osobno zadeklarowani i utworzeni instancji, ze względu na opisy i niezdolność PHP do konstruowania obiektów w czasie statycznej deklaracji członka. Myślę, że rozwiązaniem może być użycie refleksji z parsowanymi komentarzami doktorów.

Abstrakcyjne wyliczenie wygląda następująco:

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

Oto przykład konkretnego wyliczenia:

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

Które można wykorzystać w ten sposób:

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

I produkuje ten wynik:

1: Moja pierwsza wartość

array (3) {
[1] =>
object (EMyEnum) # 1 (2) {
["code": "AbstractEnum": private] =>
int (1)
["description": "AbstractEnum": private] =>
string (14) „Moja pierwsza wartość”
}
[2] =>
object (EMyEnum) # 2 (2) {
[„code”: „AbstractEnum”: private] =>
int (2)
[„description”: „AbstractEnum” : private] =>
string (15) „Moja druga wartość”
}
[3] =>
object (EMyEnum) # 3 (2) {
[„code”: „AbstractEnum”: private] =>
int (3)
[„description”: „AbstractEnum”: private] =>
string (14) „Moja trzecia wartość”
}
}

Moja druga wartość


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

Nie używaj refleksji. Utrudnia to rozumowanie twojego kodu i śledzenie, gdzie coś jest używane, i ma tendencję do niszczenia narzędzi do analizy statycznej (np. Tego, co jest wbudowane w twoje IDE).


2

Jednym z aspektów, których brakuje w niektórych innych odpowiedziach tutaj, jest sposób użycia wyliczeń z podpowiedziami typu.

Jeśli zdefiniujesz swój wyliczenie jako zbiór stałych w klasie abstrakcyjnej, np

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

to nie można wpisać schować go w parametrze funkcji - dla jednego, bo to nie jest chwilowe, ale także dlatego, że typ ShirtSize::SMALLjest int, nie ShirtSize.

Dlatego natywne wyliczenia w PHP byłyby o wiele lepsze niż cokolwiek, co możemy wymyślić. Możemy jednak zbliżyć wyliczenie, zachowując własność prywatną, która reprezentuje wartość wyliczenia, a następnie ograniczając inicjalizację tej właściwości do naszych predefiniowanych stałych. Aby zapobiec arbitralnej instancji wyliczenia (bez konieczności sprawdzania białej listy), konstruktor staje się prywatny.

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

Następnie możemy użyć w ShirtSizenastępujący sposób:

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

W ten sposób największą różnicą z punktu widzenia użytkownika jest to, że musisz przyczepić się do ()nazwy stałej.

Jedną wadą jest to, że ===(która porównuje równość obiektów) zwróci false, gdy ==zwróci true. Z tego powodu najlepiej jest podać equalsmetodę, aby użytkownicy nie musieli pamiętać o używaniu ==i ===nieporównywaniu dwóch wartości wyliczeniowych.

EDYCJA: Kilka istniejących odpowiedzi jest bardzo podobnych, w szczególności: https://stackoverflow.com/a/25526473/2407870 .


2

Opierając się na odpowiedzi @Brian Cline, pomyślałem, że mogę dać moje 5 centów

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

z jakiegoś powodu nie mogę nazwać tej funkcji. Mówi mi, że takie funkcje nie są deklarowane. Co robię źle? [podstawowa klasa Enum znajduje się w innym pliku i używam include('enums.php');]. Z jakiegoś powodu nie widzi funkcji zadeklarowanych w Enum dla klas potomnych ...
Andrew,

Również ... jak ustawić go z ciągu? coś jak$currentSeason.set("Spring");
Andrew,

1

Moja próba utworzenia wyliczenia za pomocą PHP ... jest bardzo ograniczona, ponieważ nie obsługuje obiektów jako wartości wyliczeniowych, ale wciąż jest trochę przydatna ...

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

jest ograniczony w wielu kierunkach, a istniejące odpowiedzi oferują znacznie więcej. Powiedziałbym, że tak naprawdę nie dodaje niczego pożytecznego.
hakre

1

Wczoraj napisałem tę klasę na swoim blogu . Myślę, że może być łatwy w użyciu w skryptach 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;
    }
}

Stosowanie:

class enumWorkType extends Enum
{
        const FULL = 0;
        const SHORT = 1;
}

2
Ale to dobra klasa, a nazwa funkcji jest rodzima. A także translate.google.ru może pomóc.
Arturgspb

2
Używaj chrome i tłumacz go, jeśli jesteś programistą, czytasz kod!
markus

8
Zasadniczo zawsze lepiej jest zawrzeć kod w odpowiedzi, niż linkować do zewnętrznego źródła, które może, ale nie musi istnieć w miesiącach n / miesiącach itp.
John Parker,

Moja klasa jest tak duża i myślę, że przeczytanie tego postu będzie niewygodne.
Arturgspb,

Myślę, że tutaj dwie złe rzeczy: jest po rosyjsku (każdy programista musi znać angielski i używać go, nawet w komentarzach) i nie ma go tutaj. Zobacz pomoc na temat dołączania dużego kodu.
gaRex,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.