PHP e enumerações


1149

Eu sei que o PHP não tem enumerações nativas. Mas eu me acostumei a eles do mundo Java. Eu adoraria usar enums como uma maneira de fornecer valores predefinidos que os recursos de preenchimento automático dos IDEs pudessem entender.

As constantes fazem o truque, mas há o problema de colisão de namespace e (ou na verdade porque ) são globais. As matrizes não têm o problema do espaço para nome, mas são muito vagas, podem ser substituídas em tempo de execução e os IDEs raramente (nunca?) Sabem como preencher automaticamente suas chaves.

Existem soluções / soluções alternativas que você costuma usar? Alguém se lembra se os caras do PHP tiveram alguma opinião ou decisão sobre enums?



1
Eu criei uma função alternativa que enumera constantes como bit a bit ou não. Não percebeu que você perguntou isso antes, mas eu tenho uma solução melhor do que variáveis de classe aqui: stackoverflow.com/questions/3836385/...
NoodleOfDeath


Você se importa de compartilhar um pouco mais sobre o problema das constantes? "As constantes fazem o truque, mas há o problema de colisão de namespace e (ou na verdade porque) são globais".
XuDing 28/10/19

Respostas:


1492

Dependendo do caso de uso, normalmente eu usaria algo simples como o seguinte:

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

$today = DaysOfWeek::Sunday;

No entanto, outros casos de uso podem exigir mais validação de constantes e valores. Com base nos comentários abaixo sobre reflexão e em algumas outras notas , aqui está um exemplo expandido que pode atender melhor a uma variedade muito maior de casos:

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

Ao criar uma classe enum simples que estende o BasicEnum, agora você tem a capacidade de usar métodos para validação de entrada simples:

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

Como observação lateral, sempre que eu usar reflexão pelo menos uma vez em uma classe static / const em que os dados não serão alterados (como em uma enumeração), eu armazeno em cache os resultados dessas chamadas de reflexão, desde que você use objetos de reflexão sempre atualizados acabará por ter um impacto perceptível no desempenho (armazenado em uma matriz associativa para várias enumerações).

Agora que a maioria das pessoas finalmente atualizou para pelo menos 5,3 e SplEnumestá disponível, essa também é uma opção viável - desde que você não se importe com a noção tradicionalmente não intuitiva de ter instanciações enum reais em toda a sua base de código. No exemplo acima, BasicEnume DaysOfWeeknão pode ser instanciado, nem deveria ser.


70
Eu também uso isso. Você também pode fazer a aula abstracte final, portanto, não pode ser instanciada ou estendida.
precisa saber é o seguinte

21
Você pode fazer uma aula tanto abstracte final? Eu sei que em Java isso não é permitido. Você pode fazer isso em php?
corsiKa

20
@ryeguy Parece que você não pode fazer isso tanto abstract e final. Nesse caso, eu procuraria por resumo.
22611 Nicole

45
Sobre resumo ou final; Eu torná-los final e dar-lhes um construtor privado vazia
rael_kid

21
Tenha cuidado ao usar 0, para não ter problemas imprevistos de comparação de falsas, por exemplo, equivalência com nulle amigos em uma switchdeclaração. Esteve lá.
precisa saber é o seguinte

185

Também há uma extensão nativa. O SplEnum

SplEnum oferece a capacidade de emular e criar objetos de enumeração nativamente no PHP.

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

Atenção:

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

A extensão PECL não é fornecida com o PHP.

Uma DLL para esta extensão PECL está indisponível no momento.


4
Aqui está um exemplo com splenum: dreamincode.net/forums/topic/201638-enum-in-php
Nordes

4
Recuei, gosto mais quando posso ver o link. Isso me dá informações de contexto.
Markus

5
Revirei novamente. Não quero que vocês editem o link.
markus 27/02

6
Cuidado ao usar isso. Os tipos de SPL são experimentais: "Esta extensão é EXPERIMENTAL. O comportamento desta extensão, incluindo os nomes de suas funções e qualquer outra documentação relacionada a essa extensão, pode ser alterada sem aviso prévio em uma versão futura do PHP. Essa extensão deve ser usada por seu próprio risco. "
bzeaman

