Arguments of functions as bit constants in PHP

Hi, Habr! I present to you the translation of an article by Liam Hammett : Bitmask Constant Arguments in PHP .

PHP contains many standard functions that take boolean arguments in the form of embedded constants with values ​​of binary numbers.
These values ​​are combined into a single function argument in order to pass multiple Boolean flags in a compact manner.


They may work a little differently than many people imagine and use in their code, so I propose to consider how it actually works.




How it is used in PHP functions


PHP 7.2, including extensions, contains over 1800+ predefined constants and some of them are used as function arguments.
One example of such an application is a new option in PHP 7.3 that allows you to throw an exception to a function json_encodein case of conversion errors.


json_encode($value, JSON_THROW_ON_ERROR);

Using |(or) a bitwise operator, several function arguments work as one. Example from PHP documentation:


json_encode($value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);

Very nothing.


But still, how does it work?


Using bitwise operations to achieve the same effect in the world of user-defined functions is actually simple, but it requires at least a basic knowledge of what bits are and how bitwise operations work in PHP.


Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8) or binary (base 2) number system. [...] 
To write in binary notation, you must put before the number 0b. 
- php.net

Examples of constants with a different set of binary numbers:


const A = 0b0001; // 1const B = 0b0010; // 2const C = 0b0100; // 4const D = 0b1000; // 8

Pay attention to the example and sequence of numbers. Each binary value represents a value twice as high for each zero at the end. Zeros between 0b and 1 are optional, but may help align the source code.


Fortunately, we need to understand how only two bitwise operations work.


Bitwise OR (OR)


Do not confuse the operator |( bitwise "OR" ) with the frequently used operator ||( logical "OR" ), which is usually found in constructions if else.
Bitwise "OR" is a binary operation, the action of which is equivalent to applying a logical "OR" to each pair of bits at the same positions in the binary representations of the operands. In other words, if both the corresponding bits of the operands are 0, the binary bit of the result is 0; if at least one bit of the pair is 1, the binary bit of the result is 1.


An example of a bitwise OR operation:


const A     = 0b0001;
const B     = 0b0010;
const C     = 0b0100;
const D     = 0b1000;
A | B     === 0b0011;
A | C | D === 0b1101;

Bitwise “AND” (AND)


Similarly, the operator &( bitwise "AND" ) should not be confused with the frequently used operator &&( logical "AND" ).
Bitwise "AND" is a binary operation, the action of which is equivalent to applying a logical "AND" to each pair of bits at the same positions in the binary representations of the operands. In other words, if both corresponding bits of the operands are 1, the resulting binary bit is 1; if at least one bit of the pair is 0, the resulting binary bit is 0.


const A     = 0b0001;
const B     = 0b0010;
const C     = 0b0100;
const D     = 0b1000;
const VALUE = 0b1010;
A & B     === 0b0000; // нет совпадений единичных битов в A и B
A & C & D === 0b0000; // нет совпадений единичных битов в A, B или C
A & A     === 0b0001; // тот же бит устанавливается в A дважды
A & VALUE === 0b0000; // нет совпадений единичных битов в A и VALUE
B & VALUE === 0b0010; // бит 1 встречается как в B, так и в VALUE

Can a number have a boolean type?


It is worth noting that in PHP there is the concept of "type manipulation " ( type juggling ). In the language of non-specialists, this means that he (PHP) will automatically try to convert data of one type to another, if necessary.
If you understand how such transformations occur, then this can be a useful tool.
For example, we know that a number 0acts as a falseconversion to a boolean (boolean) type, while all other numbers will be true. Remember that these binary values ​​we work with are actually integers?


Let's sum up!


Now we can combine this knowledge to create an if construct, the code in which will be executed only if the result of the bitwise operation between the numbers is not 0b0000(or 0, which is converted to false).


const A = 0b0001;
const B = 0b0010;
functionf($arg = 0){
    if ($arg & A) {
        echo'A';
    }
    if ($arg & B) {
        echo'B';
    }
}
f();        // nothing
f(A);       // 'A'
f(B);       // 'B'
f(A | B);   // 'AB'

By the same principle, other PHP built-in functions work (for example json_encode).


Is it worth it?


Perhaps you now have a desire to apply this approach to functions with a large number of arguments that participate in conditional constructions, but there are a number of drawbacks in the processing of bitwise function flags:


  • You cannot pass non-boolean values ​​through an argument.
  • There is no standard way to document arguments using docblocks.
  • You lose general tips and support for most IDEs.
  • Instead, you can pass an associative array as an argument to be able to set non-null values ​​(or even a class ).
  • There are compelling reasons why you should avoid using logical "function flags" as arguments and instead use another function or method for mutable functionality.

However, now you know how to do it.




Reading a long list of definitions of constants can be verbose and difficult, so here’s an auxiliary function that simplifies their definition.


Also popular now: