Битовая маска в PHP для настроек? - PullRequest
14 голосов
/ 16 марта 2011

Биты и битовые маски - это то, что я пытался понять некоторое время, но я хотел бы узнать, как использовать их для настроек и тому подобное в 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.
?>

Пример взятия значения битовой маски и возврата true / 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>';
    }
?>

Ответы [ 2 ]

32 голосов
/ 16 марта 2011

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

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

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

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

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

  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)

Однако вам придется пойти другим путем, если вы хотите «очистить» бит.

Aболее общий подход будет выглядеть так:

// 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 значениями на битовое поле.

9 голосов
/ 16 марта 2011

Вот как определить битовые маски.

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

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

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

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

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).В противном случае вы получите ноль.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...