6
SplEnum não é empacotado com PHP, ele precisa SPL_Types extensão
Kwadz

46

E as constantes de classe?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

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

Eu prefiro esta abordagem simples
David Lemon

echoConstantpode ser substituído por __toString. E então simplesmenteecho $c
Justinas

35

A resposta superior acima é fantástica. No entanto, se vocêextend isso de duas maneiras diferentes, qualquer extensão feita primeiro resultará em uma chamada para as funções criará o cache. Esse cache será usado por todas as chamadas subsequentes, independentemente do ramal que as chamadas sejam iniciadas por ...

Para resolver isso, substitua a variável e a primeira função por:

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
Teve esse mesmo problema. Brian ou alguém com privilégios de edição deve tocar na resposta aceita. Eu o resolvi no meu código usando o método 'static ::' em vez de 'self ::' na função getConstants () e declarando novamente o $ constCache nas enumerações filho.
Sp3igel

Pode não ser sexy, mas usar uma constante de interface pode ser o melhor caminho a percorrer no PHP.
Anthony Rutledge

27

Eu usei classes com constantes:

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

print Enum::NAME;

27

Eu uso em interfacevez de class:

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

var $today = DaysOfWeek::Sunday;

6
class Foo implements DaysOfWeek { }e então Foo::Sunday... o que?
precisa

3
O autor da pergunta pede uma solução para duas coisas: namespace e preenchimento automático pelos IDEs. Como sugeriu a resposta com a melhor classificação, a maneira mais fácil é usar class(ou interface, que é apenas uma questão de preferência).
Andi T

4
interfaces são usadas para reforçar a integridade implementação classe, esta está fora do âmbito de uma interface
user3886650

2
@ user3886650 As interfaces podem e foram / são usadas em Java para manter valores constantes. Portanto, você não é forçado a instanciar uma classe apenas para obter valores constantes e qualquer IDE oferece a conclusão de código nelas. Além disso, se você criar uma classe que implementa essa interface, ela herdará todas essas constantes - algumas vezes bastante útil.
Alex

@ user3886650 É verdade, mas no PHP, as interfaces podem ter constantes. Além disso, essas constantes da interface não podem ser substituídas pela implementação de classes ou de seus filhos. De fato, esta é a melhor resposta em termos de PHP, porque qualquer coisa que possa ser substituída não está realmente funcionando como deveria. Constante deve significar constante, não algumas vezes (embora o polimorfismo possa ser útil às vezes).
Anthony Rutledge

25

Eu comentei algumas das outras respostas aqui, então achei que também pesaria. No final do dia, como o PHP não suporta enumerações digitadas, você pode seguir um de dois modos: hackear enumerações digitadas ou viver com o fato de que são extremamente difíceis de hackear efetivamente.

Eu prefiro viver com o fato e, em vez disso, use o constmétodo que outras respostas aqui usaram de uma maneira ou de outra:

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

}

Um exemplo de enumeração:

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;

}

Usando Enumcomo uma classe de base a partir do qual todos os outros enumerações estender permite métodos auxiliares, tais como toArray, isValide assim por diante. Para mim, as enumerações digitadas ( e o gerenciamento de suas instâncias ) acabam muito confusas.


Hipotético

Se existisse um __getStaticmétodo mágico ( e de preferência um __equalsmétodo mágico também ), muito disso poderia ser mitigado com uma espécie de padrão multiton.

( O seguinte é hipotético; não funcionará, embora talvez um dia funcione )

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

Eu realmente gosto da simplicidade desta resposta. É o tipo de coisa que você pode voltar mais tarde e entender rapidamente como funciona sem parecer que você fez algum tipo de abordagem invadida. Uma pena que não tenha mais votos.
precisa saber é o seguinte

23

Bem, para um java simples como o enum no php, eu uso:

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

E para chamá-lo:

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

Mas eu sou iniciante em PHP, lutando com a sintaxe, então essa pode não ser a melhor maneira. Eu experimentei algumas com constantes de classe, usando Reflection para obter o nome constante de seu valor, pode ser mais limpo.


Boa resposta, a maioria das outras respostas está usando classes. Você não pode ter aulas aninhadas.
Keyo

Isso tem o benefício de poder iterar pelos valores com foreach. E o prejuízo que um valor ilegal não é capturado.
Bob Stein

2
Porém, nenhuma conclusão automática no IDE estimularia o trabalho de adivinhação. As constantes permitiriam a conclusão automática, soa melhor.
KrekkieD

19

Quatro anos depois, me deparei com isso novamente. Minha abordagem atual é essa, pois permite a conclusão do código no IDE, bem como o tipo de segurança:

Classe base:

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

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

Exemplo de uso:

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

Observe que todas as instâncias da mesma entrada enum são as mesmas:

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

Você também pode usá-lo dentro de uma instrução switch:

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

Você também pode criar uma entrada de enumeração por nome ou valor:

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

Ou você pode simplesmente obter o nome (ou seja, o nome da função) de uma entrada de enum existente:

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

+1 para um construtor privado. Eu não faria ajudante classe abstrata, apenas uma classe simples, construtor privado e algunsconst Monday = DaysOfWeek('Monday');
Kangur

9

Eu encontrei esta biblioteca no github e acho que ela fornece uma alternativa muito decente para as respostas aqui.

Implementação do PHP Enum inspirada no SplEnum

  • Você pode digitar uma dica: function setAction(Action $action) {
  • Você pode enriquecer a enumeração com métodos (por exemplo format,parse ...)
  • Você pode estender a enumeração para adicionar novos valores (crie sua enumeração finalpara evitá-la)
  • Você pode obter uma lista de todos os valores possíveis (veja abaixo)

Declaração

<?php
use MyCLabs\Enum\Enum;

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

Uso

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

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

valores de enumeração de dica de tipo:

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

1
Esta é a resposta correta (por enquanto, até que enumseja adicionada no PHP 7.x) porque permite dicas de tipo.
Tobia

1
Isso não apenas permite sugestões de tipos, mas, devido à __toString()mágica, permite que você faça o que geralmente deseja com enumerações - use-as em uma declaração switchou if, comparando diretamente com os valores dos consts. A melhor abordagem, com exceção do suporte a enum nativo, a IMO.
LinusR

7

Se você precisar usar enumerações globalmente únicas (ou seja, mesmo ao comparar elementos entre enumerações diferentes) e que sejam fáceis de usar, fique à vontade para usar o código a seguir. Também adicionei alguns métodos que considero úteis. Você encontrará exemplos nos comentários na parte superior do código.

<?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
Eu gosto muito disso. No entanto, uma das principais reclamações é a capacidade do IDE de captar os valores para o preenchimento automático. Não tenho certeza se isso seria capaz de fazer isso sem um complemento personalizado para o IDE. Não que isso não pudesse ser feito, seria necessário algum trabalho.
corsiKa

2
Usando eval()apenas para poder declarar o novo tempo de execução do Enums? Eek. Eu não estou sentindo isso. Como você evita que outras classes criem uma classe Enum incorreta antes de definir a correta? Os Enums não são conhecidos antes do tempo de execução? E como o @corsiKa sugeriu, nenhum preenchimento automático do IDE. O único benefício que vejo é a codificação preguiçosa.
KrekkieD

7

Também gosto de enums do java e, por esse motivo, escrevo meus enums dessa maneira, acho que esse é o comportamento mais parecido com o enums do Java, é claro, se alguns que desejam usar mais métodos do java devem escrevê-lo aqui, ou em classe abstrata, mas a ideia central está incorporada no código abaixo


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
Estou fazendo praticamente a mesma coisa, com duas pequenas adições: ocultei os valores estáticos por trás dos getters estáticos. Uma razão é que eu prefiro visualmente FruitsEnum::Apple()mais FruitsEnum::$Apple, mas a razão mais importante é impedir que outras pessoas definam $APPLE, quebrando assim a enumeração de todo o aplicativo. O outro é um sinalizador estático privado simples $initializedque garante que a chamada init()se torne inoperante depois de chamá-la pela primeira vez (para que ninguém possa mexer com isso).
Martin Ender

Eu gostei do Martin. .init()é estranho, e eu não me importo com a abordagem getter.
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

A solução mais comum que eu já vi enum no PHP foi criar uma classe enum genérica e depois estendê-la. Você pode dar uma olhada nisso .

UPDATE: Como alternativa, achei isso em phpclasses.org.


1
Embora a implementação seja esperta e provavelmente faça o trabalho, a desvantagem disso é que os IDEs provavelmente não sabem como preencher automaticamente as enumerações. Não pude inspecionar o arquivo phpclasses.org, porque ele queria que eu me registrasse.
31432 Henrik Paul

5

Aqui está uma biblioteca do github para lidar com enumerações de tipo seguro no php:

Essa biblioteca lida com geração de classes, armazenamento em cache de classes e implementa o padrão de design de Enumeração Segura de Tipo, com vários métodos auxiliares para lidar com enumerações, como recuperar um ordinal para classificação de enumerações ou recuperar um valor binário para combinações de enumerações.

O código gerado usa um arquivo de modelo php antigo simples, que também é configurável, para que você possa fornecer seu próprio modelo.

É teste completo coberto com phpunit.

php-enums no github (sinta-se à vontade para fazer um fork)

Uso: (@ veja use.php ou testes de unidade para obter mais detalhes)

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

Resultado:

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

Comecei a usar a abordagem abaixo, pois ela me dá a capacidade de ter segurança de tipo para parâmetros de função, preenchimento automático no NetBeans e bom desempenho. A única coisa que eu não gosto muito é que você precisa ligar [extended class name]::enumerate();depois de definir a classe.

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

Nada está impedindo você de redefinir os valores da enumeração:DaysOfWeek::$MONDAY = 3;
KrekkieD

@BrianFisher, eu sei que está um pouco tarde agora, mas, se você não gosta de chamar [extended class name]::enumerate();após a definição, por que não faz isso na construção?
Can O 'Spam

4

Minha definição de classe Enum abaixo é fortemente tipada e muito natural de usar e definir.

Definição:

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

Alternar 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

Passar Enum como parâmetro (fortemente digitado)

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

Eco Enum como string

echo "I have an $myFruit\n";

>> I have an APPLE

Obter Enum por número inteiro

$myFruit = Fruit::getByValue(2);

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

>> Now I have an ORANGE

Obter Enum por nome

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

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

>> But I definitely prefer an APPLE

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

}

Adição

É claro que você também pode adicionar comentários para IDEs

class Fruit extends Enum {

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

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

//This can also go to the autoloader if available.
Fruit::initialize();

4

Sei que esse é um tópico muito, muito, muito antigo, mas eu pensei sobre isso e queria saber o que as pessoas pensavam.

Notas: Eu estava brincando com isso e percebi que, se eu apenas modificasse a __call()função, você pode se aproximar ainda mais da realidadeenums . A __call()função lida com todas as chamadas de função desconhecidas. Então, digamos que você queira criar três enumsRED_LIGHT, YELLOW_LIGHT e GREEN_LIGHT. Você pode fazer isso agora fazendo o seguinte:

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

Uma vez definido, tudo o que você precisa fazer é chamá-los novamente para obter os valores:

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

e você deve obter 0, 1 e 2. Divirta-se! Agora também está disponível no GitHub.

Update: Eu fiz isso para que tanto o __get()e__set() funções agora sejam usadas. Isso permite que você não precise chamar uma função, a menos que queira. Em vez disso, agora você pode apenas dizer:

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

Para a criação e obtenção dos valores. Como as variáveis ​​não foram definidas inicialmente, o__get() função é chamada (porque não há um valor especificado) que vê que a entrada na matriz não foi feita. Portanto, ele faz a entrada, atribui o último valor fornecido mais um (+1), incrementa a última variável de valor e retorna VERDADEIRO. Se você definir o valor:

$c->RED_LIGHT = 85;

Em seguida, a __set()função é chamada e o último valor é definido como o novo valor mais um (+1). Portanto, agora temos uma maneira bastante boa de fazer enums e elas podem ser criadas em tempo real.

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

Sei que esse é um encadeamento antigo, mas nenhuma das soluções alternativas que eu vi realmente se parece com enumerações, pois quase todas as soluções exigem que você atribua valores manualmente aos itens de enumeração ou que você passe uma matriz de chaves de enumeração para uma função. Então, eu criei minha própria solução para isso.

Para criar uma classe enum usando minha solução, basta estender essa classe Enum abaixo, criar um monte de variáveis ​​estáticas (não é necessário inicializá-las) e fazer uma chamada para yourEnumClass :: init () logo abaixo da definição da sua classe enum .

edit: Isso funciona apenas em php> = 5.3, mas provavelmente pode ser modificado para funcionar também em versões mais antigas

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

Agora você pode usar a classe SplEnum para construí-la nativamente. Conforme a documentação oficial.

SplEnum oferece a capacidade de emular e criar objetos de enumeração nativamente no 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;
}
?>

