改进这个PHP位域类的设置/权限?

Jas*_*vis 14 php class-design bit-manipulation bitmask

我一直试图找出在PHP中使用位掩码或位域的最佳方法,现在我的应用程序的不同区域用于不同的用户设置和权限.我到目前为止最远的是来自PHP中用于设置的Stack Overflow post Bitmask中的svens贡献的类.我稍微对它进行了修改,将其更改为使用类常量而不是DEFINE,并确保get方法仅传递给int.我还有一些示例代码来测试下面的类的功能.

我正在寻找任何建议/代码来进一步改进这个类,以便它可以在我的应用程序中用于设置和某些情况下的用户权限.

mcrumley在下面的评论中回答

另外,我对我的常数的编号有疑问.在这种类型的其他类和代码示例中,它将以2的幂列出事物.但是,即使我对常数1,2,3,4,5,6进行编号,它似乎也能正常工作.而不是1,2,4,8,16等.所以有人也可以澄清我是否应该改变我的常数?


一些想法......我真的想找出一种方法来扩展这个类,以便它可以很容易地与其他类一起使用.假设我有User班级和Messages班级.无论是UserMessages类将扩展此类,并能够使用位掩码为他们设置/权限(与其他班级后沿).那么也许应该更改当前的类常量,以便可以传入它们或其他选项?我真的不想在站点/脚本的其他部分定义(define('PERM_READ',1);)并且希望保持它有点封装,但也是灵活的; 我对这些想法持开放态度.我希望这是坚如磐石和灵活的,就像我说要与其他多个类一起使用设置或权限.可能应该使用某种阵列?来自上一个问题链接的@Svens发表评论"实施一些自动化的getter/setter或者ArrayAccess以获得更多的优点. - svens"您对这样的事情有什么看法?

请尽可能包含示例源代码.

<?php

class BitField {

    const PERM_READ = 0;
    const PERM_WRITE = 1;
    const PERM_ADMIN = 2;
    const PERM_ADMIN2 = 3;
    const PERM_ADMIN3 = 4;

    private $value;

    public function __construct($value=0) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }

    public function get($n) {
        if (is_int($n)) {
            return ($this->value & (1 << $n)) != 0;
        }else{
            return 0;
        }
    }

    public function set($n, $new=true) {
        $this->value = ($this->value & ~(1 << $n)) | ($new << $n);
    }

    public function clear($n) {
        $this->set($n, false);
    }
}
?>
Run Code Online (Sandbox Code Playgroud)

用法示例......

<?php
    $user_permissions = 0; //This value will come from MySQL or Sessions
    $bf = new BitField($user_permissions);

    // Turn these permission to on/true
    $bf->set($bf::PERM_READ);
    $bf->set($bf::PERM_WRITE);
    $bf->set($bf::PERM_ADMIN);
    $bf->set($bf::PERM_ADMIN2);
    $bf->set($bf::PERM_ADMIN3);

    // Turn permission PERM_ADMIN2 to off/false
    $bf->clear($bf::PERM_ADMIN2); // sets $bf::PERM_ADMIN2 bit to false

    // Get the total bit value
    $user_permissions = $bf->getValue();

    echo '<br> Bitmask value = ' .$user_permissions. '<br>Test values on/off based off the bitmask value<br>' ;

    // Check if permission PERM_READ is on/true
    if ($bf->get($bf::PERM_READ)) {
        // can read
        echo 'can read is ON<br>';
    }

    if ($bf->get($bf::PERM_WRITE)) {
        // can write
        echo 'can write is ON<br>';
    }

    if ($bf->get($bf::PERM_ADMIN)) {
        // is admin
        echo 'admin is ON<br>';
    }

    if ($bf->get($bf::PERM_ADMIN2)) {
        // is admin 2
        echo 'admin 2 is ON<br>';
    }

    if ($bf->get($bf::PERM_ADMIN3)) {
        // is admin 3
        echo 'admin 3 is ON<br>';
    }
?>
Run Code Online (Sandbox Code Playgroud)

Cha*_*les 15

在这种类型的其他类和代码示例中,它将以2的幂列出,但是就我所知,即使我将常数1,2,3,4,5,6编号而不是1,2,4,8,16等所以有人也可以澄清我是否应该改变我的常数?

