【问题标题】:Why should I use bitwise/bitmask in PHP?为什么我应该在 PHP 中使用按位/位掩码?
【发布时间】:2010-11-25 16:30:37
【问题描述】:

我正在为脚本编写 PHP 中的用户角色/权限系统。

下面是我在 phpbuilder.com 上找到的使用位掩码方法获取权限的代码。

下面是一个更简单的版本 w3hich 可以在没有位部分的情况下做基本相同的事情。

许多人建议在 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.';
}

?>

【问题讨论】:

    标签: php bit-manipulation bitmask


    【解决方案1】:

    脚本检查哪个掩码设置为十进制。也许有人会需要它:

    <?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
    

    【讨论】:

      【解决方案2】:

      为什么不这样做...

      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
      

      【讨论】:

      • 这就是问题所在,我的第二个代码应该是这样的,除了使用位之外的任何其他方法,对我来说,这种方式似乎更容易,并且与使用位一样快所以我希望有人能帮助我理解为什么推荐位
      • 这确实使用了位。它只是删除了您拥有的所有不需要的代码。使用位的优点是每个权限都可以独立于所有其他权限(或不可用)。例如,您可以拥有读取权限,而不管其他任何权限的设置。
      • 作为参考,如果用户只有删除权限,则权限检查(即 $myrights & (read|write|delete) )将通过。如果您想检查所有内容(读取、写入和删除),则需要轻微的主题化。例如:function checkPerms($perms,$required,$all=true) { return ( $all ? (($perms &amp; $required) == $required) : ($perms &amp; $required) ); }
      【解决方案3】:

      问题是如果 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

      【讨论】:

        【解决方案4】:

        尝试使用 http://code.google.com/p/samstyle-php-framework/source/browse/trunk/class/bit.class.php 的 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)
        
        ?>
        

        【讨论】:

          【解决方案5】:

          也许这只是因为我不经常使用位掩码,但我发现在 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',等等。

          我发现这种方法比位掩码更易于阅读,因为方法名称可以准确地告诉您要查找的内容。

          【讨论】:

          • 使用类来隐藏实现总是一个好主意。你可以很容易地拥有: Auth::currentUser()->hasAnyPerm(PERM_READ, PERM_WRITE) 或 Auth::currentUser()->hasAnyPerm(Auth::READ, Auth::WRITE) 并在类中使用位掩码。然后用户代码永远不会看到位掩码,您仍然可以获得位掩码的空间节省值。
          • +1 这很划算。在 LAMP 中对权限位使用位掩码是多余的,实际上比具有一个权限位表的规范化数据库(例如,它可能具有一列已知权限位和第二个具有类似“ read-access”意味着代码和数据库结构都是自记录的)和一个单独的表,它将这些用户 ID 映射到权限 ID。
          【解决方案6】:

          编辑:重新阅读问题,看起来用户的权限正在从您的数据库中的位域返回。如果是这种情况,您将不得不使用按位运算符。数据库中权限为5 的用户具有PERMISSION_READPERMISSION_DENIED,因为(PERMISSION_READ &amp; 5) != 0(PERMISSION_DENIED &amp; 5) != 0。他不会有PERMISSION_ADD,因为(PERMISSION_ADD &amp; 5) == 0

          这有意义吗?您的按位示例中的所有复杂内容看起来都没有必要。


          如果您不完全了解按位运算,请不要使用它们。它只会导致很多麻烦。如果您对它们感到满意,请在您认为合适的地方使用它们。您(或编写按位代码的人)似乎没有完全掌握按位运算。它有几个问题,比如使用了pow() 函数,这会否定任何类型的性能优势。 (例如,您应该使用按位的1 &lt;&lt; $n,而不是pow(2, $n)。)

          也就是说,这两段代码似乎没有做同样的事情。

          【讨论】:

          • 对不起,我的问题和代码示例是 100% 可读的。你是对的,第二个代码没有做完全相同的事情,我只是比较主要功能。数据库中的值也不存在,因为这只是理论上的,我还在设计它。所以数据库值可以是我决定做的任何事情。我只是想找出使用​​位的性能增益在哪里,因为到目前为止,我认为从我的数据库中提取一个正数并使用该数字来确定用户是否可以做某事并没有真正的性能增益
          • 使用位,您将从数据库中提取一个正数。如果不使用位,则需要在数据库中添加几个布尔字段,每个权限一个。如果你直接从数据库中提取一个数字而不使用位,那么用户只能拥有一个权限。
          【解决方案7】:

          第一个允许人们拥有很多权限 - 例如读取/添加/更新。第二个例子,用户只有PERMISSION_UPDATE

          按位测试通过测试位的真值来工作。

          例如,二进制序列10010 将用PERMISSION_DELETEPERMISSION_READ 识别用户(识别PERMISSION_READ 的位是2 的列,识别PERMISSION_DELETE 的位是16 的列),二进制的10010 是十进制的 18 (16 + 2 = 18)。您的第二个代码示例不允许您进行那种测试。您可以进行大于样式检查,但假设每个拥有PERMISSION_DELETE 的人也应该拥有PERMISSION_UPDATE,这可能不是一个有效的假设。

          【讨论】:

            【解决方案8】:

            我想第一个示例可以让您更好地控制用户拥有的权限。在第二个中,您只有一个用户“级别”;大概较高级别会继承授予较低“级别”用户的所有权限,因此您没有如此精细的控制权。

            另外,如果我理解正确的话,那一行

            if($user_permission_level === 4)
            

            表示只有完全权限级别 4 的用户才能访问该操作 - 您肯定要检查用户是否至少拥有该级别?

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-05-16
              • 2011-05-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-04-28
              • 1970-01-01
              相关资源
              最近更新 更多