Почему мне следует использовать побитовую/битовую маску в PHP?

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

  •  21-09-2019
  •  | 
  •  

Вопрос

Я работаю над системой ролей/разрешений пользователей в PHP для сценария.

Ниже приведен код, использующий метод битовой маски для разрешений, который я нашел на 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's 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's setting mysql table
$user_permission_level = 4;

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

?>
Это было полезно?

Решение

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

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's 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

Другие советы

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

Побитовое тестирование работает путем проверки битов на истинность.

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

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

Почему бы вместо этого не создать класс, который отслеживает такие вещи, как разрешения, вошедшие в систему пользователи и так далее?Назовем это Аут.Затем, если вы хотите проверить, есть ли у пользователя разрешение, вы можете создать метод 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, которые можно просто определить как строку «прочитать» и так далее.

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

Редактировать:перечитывая вопрос, похоже, что права пользователя возвращаются из вашей базы данных в битовом поле.В этом случае вам придется использовать побитовые операторы.Пользователь, у которого есть права доступа к базе данных 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, например.)

Тем не менее, эти два фрагмента кода, похоже, не делают одно и то же.

Попробуйте использовать то, что находится в bit.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)

?>

проблема в том, что 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

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

<?php

$max = 1073741824;
$series = array(0);
$x = 1;
$input = $argv[1]; # from command line eg.'12345': php script.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

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

И еще, если я правильно понял, строка

if($user_permission_level === 4)

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

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