Все началось с того, что мне не понравилось что я не могу жестко типизировать входные параметры в методы моих классов, а именно типизировать константы. Из-за отсутствия enum в php (сейчас я говорю о php 5) мы сталкиваемся с неудобными вещами – мы храним данные в массивах с комментариями «не изменять», «это не трогать», но это по сути своей не правильно!
После недолгих поисков я не нашел стандартного решения своей проблемы, зато познакомился с некоторыми интересными статьями про то как люди борятся с этой проблемой. Вот эти статьи:
Все понятно и логично, но вот проблема – конечная производительность и простота использования и расширяемость. Итак немного позаимствовав идею, расширив её и доработав я получил интересный результат. Класс реализует паттерн Одиночка (Singleton) для уменьшения расхода памяти, но конечно лишние проверки увеличивают время выполнения. Это базовый абстрактный класс Enum и исключение для него (это стоит просто скопировать и ничего не исправлять в нём):
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 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; } } |
Ну и теперь класс наследник. В неё нужно только задать константы и не писать никаких методов – не усложняйте код ненужными вещами – каждый класс должен быть написать для одной (абстрактной) вещи! Не нужно заставлять класс Enum еще и борщ заставлять варить. Итак класс-наследник:
1 2 3 4 5 6 7 8 | /** *Типы рабочего дня сотрудников */ class enumWorkType extends Enum { const FULL = 0; const SHORT = 1; } |
Замеры быстродействия и затрат памяти
Больше ничего от от перечислений не надо. Ну и теперь замеры скорости работы и затрат памяти. Тесты довольно простые и не претендуют на полноту, но даже по ним все становится предельно ясно.
Вариант 1. Простое присваивание значений констант нашего наследованного класса enumWorkType элементам массива при 100 000 итерациях:
1 2 3 4 5 6 | $a = array(); for($i = 0; $i < 100000 ;$i++) { $a[] = enumWorkType::FULL; $a[] = enumWorkType::SHORT; } |
Запускаем в консоли:
1 2 3 4 5 6 | artur@artur-desktop:$ time php common.php Пик затрат памяти: 44800 Kb (43,75 Mb) real 0m0.256s user 0m0.190s sys 0m0.060s |
Неплохо. Оговорюсь, что пик замера памяти у меня считает функция такого плана:
1 2 3 4 5 6 7 8 9 | function getPeakMemUsage() { $message = ''; $memUsage = memory_get_peak_usage(true); $message .= 'Пик затрат памяти: '; $message .= ($memUsage / 1024).' Kb ('.($memUsage / 1024 / 1024).' Mb)'; $message .= "\r\n"; return $message; } |
Вариант 2. Присваивание элементам массива не значение констант класса, а объектов-констант типа enumWorkType. Все это делаем так же при 100 000 итерациях:
1 2 3 4 5 6 | $a = array(); for($i = 0; $i < 100000 ;$i++) { $a[] = enumWorkType::get(enumWorkType::FULL); $a[] = enumWorkType::get(enumWorkType::SHORT); } |
Запускаем и смотрим:
1 2 3 4 5 6 | artur@artur-desktop:$ time php common.php Пик затрат памяти: 29184 Kb (28,5 Mb) real 0m1.059s user 0m0.640s sys 0m0.060s |
Хм.. первое, что сразу бросается в глаза – заметное снижение потребления оперативной памяти аж на 35% по моим подсчетам! Но теперь обратите внимание на время выполнения – работа через класс Enum замедляет скорость в 4 раза, но это при 100 000 итерациях по 2 присваивания в каждом! Думаю в принципе это не так плохо. Далее я сделаю замеры с другим количеством итереаций. Итак:
| Количество итераций | Стандартное присваивание | Класс Enum | ||||
| 500 000 |
|
|
||||
| 1 000 000 |
|
|
||||
| 5 000 000 |
|
|
По мим расчетам класс Enum экономит в этом тесте в среднем ~38% оперативной памяти, просто так потребляемой стандартным методом, но при этом увеличивает время работы на 65-75%. Но обратите внимание на объемы! Для моей задачи потребление оперативной памяти играет существенную роль, так как много данных нужно обрабатывать. В конечном итоге вам выбирать.
Плюсы class Enum
Допустим вам надо типизировать входящий параметр функции:
1 2 3 4 | function setWorkTimeType($type) { ... } |
В этом примере все неплохо, но вам потребуется валидировать этот параметр везде, где вы его используете – это увеличивает риск возникновения ошибки. А если же вы сделаете так, то интерпритатор не пропустит ничего кроме объекта типа enumWorkTime:
1 2 3 4 | function setWorkTimeType(enumWorkTime $type) { ... } |
Это, пожалуй, главный плюс использования класса Enum который позволяет избавится от лишнего кода и обеспечить 100% правильность!
1 2 3 4 5 6 7 8 9 | function setWorkTimeType(enumWorkType $type){ echo 'Good'; } $enum = enumWorkType::get(enumWorkType::SHORT); setWorkTimeType($enum); // Напечатает "Good" $enum = 1; setWorkTimeType($enum); // Остановит программу и выдаст: PHP Catchable fatal error: Argument 1 passed to setWorkTimeType() must be an instance of enumWorkType, // integer given, called in /var/www/*.php on line 50 and defined in /var/www/*.php on line 45 |
К сожалению похожих по тематике статей пока нет.


0 Comments.