我一直试图找出在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
类。 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>';
}
?>
答案 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_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个权限。
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)。