Observe que é uma extensão que deve ser instalada, mas não está disponível por padrão. Que vem em Tipos Especiais descritos no próprio site php. O exemplo acima é retirado do site PHP.


3

Finalmente, uma resposta do PHP 7.1+ com constantes que não podem ser substituídas.

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

Se você estiver usando espaços para nome, a conclusão do código deve funcionar.

No entanto, ao fazer isso, você perde a capacidade de ocultar as constantes na família de classes ( protected) ou somente na classe ( private). Por definição, tudo em um Interfaceépublic .

Manual do PHP: Interfaces


Isto não é Java. Isso funciona nos casos em que o padrão polimorfismo / Estratégia não é necessário para substituir constantes em uma classe pai.
Anthony Rutledge

2

Esta é a minha opinião sobre enum "dinâmico" ... para que eu possa chamá-lo com variáveis, ex. de um formulário.

veja a versão atualizada abaixo deste código de bloqueio ...

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

ATUALIZAÇÃO: Melhor maneira de fazer isso ...

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;

}

Ligue com

EnumCategory::${$category};

5
O problema com esse ser; EnumCategory::$sport = 9;. Bem-vindo ao museu do esporte. const é a melhor maneira de fazê-lo.
precisa

2

A resposta aceita é o caminho a seguir e é realmente o que estou fazendo por simplicidade. A maioria das vantagens da enumeração é oferecida (legível, rápida etc.). Um conceito está faltando, no entanto: digite safety. Na maioria dos idiomas, as enumerações também são usadas para restringir os valores permitidos. Abaixo está um exemplo de como a segurança de tipo também pode ser obtida usando construtores particulares, métodos de instanciação estática e verificação de tipo:

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

Poderíamos ir ainda mais longe: o uso de constantes na classe DaysOfWeek pode levar ao mau uso: por exemplo, pode-se usá-lo por engano desta maneira:

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

que está errado (chama constante inteiro). Podemos evitar isso usando variáveis ​​estáticas privadas em vez de constantes:

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

}

Obviamente, não é possível acessar constantes inteiras (esse era realmente o objetivo). O método intVal permite converter um objeto DaysOfWeek em sua representação inteira.

Observe que poderíamos ir além implementando um mecanismo de cache nos métodos de instanciação para economizar memória, caso as enumerações sejam amplamente utilizadas ...

Espero que isso ajude


2

Algumas boas soluções aqui!

Aqui está a minha versão.

  • É fortemente tipado
  • Funciona com o preenchimento automático do IDE
  • As enumerações são definidas por um código e uma descrição, onde o código pode ser um número inteiro, um valor binário, uma sequência curta ou basicamente qualquer outra coisa que você desejar. O padrão pode ser facilmente estendido para suportar outras propriedades.
  • Ele suporta comparações de valor (==) e referência (===) e funciona em instruções de opção.

Eu acho que a principal desvantagem é que os membros enum precisam ser declarados e instanciados separadamente, devido às descrições e à incapacidade do PHP de construir objetos no momento da declaração de membro estático. Acho que uma maneira de contornar isso pode ser usar a reflexão com comentários de documentos analisados.

O enum abstrato é assim:

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

Aqui está um exemplo de enum concreto:

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

Que pode ser usado assim:

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

E produz esta saída:

1: Meu primeiro valor

matriz (3) {
[1] =>
objeto (EMyEnum) # 1 (2) {
["code": "AbstractEnum": private] =>
int (1)
["description": "AbstractEnum": private] =>
string (14) "Meu primeiro valor"
}
[2] =>
objeto (EMyEnum) # 2 (2) {
["code": "AbstractEnum": private] =>
int (2)
["description": "AbstractEnum" : private] =>
string (15) "Meu segundo valor"
}
[3] =>
objeto (EMyEnum) # 3 (2) {
["code": "AbstractEnum": private] =>
int (3) string (14) "Meu terceiro valor" } }
["descrição": "ResumoEnum": privado] =>


Meu segundo valor


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

Não use reflexão. Torna extremamente difícil raciocinar sobre seu código e rastrear onde algo está sendo usado e tende a quebrar as ferramentas de análise estática (por exemplo, o que está embutido no seu IDE).


2

Um dos aspectos ausentes de algumas das outras respostas aqui é uma maneira de usar enumerações com dicas de tipo.

Se você definir sua enumeração como um conjunto de constantes em uma classe abstrata, por exemplo,

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

então você não pode digitar insinuar-lo em um parâmetro de função - por um lado, porque não é instanciável, mas também porque o tipo de ShirtSize::SMALLé int, não ShirtSize.

É por isso que enums nativas em PHP seriam muito melhores do que qualquer coisa que possamos criar. No entanto, podemos aproximar um enum mantendo uma propriedade privada que represente o valor do enum e, em seguida, restringindo a inicialização dessa propriedade a nossas constantes predefinidas. Para impedir que a enum seja instanciada arbitrariamente (sem a sobrecarga de verificar uma lista de permissões), tornamos o construtor privado.

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

Então nós podemos usar ShirtSize assim:

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

Dessa forma, a maior diferença da perspectiva do usuário é que você precisa usar um ()no nome da constante.

Uma desvantagem é que ===(que compara a igualdade de objetos) retornará false quando ==retornar true. Por esse motivo, é melhor fornecer umequals método, para que os usuários não precisem se lembrar de usar ==e não ===comparar dois valores de enumeração.

EDIT: Algumas das respostas existentes são muito semelhantes, especialmente: https://stackoverflow.com/a/25526473/2407870 .


2

Pisando na resposta de @Brian Cline, pensei em dar meus 5 centavos

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

por alguma razão, não posso chamar isso de função. Está me dizendo que essas funções não são declaradas. O que estou fazendo de errado? [classe básica Enum localizada em outro arquivo e eu estou usando include('enums.php');]. Por alguma razão, não vê funções declaradas em Enum para classes filhas ...
Andrew

Além disso ... como defini-lo a partir de string? sth like$currentSeason.set("Spring");
Andrew

1

Minha tentativa de criar uma enumeração com PHP ... é extremamente limitada, pois não suporta objetos como os valores de enumeração, mas ainda é um pouco útil ...

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

é limitado em várias direções e as respostas existentes oferecem muito mais. Eu diria que isso realmente não está adicionando nada de útil.
hakre

1

Ontem escrevi essa aula no meu blog . Eu acho que talvez seja fácil de usar em scripts 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;
    }
}

Uso:

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

2
Mas é uma boa classe e as funções dos nomes são nativas. E também translate.google.ru talvez ajude.
Arturpb

2
Use pessoal do chrome e traduza-o, se você é programador, você lê código!
Markus

8
Em geral, é sempre melhor para incluir o código dentro a resposta, em vez de ligar para um recurso externo que podem ou não podem estar lá em 'n' meses / anos, etc.
John Parker

Minha turma é muito grande e acho que ler este post será inconveniente.
Arturgspb # 8/11

Eu acho duas coisas ruins aqui: é em russo (todo programador deve saber inglês e usá-lo, mesmo nos comentários) e não está incluído aqui. Veja a ajuda sobre como incluir código enorme.
gaRex
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.