Рекомендации по использованию битовых флагов в 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-я собираюсь остаться с битовым целым числом на данный момент-в основном потому, что я почти закончил с программным обеспечением и не хочу это менять больше. Это вообще не меняет читаемость.