Почему я должен использовать битовую / битовую маску в PHP?
Я работаю над системой пользовательских ролей / разрешений в 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.';
}
?>
8 ответов
Почему бы просто не сделать это...
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, где производительность разработчика и читаемость кода важнее, чем скорость или использование памяти (в определенных пределах, очевидно), нет реальной причины использовать битовую маску,
Почему бы вместо этого не создать класс, который бы отслеживал такие вещи, как права доступа, вход в систему пользователей и т. Д.? Давайте назовем это 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", и так далее.
Я считаю, что этот подход легче читать, чем битовые маски, потому что имена методов говорят вам, что именно вы ищете.
Проблема в том, что если 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
Редактировать: перечитывая вопрос, похоже, что разрешения пользователя возвращаются из вашей базы данных в битовом поле. Если это так, вам придется использовать побитовые операторы. Пользователь, который имеет разрешение в базе данных 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
, например.)
Тем не менее, две части кода, кажется, не делают то же самое.
Скрипт проверяет, какая маска была установлена в десятичном виде. Может кому-то это понадобится
<?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
Попробуйте использовать то, что находится в 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)
?>
Я предполагаю, что первый пример дает вам больше контроля над тем, какие именно разрешения имеет пользователь. Во втором у вас просто есть пользовательский уровень; предположительно более высокие уровни наследуют все разрешения, предоставленные пользователю более низкого уровня, поэтому у вас нет такого точного контроля.
Кроме того, если я правильно понял, строка
if($user_permission_level === 4)
означает, что только пользователи с уровнем разрешений 4 имеют доступ к действию - наверняка вы захотите проверить, имеют ли пользователи как минимум этот уровень?