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

我一直试图找出在PHP中使用位掩码或位域的最佳方法,现在我的应用程序的不同区域用于不同的用户设置和权限。我到目前为止最远的是来自Stack Overflow中由svens贡献的类 在PHP中发布用于设置的位掩码?我稍微对它进行了修改,将其更改为使用类常量而不是DEFINE,并确保get方法仅传递给int。我还有一些示例代码来测试下面的类的功能。 我正在寻找任何建议/代码来进一步改进这个类,以便它可以在我的应用程序中用于设置和某些情况下的用户权限。 mcrumley在下面的评论中回答 另外,我对我的常数的编号有疑问。在这种类型的其他类和代码示例中,它将以2的幂列出的东西。但是,即使我对常量1,2,3,4,5,6进行编号,它似乎也能正常工作。而不是1,2,4,8,16等。所以有人也可以澄清我是否应该改变我的常数? 一些想法......我真的想找出一种方法来扩展这个类,以便它可以很容易地与其他类一起使用。假设我有一个
User
级和一个
Messages
级。
User
Messages
类都将扩展此类,并且能够使用位掩码进行设置/权限(以及稍后的其他类)。那么也许应该更改当前的类常量,以便可以传入它们或其他选项?我真的不想在站点/脚本的其他部分定义(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>';
    }
?>
    
已邀请:
其他人帮助进一步解释了这个位掩码位,所以我会专注于   “我确实喜欢让它更多的想法   可扩展/通用如此不同   类可以扩展它并将其用于   不同的部分,我只是不确定   该怎么做呢“ 来自你对@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的幂列出,但是就我所知,即使我将常量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);
让我们在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_NONE
0 & 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个权限。 GMP扩展包括可以在任意长度整数上运行的按位运算。 另一种选择可能是在大整数上使用这个答案的代码,这可能允许你将一个巨大的整数表示为一个字符串,尽管对它进行按位操作可能会很有趣。 (您可以将其下转换为base-2,然后在预期位置对字符串“1”或“0”进行子检查,但这将是一个巨大的性能阻力。)     
这是我的建议:
<?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
如果结果不为零,则表示您拥有该权限,否则您将获得该权限。     
我在课堂上看到的最大错误是你将业务逻辑混合到一个数据结构中。您的类的目的是将多个布尔值(即true / false)存储在一个整数中。这不必在课堂上完成,但很方便。这就是它的目的。 我会删除类中的权限标志,并将它们外包到您的业务逻辑类中。 &LT;编辑&gt; 数据结构是处理一件事的实体:数据。不以任何方式解释数据。例如,堆栈是一种可以放入内容的数据结构,它将首先为您提供最后一项。这里有一点:它并不关心,你放在那里:整数,用户对象,指针,汽车,大象,它只会处理数据的存储和检索。 另一方面,业务逻辑是您定义数据结构如何相互交互的地方。这是定义权限的位置,您声明创建博客帖子的人可以编辑它,而不允许其他人。 这些是您的应用程序的两个根本不同的视图,不应混合使用。您可以将权限存储在另一个数据结构中(例如整数数组,或者Permission对象的哈希表,或者任何其他数据结构),并且可以在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;
    
  “我确实喜欢让它更具可扩展性/通用性的想法,所以不同的类可以扩展它并将它用于不同的部分,我只是不知道该怎么做呢” 不要这样做,有各种原因。没有特定的顺序,简而言之:从数据对象中分离出功能类。不要扩展不需要继承的东西。使用属性,扩展类通常不需要与位掩码类紧密耦合就可以工作。此外,在PHP中,您只能从一个类扩展。如果您将此用于此类有限用途,则扩展对象已经烧毁了该功能。 因此,您可能不喜欢在大脑中进行二进制计算,而是拥有一个类,而不是为您封装二进制计算,并且提供了一个更人性化的接口(名称而不是数字,至少可以说)与之交互。精细。但那就是它。您可以通过传递二进制值来传递位掩码。如果你不需要二进制值,那么enum类可能就是你正在寻找的(具体来看FlagsEnum)。     

要回复问题请先登录注册