Бит-маска в PHP для настроек?

Биты и битовая маска - это то, что я пытался понять на некоторое время, но я хотел бы узнать, как их использовать для настроек и т.д. в PHP.

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

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

Класс...

<?php
    class bitmask
    {
        /**
         * This array is used to represent the users permission in usable format.
         *
         * You can change remove or add valuesto suit your needs.
         * Just ensure that each element defaults to false. Once you have started storing
         * users permsisions a change to the order of this array will cause the
         * permissions to be incorectly interpreted.
         *
         * @type Associtive array
         */
        public $permissions = array(
                                    "read" => false,
                                    "write" => false,
                                    "delete" => false,
                                    "change_permissions" => false,
                                    "admin" => false
                                    );

        /**
         * This function will use an integer bitmask (as created by toBitmask())
         * to populate the class vaiable
         * $this->permissions with the users permissions as boolean values.
         * @param int $bitmask an integer representation of the users permisions.
         * This integer is created by toBitmask();
         *
         * @return an associatve array with the users permissions.
         */
        public function getPermissions($bitMask = 0)
        {
            $i = 0;
            foreach ($this->permissions as $key => $value)
            {
                $this->permissions[$key] = (($bitMask & pow(2, $i)) != 0) ? true : false;

                // Uncomment the next line if you would like to see what is happening.
                //echo $key . " i= ".strval($i)." power=" . strval(pow(2,$i)). "bitwise & = " . strval($bitMask & pow(2,$i))."<br>";
                $i++;
            }
            return $this->permissions;
        }

        /**
         * This function will create and return and integer bitmask based on the permission values set in
         * the class variable $permissions. To use you would want to set the fields in $permissions to true for the permissions you want to grant.
         * Then call toBitmask() and store the integer value.  Later you can pass that integer into getPermissions() to convert it back to an assoicative
         * array.
         *
         * @return int an integer bitmask represeting the users permission set.
         */
        function toBitmask()
        {
            $bitmask = 0;
            $i = 0;
            foreach ($this->permissions as $key => $value)
            {

                if ($value)
                {
                    $bitmask += pow(2, $i);
                }
                $i++;
            }
            return $bitmask;
        }
    }
?>

Как установить/сохранить разрешения как значение битмаски?

<?php
    /**
     * Example usage
     * initiate new bitmask object
     */
    $perms = new bitmask();

    /**
     * How to set permissions for a user
     */
    $perms->permissions["read"] = true;
    $perms->permissions["write"] = true;
    $perms->permissions["delete"] = true;
    $perms->permissions["change_permissions"] = true;
    $perms->permissions["admin"] = false;

    // Converts to bitmask value to store in database or wherever
    $bitmask = $perms->toBitmask();  //in this example it is 15
    $sql = "insert into user_permissions (userid,permission) values(1,$bitmask)";
    echo $sql; //you would then execute code to insert your sql.
?>

Пример принятия значения битмаски и возврата истины /false для каждого элемента массива на основе значения бита....

<?php
    /**
     * Example usage to get the bitmask value from database or session/cache.... then put it to use.
     * $permarr returns an array with true/false for each array value based on the bit value
     */
    $permarr = $perms->getPermissions($bitmask);

    if ($permarr["read"])
    {
        echo 'user can read: <font color="green">TRUE</font>';
    }
    else {
        echo 'user can read: <font color="red">FALSE</font>';
    }

    //user can WRITE permission
    if ($permarr["write"])
    {
        echo '<br>user can write: <font color="green">TRUE</font>';
    }
    else {
        echo '<br>user can write: <font color="red">FALSE</font>';
    }
?>

Ответы

Ответ 1

Бит-поля - очень удобный и эффективный инструмент для работы с флагами или любым набором логических значений в целом.

Чтобы понять их, вам сначала нужно знать, как работают двоичные числа. После этого вы должны проверить записи руководства на побитовые операторы и убедитесь, что знаете, как работает побитовая операция AND, OR и слева/справа.

