Я знаю, что в PHP еще нет встроенных перечислений. Но я привык к ним из мира Java. Я бы хотел использовать перечисления как способ задавать предопределенные значения, которые могли бы понять функции автозаполнения в IDE.
Константы делают это, но возникает проблема конфликта пространств имен, и (или, на самом деле, потому что) они глобальны. Массивы не имеют проблемы пространства имен, но они слишком неопределенны, могут быть перезаписаны во время выполнения, и IDE редко знают, как автозаполнить их ключи без дополнительных аннотаций статического анализа или атрибутов.
Есть ли какие-то решения/обходные пути, которые вы обычно используете? Кто-нибудь помнит, были ли у ребят из PHP какие-либо мысли или решения по поводу перечислений?
Ответ 1
В зависимости от условий использования, я бы обычно использовал что-то простое, например, следующее:
abstract class DaysOfWeek {
const Sunday = 0;
const Monday = 1;
// и т.д.
}
$today = DaysOfWeek::Sunday;
Однако в других случаях может потребоваться более тщательная проверка констант и значений. На основе комментариев ниже о reflection и некоторых других замечаний, вот расширенный пример, который может лучше служить гораздо более широкому кругу вариантов:
abstract class BasicEnum {
private static $constCacheArray = NULL;
private static function getConstants() {
if (self::$constCacheArray == NULL) {
self::$constCacheArray = [];
}
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$constCacheArray[$calledClass];
}
public static function isValidName($name, $strict = false) {
$constants = self::getConstants();
if ($strict) {
return array_key_exists($name, $constants);
}
$keys = array_map('strtolower', array_keys($constants));
return in_array(strtolower($name), $keys);
}
public static function isValidValue($value, $strict = true) {
$values = array_values(self::getConstants());
return in_array($value, $values, $strict);
}
}
Создав простой класс enum, который расширяет BasicEnum, вы теперь можете использовать методы таким образом для простой проверки ввода:
abstract class DaysOfWeek extends BasicEnum {
const Sunday = 0;
const Monday = 1;
const Tuesday = 2;
const Wednesday = 3;
const Thursday = 4;
const Friday = 5;
const Saturday = 6;
}
DaysOfWeek::isValidName('Humpday'); // false
DaysOfWeek::isValidName('Monday'); // true
DaysOfWeek::isValidName('monday'); // true
DaysOfWeek::isValidName('monday', $strict = true); // false
DaysOfWeek::isValidName(0); // false
DaysOfWeek::isValidValue(0); // true
DaysOfWeek::isValidValue(5); // true
DaysOfWeek::isValidValue(7); // false
DaysOfWeek::isValidValue('Friday'); // false
В качестве дополнительного примечания: каждый раз, когда я использую reflection хотя бы один раз в статическом/константном классе, где данные не будут меняться (например, в перечислении), я кэширую результаты этих вызовов reflections, так как использование новых объектов каждый раз в конечном итоге будет иметь заметное влияние на производительность (хранение в ассоциативном массиве для нескольких перечислений).
Теперь, когда большинство людей, наконец, обновились по крайней мере до 5.3, и SplEnum доступен, это, безусловно, также жизнеспособный вариант — если вы не возражаете против традиционно неинтуитивного понятия наличия фактических инстанций перечислений в вашей кодовой базе. В приведенном выше примере BasicEnum и DaysOfWeek вообще не могут быть инстанцированы, да и не должны.
Ответ 2
Как насчет констант класса?
<?php
class YourClass {
const SOME_CONSTANT = 1;
public function echoConstant() {
echo self::SOME_CONSTANT;
}
}
echo YourClass::SOME_CONSTANT;
$c = new YourClass;
$c->echoConstant();
Ответ 3
Ответ выше является недоделанным. Однако если вы расширите его двумя разными способами, то какое бы расширение ни было сделано первым, в результате вызова функций будет создан кэш. Этот кэш затем будет использоваться всеми последующими вызовами, независимо от того, какое расширение инициирует вызов ...
Чтобы решить эту задачу, замените переменную и первую функцию на:
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];
}
Ответ 4
Я предпочитаю использовать метод const, который так или иначе использовался в других ответах здесь:
abstract class Enum {
const NONE = null;
final private function __construct() {
throw new NotSupportedException(); //
}
final private function __clone() {
throw new NotSupportedException();
}
final public static function toArray() {
return (new ReflectionClass(static::class))->getConstants();
}
final public static function isValid($value) {
return in_array($value, static::toArray());
}
}
Пример перечисления:
final class ResponseStatusCode extends Enum {
const OK = 200;
const CREATED = 201;
const ACCEPTED = 202;
// ...
const SERVICE_UNAVAILABLE = 503;
const GATEWAY_TIME_OUT = 504;
const HTTP_VERSION_NOT_SUPPORTED = 505;
}
Использование Enum в качестве базового класса, от которого расширяются все остальные перечисления, позволяет использовать вспомогательные методы, такие как toArray, isValid и так далее. На мой взгляд, типизированные перечисления (и управление их экземплярами) становятся слишком запутанными.
Если бы существовал магический метод __getStatic (и, желательно, магический метод __equals), многое из этого можно было бы смягчить с помощью своего рода многотонового шаблона.
(Нижеследующее является гипотетическим; это не будет работать, хотя, возможно, когда-нибудь это произойдет)
final class TestEnum {
private static $_values = [
'FOO' => 1,
'BAR' => 2,
'QUX' => 3,
];
private static $_instances = [];
public static function __getStatic($name) {
if (isset(static::$_values[$name])) {
if (empty(static::$_instances[$name])) {
static::$_instances[$name] = new static($name);
}
return static::$_instances[$name];
}
throw new Exception(sprintf('Invalid enumeration value, "%s"', $name));
}
private $_value;
public function __construct($name) {
$this->_value = static::$_values[$name];
}
public function __equals($object) {
if ($object instanceof static) {
return $object->_value === $this->_value;
}
return $object === $this->_value;
}
}
$foo = TestEnum::$FOO; // object(TestEnum)#1 (1) {
// ["_value":"TestEnum":private]=>
// int(1)
// }
$zap = TestEnum::$ZAP; // Не пойманное исключение 'Exception' с сообщением
// 'Недопустимый член перечисления, "ZAP"
$qux = TestEnum::$QUX;
TestEnum::$QUX == $qux; // true
'hello world!' == $qux; // false
Web