你不需要,因为代码已经在处理.这个解释有点迂回.

位字段被处理为2的的原因是每个2的幂由单个位表示.这些单独的位可以一起按位或运算成一个可以传递的整数.在较低级别的语言中,传递一个数字比使用结构更容易.

让我演示一下这是如何工作的.让我们使用两个权限设置一些权限:

define('PERM_NONE', 0);
define('PERM_READ', 1);
define('PERM_WRITE', 2);
define('PERM_EDIT', 4);
define('PERM_DELETE', 8);
define('PERM_SUPER', 16);
Run Code Online (Sandbox Code Playgroud)

让我们在PHP交互式提示符下检查这些权限的位值:

php > printf('%08b', PERM_SUPER);
00010000
php > printf('%08b', PERM_DELETE);
00001000
php > printf('%08b', PERM_EDIT);
00000100
php > printf('%08b', PERM_WRITE);
00000010
php > printf('%08b', PERM_READ);
00000001
php > printf('%08b', PERM_NONE);
00000000
Run Code Online (Sandbox Code Playgroud)

现在让我们创建一个具有READ访问权限和WRITE访问权限的用户.

php > printf('%08b', PERM_READ | PERM_WRITE);
00000011
Run Code Online (Sandbox Code Playgroud)

或者可以读取,写入,删除但不能编辑的用户:

php > printf('%08b', PERM_READ | PERM_WRITE | PERM_DELETE);
00001011
Run Code Online (Sandbox Code Playgroud)

我们可以使用bitwise-AND检查权限并确保结果不为零:

php > $permission = PERM_READ | PERM_WRITE | PERM_DELETE;
php > var_dump($permission & PERM_WRITE); // This won't be zero.
int(2)
php > var_dump($permission & PERM_EDIT); // This will be zero.
int(0)
Run Code Online (Sandbox Code Playgroud)

(值得注意的PERM_NONE & PERM_NONE0 & 0,这是零.我创建的"无"权限实际上并不适用于此,并且可以立即被遗忘.)

你的班级正在做一些略有不同的事情,但最终结果是相同的.它使用位移来将"on"位向左移动X次,其中X是权限的数量.实际上,这会使权限值增加2.示范:

php > echo BitField::PERM_ADMIN3;
4
php > echo pow(2, BitField::PERM_ADMIN3);
16
php > printf('%08b', pow(2, BitField::PERM_ADMIN3));
00010000
php > echo 1 << BitField::PERM_ADMIN3;
16
php > printf('%08b', 1 << BitField::PERM_ADMIN3);
00010000
Run Code Online (Sandbox Code Playgroud)

虽然这些方法实际上是相同的,但我认为简单的ANDing和ORing比XORing和位移更容易阅读.

我正在寻找任何建议/代码来进一步改进这个类,所以它可以在我的应用程序中用于设置和某些情况下的用户权限.

我有一个建议,一个警告.

我的建议是使类抽象而不是在其中定义任何权限.相反,构建从其继承并定义自己的权限的类.您不希望考虑在不相关的位字段之间共享相同的权限名称,并且为它们添加类名称是非常明智的.无论如何,我希望你会这样做.

我的警告很简单但很可怕: PHP无法可靠地表示大于31位的整数. 实际上,当它在64位系统上编译时,它只能代表63位整数.这意味着,如果您要将应用程序分发给公众,如果您希望使用内置数学函数,则限制为不超过31个权限.

GMP扩展包括可以在任意长度整数上运行的按位运算.

另一种选择可能是在大整数上使用这个答案中的代码,这可能允许你将一个巨大的整数表示为一个字符串,尽管对它进行按位操作可能会很有趣.(您可以将其下转换为base-2,然后在预期位置对字符串"1"或"0"进行子检查,但这将是一个巨大的性能阻力.)


Nil*_*ton 13

其他人帮助进一步解释了这个位掩码位,所以我会专注于

"我确实喜欢让它更具可扩展性/通用性的想法,所以不同的类可以扩展它并将它用于不同的部分,我只是不知道该怎么做呢"

来自你对@Charles帖子的评论.

正如Charles正确地说的那样,您可以通过将功能提取到抽象类中,并将实际的"设置"(在本例中为权限)放入派生的具体类中来重用Bitmask类的功能.

