Improve this PHP bitfield class for settings/permissions?

后端 未结 5 1437
名媛妹妹
名媛妹妹 2020-12-05 03:48

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 pe

5条回答
  •  -上瘾入骨i
    2020-12-05 04:13

    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?

    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:

    define('PERM_NONE', 0);
    define('PERM_READ', 1);
    define('PERM_WRITE', 2);
    define('PERM_EDIT', 4);
    define('PERM_DELETE', 8);
    define('PERM_SUPER', 16);
    

    Let's inspect the bit values of these permissions at the PHP interactive prompt:

    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
    

    Now let's create a user that has READ access and WRITE access.

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

    Or a user that can read, write, delete, but not edit:

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

    We can check permission using bitwise-AND and making sure the result is not zero:

    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)
    

    (It's worth noting that PERM_NONE & PERM_NONE is 0 & 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:

    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
    

    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 am looking for any suggestions/code to improve this class even more so it can be used in my app for settings and in some cases user permissions.

    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.)

提交回复
热议问题