Рекомендации по использованию битовых флагов в PHP
Я пишу небольшое приложение в PHP + MySQL и дошел до точки, где есть объект, который имеет пару (8 до сих пор, но не ожидается увеличения) флагов, связанных с ним. Флаги в значительной степени не связаны, хотя есть некоторые комбинации, которые не имеют смысла. Объект представляет строку в БД (имеет некоторые методы для ее сохранения/загрузки), поэтому вопрос также относится к выбору метода хранения.
вопрос - как лучше представлять их как в коде, так и в БД? Я могу придумать несколько способов:
один из способов сохранить их в БД - в одном целочисленном поле в виде побитовых флагов. На стороне PHP я могу представить несколько способов их представления:
- просто экспортируйте целочисленное значение и определите пару констант флага; пусть каждое место, которому нужны флаги, делает свою собственную побитовую магию;
- определить методы класса
GetFlag()
,SetFlag()
иUnsetFlag()
это делает побитовое magic на частной целочисленной переменной; эти методы затем будут переданы одной из констант флага в качестве параметров. - определение методов
GetFlagA()
,GetFlagB()
etc. (вместе с set и Unset аналогами); - определите кучу переменных-членов, каждая из которых представляет один флаг; установите их при загрузке из БД и соберите их при сохранении.
- создайте переменную-член, которая является массивом всех значений флага. Используйте предопределенные константы в качестве индексов массива для доступа к каждому флаг. Также заполните/соберите массив при загрузке / сохранении.
другой способ-хранить их в БД как отдельные битовые поля. В PHP, который затем будет переведен на несколько переменных-членов. ИМХО это усложнило бы запросы.
и последним способом было бы определить другую таблицу для всех флагов и промежуточную таблицу для отношений "многие ко многим" между флагами и исходными объектами. ИМХО-самое грязное из всех решений, учитывая, что будет только как 3 таблицы в противном случае.
Я не сделал много разработки PHP, поэтому я не знаю, какой будет лучшая практика. В C# я бы, вероятно, сохранил их как побитовые флаги и сделал свойства, которые выполняют побитовую магию на частном целочисленном. Но PHP не имеет свойств (я использую последнюю стабильную версию)...
7 ответов
в своем модель объект имеет 8 логических свойств. Это подразумевает 8 логических столбцов (TINYINT для MySQL) в таблице базы данных и 8 методов getter/setter в вашем объекте. Простой и обычный.
переосмыслите свой текущий подход. Представьте, что будет говорить следующий парень, который должен поддерживать эту вещь.
CREATE TABLE mytable (myfield BIT(8));
хорошо, похоже, у нас здесь будут двоичные данные.
INSERT INTO mytable VALUES (b'00101000');
подождите, кто-нибудь, скажите мне еще раз что означает каждый из этих 1 и 0.
SELECT * FROM mytable;
+------------+
| mybitfield |
+------------+
| ( |
+------------+
что?
SELECT * FROM mytable WHERE myfield & b'00101000' = b'00100000';
WTF!? WTF!?
ударяет себя в лицо
-- между тем, в альтернативной вселенной, где феи играют с единорогами, а программисты не ненавидят DBAs... --
SELECT * FROM mytable WHERE field3 = 1 AND field5 = 0;
счастье и солнечный свет!
Если вам действительно нужно использовать битовые флаги, используйте столбец SET для их хранения в БД, ассоциативный массив в коде и методы включения/выключения флагов. Добавьте отдельные методы для преобразования массива в/из одного целого числа, Если вам это нужно в этом формате.
нет ничего, что можно получить с помощью низкоуровневых битовых операторов, поэтому вы можете также сделать код читаемым.
я закодировал эту простую функцию, чтобы заполнить пробел между перечислениями PHP и MySQL:
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.
предполагая, что вы используете версию MySQL после 5.0.5, вы можете определить столбец как бит[количество бит здесь]. Что касается стороны PHP, я бы, вероятно, пошел с подходом Get/SetFlagA, Get/SetFlagB, нет необходимости в методе unset, поскольку вы можете просто взять логическое значение метода set.
Я бы держался подальше от побитовых операций, поскольку становится очень трудно узнать, какие флаги установлены, глядя только на базу данных (если вы не пытаетесь быть супер эффективным по какой-то причине). Между двумя другими вариантами, я думаю, это зависит от того, сколько значения имеет каждый из этих флагов. Учитывая, что их уже 8, я бы наклонился к другому столу со многими отношениями между ними (извините за выбор вашего наименее любимого).
один из способов сохранить их в БД в одно целое поле как побитовое флаги.
Если вы хотите это сделать, вы можете использовать _ _ get и _ _ set методы перегрузки таким образом, вы можете просто получить поле, а затем выполнить побитовую арифметику по мере необходимости.
ОК, подумав об этом, я решил пойти с одной переменной на флаг. Я мог бы использовать метод getter/setter, но я не использую их нигде в своем коде, поэтому это было бы не в стиле. Кроме того, таким образом, я также абстрагируюсь от метода хранения БД и могу легко изменить его при необходимости.
Что касается DB-я собираюсь остаться с битовым целым числом на данный момент-в основном потому, что я почти закончил с программным обеспечением и не хочу это менять больше. Это вообще не меняет читаемость.