Я пытался найти лучший способ использовать битовые маски или битовые поля в PHP в течение длительного времени для разных областей моего приложения для разных пользовательских настроек и разрешений. Самое дальнее, что я дошел до сих пор, – это класс, внесенный svens в столбчатую битмаску Stack Overflow в PHP для настроек? , Я немного изменил его ниже, изменив его на использование констант класса вместо DEFINE и убедившись, что метод get передается только int. У меня также есть образец кода для тестирования функциональности класса ниже.
Я ищу любые предложения / код, чтобы еще больше улучшить этот класс, чтобы он мог использоваться в моем приложении для настроек и в некоторых случаях для пользовательских разрешений.
Ответил в комментарии ниже mcrumley
Кроме того, у меня есть вопрос о нумерации моих констант. В других классах и образцах кода для этого типа у него будут вещи, перечисленные в силе 2. Однако, похоже, это работает так же, насколько я могу судить, даже если я числю свои константы 1,2,3,4,5,6 вместо 1, 2, 4, 8, 16 и т. д. Так может ли кто-то также уточнить, должен ли я изменить свои константы?
Некоторые идеи … Я бы очень хотел выяснить способ расширения этого класса, поэтому он прост в использовании с другими классами. Предположим, у меня есть класс User
класс Messages
. Как класс « User
и « Messages
» расширят этот класс и смогут использовать битмаску для своих настроек / разрешений (вместе с другими классами позже). Так что, возможно, текущие константы класса должны быть изменены, чтобы они могли быть переданы или какой-либо другой вариант? Я бы предпочел не определять (определять («PERM_READ», 1);) в других частях сайта / скрипта и хотел бы сохранить его несколько инкапсулированным, но гибким; Я открыт для идей. Я хочу, чтобы это было твердо и гибко, как я сказал, чтобы использовать с несколькими другими классами для настроек или разрешений. Возможно, какой-то массив должен использоваться? @Svens из моего предыдущего вопроса, связанного выше, отправил комментарий с «внедрить некоторые автоматические геттеры / сеттеры или ArrayAccess для дополнительной awesomness. – svens». Что вы думаете о чем-то подобном?
Включите пример исходного кода, если возможно, пожалуйста.
<?php class BitField { const PERM_READ = 0; const PERM_WRITE = 1; const PERM_ADMIN = 2; const PERM_ADMIN2 = 3; const PERM_ADMIN3 = 4; private $value; public function __construct($value=0) { $this->value = $value; } public function getValue() { return $this->value; } public function get($n) { if (is_int($n)) { return ($this->value & (1 << $n)) != 0; }else{ return 0; } } public function set($n, $new=true) { $this->value = ($this->value & ~(1 << $n)) | ($new << $n); } public function clear($n) { $this->set($n, false); } } ?>
Пример использования …
<?php $user_permissions = 0; //This value will come from MySQL or Sessions $bf = new BitField($user_permissions); // Turn these permission to on/true $bf->set($bf::PERM_READ); $bf->set($bf::PERM_WRITE); $bf->set($bf::PERM_ADMIN); $bf->set($bf::PERM_ADMIN2); $bf->set($bf::PERM_ADMIN3); // Turn permission PERM_ADMIN2 to off/false $bf->clear($bf::PERM_ADMIN2); // sets $bf::PERM_ADMIN2 bit to false // Get the total bit value $user_permissions = $bf->getValue(); echo '<br> Bitmask value = ' .$user_permissions. '<br>Test values on/off based off the bitmask value<br>' ; // Check if permission PERM_READ is on/true if ($bf->get($bf::PERM_READ)) { // can read echo 'can read is ON<br>'; } if ($bf->get($bf::PERM_WRITE)) { // can write echo 'can write is ON<br>'; } if ($bf->get($bf::PERM_ADMIN)) { // is admin echo 'admin is ON<br>'; } if ($bf->get($bf::PERM_ADMIN2)) { // is admin 2 echo 'admin 2 is ON<br>'; } if ($bf->get($bf::PERM_ADMIN3)) { // is admin 3 echo 'admin 3 is ON<br>'; } ?>
Другие помогли с дальнейшим объяснением бит-маскировки этого, поэтому я сосредоточусь на
«Мне нравится идея сделать ее более расширяемой / универсальной, поэтому разные классы могут расширить ее и использовать ее для разных разделов, я просто не уверен, как это сделать»
из вашего комментария на пост @Charles.
Как справедливо сказал Чарльз, вы можете повторно использовать функциональность вашего класса битмаски, извлекая функциональность в абстрактный класс и помещая фактические «настройки» (в данном случае разрешения) в производные конкретные классы.
Например:
<?php abstract class BitField { private $value; public function __construct($value=0) { $this->value = $value; } public function getValue() { return $this->value; } public function get($n) { if (is_int($n)) { return ($this->value & (1 << $n)) != 0; }else{ return 0; } } public function set($n, $new=true) { $this->value = ($this->value & ~(1 << $n)) | ($new << $n); } public function clear($n) { $this->set($n, false); } } class UserPermissions_BitField extends BitField { const PERM_READ = 0; const PERM_WRITE = 1; const PERM_ADMIN = 2; const PERM_ADMIN2 = 3; const PERM_ADMIN3 = 4; } class UserPrivacySettings_BitField extends BitField { const PRIVACY_TOTAL = 0; const PRIVACY_EMAIL = 1; const PRIVACY_NAME = 2; const PRIVACY_ADDRESS = 3; const PRIVACY_PHONE = 4; }
И тогда использование просто становится:
<?php $user_permissions = 0; //This value will come from MySQL or Sessions $bf = new UserPermissions_BitField($user_permissions); // turn these permission to on/true $bf->set($bf::PERM_READ); $bf->set($bf::PERM_WRITE); $bf->set($bf::PERM_ADMIN); $bf->set($bf::PERM_ADMIN2); $bf->set($bf::PERM_ADMIN3);
И чтобы установить параметры конфиденциальности, вы просто создаете новый объект UserPrivacySettings_BitField и используете это вместо этого.
Таким образом, вы можете создать столько разных наборов объектов BitField, сколько требуется вашему приложению, определив набор констант, которые представляют ваши параметры.
Надеюсь, это будет вам полезно, но если нет, возможно, это будет полезно для кого-то, кто читает это.
В других классах и образцах кода для этого типа у него будут вещи, перечисленные в силе 2, но, похоже, они работают одинаково, насколько я могу судить, даже если я буду указывать свои константы 1,2,3,4,5,6 вместо 1,2,4,8,16 и т. Д. Так может ли кто-то также уточнить, следует ли мне менять свои константы?
Вам не нужно, потому что код уже позаботится об этом. Это объяснение будет немного окольным.
Причина, по которой битовые поля обрабатываются как две степени, состоит в том, что каждая степень из двух представлена одним битом. Эти отдельные биты могут быть побитовыми ИЛИ объединены в одно целое, которое может быть передано. На более низкоуровневых языках «легче» передавать число, чем, скажем, структуру.
Позвольте мне продемонстрировать, как это работает. Давайте создадим некоторые разрешения, используя полномочия двух:
define('PERM_NONE', 0); define('PERM_READ', 1); define('PERM_WRITE', 2); define('PERM_EDIT', 4); define('PERM_DELETE', 8); define('PERM_SUPER', 16);
Давайте проверим битовые значения этих разрешений в интерактивной подсказке PHP:
php > printf('%08b', PERM_SUPER); 00010000 php > printf('%08b', PERM_DELETE); 00001000 php > printf('%08b', PERM_EDIT); 00000100 php > printf('%08b', PERM_WRITE); 00000010 php > printf('%08b', PERM_READ); 00000001 php > printf('%08b', PERM_NONE); 00000000
Теперь давайте создадим пользователя с доступом READ и WRITE.
php > printf('%08b', PERM_READ | PERM_WRITE); 00000011
Или пользователь, который может читать, писать, удалять, но не редактировать:
php > printf('%08b', PERM_READ | PERM_WRITE | PERM_DELETE); 00001011
Мы можем проверить разрешение с помощью побитового-И и убедиться, что результат не равен нулю:
php > $permission = PERM_READ | PERM_WRITE | PERM_DELETE; php > var_dump($permission & PERM_WRITE); // This won't be zero. int(2) php > var_dump($permission & PERM_EDIT); // This will be zero. int(0)
(Стоит отметить, что PERM_NONE & PERM_NONE
равен 0 & 0
, что равно нулю. «Я», которое я создал, на самом деле не работает здесь и может быть незамедлительно забыт.)
Ваш класс делает что-то немного другое , но конечный результат идентичен. Он использует сдвиг бит для перемещения бит «on» влево X раз, где X – номер разрешения. По сути, это повышает 2 до значения разрешения . Демонстрация:
php > echo BitField::PERM_ADMIN3; 4 php > echo pow(2, BitField::PERM_ADMIN3); 16 php > printf('%08b', pow(2, BitField::PERM_ADMIN3)); 00010000 php > echo 1 << BitField::PERM_ADMIN3; 16 php > printf('%08b', 1 << BitField::PERM_ADMIN3); 00010000
Хотя эти методы фактически идентичны, я бы сказал, что простые ANDing и ORing легче читать, чем XORing и смещение бит.
Я ищу любые предложения / код, чтобы улучшить этот класс еще больше, чтобы его можно было использовать в моем приложении для настроек и, в некоторых случаях, для пользовательских разрешений.
У меня есть одно предложение и одно предупреждение.
Мое предложение сделало бы класс абстрактным и не определяло бы никаких разрешений внутри него. Вместо этого создайте классы, наследующие от него, и определите их собственные разрешения. Вы не хотите рассматривать общие имена разрешений в несвязанных битовых полях, а префикс их с именами классов вполне нормальный. Я ожидаю, что вы все равно это сделаете.
Мое предупреждение прост, но ужасно: PHP не может надежно представлять целое число, превышающее 31 бит. Фактически, он может представлять только 63-битные целые числа, когда он скомпилирован в 64-разрядной системе. Это означает, что если вы распространяете свое приложение среди широкой общественности, вы будете ограничены не более чем 31 разрешением, если хотите использовать встроенные математические функции.
Расширение GMP включает в себя побитовые операции, которые могут функционировать на целые числа произвольной длины.
Другим вариантом может быть использование кода из этого ответа для больших целых чисел , что может позволить вам представлять огромное целое число в виде строки, хотя выполнение побитовых операций на этом может быть … интересным. (Вы можете преобразовать его в base-2, затем выполнить проверку подстроки для строки «1» или «0» в ожидаемом месте, но это будет огромная перетаскиваемость.)
Вот мое предложение:
<?php class BitField { const PERM_READ = 1; const PERM_WRITE = 2; const PERM_ADMIN = 4; const PERM_ADMIN2 = 8; const PERM_ADMIN3 = 16; private $value; public function __construct($value=0) { $this->value = $value; } public function getValue() { return $this->value; } public function get($n) { return $this->value & $n; } public function set($n, $new=true) { $this->value |= $n; } public function clear($n) { $this->value &= ~$n; } } ?>
Как вы можете видеть, я использовал 1, 2, 4, 8 и т. Д. (Полномочия 2) для упрощения вычислений. Если вы сопоставляете одно разрешение с одним битом, у вас есть:
0 0 0 0 0 0 0 1 = PERM_READ = 1 0 0 0 0 0 0 1 0 = PERM_WRITE = 2 0 0 0 0 0 1 0 0 = PERM_ADMIN = 4 etc...
Затем вы можете использовать логические операции, например, у вас есть это на начальном этапе:
0 0 0 0 0 0 0 1 = PERM_READ = 1
Если вы хотите добавить разрешения для записи, вам нужно всего лишь использовать побитовый оператор OR:
0 0 0 0 0 0 0 1 = PERM_READ = 1 OR 0 0 0 0 0 0 1 0 = PERM_WRITE = 2 = 0 0 0 0 0 0 1 1 = both bits enabled R & W
Чтобы удалить один бит, вам нужно использовать $ value & ~ $ bit, например, удалить бит записи:
0 0 0 0 0 0 1 1 = both bits enabled R & W AND 1 1 1 1 1 1 0 1 = Bitwise negated PERM_WRITE = 0 0 0 0 0 0 0 1 = result, only the R bit
Наконец, если вы хотите проверить, включен ли один бит, вы должны иметь значение AND $ для PERM_XXX, которое вы хотите проверить:
0 0 0 0 0 0 1 1 = both bits enabled R & W AND 0 0 0 0 0 0 1 0 = Want to test PERM_WRITE = 0 0 0 0 0 0 1 0 = result
Если результат не равен нулю, у вас есть разрешение, иначе вы этого не сделаете.
Самая большая ошибка, которую я вижу в вашем классе, заключается в том, что вы смешиваете бизнес-логику с структурой данных. Цель вашего класса – хранить несколько логических значений (т.е. true / false) в одном целом. Это не нужно делать в классе, но это удобно. И это его цель.
Я бы сбросил флаги разрешений в классе и передал их в свои классы бизнес-логики.
<EDIT>
Структура данных – это объект, который обрабатывает одну вещь: данные. Данные не интерпретируются никоим образом. Например, стек , это структура данных, в которую вы можете поместить вещи, которые сначала дадут вам последний элемент. И вот в чем дело: все равно, что вы там вложили : целые числа, объекты пользователя, указатели, автомобили, слоны, он будет просто обрабатывать хранение и извлечение данных.
С другой стороны, бизнес-логика определяет, как ваши структуры данных взаимодействуют друг с другом. Здесь определяются разрешения, в которых вы указываете, что человек, создавший сообщение в блоге, может редактировать его, и никто другой не разрешен.
Это два принципиально разных взгляда на ваше приложение и их нельзя смешивать. Вы можете хранить свои разрешения в другой структуре данных (как массив целых чисел или хеш-таблицу объектов Permission, например, или любую другую структуру данных ), и вы можете хранить другие флаги в структуре данных BitField (например, логические настройки вашего пользователи, такие как «хочет получать информационный бюллетень» или «адрес электронной почты был проверен»).
</ EDIT>
Другим улучшением является использование шестнадцатеричных значений для этих констант, это гарантирует, что ваше 16-е значение все еще доступно для чтения. (Я бы предпочел использовать операторы бит-сдвига в объявлениях констант, что еще более удобочитаемо, но это невозможно с текущим интерпретатором PHP по соображениям производительности.)
class Permission { const READ = 0x0001; const UPDATE = 0x0002; const DELETE = 0x0004; const COMMENT = 0x0008; const GRANT = 0x0010; const UNDELETE = 0x0020; const WHATEVER = 0x0040; } $permissions = new BitField(); $permissions->set(Permission::READ); $permissions->set(Permission::WRITE);
<EDIT>
Тот же класс без шестнадцатеричных значений менее читабельен, особенно если вы добавляете больше флагов:
class Permission { const READ = 1; const UPDATE = 2; const DELETE = 4; const COMMENT = 8; const GRANT = 16; const UNDELETE = 32; const WHATEVER = 64; const PERMISSION8 = 128; const PERMISSION9 = 256; const PERMISSION10 = 512; const PERMISSION11 = 1024; const PERMISSION12 = 2048; const PERMISSION13 = 4096; const PERMISSION14 = 8192; const PERMISSION15 = 16384; const PERMISSION16 = 32768; # the 16th value I mentioned above. Would # you immediately recognize this value as 2^16? # I wouldn't. const PERMISSION17 = 65536; const PERMISSION18 = 131072; const PERMISSION19 = 262144; }
</ EDIT>
Я бы также определил, что параметр set () должен быть однобитовым целым числом, а не номером флага. Реализация set () демоном – это то, что я имею в виду:
$this->value |= $n;
«Мне нравится идея сделать ее более расширяемой / универсальной, поэтому разные классы могут расширить ее и использовать ее для разных разделов, я просто не уверен, как это сделать»
Не делайте этого, есть разные причины. В конкретном порядке и просто вкратце: отдельные функциональные классы из объектов данных. Не расширяйте то, что не требует наследования. Вместо этого используйте свойство, расширяющие классы обычно не должны быть тесно связаны с классом битмаски для работы вообще. Кроме того, в PHP вы можете перейти только из одного класса. Если вы используете это для такого ограниченного использования, расширенные объекты уже сожгли эту функцию.
Поэтому, вероятно, вам не нужно делать двоичные вычисления в вашем мозгу, но вместо этого вместо этого есть класс, который инкапсулировал вам бинарный расчет, и он предлагает интерфейс, который является более человечным (вместо имен, по меньшей мере, для того, чтобы сказать, по крайней мере) для взаимодействия. Хорошо. Но это все. Вы можете передать битмаску, передав двоичное значение. Если вам не нужны двоичные значения, вместо этого вы можете использовать класс enum, который вам уже нужен (проверьте флажок FlagsEnum).