Bitmasks and Bitwise Operators in PHP
Using bitmasks and bitwise operators to store, evaluate and manipulate system settings in PHP
Sometime ago I was speaking with a colleague about using bitmasks to store system flag data. He said it was a waste of time and effort.
In an employee rostering application I created, I have a timesheet feature that clients may want active only for specific employee employment types (i.e. any combination of Fulltime, Part-time, Casual, and/or Contractors).
Some developers might store each selected option as separate values (individual table columns/rows), whereas my colleague suggested I should simply store it in a single column as a string of ones and zeros with 1
representing a selected option and 0
an unselected option.
Using that approach, the above options set would be stored as 1111
and the below options set stored as 1001
.
The first method is not something I wanted to pursue and the second method didn’t seem terribly efficient.
Instead of those two methods, to learn something new, I decided to investigate an alternative involving bitmasks and bitwise operators.
So, what did I learn?…
Bitmask vs Bitwise
Firstly, a bit (binary digit) is a single integer like a one or zero and a byte is a concatenation of eight bits. For example, a 6 represented in 8bit binary is 00000110
.
How is that a six? Well:
- 1 is
00000001
- 2 is
00000010
- 4 is
00000100
- making 6 a combination of 2 and 4
00000110
Bitmasks and bitwise are related but distinct concepts. Bitwise operations evaluate and manipulate specific ‘bits’ within a binary representation, while a bitmask is the value representing a pattern/set of bits.
Our 00000110
above is a binary representation of 6, but it’s also a bitmask for 2 and 4.
Using Bitmasks and Bitwise Operators for Settings
Using code examples, I will demonstrate how to implement bitwise operators to create a bitmask for a single option set.
What are the bitwise operators?
PHP.net documentation lists all available bitwise operators, but for this use case, we are only concerned with the And, Or and Not operators.
Assigning constant values
In this example, we have three classes, an Employee
, TimsheetSettings
and BitmaskFlags
.
The Employee
object will have an employment_type
property and class constants for each possible employment type. Each employment type/constant is assigned an integer, double the previous.
These integers are representations of a binary value as commented.
<?php
class Employee
{
/**
* Employment type options
*/
const TYPE_CONTRACTOR = 1; // 00000001
const TYPE_CASUAL = 2; // 00000010
const TYPE_PARTTIME = 4; // 00000100
const TYPE_FULLTIME = 8; // 00001000
const TYPE_PROJECT = 16; // 00010000
}
Saving the bitmask value
The value representing the set of selected employment type options will be saved on the TimesheetSettings
model against a property called import_template_for_types
.
Retrieving and evaluating the bitmask
When evaluating or manipulating the setting value, create a new BitmaskFlags
class and pass the current value of the setting to the constructor.
$flags = new BitmaskFlags($settings->import_template_for_types);
For the purposes of the following examples, the current stored value of import_template_for_types
is 12
(only fulltime and parttime were set ON).
<?php
class BitmaskFlags {
/**
* Hold all values
*
* @var integer
*/
protected $flags;
/**
* Initialise with the current value of the flag option set
*
* @param integer $bitwise
*/
public function __construct(int $bitmask = 0)
{
$this->flags = $bitmask;
}
}
To check if a particular employment type has been set within the flags we use the & (And) bitwise operator. The & operator will evaluate to determine if the bits are set in both values (the current option set and the employment type under evaluation).
$flags->checkFlag(Employee::TYPE_FULLTIME); // true
$flags->checkFlag(Employee::TYPE_CASUAL); // false
<?php
class BitmaskFlags {
protected $flags;
/**
* Check if a particular flag is set within the flag option set
*
* @param integer $flag
* @return boolean
*/
public function checkFlag(int $flag): bool
{
return $this->flags & $flag;
}
}
Manipulating the bitmask
When you need to manipulate the bitmask to turn settings ON, use the | (Or) bitwise operator. This operator will set any bits missing from the $flags
property.
$flags->setFlag(Employee::STATUS_PROJECT, true);
The $flags
property will now have a value of 28
as it contains the setting value for fulltime, parttime and project.
To turn a setting OFF, use the & (And) operator in conjunction with the ~ (Not) bitwise operator. The Not operator will not include (or unset) the setting value from the $flags
property.
We can use the same method for both ON and OFF, by passing a boolean state and perform a conditional bitwise operation against the $flags
property.
$flags->setFlag(Employee::STATUS_PROJECT, false);
The $flags
property is now back to 12
.
<?php
class BitmaskFlags {
protected $flags;
/**
* Update flag option within the option set,
* using state to set whether the option is on or off
*
* @param integer $flag
* @param boolean $state
* @return integer
*/
public function setFlag(int $flag, bool $state): int
{
return $state
? $this->flags |= $flag
: $this->flags &= ~$flag;
}
}
My BitmaskFlags
class also has two other methods that may be of use. A reset and a getter.
<?php
class BitmaskFlags {
protected $flags;
/**
* Get current value of flag option set
*
* @return integer
*/
public function getFlags()
{
return $this->flags;
}
/**
* Reset flag option set
*
* @return void
*/
public function reset()
{
return $this->flags = 0;
}
}
What are some other applications?
Although fun, using bitmasks and bitwise could be too “clever” or complex in a project with multiple developers. When looking at the import_template_for_types
value in the database, it may not be immediately clear to a developer which options the user had selected.
I know of another application that is using bitmasks to store the status of an invoice (draft, approved, void, deleted, processing, paid, emailed etc). That’s no doubt even more confusing for developers.
Possibly more relatable, PHP itself uses bitmasks for the error_reporting
php.ini
setting. “To show all errors, except for notices, the php.ini file instructions say to use: E_ALL & ~E_NOTICE
”.
You can read more about that on in the Bitwise Operators php.net documentation.
Want more?
Not necessarily bitwise or bitmask, but if you’d like information on counting in binary, check this page out and if you’re super geeky you might learn to count binary on your hands. My favourite is 4.