Why should I use bitwise/bitmask in PHP?
Asked Answered
E

8

18

I am working on a user-role / permission system in PHP for a script.

Below is a code using a bitmask method for permissions that I found on phpbuilder.com.

Below that part is a much simpler version w3hich could do basicly the same thing without the bit part.

Many people have recommended using bit operators and such for settings and other things in PHP, I have never understood why though. In the code below is there ANY benefit from using the first code instead of the second?

<?php
/**
 * Correct the variables stored in array.
 * @param    integer    $mask Integer of the bit
 * @return    array
 */
function bitMask($mask = 0) {
    $return = array();
    while ($mask > 0) {
        for($i = 0, $n = 0; $i <= $mask; $i = 1 * pow(2, $n), $n++) {
            $end = $i;
        }
        $return[] = $end;
        $mask = $mask - $end;
    }
    sort($return);
    return $return;
}


define('PERMISSION_DENIED', 0);
define('PERMISSION_READ', 1);
define('PERMISSION_ADD',  2);
define('PERMISSION_UPDATE', 4);
define('PERMISSION_DELETE', 8);

//run function
// this value would be pulled from a user's setting mysql table
$_ARR_permission = bitMask('5');

if(in_array(PERMISSION_READ, $_ARR_permission)) {
    echo 'Access granted.';
}else {
    echo 'Access denied.';
}
?>

non-bit version

<?PHP
/*
   NON bitwise method
*/

// this value would be pulled from a user's setting mysql table
$user_permission_level = 4;

if($user_permission_level === 4) {
    echo 'Access granted.';
}else {
    echo 'Access denied.';
}

?>
Esoteric answered 4/9, 2009 at 15:45 Comment(0)
K
41

Why not just do this...

define('PERMISSION_DENIED', 0);
define('PERMISSION_READ', 1);
define('PERMISSION_ADD',  2);
define('PERMISSION_UPDATE', 4);
define('PERMISSION_DELETE', 8);

//run function
// this value would be pulled from a user's setting mysql table
$_ARR_permission = 5;

if($_ARR_permission & PERMISSION_READ) {
    echo 'Access granted.';
}else {
    echo 'Access denied.';
}

You can also create lots of arbitrary combinations of permissions if you use bits...

$read_only = PERMISSION_READ;
$read_delete = PERMISSION_READ | PERMISSION_DELETE;
$full_rights = PERMISSION_DENIED | PERMISSION_READ | PERMISSION_ADD | PERMISSION_UPDATE | PERMISSION_DELETE;

//manipulating permissions is easy...
$myrights = PERMISSION_READ;
$myrights |= PERMISSION_UPDATE;    // add Update permission to my rights
Keim answered 4/9, 2009 at 15:55 Comment(3)
that is kind of what the question is about,my second code was meant to be something like this, just any other method BESIDE using bits, to me this way seems easiar and just as fast as using bits so I was hoping someone could help me understand why bits are recommendedEsoteric
This does use the bits. It just removed all the code you had that was not really required. The advantage of using bits is that each of the permissions can be made available (or not) independent of all the others. For example, you can have read permission, regardless of the setting for any of the other permissions.Keim
For reference, the check for rights (ie $myrights & (read|write|delete) ) will pass if user just has delete rights. If you want to check for all (read and write and delete), a slight motification is needed. example: function checkPerms($perms,$required,$all=true) { return ( $all ? (($perms & $required) == $required) : ($perms & $required) ); }Immemorial
R
10

The first allows people to have lots of permissions - read/add/update for example. The second example, the user has just PERMISSION_UPDATE.

Bitwise testing works by testing bits for truth values.

For example, the binary sequence 10010 would identify a user with PERMISSION_DELETE and PERMISSION_READ (the bit identifying PERMISSION_READ is the column for 2, the bit identifying PERMISSION_DELETE is the column for 16), 10010 in binary is 18 in decimal (16 + 2 = 18). Your second code sample doesn't allow you to do that sort of testing. You could do greater-than style checks, but that assumes everyone with PERMISSION_DELETE should also have PERMISSION_UPDATE, which may not be a valid assumption.

Ruffner answered 4/9, 2009 at 15:54 Comment(0)
R
10

Maybe it's just because I don't use bitmasks very often, but I find that in a language like PHP where developer productivity and code readability are more important than speed or memory usage (within limits, obviously), there's no real reason to use bitmasking.

Why not instead create a class that tracks things like permissions, and logged in users, and so on? Let's call it Auth. Then, if you want to check that a user has a permission, you can create a method HasPermission. e.g.,

if(Auth::logged_in() && Auth::currentUser()->hasPermission('read'))
    //user can read

then if you want to check whether they have some combination of permissions:

if(Auth::logged_in() && Auth::currentUser()->hasAllPermissions('read', 'write'))
    //user can read, and write

or if you want to check whether they have any of a certain group of permissions:

if(Auth::logged_in() && Auth::currentUser()->hasAnyPermissions('read', 'write'))
    //user can read, or write

Of course, it may not be a bad idea to define constants, such as PERMISSION_READ, which you can just define to be the string 'read', and so on.

I find this approach easier to read than bitmasks because the method names tell you exactly what it is you're looking for.

