Перечисления это удобно. Порой очень удобно. Они облегчают понимание кода, а так же его написание и сопровождение. По сути перечисления – это наборы связанных по смыслу констант.
По реализации – перечисление это класс. К сожалению в PHP нету типа Enum, следовательно PHP программст не может получить все удобства Enum такие же, как, например, программист Java.
Но все же кое-что сделать можно.
Для чего оно надо?
Представьте, что у вас в коде есть какой-то набор связанных понятий, например типы какой-то сущности, или набор статусов. Так же может присутствовать соответствующий справочник в БД. И ваш код во многих местах зависит от этого набора. Например, по коду раскиданы проверки if вида:
1 2 3 4 5 6 7 8 9 10 11 12 |
if ($status == 0) { // do } else if ($status == 3) { //error } switch $type: case "awesomeType": //do break; default: //some code |
С этим кодом есть две проблемы. Во первых – магические числа. Что означают эти вот 0, 3, awesomeType? Для начала можно было бы вынести эти значения в константы класса.
Но что если эти же значения используются в рамках модуля или проекта? Тогда мы можем вынести константы в отдельный класс, и назвать его MyModuleEnumTypes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class MyModuleEnumTypes { const STATUS_NEW = 0; const STATUS_OK = 2; const STATUS_ERR = 3; } ... //сипользуем где-то if($status == MyModuleEnumTypes::STATUS_NEW) { } |
Отлично. Теперь у констант есть осмысленное имя и они повторно используемы.
Так же мы решили вторую проблему. Что если надо будет однажды сменить какое-то значение? Теперь оно меняется в одном месте в классе перечислении.
На этом можно было бы остановиться, но все же хочется добавить себе удобств, примерно таких как в Java. Каким образом мы можем получить массив значений перечисления? Имен ключей? Проверить значение на допустимость в конкретном перечислении?
Для всего этого я использую базовый класс BaseEnum.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
<?php namespace Model\Enum; /** * Description of BaseEnum * * @author seyfer */ abstract class BaseEnum { /** * cache reflection request * @var array */ protected static $constCache = []; public static $available = []; /** * get class constant array * key = constant name, value = value * @return array */ public static function getConstants() { if (empty(static::$constCache[get_called_class()])) { $reflect = new \ReflectionClass(get_called_class()); static::$constCache[get_called_class()] = $reflect->getConstants(); } return static::$constCache[get_called_class()]; } /** * is there constant with name * @param string $name * @param boolean $strict * @return boolean */ public static function isValidName($name, $strict = false) { $constants = static::getConstants(); if ($strict) { return array_key_exists($name, $constants); } $keys = array_map('strtolower', array_keys($constants)); return in_array(strtolower($name), $keys); } /** * is there cinstant with this value * @param string $value * @return boolean */ public static function isValidValue($value) { $values = array_values(static::getConstants()); return in_array($value, $values, $strict = true); } public static function isAvailable($name) { return in_array($name, static::$available); } public static function getAvailable() { return static::$available; } public static function setAvailable($available) { static::$available = $available; } } |
Пройдемся по коду. Как мы видим, для доступа к списку констант используется ReflectionClass от get_called_class(), а не $this, такм образом мы берем класс перечисления наследника.
Далее сохраняем рез-т по имени класса в кеш. Остальные методы – это просто работа с полученным массивом.
Пример использования:
1 2 3 4 5 |
if (\Model\Enum\Domain::isValidValue($path)) { $this->setDomain($path); } |
А что это за статическая переменная $available ?
Тут я добавил некоторую бузнес логику, которая позволяет определить, какие из значений перечисления активны, а какие нет.
На примере конкретного перечисления понятно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?php namespace Model\Enum; use Model\Enum\BaseEnum; /** * Description of Currency * * @author seyfer */ class Currency extends BaseEnum { const USD = 'USD'; const EUR = 'EUR'; const RUB = 'RUB'; public static $available = array(self::USD, self::RUB); } |
Кроме всего списка, я опеределяю список доступных. Теперь если метод isValidValue(‘EUR’) выдаст TRUE, то метод isAvailable(‘EUR’) выдаст FALSE.
Вот так можно получить, хотя бы частично, функционал перечислений Enum в PHP и сделать свой код чище и понятнее.
Стоит еще сказать об полуофициальном расширении SPL Types. Устанавливается оно через PECL и псоле установки становится доступным класс SplEnum.
Почитать документацию можно по ссылке http://php.net//manual/ru/class.splenum.php.
На данный момент версия расширения 0.1.0 и лично я не стал использовать его в продакшене. Так же не известно, будет ли оно своевременно обновляться с версиями PHP.