例如:

<?php

abstract class BitField {

    private $value;

    public function __construct($value=0) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }

    public function get($n) {
        if (is_int($n)) {
            return ($this->value & (1 << $n)) != 0;
        }else{
            return 0;
        }
    }

    public function set($n, $new=true) {
        $this->value = ($this->value & ~(1 << $n)) | ($new << $n);
    }

    public function clear($n) {
        $this->set($n, false);
    }
}

class UserPermissions_BitField extends BitField
{
    const PERM_READ = 0;
    const PERM_WRITE = 1;
    const PERM_ADMIN = 2;
    const PERM_ADMIN2 = 3;
    const PERM_ADMIN3 = 4;
}

class UserPrivacySettings_BitField extends BitField
{
    const PRIVACY_TOTAL = 0;
    const PRIVACY_EMAIL = 1;
    const PRIVACY_NAME = 2;
    const PRIVACY_ADDRESS = 3;
    const PRIVACY_PHONE = 4;
}
Run Code Online (Sandbox Code Playgroud)

然后使用简单地变成:

<?php
$user_permissions = 0; //This value will come from MySQL or Sessions
$bf = new UserPermissions_BitField($user_permissions); 

// turn these permission to on/true
$bf->set($bf::PERM_READ);
$bf->set($bf::PERM_WRITE);
$bf->set($bf::PERM_ADMIN);
$bf->set($bf::PERM_ADMIN2);
$bf->set($bf::PERM_ADMIN3);
Run Code Online (Sandbox Code Playgroud)

要设置隐私设置,您只需实例化一个新的UserPrivacySettings_BitField对象并使用它.

这样,您只需定义一组代表您选项的常量,就可以根据应用程序的需要创建任意数量的不同BitField对象集.

我希望这对你有一些用处,但如果没有,也许它会对读这篇文章的其他人有所帮助.


Jav*_*i R 7

这是我的建议:

<?php

class BitField {

    const PERM_READ = 1;
    const PERM_WRITE = 2;
    const PERM_ADMIN = 4;
    const PERM_ADMIN2 = 8;
    const PERM_ADMIN3 = 16;

    private $value;

    public function __construct($value=0) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }

    public function get($n) {
                return $this->value & $n;
    }

    public function set($n, $new=true) {
        $this->value |= $n;
    }

    public function clear($n) {
        $this->value &= ~$n;
    }

}
?>
Run Code Online (Sandbox Code Playgroud)

如您所见,我使用了1,2,4,8等(2的幂)来简化计算.如果您将一个权限映射到一个位,您有:

0 0 0 0 0 0 0 1 = PERM_READ = 1
0 0 0 0 0 0 1 0 = PERM_WRITE = 2
0 0 0 0 0 1 0 0 = PERM_ADMIN = 4
etc...
Run Code Online (Sandbox Code Playgroud)

然后你可以使用逻辑运算,例如你最初有这个:

    0 0 0 0 0 0 0 1 = PERM_READ = 1
Run Code Online (Sandbox Code Playgroud)

如果要添加写入权限,则只需使用按位OR运算符:

    0 0 0 0 0 0 0 1 = PERM_READ = 1
OR  0 0 0 0 0 0 1 0 = PERM_WRITE = 2
=   0 0 0 0 0 0 1 1 = both bits enabled R & W
Run Code Online (Sandbox Code Playgroud)

要删除一位,您必须使用$ value&〜$ bit,例如删除写位:

    0 0 0 0 0 0 1 1 = both bits enabled R & W
AND 1 1 1 1 1 1 0 1 = Bitwise negated PERM_WRITE
=   0 0 0 0 0 0 0 1 = result, only the R bit
Run Code Online (Sandbox Code Playgroud)

最后,如果要测试是否启用了一个位,则必须对要测试的PERM_XXX进行AND $值操作:

    0 0 0 0 0 0 1 1 = both bits enabled R & W
AND 0 0 0 0 0 0 1 0 = Want to test PERM_WRITE
=   0 0 0 0 0 0 1 0 = result
Run Code Online (Sandbox Code Playgroud)

如果结果不为零,则表示您拥有该权限,否则您将获得该权限.