Почему я должен использовать bitwise/bitmask в PHP?

Я работаю над системой user-role/permission в PHP для script.

Ниже приведен код с использованием метода битмаски для разрешений, которые я нашел на phpbuilder.com.

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

Многие люди рекомендуют использовать битовые операторы и такие параметры и другие вещи на PHP, но я никогда не понимал, почему. В приведенном ниже коде есть ЛЮБОЕ преимущество от использования первого кода вместо второго?

<?php
/**
 * Correct the variables stored in array.
 * @param    integer    $mask Integer of the bit
 * @return    array
 */
function bitMask($mask = 0) {
    $return = array();
    while ($mask > 0) {
        for($i = 0, $n = 0; $i <= $mask; $i = 1 * pow(2, $n), $n++) {
            $end = $i;
        }
        $return[] = $end;
        $mask = $mask - $end;
    }
    sort($return);
    return $return;
}


define('PERMISSION_DENIED', 0);
define('PERMISSION_READ', 1);
define('PERMISSION_ADD',  2);
define('PERMISSION_UPDATE', 4);
define('PERMISSION_DELETE', 8);

//run function
// this value would be pulled from a user setting mysql table
$_ARR_permission = bitMask('5');

if(in_array(PERMISSION_READ, $_ARR_permission)) {
    echo 'Access granted.';
}else {
    echo 'Access denied.';
}
?>

небитовая версия

<?PHP
/*
   NON bitwise method
*/

// this value would be pulled from a user setting mysql table
$user_permission_level = 4;

if($user_permission_level === 4) {
    echo 'Access granted.';
}else {
    echo 'Access denied.';
}

?>

Ответ 1

Почему бы просто не сделать это...

define('PERMISSION_DENIED', 0);
define('PERMISSION_READ', 1);
define('PERMISSION_ADD',  2);
define('PERMISSION_UPDATE', 4);
define('PERMISSION_DELETE', 8);

//run function
// this value would be pulled from a user setting mysql table
$_ARR_permission = 5;

if($_ARR_permission & PERMISSION_READ) {
    echo 'Access granted.';
}else {
    echo 'Access denied.';
}

Вы также можете создавать множество произвольных комбинаций разрешений, если вы используете бит...

$read_only = PERMISSION_READ;
$read_delete = PERMISSION_READ | PERMISSION_DELETE;
$full_rights = PERMISSION_DENIED | PERMISSION_READ | PERMISSION_ADD | PERMISSION_UPDATE | PERMISSION_DELETE;

//manipulating permissions is easy...
$myrights = PERMISSION_READ;
$myrights |= PERMISSION_UPDATE;    // add Update permission to my rights

Ответ 2

Первый позволяет людям иметь множество разрешений - например, чтение/добавление/обновление. Во втором примере пользователь имеет только PERMISSION_UPDATE.

Побитовое тестирование работает путем тестирования бит для значений истинности.

Например, двоичная последовательность 10010 идентифицирует пользователя с PERMISSION_DELETE и PERMISSION_READ (идентификация бит PERMISSION_READ - это столбец для 2, идентификация бит PERMISSION_DELETE - это столбец для 16), 10010 в двоичном формате - 18 в десятичной форме (16 + 2 = 18). Второй образец кода не позволяет вам выполнять такое тестирование. Вы можете делать проверки больше, чем стиль, но это предполагает, что все, у кого есть PERMISSION_DELETE, также должны иметь PERMISSION_UPDATE, что может быть недействительным.

Ответ 3

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

Почему бы не создать класс, который отслеживает такие права, как разрешения, и регистрирует пользователей, и так далее? Позвольте называть его Auth. Затем, если вы хотите проверить, что у пользователя есть разрешение, вы можете создать метод HasPermission. например.

if(Auth::logged_in() && Auth::currentUser()->hasPermission('read'))
    //user can read

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

if(Auth::logged_in() && Auth::currentUser()->hasAllPermissions('read', 'write'))
    //user can read, and write

или если вы хотите проверить, есть ли у них какая-либо определенная группа разрешений:

if(Auth::logged_in() && Auth::currentUser()->hasAnyPermissions('read', 'write'))
    //user can read, or write

Конечно, не может быть плохой идеей определять константы, такие как PERMISSION_READ, которые вы можете просто определить как строку "read" и т.д.

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

Ответ 4

Изменить: перечитывая вопрос, похоже, что разрешения пользователя возвращаются из вашей базы данных в битовом поле. Если это произойдет, вам придется использовать побитовые операторы. Пользователь, у которого в базе данных есть 5, имеет PERMISSION_READ и PERMISSION_DENIED, потому что (PERMISSION_READ & 5) != 0 и (PERMISSION_DENIED & 5) != 0. У него не было бы PERMISSION_ADD, потому что (PERMISSION_ADD & 5) == 0

Это имеет смысл? Весь сложный материал в вашем побитом примере выглядит ненужным.


Если вы не полностью понимаете поразрядные операции, тогда не используйте их. Это приведет только к множеству головных болей. Если вам удобно с ними, то используйте их там, где вы считаете, что они уместны. Вы (или кто-то, кто написал побитовый код), похоже, не полностью разбираются в побитовых операциях. Есть несколько проблем с ним, например, тот факт, что используется функция pow(), что отрицательно скажется на любом виде производительности. (Вместо pow(2, $n), вы должны использовать поразрядный 1 << $n, например.)

Тем не менее, две части кода, похоже, не делают то же самое.

Ответ 5

Попробуйте использовать то, что находится в бит .class.php, в http://code.google.com/p/samstyle-php-framework/source/browse/trunk/class/bit.class.php

Проверка на определенный бит:

<?php

define('PERMISSION_DENIED', 1);
define('PERMISSION_READ', 2);
define('PERMISSION_ADD',  3);
define('PERMISSION_UPDATE', 4);
define('PERMISSION_DELETE', 5);


if(bit::query($permission,PERMISSION_DENIED)){
echo 'Your permission is denied';
exit();
}else{
// so on
}

?>

И для включения и выключения:

<?php

$permissions = 8;
bit::toggle(&$permissions,PERMISSION_DENIED);

var_dump($permissions); // outputs int(9)

?>

Ответ 6

проблема заключается в том, что PERMISSION_READ является самой маской

if($ARR_permission & PERMISSION_READ) {
    echo 'Access granted.';
}else {
    echo 'Access denied.';

то для 0101 - $rightWeHave 0011 - $rightWeRequire

это доступ, который мы, вероятно, не хотим, поэтому он должен быть

if (($rightWeHave & $rightWeRequire) == $rightWeRequire) {
echo 'access granted';
}

так что теперь для

0101 0011

результат

0001, поэтому доступ не предоставляется, поскольку он не равен 0011

но для

1101 0101

это нормально, так как результат 0101

Ответ 7

Script проверяет, какая маска была установлена ​​в десятичном формате. Возможно, кому-то это понадобится:

<?php

$max = 1073741824;
$series = array(0);
$x = 1;
$input = $argv[1]; # from command line eg.'12345': PHP скрипт.php 12345
$sum = 0;

# generates all bitmasks (with $max)
while ($x <= $max) {
    $series[] = $x;
    $x = $x * 2;
}

# show what bitmask has been set in '$argv[1]'
foreach ($series as $value) {
    if ($value & $input) {
        $sum += $value;
        echo "$value - SET,\n";
    } else {
        echo "$value\n";
    }
}

# sum of set masks
echo "\nSum of set masks: $sum\n\n";

Выход (php maskChecker.php 123):

0
1 - SET,
2 - SET,
4
8 - SET,
16 - SET,
32 - SET,
64 - SET,
128
256
512
1024
2048
4096
8192
(...)

Sum of set mask: 123

Ответ 8

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

Кроме того, если я правильно понял, строка

if($user_permission_level === 4)

означает, что доступ к действию имеют только пользователи с точно уровнем разрешений 4 - наверняка, вы захотите проверить, что у пользователей есть хотя бы этот уровень?