Улучшить этот класс битовых полей PHP для настроек / разрешений?

StackOverflow https://stackoverflow.com/questions/5380506

Вопрос

Я уже давно пытаюсь найти наилучший способ использования битовой маски или битовых полей в PHP для разных областей моего приложения для разных пользовательских настроек и разрешений.Самое далекое, чего я достиг до сих пор, - это класс, предоставленный svens в Stack Overflow post Битовая маска в PHP для настроек?.Я немного изменил его ниже, изменив его на использование констант класса вместо DEFINE и убедившись, что методу get передается только int.У меня также есть несколько примеров кода для тестирования функциональности класса, приведенных ниже.

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

Ответил в комментарии ниже Макрамли

Кроме того, у меня есть вопрос о нумерации моих констант.В других классах и примере кода для этого типа он будет содержать вещи, перечисленные в степенях 2.Однако, насколько я могу судить, это работает так же, даже если я пронумерую свои константы 1,2,3,4,5,6 вместо 1, 2, 4, 8, 16, и т.д.Итак, может ли кто-нибудь также уточнить, должен ли я изменить свои константы?


Несколько идей...Я бы действительно хотел найти способ расширить этот класс, чтобы его было легко использовать с другими классами.Допустим, у меня есть User класс а Messages класс.Оба User и Messages class расширит этот класс и сможет использовать битовую маску для своих настроек / разрешений (наряду с другими классами позже).Так, может быть, текущие константы класса следует изменить, чтобы их можно было передавать, или какой-то другой вариант?Я действительно предпочел бы не определять (define('PERM_READ', 1);) в других частях сайта / скрипта и хотел бы сохранить его несколько инкапсулированным, но в то же время гибким;Я открыт для идей.Я хочу, чтобы это было надежным и гибким, как я уже сказал, для использования с несколькими другими классами для настроек или разрешений.Возможно, следует использовать какой-то массив?@Svens из моего предыдущего вопроса, связанного выше, опубликовал комментарий с "внедрите некоторые автоматические средства получения / установки или ArrayAccess для дополнительной привлекательности.– свенс: "Что ты тоже думаешь о чем-то подобном?

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

<?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.

Как справедливо сказал Чарльз, вы можете повторно использовать функциональность своего класса Bitmask, извлекая функциональность в абстрактный класс и поместив фактические «настройки» (в данном случае разрешения) в полученные конкретные классы.

Например:

<?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

Теперь давайте создадим пользователя, который имеет доступ на чтение и запись.

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, который равен нулю.Созданное мной разрешение "нет" на самом деле здесь не работает, и о нем можно быстро забыть.)

Ваш класс что-то делает немного другой, но конечный результат идентичен.Он использует битовый сдвиг для перемещения бита "вкл." влево 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

В то время как эти методы являются эффективно идентично, я бы сказал, что простые операции ввода и вывода легче читать, чем операции XORing и сдвига битов.

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

У меня есть одно предложение и одно предупреждение.

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

Мое предупреждение простое, но страшное: PHP не может надежно представлять целое число размером более 31 бита. Фактически, он может представлять 63-битные целые числа только в том случае, если он скомпилирован в 64-битной системе.Это означает, что, если вы распространяете свое приложение среди широкой публики, вы будете ограничено не более чем 31 разрешением если вы хотите использовать встроенные математические функции.

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

Другим вариантом может быть использование кода из этого ответа для больших целых чисел, что могло бы позволить вам представлять огромное целое число в виде строки, хотя выполнение побитовых операций над этим могло бы быть...интересно.(Вы могли бы преобразовать его в base-2, затем выполнить проверку substr на наличие строки "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

Если вы хотите добавить разрешения для написания, вам нужно только использовать бить или оператор:

    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

Наконец, если вы хотите проверить, включена ли один бит, операция, которую вы должны, и $ значения против 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) в одном целом. Это не так имеют делать это в классе, но это удобно. И это его цель.

Я бы сбросил флаги разрешения в классе и передал их на уроки вашей бизнес -логики.

u003CEDIT>

А структура данных это сущность, которая обрабатывает одну вещь: данные. Данные никоим образом не интерпретируются. А куча, На первый взгляд, это структура данных, в которую вы можете поместить вещи, которая сначала даст вам последний пункт. А вот в чем дело: Это все равно, что вы там вкладываете: Целые числа, пользовательские объекты, указатели, автомобили, слоны, он просто обрабатывает хранение и поиск данных.

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

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

u003C/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);

u003CEDIT>

Тот же класс без шестнадцатеричных значений менее читабелен, особенно если вы добавляете больше флагов:

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

u003C/EDIT>

Я бы также определил, что параметр для set () должен быть целым числом, а не номером флага. Реализация set () от Demon - это то, что я имею в виду:

$this->value |= $n;

«Мне нравится идея сделать его более расширяемым/общим, чтобы разные классы могли расширить это и использовать его для разных разделов, я просто еще не уверен, как это сделать»

Не делайте этого, есть разные причины, почему. В каком -то конкретном порядке и вкратце: отдельные функциональные классы из объектов данных. Не распространяйте то, что не нужно наследовать. Вместо этого используйте свойство, расширяющие классы обычно не должны быть тесно связаны с классом BitMask для работы вообще. Кроме того, в PHP вы можете простираться только от одного класса. Если вы используете это для такого ограниченного использования, расширительные объекты уже сжигали эту функцию.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top