Лучшие практики для битовых флагов в PHP

Я пишу небольшое приложение на PHP + MySQL и дошел до того, что есть объект, у которого есть пара (пока 8, но не ожидается увеличения) флагов, связанных с ним. Флаги в значительной степени не связаны, хотя есть некоторые комбинации, которые не имеют смысла. Объект представляет собой строку в БД (также имеет некоторые методы для ее сохранения/загрузки), поэтому вопрос также относится к выбору метода хранения.

Вопрос в том, как лучше всего представить их как в коде, так и в БД? Я могу придумать несколько способов:

Один из способов сохранить их в БД — в одном целочисленном поле в виде побитовых флагов. На стороне PHP я могу представить несколько способов их представления:

  • Просто экспортируйте целочисленное значение и определите пару констант флагов; Пусть каждое место, которому нужны флаги, творит свою собственную побитовую магию;
  • Определите методы класса GetFlag(), SetFlag() и UnsetFlag(), которые выполняют побитовую магию над закрытой целочисленной переменной; Затем этим методам будет передана одна из констант флага в качестве параметров.
  • Определите методы GetFlagA(), GetFlagB() и т. д. (вместе с аналогами Set и Unset);
  • Определите группу переменных-членов, каждая из которых представляет один флаг; Установите их на загрузку из БД и соберите при сохранении.
  • Создайте переменную-член, которая представляет собой массив всех значений флага. Используйте предопределенные константы в качестве индексов массива для доступа к каждому флагу. Также заполнять/собирать массив при загрузке/сохранении.

Другим способом было бы хранить их в БД как отдельные поля BIT. В PHP это будет преобразовано в несколько переменных-членов. ИМХО, это усложнит запросы.

И последний способ — определить другую таблицу для всех флагов и промежуточную таблицу для отношения «многие ко многим» между флагами и исходными объектами. ИМХО, самое беспорядочное из всех решений, учитывая, что в противном случае было бы всего 3 таблицы.

Я не занимался разработкой PHP, поэтому не знаю, какой будет наилучшая практика. В C# я бы, вероятно, сохранил их как побитовые флаги и создал свойства, которые выполняют побитовую магию для частного целого числа. Но у PHP нет свойств (я использую последнюю стабильную версию)...


person Vilx-    schedule 09.01.2009    source источник


Ответы (7)


В вашей модели объект имеет 8 логических свойств. Это подразумевает 8 логических (TINYINT для MySQL) столбцов в вашей таблице базы данных и 8 методов получения/установки в вашем объекте. Простой и обычный.

Переосмыслите свой текущий подход. Представьте, что скажет следующий парень, который должен обслуживать эту штуку.

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';

ВТФ!? ВТФ!?

наносит себе удар ножом в лицо


-- тем временем, в альтернативной вселенной, где феи играют с единорогами, а программисты не ненавидят администраторов баз данных... --

SELECT * FROM mytable WHERE field3 = 1 AND field5 = 0;

Счастья и солнца!

person Preston    schedule 10.01.2009
comment
На самом деле...SELECT * FROM mytable WHERE field3 AND !field5; - person Christian; 14.02.2011
comment
Просто самый юмористический ответ на эту тему, который я когда-либо видел. Спасибо за смех, проголосуйте. - person damianb; 28.11.2011
comment
ХАХАХА... WTF--- Хороший ответ! - person Philip F; 05.12.2012
comment
В флагах нет ничего плохого... они расширяемые. Вместо битов я использую обычные поля int. Во вселенной, где программисты ненавидят администраторов баз данных, вам никогда не придется добавлять в таблицу еще одно поле для нового флага. Между тем во вселенной, где программисты любят администраторов баз данных, каждый раз, когда новый клиент или компания хотят добавить новые флаги, вы должны бежать к администратору баз данных и просить его добавить еще одно поле в таблицу. - person Mick; 04.05.2017
comment
@Christian Я бы не стал использовать ! в MySQL, у них есть некоторые ... странности. - person Brian Leishman; 19.12.2018

Если вам действительно нужно использовать битовые флаги, используйте столбец SET для их хранения в БД, ассоциативный массив в коде и методы для включения/выключения флагов. Добавьте отдельные методы для преобразования массива в/из одного целого числа, если вам это нужно в этом формате.

Низкоуровневые битовые операторы ничего не дадут, так что вы можете сделать код читабельным.

person Community    schedule 09.01.2009
comment
Точно. Вопрос только в том, что более читабельно? Кроме того, я хотел бы избежать определения значений на стороне БД, потому что тогда любые изменения должны были бы отражаться как в БД, так и в PHP. Изменения в побитовых флагах будут касаться только PHP. - person Vilx-; 09.01.2009
comment
Если вы рассмотрели оба метода и не можете определить, какой из них более удобочитаем, то решение кажется произвольным. Попробуйте показать примеры другим разработчикам или представьте, что вам нужно повторно ознакомиться с кодом после того, как вы его забыли, чтобы получить лучшее представление о его удобочитаемости. - person Phantom Watson; 09.01.2009
comment
Вилкс: Не так. Изменения должны быть отражены в базе данных при любом методе. - person Preston; 10.01.2009

Я написал эту простую функцию, чтобы заполнить пробел между 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.

person Alix Axel    schedule 23.06.2010

Предполагая, что вы используете версию MySQL после 5.0.5, вы можете определить столбец как BIT[количество бит здесь]. Что касается стороны PHP, я бы, вероятно, выбрал подход Get/SetFlagA, Get/SetFlagB, нет необходимости в методе unset, поскольку вы можете просто взять логическое значение в метод set.

person Kevin Loney    schedule 09.01.2009

Я бы держался подальше от побитовых операций, так как становится очень трудно узнать, какие флаги установлены, глядя только на базу данных (если вы по какой-то причине не пытаетесь быть суперэффективным). Между двумя другими вариантами, я думаю, это зависит от того, сколько значения имеет каждый из этих флагов. Учитывая, что их уже 8, я бы склонился к другой таблице с отношением «многие ко многим» между ними (извините, что выбрал наименее любимый).

person Brian Fisher    schedule 09.01.2009

Один из способов сохранить их в БД — в одном целочисленном поле в виде побитовых флагов.

Если вы хотите сделать это, вы можете использовать __get и __set методы перегрузки, чтобы может просто получить поле, а затем выполнить побитовую арифметику по мере необходимости.

person Zoredache    schedule 09.01.2009
comment
Да, я забыл упомянуть об этой возможности. Это еще один, который мне не нравится, потому что он в значительной степени портит завершение кода в IDE и, ИМХО, делает код еще менее читаемым. - person Vilx-; 09.01.2009

Хорошо, подумав еще немного об этом, я решил использовать одну переменную-член для каждого флага. Я мог бы использовать метод getter/setter, но я больше нигде не использую их в своем коде, так что это было бы не в стиле. Кроме того, таким образом я также абстрагируюсь от метода хранения БД и позже могу легко изменить его, если это необходимо.

Что касается БД - я пока останусь с побитовым целым числом - в основном потому, что я почти закончил с программным обеспечением и больше не хочу его менять. Это никак не влияет на читабельность.

person Vilx-    schedule 09.01.2009