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

时间:2011-03-21 16:23:01

标签: php class-design bit-manipulation bitmask

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

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

mcrumley在以下评论中回答

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


一些想法......我真的想找出一种方法来扩展这个类,以便它可以很容易地与其他类一起使用。假设我有一个User类和一个Messages类。 UserMessages类都将扩展此类,并且能够将位掩码用于其设置/权限(以及稍后的其他类)。那么也许应该更改当前的类常量,以便可以传入它们或其他选项?我真的不想在站点/脚本的其他部分定义(define('PERM_READ',1);)并且希望保持它有点封装,但也是灵活的;我对这些想法持开放态度。我希望这是坚如磐石和灵活的,就像我说要与其他多个类一起使用设置或权限。可能应该使用某种阵列?上面链接的上一个问题的@Svens发布了一条评论,“实现一些自动获取器/设置器或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);
    }
}
?>

使用示例......

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

5 个答案:

答案 0 :(得分:15)

  

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

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

位字段作为 2的幂处理的原因是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);

让我们在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

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

php > printf('%08b', PERM_READ | PERM_WRITE);
00000011

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

php > printf('%08b', PERM_READ | PERM_WRITE | PERM_DELETE);
00001011

我们可以使用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)

(值得注意的是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

虽然这些方法有效相同,但我认为简单的ANDing和ORing比XORing和位移更容易阅读。

  

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

我有一个建议和一个警告。

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

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

The GMP extension包括可以在任意长度整数上运行的按位运算。

另一个选项可能是using code from this answer on large integers,它可以允许你将一个巨大的整数表示为一个字符串,尽管对它进行按位操作可能会很有趣。 (您可以将其下转换为base-2,然后在预期位置对字符串“1”或“0”进行子检查,但这将是一个巨大的性能阻力。)

答案 1 :(得分: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;
}

然后用法变成:

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

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

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

我希望这对你有用,但如果没有,也许这对读这篇文章的其他人有用。

答案 2 :(得分: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;
    }

}
?>

如您所见,我使用了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...

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

    0 0 0 0 0 0 0 1 = PERM_READ = 1

如果要添加写入权限,只需使用按位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

要删除一位,您必须使用$ value&amp; 〜$ 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

最后,如果你想测试是否启用了一个位,你必须对要测试的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

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

答案 3 :(得分:4)

我在课堂上看到的最大错误是你将业务逻辑混合到一个数据结构中。您的类的目的是将多个布尔值(即true / false)存储在一个整数中。这不是在一个类中完成,但它很方便。这就是它的目的。

我会删除类中的权限标志,并将它们外包到您的业务逻辑类中。

<强>&LT;编辑&gt;

data structure是处理一件事的实体:数据。不以任何方式解释数据。例如,stack是一个可以放入内容的数据结构,它将首先为您提供最后一项。这里有一点:它不在乎,你放在那里:整数,用户对象,指针,汽车,大象,它只会处理数据的存储和检索。

另一方面,

Business logic用于定义数据结构如何相互交互。这是定义权限的位置,您声明创建博客帖子的人可以编辑它,而不允许其他人。

这些是您的应用程序的两个根本不同的视图,不应混合使用。您可以将权限存储在另一个数据结构中(例如整数数组或hash table权限对象,例如 - 或any other data structure),您可以在BitField数据结构中存储其他标志(如您的用户的布尔首选项,例如“想要接收简报”或“电子邮件地址已经过验证”。)

<强>&LT; /编辑&gt;

另一个改进是对这些常量使用十六进制值,这将确保您的第16个值仍然可读。 (我宁愿建议在常量声明中使用位移运算符,这更加可读,但出于性能原因,使用当前的PHP解释器是不可能的。)

class Permission {
    const READ     = 0x0001;
    const UPDATE   = 0x0002;
    const DELETE   = 0x0004;
    const COMMENT  = 0x0008;
    const GRANT    = 0x0010;
    const UNDELETE = 0x0020;
    const WHATEVER = 0x0040;
}

$permissions = new BitField();
$permissions->set(Permission::READ);
$permissions->set(Permission::WRITE);

<强>&LT;编辑&gt;

没有十六进制值的同一个类的可读性较差,特别是如果添加更多标记:

class Permission {
    const READ         = 1;
    const UPDATE       = 2;
    const DELETE       = 4;
    const COMMENT      = 8;
    const GRANT        = 16;
    const UNDELETE     = 32;
    const WHATEVER     = 64;
    const PERMISSION8  = 128;
    const PERMISSION9  = 256;
    const PERMISSION10 = 512;
    const PERMISSION11 = 1024;
    const PERMISSION12 = 2048;
    const PERMISSION13 = 4096;
    const PERMISSION14 = 8192;
    const PERMISSION15 = 16384;
    const PERMISSION16 = 32768; # the 16th value I mentioned above. Would
                                # you immediately recognize this value as 2^16?
                                # I wouldn't.
    const PERMISSION17 = 65536;
    const PERMISSION18 = 131072;
    const PERMISSION19 = 262144;
}

<强>&LT; /编辑&gt;

我将进一步定义set()的参数必须是单位整数,而不是标志号。恶魔的set()实现就是我的意思:

$this->value |= $n;

答案 4 :(得分:0)

  

“我确实喜欢让它更具可扩展性/通用性,因此不同的类可以扩展它并将它用于不同的部分,我只是不知道该怎么做”

不要这样做,有各种原因。没有特定的顺序,简而言之:从数据对象中分离出功能类。不要扩展不需要继承的东西。使用属性,扩展类通常不需要与位掩码类紧密耦合就可以工作。此外,在PHP中,您只能从一个类扩展。如果您将此用于此类限制用途,则扩展对象已经烧掉了该功能。

所以你可能不喜欢不需要在你的大脑中进行二进制计算,而是拥有一个类,而不是为你封装了二进制计算,它提供了一个更人性的接口(名称代替数字,至少可以说)与。。。相互作用。精细。但那就是它。您可以通过传递二进制值来传递位掩码。如果您不需要二进制值,enum class instead可能就是您要查找的内容(具体请查看FlagsEnum)。