Статьи

Побитовые операторы по-прежнему актуальны в современном PHP?

Многие из вас, вероятно, почесали голову, читая этот заголовок. «Bitwhat?»

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

Единицы и нули стоковое фото

Пример использования

Побитовые операторы перечислены здесь , но чтобы по-настоящему показать пример, мы сосредоточимся только на одном: побитовом и ( & Пример заставил это щелкнуть для меня. Вот что мы сделаем — погрузимся прямо в пример.

Представьте, что у вас есть веб-сайт, на который данный пользователь может иметь определенные разрешения. Например, такой журнал, как SitePoint:

  • Автор может создавать проекты и редактировать их профиль.
  • Редактор может, в дополнение к вышесказанному, черновики и готовые сообщения CRUD, а также профили авторов CRUD.
  • администратор может, в дополнение к вышесказанному, добавить разрешения администратора.

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

Двойное соединение

Добавьте роли, добавьте разрешения, прикрепите разрешения к ролям в таблице соединений, затем создайте другую таблицу соединений и свяжите некоторые роли с некоторыми пользователями.

Этот подход создает четыре дополнительные таблицы:

  • разрешений
  • роли
  • разрешений <-> ролей
  • Роли <-> пользователи

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

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

Единственное Соединение

Добавить разрешения, добавить таблицу соединений, прикрепить некоторые разрешения для некоторых пользователей

Этот подход создает две дополнительные таблицы:

  • разрешений
  • разрешения <-> пользователи

Гораздо меньше накладных расходов, чем в предыдущем примере, но у вас есть гораздо больше записей в таблице соединений, потому что у пользователя может быть МНОГО разрешений (только CRUD для составления составляет 4 разрешения самостоятельно). Благодаря большому количеству пользователей и большому количеству разрешений эта таблица может быстро набрать вес.

Колонна Паническое бегство

Добавьте столбец в таблицу пользователей для каждого разрешения, затем сделайте его тип данных tinyint (1) (в основном логическим), чтобы проверить разрешение как «включено» или «выключено».

Установка прав доступа для пользователя будет выглядеть примерно так:

 UPDATE `users` SET `editProfile` = 1, `deleteProfile` = 0, `createDraft` = 1, `publishDraft` = 0 ... WHERE `id` = 5

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

Однако, поскольку список столбцов, если смотреть издалека, напоминает двоичное число (1010), этот подход является отличным переходом в другое…

Побитовый подход

Прежде чем мы углубимся в этот подход, давайте проведем ускоренный курс в двоичном формате.

Двоичные числа

Все компьютеры хранят данные в двоичном виде: 0 или 1. Таким образом, число 14 фактически хранится как: 1110. Как это?

Двоичные числа оцениваются справа налево при расчете их значения, как действительные числа. Итак, число 1337 означает:

  • 1 х 7
  • + 3 х 10
  • + 3 х 100
  • + 1 х 1000

Поскольку каждая цифра в десятичной системе (основание 10) умножается на 10. Первая цифра 1, следующая 10, следующая после 100, следующая 1000 и т. Д.

В двоичном виде основание равно 2, поэтому каждая цифра умножается на 2. Следовательно, число 1110:

  • 0 х 1
  • + 1 х 2
  • + 1 х 4
  • + 1 х 8

Это 2 + 4 + 8, что составляет 14.

Да, это так просто конвертировать двоичные числа в десятичные.

Поэтому, когда мы смотрим на наши столбцы разрешений до 1010, это также можно рассматривать как число 10, записанное в двоичной форме. Хм, может быть, мы на что-то здесь.

Если у нас есть права доступа 1010, это означает, что 2-й и 4-й биты установлены, а первый и третий — нет (потому что они равны 0).

В бинарном языке мы фактически говорим, что 0-й и 2-й бит не установлены, потому что они отсчитываются от 0, как массивы. Это потому, что их порядковый номер (1, 2, 3) соответствует их показателю степени. 0-й бит фактически равен 2 степени 0 (2 ^ 0), которая равна 1. 1-й бит равен 2 степени 1 (2 ^ 1), равной 2. 2-й бит равен 2 в квадрате (2 ^ 2), что равно 4 и т. д. Таким образом, все очень легко запомнить.

Так как это поможет нам?

Побитовый подход

Что ж, глядя на разрешения издалека, мы можем представить состояние всех столбцов одновременно одним двоичным числом. Если мы можем представить все столбцы одновременно одним двоичным числом, это означает, что мы можем также представить его одним целым числом при переводе в десятичное число!

Если бы у нас был один столбец permissions141110 А какие 3 наши из 4?

Представьте себе следующее отображение разрешений:

ИЗМЕНИТЬ РАЗРЕШЕНИЯ ПРОФИЛЬ СОЗДАТЬ РЕДАКТИРОВАНИЕ ПРОФИЛЯ ПРОФИЛЬ УДАЛИТЬ ПРОЕКТ СОЗДАТЬ ПРОЕКТ РЕДАКТИРОВАНИЯ ПРОЕКТ УДАЛИТЬ ПРОЕКТ ПУБЛИКАЦИИ Законченное редактирование Законченное удаление
512 256 128 64 32 16 8 4 2 1

Число 14 в двоичном виде — 1110, но число нулей слева не имеет значения, поэтому мы можем дополнить его, пока не достигнем числа разрешений в таблице: 0000001110. Это по-прежнему 14, только представитель разрешений из таблицы выше. Для всех намерений и целей, 0000001110 === 1110.

В соответствии с этим мы видим, что учетная запись с разрешением 14DRAFT_DELETEDRAFT_PUBLISHFINISHED_EDIT Конечно, это не совсем соответствует настройке прав доступа в реальном мире, но это всего лишь пример, с помощью которого мы можем экстраполировать, что если бы у кого-то было 1111111111, у них были бы ВСЕ разрешения (вероятно, пользователь-администратор). В десятичном формате это 1023. Таким образом, кто-то со значением 1023permissions

Но как бы мы проверили это в нашем коде? Другими словами, как мы можем узнать, установлен ли бит разрешения (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_DELETEFINISHED_EDIT Поэтому, чтобы сохранить их разрешения, вы просто суммируете их (1 + 2 = 3) и сохраняете 3 в столбце permissions Нет другого способа получить число 3 с двоичными комбинациями — число 3 не может быть представлено в двоичном виде иначе, чем 0011 — так что вы можете быть на 100% уверены, что число 3 всегда означает, что у пользователя есть разрешение 1 и разрешение 2, соответствующие их значениям в константах.

Это кажется слишком простым и практичным, верно? В чем подвох?

Предостережения

Есть два основных предостережения:

  1. Вы должны помнить, что при вычислении значения бита следующего разрешения следует использовать степень 2. Поэтому, если вам нужно добавить новое разрешение, вы не можете просто выбрать 543, если у вас уже есть 512 — это должно быть 1024. Это становится немного сложнее, когда числа становятся больше.
  2. Так как наши компьютеры работают под управлением 64-разрядных операционных систем на 64-разрядных процессорах (в основном — некоторые из них даже по-прежнему зависают на 32-разрядных!), Это означает, что число может иметь максимум только 64-разрядные значения. Это означает, что вы можете хранить только перестановки с максимальным разрешением 64 для данного пользователя. Для небольших и средних сайтов этого вполне достаточно, но на огромных сайтах это может стать проблемой. Решение в том, чтобы использовать разные столбцы для разных контекстов разрешений ( draft_permissionsaccount_permissions Каждый из этих столбцов может содержать собственные 64 разрешения, что достаточно даже для самых требовательных веб-сайтов.

Вывод

Битовые операции определенно все еще имеют место в современном программировании. Хотя может быть нелогично использовать что-то настолько сложное (на самом деле это не так — оно совсем не так знакомо, как современные таблицы объединения), этот подход приносит много преимуществ — не в последнюю очередь это значительное повышение производительности, как в отношении данных размер (намного меньше информации для хранения в базе данных и последующего извлечения) и скорость (пользовательский объект может предварительно извлекать значение своего разрешения — это просто целое число — и, таким образом, его можно всегда проверять).

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

Как вы относитесь к использованию побитовых операторов для проверки разрешений и такого подхода к их хранению? Есть очевидные плюсы / минусы? Дайте нам знать, как вы это делаете и почему!