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


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

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

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

Многие люди рекомендовали использовать битовые операторы и тому подобное для настроек и других вещей в 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.';
}

?>
Author: Anthony, 2009-09-04

8 answers

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

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

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

$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
 40
Author: Rik Heywood, 2013-01-03 09:31:55

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

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

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

 9
Author: Dominic Rodger, 2009-09-04 15:54:01

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

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

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, которые вы можете просто определить как строку "чтение" и так далее.

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

 9
Author: davidtbernal, 2009-09-04 16:24:38

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

Что сказал, что два фрагмента кода, похоже, не делают одно и то же.

 1
Author: Kip, 2009-09-04 16:06:51

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

?>
 1
Author: mauris, 2009-09-05 15:32:28

Проблема в том, что если PERMISSION_READ - это сама маска

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

Затем для 0101 - $right у нас есть 0011 - $rightwerequire

Доступ предоставлен, чего мы, вероятно, не хотим, поэтому он должен быть

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

Итак, теперь для

0101 0011

Результат

0001 таким образом, доступ не предоставляется, потому что он не равен 0011

Но для

1101 0101

Все в порядке, так как результат 0101

 1
Author: Lukas Hanacek, 2012-03-21 10:37:05

Скрипт проверяет, какая маска была установлена в десятичном формате. Может быть, кому-то это понадобится:

<?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
 1
Author: Lucas, 2016-06-16 11:19:49

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

Также, если я правильно понял, строка

if($user_permission_level === 4)

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

 0
Author: Tom Haigh, 2009-09-04 15:53:02