93

I'm not sure if bitmask is the correct term. Let me explain:

In php, the error_reporting function can be called multiple ways:

// Report simple running errors
error_reporting(E_ERROR | E_WARNING | E_PARSE);

// Reporting E_NOTICE can be good too (to report uninitialized
// variables or catch variable name misspellings ...)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);

// Report all errors except E_NOTICE
// This is the default value set in php.ini
error_reporting(E_ALL ^ E_NOTICE);

I got the term bitmask from the php.net page here

Anyway the point of this is, I have implemented a SIMPLE method called ls which returns the contents of a directory.

This function takes 3 args... ( $include_hidden = false, $return_absolute = false, $ext = false )

So when i call the function, i set how i want the results. Whether i want the results to return hidden directories, whether i want basenames only etc.

so when i call the function i'm writing

ls(true, false, true)
ls(false, false, true)
ls(true, true, true)
etc...

I thought it would be much more readable if i could just flag how i want the data returned?

so something like:

ls( INCLUDE_HIDDEN | HIDE_EXTS );
ls( SHOW_ABSOLUTE_PATHS | HIDE_EXTS );

etc...

How would i implement this in terms of testing which flags have been called?

tshepang
  • 12,111
  • 21
  • 91
  • 136
AlexMorley-Finch
  • 6,785
  • 15
  • 68
  • 103

4 Answers4

173

It's quite simple actually. First a bit of code to demonstrate how it can be implemented. If you don't understand anything about what this code is doing or how it works, feel free to ask additional questions in the comments:

const FLAG_1 = 0b0001; // 1
const FLAG_2 = 0b0010; // 2
const FLAG_3 = 0b0100; // 4
const FLAG_4 = 0b1000; // 8
// Can you see the pattern? ;-)

function show_flags ($flags) {
  if ($flags & FLAG_1) {
    echo "You passed flag 1!<br>\n";
  }
  if ($flags & FLAG_2) {
    echo "You passed flag 2!<br>\n";
  }
  if ($flags & FLAG_3) {
    echo "You passed flag 3!<br>\n";
  }
  if ($flags & FLAG_4) {
    echo "You passed flag 4!<br>\n";
  }
}

show_flags(FLAG_1 | FLAG_3);

Demo


Because the flags are integers, on a 32-bit platform you define up to 32 flags. On a 64-bit platform, it's 64. It is also possible to define the flags as strings, in which case the number of available flags is more or less infinite (within the bounds of system resources, of course). Here's how it works in binary (cut down to 8-bit integers for simplicity).

FLAG_1
Dec:    1
Binary: 00000001

FLAG_2
Dec:    2
Binary: 00000010

FLAG_3
Dec:    4
Binary: 00000100

// And so on...

When you combine the flags to pass them to the function, you OR them together. Let's take a look at what happens when we pass FLAG_1 | FLAG_3

  00000001
| 00000100
= 00000101

And when you want to see which flags were set, you AND the bitmask with the flag. So, lets take the result above and see if FLAG_3 was set:

  00000101
& 00000100
= 00000100

...we get the value of the flag back, a non-zero integer - but if we see if FLAG_2 was set:

  00000101
& 00000010
= 00000000

...we get zero. This means that you can simply evaluate the result of the AND operation as a boolean when checking if the value was passed.

DaveRandom
  • 87,921
  • 11
  • 154
  • 174
  • 12
    So basic bitwise operations are: `$flags & FLAG_1` - check if FLAG_1 is set, `$flags | FLAG_1` - set FLAG_1, `$flags & ~FLAG_1` - unset FLAG_1, `~$flags` - invert flags – Konstantin Pereiaslov Nov 01 '13 at 16:53
  • 8
    I don't know PHP and will likely never learn it, but I'm glad I stumbled upon this question - your explanation can help anyone implement bit-mapping in any language :) – Chris Cirefice Dec 03 '13 at 04:29
  • 7
    I would recommend defining the power 2 decimals as bit wise shift operation of 1. `define('FLAG_1', 1<<0); define('FLAG_2', 1<<2); define('FLAG_3', 1<<3); define('FLAG_4', 1<<4);` This is how it's usually done in C – AmitP Jan 06 '14 at 16:12
  • 7
    @AmitP There are a couple of (minor) issues with this approach in PHP. 1) currently the `const` keyword does not support expressions, even if the result is constant (i.e. `const FLAG_1 = 1 << 0;` is a parse error) - obviously this is not an issue with `define()` 2) in C a constant expression like `const int FLAG_1 = 1 << 0;` will be resolved at compile time, and the actual compiled value will be the result of the expression, whereas in PHP this is evaluated every time, a microscopic performance hit. Neither of these issues are good reasons to avoid the more readable version you suggest though. – DaveRandom Jan 06 '14 at 17:32
  • Note: [constant scalar expressions](https://wiki.php.net/rfc/const_scalar_exprs) are available since PHP 5.6! – BenMorel Apr 05 '19 at 20:45
  • 1
    @Benjamin unrelated to this question but a useful thing you can do with const scalar exprs: `const IS_WINDOWS = \PHP_OS & "\xDF\xDF\xDF" === 'WIN';` is a compile-time equivalent of the very common `stripos(\PHP_OS, 'WIN') === 0` idiom. As it happens this is currently pointless (`PHP_OS` is currently always `'WINNT'` on all versions of Windows) but it is future-proof I guess... – DaveRandom Apr 05 '19 at 20:59
  • Somehow found my way from here to this library: https://github.com/cruxinator/php-bitmask; have a look if you're in need of working with bitmasks. – ExternalUse Aug 14 '20 at 13:42
19
define( "INCLUDE_HIDDEN", 0x1 );
define( "HIDE_EXTS", 0x2 );
define( "SHOW_ABSOLUTE_PATHS", 0x4 );
//And so on, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800 etc..

You can then check for individual flags in your ls function:

if( $flags & INCLUDE_HIDDEN ) { //<-- note just a single &, bitwise and
    //$flags have INCLUDE_HIDDEN
}
Esailija
  • 138,174
  • 23
  • 272
  • 326
1

The others have offered good suggestions, but these days it's much more common to pass in associative arrays instead of bitmasks. It's much more readable and allows you to pass in other variables other than just true/false values. Something like this:

myFunction(['includeHidden' => true, 'fileExts' => false, 'string' => 'Xyz']);

function myFunction($options) {
    // Set the default options
    $options += [
        'includeHidden' => false,
        'fileExts' => true,
        'string' => 'Abc',
    ];

    if ($options['includeHidden']) {
        ...
    }
    ...
}
Simon East
  • 55,742
  • 17
  • 139
  • 133
  • 3
    I used to do it this way too, but fellow companions complain that there is no "type hinting" in their IDE's, no proper [doc comments](https://en.wikipedia.org/wiki/PHPDoc) and the function does not [throw errors when required params are not passed](http://symfony.com/doc/current/components/options_resolver.html) . Ideally "named params" would solve all of these issues if they existed in php – Timo Huovinen Oct 31 '15 at 08:43
  • Hi @TimoHuovinen - thanks for the comment. It's easy to add doc comments which outline the options & defaults, throw an exception if you need to, and make the parameter default to an array which allows for type hinting. Will that work for you? – Simon East Oct 31 '15 at 08:49
  • Oh I see. The Symfony OptionsResolver class goes a step further to offer even more features. Cool. – Simon East Oct 31 '15 at 08:53
  • "_It's easy to add doc comments which outline the options & defaults_" yeah, you can add them like [twig does it](https://github.com/nikic/Twig/blob/master/lib/Twig/Environment.php) but it's still a workaround. "_and make the parameter default to an array which allows for type hinting_" how would you do this? I meant IDE type hinting and code completion, where the editor tells you what parameters the function can accept. (Keep in mind, I'm also for using arrays for options, just that I need to figure out how to keep the companions happy) – Timo Huovinen Oct 31 '15 at 09:01
  • The problem with this approach is you will end up with a huge bumber of if else statements – numediaweb Jul 28 '17 at 09:38
  • @numediaweb Perhaps you could explain more. The `if()` statement in my example was just one way of making use of the provided options, but it's not a necessity. Do you have an improved technique? – Simon East Jul 30 '17 at 02:47
  • This doesn't answer the OP's question which is specifically about Bitmasks. Suggesting not to use them instead of answering the question asked isn't very helpful. – lots0logs Aug 29 '22 at 18:48
0

I had an API I was working with - the documentation was pretty thin. It was giving us a single integer and said:

"Information is encoded in these values as a bit field. Bit fields work by storing multiple true/false values in the same integer, instead of having multiple integers for each value. To look up a value in a bitfield, you'll want to make use of Bit Masks."

Bit     Decimal Value    Setting
0       1                Display
1       2                Sell
2       4                Kiosk Display
3       8                No Passes
4       16               Dolby Digital
5       32               THX
6       64               DLP
[etc]

I figured out how to solve it based on the other answers here, and though I would share the function I came up with.

I ended up writing this function:

<?php
function bitmap_decode( $label_array, $value, $return_item ) {
    $label_array = array_flip( $label_array ); // swap the keys and values
    $i = $label_array[ $return_item ]; // get the decimal key value for the item on the list
    $i =  2**$i; // use 2 to the nth power to get the decimal bitmap value for the item
    return $value & $i ? 1 : 0; // use the & operator to determine if the value is true or false. return 1 or 0 accordingly
}

... which would parse out the values

$info1 = array(
    'Display',
    'Sell',
    'Kiosk Display',
    'No Passes',
    'Dolby Digital',
    'THX',
    'DLP',
);


$api_value = 5;
echo bitmap_decode( $info1, $api_value, 'Sell' ); // 0

$api_value = 35;
echo bitmap_decode( $info1, $api_value, 'Sell' ); // 1

And then we can do the opposite - encode the values without having to think about any non-binary numbers or anything like that:

function bitmap_encode( $items, $label_array ) {
    $return = 0;
    $label_array = array_flip( $label_array ); // swap the keys and values
    foreach ( $items as $item ) {
        $i = $label_array[ $item ]; // get the decimal key value for the item on the list
        $i = 2**$i; // use 2 to the nth power to get the decimal bitmap value for the item
        $return += $i; // add $i to the return value
    }
    return $return;
}

// set these flags as true
$flags = array( 'Display', 'Sell', 'THX' );
echo bitmap_encode( $flags, $info1 ); // 35

I know this does not precisely answer the OP question, but it's another approach to making a bitmask system in PHP.

squarecandy
  • 4,894
  • 3
  • 34
  • 45