Лучшие практики для бит-флагов в PHP
Я пишу небольшое приложение в PHP + MySQL и дошел до того момента, когда есть объект, у которого есть пара (до сих пор, но не ожидается увеличения) связанных с ним флагов. Флаги довольно несвязаны, хотя есть несколько комбинаций, которые не имели бы никакого смысла. Объект представляет строку в БД (имеет некоторые методы для ее сохранения и загрузки), поэтому вопрос также относится к выбору метода хранения.
Вопрос - как наилучшим образом представить их как в коде, так и в БД? Я могу представить несколько способов:
Один из способов хранения их в БД находится в одном целочисленном поле в виде поразрядных флагов. На стороне PHP я могу представить несколько способов их представления:
- Просто экспортируйте целочисленное значение и определите пару констант флага; Пусть каждое место, где нужны флаги, выполняет свою собственную поразрядную магию;
- Определить методы класса
GetFlag()
, SetFlag()
и UnsetFlag()
, которые выполняют побитную магию для частной целочисленной переменной; Эти методы затем будут переданы одной из констант флага в качестве параметров.
- Определить методы
GetFlagA()
, GetFlagB()
и т.д. (вместе с Set и Unset counterparts);
- Определите группу переменных-членов, каждая из которых представляет один флаг; Установите их при загрузке из БД и соберите их при сохранении.
- Создайте переменную-член, являющуюся массивом всех значений флага. Используйте предопределенные константы в качестве индексов массива для доступа к каждому флагу. Также заполните/собрайте массив при загрузке/сохранении.
Другой способ - сохранить их в БД как отдельные поля BIT. В PHP, который затем переводится на несколько переменных-членов. ИМХО это усложнит запросы.
И последним способом было бы определить анонированную таблицу для всех флагов и промежуточную таблицу для отношений "многие ко многим" между флагами и исходными объектами. ИМХО - самый грязный из всех решений, учитывая, что в противном случае будет только 3 таблицы.
Я не делал много PHP-разработки, поэтому я не знаю, какой будет лучшая практика. В С# я, вероятно, сохранил бы их как побитовые флаги и делал бы свойства, которые выполняют побитную магию на частном целое. Но у PHP нет свойств (я использую последнюю стабильную версию)...
Ответы
Ответ 1
В model объект имеет 8 булевых свойств. Это означает 8 столбцов boolean (TINYINT для MySQL) в вашей таблице базы данных и 8 методов getter/setter в вашем объекте. Простой и обычный.
Переосмыслите свой нынешний подход. Представьте себе, что говорит следующий парень, который должен поддерживать эту вещь.
CREATE TABLE mytable (myfield BIT(8));
ОК, похоже, что у нас будут бинарные данные, которые здесь происходят.
INSERT INTO mytable VALUES (b'00101000');
Подождите, кто-нибудь снова скажет мне, что означает каждый из этих 1s и 0s.
SELECT * FROM mytable;
+------------+
| mybitfield |
+------------+
| ( |
+------------+
Что?
SELECT * FROM mytable WHERE myfield & b'00101000' = b'00100000';
WTF!? WTF!?
ударяет себя в лицо
- тем временем, в альтернативной вселенной, где феи играют с единорогами, а программисты не ненавидят администраторов баз данных... -
SELECT * FROM mytable WHERE field3 = 1 AND field5 = 0;
Счастье и солнце!
Ответ 2
Если вам действительно нужно использовать битовые флаги, используйте столбец SET для их хранения в БД, ассоциативный массив в коде и методы включения/выключения флагов. Добавьте отдельные методы для преобразования массива в/из одного целого числа, если оно вам нужно в этом формате.
Ничего не получается, если использовать низкоуровневые битовые операторы, чтобы вы могли читать код.
Ответ 3
Я закодировал эту простую функцию, чтобы заполнить пробел между PHP и MySQL ENUM:
function Enum($id)
{
static $enum = array();
if (func_num_args() > 1)
{
$result = 0;
if (empty($enum[$id]) === true)
{
$enum[$id] = array();
}
foreach (array_unique(array_map('strtoupper', array_slice(func_get_args(), 1))) as $argument)
{
if (empty($enum[$id][$argument]) === true)
{
$enum[$id][$argument] = pow(2, count($enum[$id]));
}
$result += $enum[$id][$argument];
}
return $result;
}
return false;
}
Использование:
// sets the bit flags for the "user" namespace and returns the sum of all flags (15)
Enum('user', 'anonymous', 'registed', 'active', 'banned');
Enum('user', 'anonymous'); // 1
Enum('user', 'registed', 'active'); // 2 + 4 = 6
Enum('user', 'registed', 'active', 'banned'); // 2 + 4 + 8 = 14
Enum('user', 'registed', 'banned'); // 2 + 8 = 10
Вам просто нужно убедиться, что вы указали нумерованный список в том же порядке MySQL ENUM.
Ответ 4
Предполагая, что вы используете версию MySQL после 5.0.5, вы можете определить столбец как BIT [количество бит здесь]. Что касается стороны PHP, я бы, вероятно, пошел бы с помощью метода Get/SetFlagA, Get/SetFlagB, нет необходимости в отключенном методе, поскольку вы можете просто взять логический метод set.
Ответ 5
Я бы держался подальше от побитовых операций, так как очень сложно узнать, какие флаги установлены, глядя только в базу данных (если вы по какой-то причине не пытаетесь быть суперэффективными). Между двумя другими вариантами я думаю, что это зависит от того, насколько значителен каждый из этих флагов. Учитывая, что их уже 8, я склоняюсь к другому столу со многими и многими отношениями между ними (извините за выбор наименее любимого).
Ответ 6
Один способ хранения их в БД находится в одно целое поле как поразрядное флаги.
Если вы хотите сделать это, вы можете использовать методы перегрузки __get и __set чтобы вы могли просто извлечь поле, а затем сделать поразрядная арифметика по мере необходимости.
Ответ 7
Хорошо, подумав еще об этом, я решил пойти с одной переменной-членом на один флаг. Я мог бы использовать метод метода getter/setter, но я не использую их нигде в моем коде, поэтому это было бы не в стиле. Плюс, таким образом, я также абстрагируюсь от метода хранения БД и позже могу легко изменить это при необходимости.
Что касается БД - я собираюсь остаться с побитовым целым на данный момент - главным образом потому, что я почти закончил с программным обеспечением и больше не хочу его менять. Это не изменяет читаемость вообще.