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

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

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

Отвечено в комментарии ниже mcrumley

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


Некоторые идеи... Я бы очень хотел выяснить способ расширения этого класса, поэтому он прост в использовании с другими классами. Скажем, у меня есть класс User и Messages. Оба класса User и Messages будут расширять этот класс и иметь возможность использовать битовую маску для своих настроек/разрешений (вместе с другими классами позже). Так что, возможно, текущие константы класса должны быть изменены, чтобы они могли быть переданы или какой-либо другой вариант? Я бы предпочел не определять (определять ( "PERM_READ", 1);) в других частях сайта /script и хотел бы сохранить его несколько инкапсулированным, но гибким; Я открыт для идей. Я хочу, чтобы это было твердо и гибко, как я сказал, чтобы использовать с несколькими другими классами для настроек или разрешений. Возможно, какой-то массив должен использоваться? @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>';
    }
?>

Ответ 1

Другие помогли с дальнейшим объяснением бит-маскировки этого, поэтому я сосредоточусь на

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

из вашего комментария на пост @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

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

Вам не нужно, потому что код уже позаботится об этом. Это объяснение будет немного окольным.

Причина, по которой битовые поля обрабатываются как полномочия двух, состоит в том, что каждая степень из двух представлена ​​одним битом. Эти отдельные биты могут быть побитовыми ИЛИ объединены в одно целое, которое может быть передано. На языках более низкого уровня "проще" передавать число, чем, скажем, struct.

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

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 is 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 и смещение бит.

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

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

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

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

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

Другой вариант может быть с использованием кода из этого ответа для больших целых чисел, который может позволить вам представлять огромное целое число в виде строки, хотя выполнение побитовых операций на них может быть... интересным. (Вы можете преобразовать его в base-2, затем выполнить проверку подстроки для строки "1" или "0" в ожидаемом месте, но это будет огромным перетаскиванием.)

Ответ 3

Вот мое предложение:

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

Если результат не равен нулю, у вас есть разрешение, иначе вы этого не сделаете.

Ответ 4

Самая большая ошибка, которую я вижу в вашем классе, заключается в том, что вы смешиваете бизнес-логику с структурой данных. Цель вашего класса - хранить несколько логических значений (т.е. True/false) в одном целом. Это не нужно делать в классе, но это удобно. И это его цель.

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

< РЕДАКТИРОВАТЬ >

A структура данных - это объект, который обрабатывает одно: данные. Данные не интерпретируются никоим образом. A stack, например, представляет собой структуру данных, в которую вы можете поместить материал, который сначала даст вам последний элемент. И вот в чем дело: все равно, что вы там вложили: целые числа, пользовательские объекты, указатели, автомобили, слоны, он просто обрабатывает хранение и извлечение данных.

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

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

</РЕДАКТИРОВАТЬ >

Еще одно улучшение - использование шестнадцатеричных значений для этих констант, это гарантирует, что ваше 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);

< РЕДАКТИРОВАТЬ >

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

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

</РЕДАКТИРОВАТЬ >

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

$this->value |= $n;

Ответ 5

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

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

Итак, вам, вероятно, не нужно делать двоичные вычисления в вашем мозгу, но вместо этого у вас есть класс, который инкапсулировал бинарный расчет для вас, и он предлагает интерфейс, более человечный (вместо имен, по меньшей мере, для чисел) взаимодействовать с. Хорошо. Но это все. Вы можете передать битмаску, передав двоичное значение. Если вам не нужны двоичные значения, вместо enum class может быть то, что вы ищете уже (см. Флажок FlagsEnum).