改进这个 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 发表了一条评论,其中包含“实现一些自动魔法 getters/setters 或 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>';
}
?>
I have been trying to figure out the best way to use bitmask or bitfields in PHP for a long time now for different areas of my application for different user settings and permissions. The farthest I have come so far is from a class contributed by svens in the Stack Overflow
post Bitmask in PHP for settings?. I have slightly modified it below, changing it to use class constants instead of DEFINE and making sure the get method is passed an int only. I also have some sample code to test the class's functionality below.
I am looking for any suggestions/code to improve this class even more so it can be used in my application for settings and in some cases user permissions.
Answered in the comment below by mcrumley
In addition, I have a question about the numbering of my constants. In other classes and code sample for this type it will have things listed in powers of 2. However, it seems to work the same as far as I can tell even if I number my constants 1,2,3,4,5,6 instead of 1, 2, 4, 8, 16, etc. So can someone also clarify if I should change my constants?
Some ideas... I would really like to figure out a way to extend this class so it is easy to use with other classes. Let's say I have a User
class and a Messages
class. Both the User
and Messages
class will extend this class and be able to use the bitmask for their settings/permissions (along with other classes later on). So maybe the current class constants should be changed so they can be passed in or some other option? I really would rather not have to define (define('PERM_READ', 1);) in other parts of the site/script and would like to keep it somewhat encapsulated, but flexible as well; I am open to ideas. I want this to be rock solid and flexible like I said to use with multiple other classes for settings or permissions. Possibly some kind of array should be used? @Svens from my previous question linked above posted a comment with "implement some automagic getters/setters or ArrayAccess for extra awesomness. – svens" What do you think about something like that as well?
Include example source code if possible, please.
<?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);
}
}
?>
Example Usage...
<?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>';
}
?>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您不需要这样做,因为代码已经处理了这个问题。这个解释有点迂回。
位字段被处理为2的幂的原因是每个2的幂都由单个位表示。这些单独的位可以按位或组合在一起形成一个可以传递的整数。在较低级语言中,传递数字比传递结构体“更容易”。
让我演示一下这是如何工作的。让我们使用 2 的幂设置一些权限:
让我们在 PHP 交互式提示符下检查这些权限的位值:
现在让我们创建一个具有 READ 访问权限和 WRITE 访问权限的用户。
或者一个可以读、写、删除但不能编辑的用户:
我们可以使用按位与来检查权限并确保结果不为零:(
值得注意的是
PERM_NONE & PERM_NONE
是 < code>0 & 0,我创建的“none”权限实际上在这里不起作用,并且很快就会被忘记。)您的类正在做一些稍微不同的事情。 >,但最终结果是相同的。它使用位移位将“on”位向左移动 X 次,其中 X 是权限的编号。实际上,这是权限值的 2 次方。演示:
虽然这些方法实际上相同,但我认为简单的 AND 运算和 OR 运算比 XOR 运算和位移位更容易阅读。
我有一个建议,还有一个警告。
我的建议是使类抽象并且不在其中定义任何权限。相反,构建从它继承的类并定义它们自己的权限。您不想考虑在不相关的位字段之间共享相同的权限名称,并且使用类名作为前缀是非常明智的。我希望你无论如何都会这么做。
我的警告很简单但很可怕:PHP 无法可靠地表示大于 31 位的整数。事实上,当它在 64 位系统上编译时,它只能表示 63 位整数。这意味着,如果您将应用程序分发给公众,并且希望使用内置数学函数,您将被限制为不超过 31 个权限。
GMP 扩展 包括可以对任意长度整数进行按位运算。
另一种选择可能是使用此代码回答大整数,这可以让您将大整数表示为字符串,尽管对其进行按位运算可能......有趣。 (您可以将其下转换为基数 2,然后在预期位置对字符串“1”或“0”进行 substr 检查,但这将是一个巨大的性能拖累。)
You don't need to, because the code is already taking care of that. This explanation is going to be a bit roundabout.
The reason that bit fields are handled as powers of two is that each power of two is represented by a single bit. These individual bits can be bitwise-ORed together into a single integer that can be passed around. In lower-level languages, it's "easier" to pass around a number than, say, a struct.
Let me demonstrate how this works. Let's set up some permissions using the powers of two:
Let's inspect the bit values of these permissions at the PHP interactive prompt:
Now let's create a user that has READ access and WRITE access.
Or a user that can read, write, delete, but not edit:
We can check permission using bitwise-AND and making sure the result is not zero:
(It's worth noting that
PERM_NONE & PERM_NONE
is0 & 0
, which is zero. The "none" permission I created doesn't actually work here, and can promptly be forgotten about.)Your class is doing something slightly different, but the end result is identical. It's using bit shifting to move an "on" bit over to the left X times, where X is the number of the permission. In effect, this is raising 2 to the power of the permission's value. A demonstration:
While these methods are effectively identical, I'd argue that simple ANDing and ORing is easier to read than the XORing and bit-shifting.
I have one suggestion, and one warning.
My suggestion would be making the class abstract and not defining any permissions within it. Instead, build classes that inherit from it and define their own permissions. You don't want to consider sharing the same permission names across unrelated bit fields, and prefixing them with class names is pretty sane. I expect you were going to do this anyway.
My warning is simple but dire: PHP can not reliably represent an integer larger than 31 bits. In fact, it can only represent 63-bit integers when it's compiled on a 64-bit system. This means that, if you are distributing your application to the general public, you will be restricted to no more than 31 permissions if you wish to use the built-in math functions.
The GMP extension includes bitwise operations that can function on arbitrary-length integers.
Another option might be using code from this answer on large integers, which could allow you to represent a huge integer as a string, though doing bitwise operations on that might be ... interesting. (You could down-convert it to base-2, then do a substr check for string "1" or "0" at the expected location, but that's gonna be a huge performance drag.)
其他人帮助进一步解释了其中的位屏蔽位,所以我将集中精力
来自您对 @Charles 帖子的评论。
正如 Charles 正确所说,您可以通过将功能提取到抽象类中并放入实际的“设置”来重用 Bitmask 类的功能(在此 。
例如:
然后用法就变成了:
要设置隐私设置,您只需实例化一个新的 UserPrivacySettings_BitField 对象并使用它即可,
这样您就可以创建尽可能多的不同 BitField 对象集 应用程序只需要定义一组代表您的选项的常量,
我希望这对您有用,但如果没有,也许对阅读本文的其他人有用。
Others have helped with further explaining the bit masking bit of this, so I'll concentrate on
from your comment on @Charles' post.
As Charles rightly said, you can re-use the functionality of your Bitmask class by extracting the functionality into an abstract class, and putting the actual "settings" (in this case permissions) into derived concrete classes.
For example:
And then usage simply becomes:
And to set privacy settings, you just instantiate a new UserPrivacySettings_BitField object and use that instead.
This way, you can create as many different sets of BitField objects as your application requires simply by defining a set of constants that represent your options.
I hope this is of some use to you, but if not, perhaps it will be of some use to someone else who reads this.
这是我的建议:
如您所见,我使用 1、2、4、8 等(2 的幂)来简化计算。如果你将一个权限映射到你拥有的一位:
那么你可以使用逻辑运算,例如你最初有这样的:
如果你想添加写权限,你只需要使用按位或运算符:
要删除你拥有的一位使用 $value & ~$bit,例如删除写入位:
最后,如果您想测试是否启用了一位操作,您必须将 $value 与要测试的 PERM_XXX 进行 AND 操作:
如果结果不为零,您就有权限,否则你就不会。
Here is my proposal:
As you can see, I used 1, 2, 4, 8, etc (powers of 2) to simplify the calculations. If you map one permission to one bit you have:
Then you can use logic operations, for example you have this initially:
If you want to add permissions to write, you only need to use the bitwise OR operator:
To remove one bit you have to use $value & ~$bit, for example remove the write bit:
Finally, if you want to test if one bit is enabled the operation you have to AND $value against the PERM_XXX you want to test:
If the result is not zero you have the permission, otherwise you don't.
我在你的课堂上看到的最大错误是你将业务逻辑混合到数据结构中。您的类的目的是将多个布尔值(即 true/false)存储在单个整数中。这不一定要在课堂上完成,但很方便。这就是它的目的。
我会将权限标志放在类中,并将它们外包到您的业务逻辑类中。
<编辑>
数据结构是处理一件事的实体: 数据。数据不会以任何方式解释。例如,堆栈是一种可以放入内容的数据结构,这将首先给你最后一个项目。重点是:它并不关心你放在那里的内容:整数、用户对象、指针、汽车、大象,它只会处理数据的存储和检索。
另一方面,业务逻辑是定义数据结构如何相互交互的地方。这是定义权限的地方,您可以在其中声明创建博客文章的人可以对其进行编辑,而其他人则不允许这样做。
这是应用程序的两种根本不同的视图,不应混合使用。您可以将您的权限存储在另一个数据结构中(作为整数数组,或哈希表权限对象,例如 - 或任何其他数据结构),您可以在 BitField 中存储其他标志数据结构(例如用户的布尔偏好,例如“想要接收新闻通讯”或“电子邮件地址已验证”)。
另一个改进是这些常量使用十六进制值,这将确保您的第 16 个值仍然可读。 (我宁愿建议在常量声明中使用位移运算符,这更具可读性,但出于性能原因,当前的 PHP 解释器不可能这样做。)
相同的类没有十六进制值的可读性较差,特别是如果添加更多标志:
我将进一步定义 set() 的参数必须是一位整数,而不是标志号。 demo 的 set() 实现就是我的意思:
The biggest mistake I see in your class is that you're mixing business logic into a data structure. The purpose of your class is to store multiple boolean values (i.e. true/false) in a single integer. This doesn't have to be done in a class, but it is convenient. And that is its purpose.
I would drop the permission flags in the class and outsource them into your business logic classes.
<EDIT>
A data structure is an entity that handles one thing: data. The data is not interpreted in any way. A stack, fore example, is a data structure that you can put stuff into, that will give you the last item first. And here is the point: It doesn't care, what you put in there: integers, User objects, pointers, cars, elephants, it will just handle the storage and retrieval of the data.
Business logic on the other hand is where you define how your data structures interact with each other. This is where permissions are defined, where you state that a person who created a blog post may edit it, and no one else is allowed to.
These are two fundamentally different views of your application and should not be mixed. You can store your permissions in another data structure (as an array of integers, or a hash table of Permission objects, for example - or any other data structure) and you can store other flags in your BitField data structure (like boolean preferences of your users, like "wants to receive newsletter" or "email address was verified").
</EDIT>
Another improvement is the usage of hex values for these constants, this will ensure that your 16th value is still readable. (I would rather recommend using bit-shift operators in the constant declarations, which is even more readable, but this is not possible with the current PHP interpreter for performance reasons.)
<EDIT>
The same class without hexadecimal values is less readable, especially if you add more flags:
</EDIT>
I would further define that the parameter to set() must be a single-bit integer, and not a flag number. The set() implementation by demon is what I mean:
不要这样做,有多种原因。没有特定的顺序,只是简单地说:将功能类与数据对象分开。不要扩展不需要继承的东西。使用属性代替,扩展类通常不需要与位掩码类紧密耦合即可工作。另外,在 PHP 中,您只能从一个类进行扩展。如果您将其用于如此有限的用途,则扩展对象已经烧毁了该功能。
因此,您可能不喜欢在大脑中进行二进制计算,而是有一个类来为您封装二进制计算,并提供一个更人性化的界面(至少可以说是名称而不是数字)进行交互。美好的。但仅此而已。您可以通过传递二进制值来传递位掩码。如果您不需要二进制值,可以使用枚举 class 可能就是您已经在寻找的内容(具体检查 FlagsEnum)。
Don't do that, there are various reasons why. In no specific order and just in short: Separate functional classes from data objects. Don't extend what does not need inheritance. Use a property instead, the extending classes normally do not need to be tightly coupled with the bitmask class to work at all. Additionally in PHP you can only extend from one class. If you make use of that for such a limited use, extending objects already have burned that feature.
So probably you love to not need to do binary calculations in your brain but have a class instead that has encapsulated the binary calculation for you and that offers an interface that is more human (names instead of numbers to say at least) to interact with. Fine. But that's just is it. You can pass along the bitmask by passing the binary value. If you don't need binary values, an enum class instead might be what you're looking for already (check the FlagsEnum in specific).