Поле бит - это не более чем целое значение. Пусть предположим, что размер поля бит фиксирован и только один байт. Компьютеры работают с двоичными числами, поэтому, если значение нашего номера 29, вы действительно найдете 0001 1101 в памяти.

Используя побитовое И (&) и побитовое ИЛИ (|), вы можете считывать и устанавливать каждый бит числа индивидуально. Они оба принимают два целых числа в качестве входных данных и выполняют AND/OR для каждого бита индивидуально.

Чтобы прочитать самый первый бит вашего номера, вы можете сделать что-то вроде этого:

  0001 1101 (=29, our number)
& 0000 0001 (=1, bit mask)
= 0000 0001 (=1, result)

Как вы видите, вам нужен специальный номер, где установлен только бит, который нам интересен, так называемая "бит-маска". В нашем случае это 1. Чтобы прочитать второй бит, нам нужно "нажимать" одно в битовой маске на одну цифру влево. Мы можем это сделать с помощью левого оператора сдвига ($number << 1) или путем умножения на два.

  0001 1101
& 0000 0010
= 0000 0000 (=0, result) 

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

Если вы хотите установить один из битов, вы можете использовать побитовое ИЛИ:

  0001 1101
| 0010 0000 (=32, bit mask)
= 0011 1101 (=29+32)

Тем не менее, вам придется идти по-другому, если вы хотите немного "очистить".

Более общий подход:

// To get bit n
$bit_n = ($number & (1 << $n)) != 0
// Alternative
$bit_n = ($number & (1 << $n)) >> $n

// Set bit n of number to new_bit
$number = ($number & ~(1 << $n)) | ($new_bit << $n)

Сначала это может выглядеть немного загадочно, но на самом деле это довольно легко.

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

Класс, который вы разместили, для меня немного особенный. Например, такие вещи, как ... ? true : false, являются ненадежной практикой. Если вы хотите использовать битовые поля, вам, вероятно, лучше определить некоторые константы и использовать описанный выше метод. Нетрудно придумать простой класс.

define('PERM_READ', 0);
define('PERM_WRITE', 1);

class BitField {
    private $value;

    public function __construct($value=0) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }

    public function get($n) {
        return ($this->value & (1 << $n)) != 0;
    }

    public function set($n, $new=true) {
        $this->value = ($this->value & ~(1 << $n)) | ($new << $n);
    }
    public function clear($n) {
        $this->set($n, false);
    }
}


$bf = new BitField($user->permissions);

if ($bf->get(PERM_READ)) {
    // can read
}

$bf->set(PERM_WRITE, true);
$user->permissions = $bf->getValue();
$user->save();

Я не пробовал какой-либо фрагмент кода этого ответа, но он должен начать работу, даже если он не работает из коробки.

Обратите внимание, что вы ограничены 32 значениями в поле бит.

Ответ 2

Здесь, как определить битмаски.

// the first mask.  In binary, it 00000001
define('BITWISE_MASK_1', 1 << 0); // 1 << 0 is the same as 1

// the second mask.  In binary, it 00000010
define('BITWISE_MASK_2', 1 << 1);

// the third mask.  In binary, it 00000100
define('BITWISE_MASK_3', 1 << 2);

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

function computeMasks($masks) {
    $masksPresent = array();
    if ($masks & BITWISE_MASK_1)
        $masksPresent[] = 'BITWISE_MASK_1';
    if ($masks & BITWISE_MASK_2)
        $masksPresent[] = 'BITWISE_MASK_2';
    if ($masks & BITWISE_MASK_3)
        $masksPresent[] = 'BITWISE_MASK_3';
    return implode(' and ', $masksPresent);
}

Это работает, потому что, когда вы ИЛИ два байта (скажем, 00000001 и 00010000), вы получаете их вместе: 00010001. Если вы И результат и оригинальная маска (00010001 и скажите, 00000001), вы получите маску, если она присутствует (в данном случае 00000001). В противном случае вы получите нуль.