Reverential answered 4/9, 2009 at 16:24 Comment(2)
Using a class to hide an implementation is always a good idea. You could just as easily have: Auth::currentUser()->hasAnyPerm(PERM_READ, PERM_WRITE) or Auth::currentUser()->hasAnyPerm(Auth::READ, Auth::WRITE) and in the class use bitmasks. Then the user code never sees the bitmasks and you still get the space-savings value of bitmasks.Cusick
+1 This is on the money. Using bitmasks for permissions bits in LAMP is overkill, and actually more limiting, harder to maintain and potentially slower than a normalised database with one table of permission bits (e.g. which might have a column of known permission bits and a second with a string like "read-access" meaning both code and the DB structure is self-documenting) and and a separate table which maps those User ID's to Permission ID's.Archeozoic
M
1

Edit: rereading the question, it looks like the user's permissions are coming back from your database in a bitfield. If that's the case, you are going to have to use bitwise operators. A user who's permission in the database is 5 has PERMISSION_READ and PERMISSION_DENIED because (PERMISSION_READ & 5) != 0, and (PERMISSION_DENIED & 5) != 0. He wouldn't have PERMISSION_ADD, because (PERMISSION_ADD & 5) == 0

Does that make sense? All the complex stuff in your bitwise example looks unnecessary.


If you don't fully understand bitwise operations, then don't use them. It will only lead to lots of headaches. If you are comfortable with them, then use them where you feel they are appropriate. You (or whoever wrote the bitwise code) doesn't seem to fully grasp bitwise operations. There are several problems with it, like the fact that the pow() function is used, that would negate any kind of performance benefit. (Instead of pow(2, $n), you should use the bitwise 1 << $n, for example.)

That said, the two pieces of code do not seem to do the same things.

Memorialize answered 4/9, 2009 at 15:53 Comment(2)
Sorry my question and code examples are 100% readable. You are right the second code does not do teh EXACT same thing, I am just comparing the main function. Also the value in the DB does not exist yet because this is just in theory, I am still designing it. So the database value couldbe anything I decide to make it. I am just trying to find out where the performance gain in using bits is because as I see it so far, there is no real performance gain over pulling a straight number from my DB and using that number to determine if a user can do somethingEsoteric
using bits you'd be pulling a straight number from the database. If you don't use bits, you'll need to add several boolean fields in your database, one for each permission. if you pull a number straight from the database without using bits, then the user can only have exactly one permission.Memorialize
G
1

Try using what is in the bit.class.php at http://code.google.com/p/samstyle-php-framework/source/browse/trunk/class/bit.class.php

Checking against a specific bit:

<?php

define('PERMISSION_DENIED', 1);
define('PERMISSION_READ', 2);
define('PERMISSION_ADD',  3);
define('PERMISSION_UPDATE', 4);
define('PERMISSION_DELETE', 5);


if(bit::query($permission,PERMISSION_DENIED)){
echo 'Your permission is denied';
exit();
}else{
// so on
}

?>

And to turn on and off:

<?php

$permissions = 8;
bit::toggle(&$permissions,PERMISSION_DENIED);

var_dump($permissions); // outputs int(9)

?>
Gazpacho answered 5/9, 2009 at 15:32 Comment(0)
C
1

problem of this is if PERMISSION_READ is a mask itself

if($ARR_permission & PERMISSION_READ) {
    echo 'Access granted.';
}else {
    echo 'Access denied.';

then for 0101 - $rightWeHave 0011 - $rightWeRequire

it is access granted, which we probably do not want so it should be

if (($rightWeHave & $rightWeRequire) == $rightWeRequire) {
echo 'access granted';
}

so now for

0101 0011

result is

0001 so access is not granted because it is not equal to 0011

but for

1101 0101

it is ok as the result is 0101

Clonus answered 21/3, 2012 at 10:37 Comment(0)
P
1

Script checks which mask has been set in decimal. Maybe someone will need it:

<?php

$max = 1073741824;
$series = array(0);
$x = 1;
$input = $argv[1]; # from command line eg.'12345': php script.php 12345
$sum = 0;

# generates all bitmasks (with $max)
while ($x <= $max) {
    $series[] = $x;
    $x = $x * 2;
}

# show what bitmask has been set in '$argv[1]'
foreach ($series as $value) {
    if ($value & $input) {
        $sum += $value;
        echo "$value - SET,\n";
    } else {
        echo "$value\n";
    }
}

# sum of set masks
echo "\nSum of set masks: $sum\n\n";

Output (php maskChecker.php 123):

0
1 - SET,
2 - SET,
4
8 - SET,
16 - SET,
32 - SET,
64 - SET,
128
256
512
1024
2048
4096
8192
(...)

Sum of set mask: 123
Pattypatulous answered 16/6, 2016 at 9:58 Comment(0)
Y
0

I guess the first example gives you more control of exactly what permissions a user has. In the second you just have a user 'level'; presumably higher levels inherit all the permissions granted to a lower 'level' user, so you don't have such fine control.

Also, if I have understood correctly, the line

if($user_permission_level === 4)

means that only users with exactly permission level 4 have access to the action - surely you would want to check that users have at least that level?

Yalta answered 4/9, 2009 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.