Так как это поможет нам?
Побитовый подход
Что ж, глядя на разрешения издалека, мы можем представить состояние всех столбцов одновременно одним двоичным числом. Если мы можем представить все столбцы одновременно одним двоичным числом, это означает, что мы можем также представить его одним целым числом при переводе в десятичное число!
Если бы у нас был один столбец permissions
14
1110
А какие 3 наши из 4?
Представьте себе следующее отображение разрешений:
ИЗМЕНИТЬ РАЗРЕШЕНИЯ |
ПРОФИЛЬ СОЗДАТЬ |
РЕДАКТИРОВАНИЕ ПРОФИЛЯ |
ПРОФИЛЬ УДАЛИТЬ |
ПРОЕКТ СОЗДАТЬ |
ПРОЕКТ РЕДАКТИРОВАНИЯ |
ПРОЕКТ УДАЛИТЬ |
ПРОЕКТ ПУБЛИКАЦИИ |
Законченное редактирование |
Законченное удаление |
512 |
256 |
128 |
64 |
32 |
16 |
8 |
4 |
2 |
1 |
Число 14 в двоичном виде — 1110, но число нулей слева не имеет значения, поэтому мы можем дополнить его, пока не достигнем числа разрешений в таблице: 0000001110. Это по-прежнему 14, только представитель разрешений из таблицы выше. Для всех намерений и целей, 0000001110 === 1110.
В соответствии с этим мы видим, что учетная запись с разрешением 14
DRAFT_DELETE
DRAFT_PUBLISH
FINISHED_EDIT
Конечно, это не совсем соответствует настройке прав доступа в реальном мире, но это всего лишь пример, с помощью которого мы можем экстраполировать, что если бы у кого-то было 1111111111, у них были бы ВСЕ разрешения (вероятно, пользователь-администратор). В десятичном формате это 1023. Таким образом, кто-то со значением 1023
permissions
Но как бы мы проверили это в нашем коде? Другими словами, как мы можем узнать, установлен ли бит разрешения (1) или нет (0), особенно если число хранится как десятичное, а не двоичное?
Это то, для чего нужны побитовые операторы — в частности, одиночный амперсанд &
побитовый и .
MySQL поддерживает это так:
SELECT * FROM user WHERE id = 5 AND 512 & permissions
Это буквально переводится как «выбрать всех пользователей с идентификатором 5, у которых также для 512-битного permissions
Вы можете проверить другие биты, просто изменив их значение: 256, 128, 64, 32, 16, 8, 4, 2 или 1.
[Опционально] примечание «давайте разберемся»
Пропустите этот разделенный раздел, если вы не хотите знать, как работает этот оператор или подобные операторы, а просто хотите продолжить с примером.
Когда мы говорим « AND 512 & permissions
Следовательно, 512 & permissions
Мы знаем, что любое ненулевое значение, будь то целое число, логическое значение, которое говорит «true», или строка, которая не является пустой, на самом деле считается «true». Так что 512 это правда. 1 верно. 0 ложно. 128 это правда. И т.п.
512 является целым числом 10, а permissions
Побитовый и фактически смотрит на поперечное сечение этих двух чисел, и возвращает биты, которые установлены (1) в обоих из них. Итак, если число 512 равно 1000000000, и если значение разрешений равно 1023, то при преобразовании в двоичный код это 1111111111. Сечение этих значений возвращает 1000000000, поскольку в обоих числах установлен только самый левый бит. Когда мы конвертируем это обратно в десятичное число, это 512, что считается true
Это на самом деле логические, а не арифметические операторы в том смысле, что они проверяют правильность на основе условия. Если у нас есть числа 1110 и 1010, вот что они производят, учитывая разные побитовые операторы:
— |
& |
| |
^ |
~ |
Операнд А |
1110 |
1110 |
1110 |
1110 |
Операнд Б |
1010 |
1010 |
1010 |
/ |
Результат |
1010 |
1110 |
0100 |
0001 |
-
&
-
|
возвращает двоичное число со всеми установленными битами, которые установлены в любом операнде.
-
^
-
~
Есть также операторы побитового сдвига: сдвиг влево <<
>>
Они резко изменяют значения двоичных чисел, буквально перемещая все установленные биты на одно место вправо или влево. Их использование в нашем контексте сомнительно, поэтому мы не будем их здесь рассматривать.
И в PHP мы можем проверить, установлен ли бит так:
if (1023 & 1) {
}
Но это действительно, очень трудно расшифровать — просто смотреть на необработанные числа не очень читабельно или понятно. Итак, в PHP лучше использовать константы, определяющие разрешения как биты, и извлекающие целочисленное значение разрешения из столбца. Затем вы получите что-то вроде этого:
if ($user->permissions & \MyNamespace\Role::FINISHED_DELETE) {
}
Здесь мы предполагаем, что у нас есть класс \MyNamespace\Role
const FINISHED_DELETE = 1;
const FINISHED_EDIT = 2;
const DRAFT_PUBLISH = 8;
...
const CHANGE_PERMISSIONS = 512;
Внезапно у вас есть действительно простой способ хранения нескольких разрешений на пользователя без использования дополнительных таблиц и создания ненужных накладных расходов.
Итак, как мы можем сохранить это в базе данных при изменении разрешений? Просто суммируйте разрешения вместе и сохраните их как целые числа! Человек, который может FINISHED_DELETE
FINISHED_EDIT
Поэтому, чтобы сохранить их разрешения, вы просто суммируете их (1 + 2 = 3) и сохраняете 3 в столбце permissions
Нет другого способа получить число 3 с двоичными комбинациями — число 3 не может быть представлено в двоичном виде иначе, чем 0011 — так что вы можете быть на 100% уверены, что число 3 всегда означает, что у пользователя есть разрешение 1 и разрешение 2, соответствующие их значениям в константах.
Это кажется слишком простым и практичным, верно? В чем подвох?
Предостережения
Есть два основных предостережения:
- Вы должны помнить, что при вычислении значения бита следующего разрешения следует использовать степень 2. Поэтому, если вам нужно добавить новое разрешение, вы не можете просто выбрать 543, если у вас уже есть 512 — это должно быть 1024. Это становится немного сложнее, когда числа становятся больше.
- Так как наши компьютеры работают под управлением 64-разрядных операционных систем на 64-разрядных процессорах (в основном — некоторые из них даже по-прежнему зависают на 32-разрядных!), Это означает, что число может иметь максимум только 64-разрядные значения. Это означает, что вы можете хранить только перестановки с максимальным разрешением 64 для данного пользователя. Для небольших и средних сайтов этого вполне достаточно, но на огромных сайтах это может стать проблемой. Решение в том, чтобы использовать разные столбцы для разных контекстов разрешений (
draft_permissions
account_permissions
Каждый из этих столбцов может содержать собственные 64 разрешения, что достаточно даже для самых требовательных веб-сайтов.
Вывод
Битовые операции определенно все еще имеют место в современном программировании. Хотя может быть нелогично использовать что-то настолько сложное (на самом деле это не так — оно совсем не так знакомо, как современные таблицы объединения), этот подход приносит много преимуществ — не в последнюю очередь это значительное повышение производительности, как в отношении данных размер (намного меньше информации для хранения в базе данных и последующего извлечения) и скорость (пользовательский объект может предварительно извлекать значение своего разрешения — это просто целое число — и, таким образом, его можно всегда проверять).
Пакеты, представленные здесь, безусловно, упрощают работу, но только если вы еще не знаете даже о более простых альтернативах, подобных тем, которые были показаны выше.
Как вы относитесь к использованию побитовых операторов для проверки разрешений и такого подхода к их хранению? Есть очевидные плюсы / минусы? Дайте нам знать, как вы это делаете и почему!