5
\$\begingroup\$

I have been trying to learn more about using bits/bitfields/bitmask or whatever you call them exactly in PHP for different settings and permissions. With the help of others, I have come up with this class. It has come a long way from when I started it but I am looking for any ideas on how to improve it more.

<?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;
}
?>

Example usage:

<?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);

//turn permission PERM_ADMIN2 to off/false
$bf->clear($bf::PERM_ADMIN2); // sets $bf::PERM_ADMIN2 bit to false


// 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>';
}

?>
\$\endgroup\$

2 Answers 2

6
\$\begingroup\$

You are kind of missing the point of using bitmasks.

You should be making use of simple bit operations.

If each bit represents a privacy settings, then the operations should be:

  1. Check if a value is set, using & operator.

    ($this->value & $n) == $n;

  2. Set a value using the | operator (or |=).

    $this->value |= $n;

  3. Clear a value using the & operator (or &=).

    $this->value &= ~$n;

And then your values should be powers of 2. Usually 0 is saved for a null value or invalid value.

class UserPrivacySettings_BitField extends BitField
{
    const PRIVACY_EMAIL = 1;
    const PRIVACY_NAME = 2;
    const PRIVACY_ADDRESS = 4;
    const PRIVACY_PHONE = 8;
    const PRIVACY_ALL = 15;
}

Now the first 4 values correspond to 1 bit. And you can make use of PRIVACY_ALL by using the combination of all the other values.

i.e. In bits

PRIVACY_EMAIL   = 0001
PRIVACY_NAME    = 0010
PRIVACY_ADDRESS = 0100
PRIVACY_PHONE   = 1000
PRIVACY_ALL     = 1111

This way if you set all the values individually, and then check PRIVACY_ALL, that will evaluate to true.

Here's some revised code, with examples at the end.

<?php
abstract class BitField {

    private $value;

    public function __construct($value=0) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }

    public function get($n) {
        return ($this->value & $n) == $n;
    }

    public function set($n) {
        $this->value |= $n;
    }

    public function clear($n) {
        $this->value &= ~$n;
    }
}

class UserPrivacySettings_BitField extends BitField
{
    const PRIVACY_EMAIL = 1;
    const PRIVACY_NAME = 2;
    const PRIVACY_ADDRESS = 4;
    const PRIVACY_PHONE = 8;
    const PRIVACY_ALL = 15;
}

$bf = new UserPrivacySettings_BitField();
echo "Setting PRIVACY_EMAIL<br/>";
$bf->set(UserPrivacySettings_BitField::PRIVACY_EMAIL);
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_EMAIL));
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_NAME));
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_ADDRESS));
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_PHONE));
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_ALL));
echo "Setting PRIVACY_NAME<br/>";
$bf->set(UserPrivacySettings_BitField::PRIVACY_NAME);
echo "Setting PRIVACY_ADDRESS<br/>";
$bf->set(UserPrivacySettings_BitField::PRIVACY_ADDRESS);
echo "Setting PRIVACY_PHONE<br/>";
$bf->set(UserPrivacySettings_BitField::PRIVACY_PHONE);
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_EMAIL));
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_NAME));
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_ADDRESS));
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_PHONE));
var_dump($bf->get(UserPrivacySettings_BitField::PRIVACY_ALL));

As an example, look at the values for the Error Constants in PHP. http://php.net/manual/en/errorfunc.constants.php

\$\endgroup\$
5
  • \$\begingroup\$ jason's idea here is to pass in bit indexes (3) instead of the raw bit fields (8). Otherwise he may as well just use a raw integer. \$\endgroup\$ Commented Mar 29, 2011 at 1:36
  • \$\begingroup\$ @David Harkness There doesn't seem to be much point in using bit fields at all in that case, why not use an associative array? \$\endgroup\$
    – Jacob
    Commented Mar 29, 2011 at 2:04
  • \$\begingroup\$ thanks for the updated code, I will deffinately play with it, I ran some test and you code seems to be slightly faster, other then a speed increase, both your class and my original are doing the same thing right? Just you'r is less complex as mine has to get the power of 2 for the constants and your's skips this step, am i right? Thanks a bunch for the help \$\endgroup\$
    – JasonDavis
    Commented Mar 29, 2011 at 2:44
  • \$\begingroup\$ @jasondavis, yes they are doing the same thing but in mine the left shifting it part of the constant, whereas yours does it every time. Plus mine allows for a combination (PRIVACY_ALL is a combination of all the others). You could define the constants like const PRIVACY_EMAIL = 1 << 0 and const PRIVACY_NAME = 1 << 1 \$\endgroup\$
    – Jacob
    Commented Mar 29, 2011 at 3:07
  • \$\begingroup\$ I would most likely use a bare int with a class that holds the pre-shifted bit field constants. This makes dealing with them really easy for the user. But if you're going to design a class to use indexes, you may as well go hole-hog. ;) \$\endgroup\$ Commented Mar 29, 2011 at 6:25
4
\$\begingroup\$

If you really want to drink the PHP Kool-Aid, you could implement the ArrayAccess interface so that clients could use

$bf[UserPermissions_BitField::PERM_READ] = true;
...
if ($bf[UserPermissions_BitField::PERM_READ]) { ... }

Given that you're encapsulating the integer implementation of the bit field, you should probably add a $size parameter to the constructor to allow the accessor methods to make sure the given $field is within bounds.

\$\endgroup\$
2
  • \$\begingroup\$ thanks for the ideas, I am new to all this, been stuck in the procedural ways for a few years now but i'm learning...anyways I was recommended this by another user before and I checked it out, i'm just not sure is there any real benefit of making it look or work as an array? I'm just curious if i'm missing something as you are the 2nd person to suggest this, thanks for the comments \$\endgroup\$
    – JasonDavis
    Commented Mar 29, 2011 at 2:42
  • \$\begingroup\$ @jasondavis - As I said in a comment to Jacob's answer, I probably wouldn't use a class here, but that's not because it's a bad idea. Rather, I would probably have whoever owns the bit field manage the values with named accessors instead of exposing the constants to the user. With your class, making it work like an array is more a neat ability of PHP. Named methods are probably better and more intuitive. You'll get autocomplete help for them unlike the array access. \$\endgroup\$ Commented Mar 29, 2011 at 6:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.