Привет! Представляю вашему вниманию перевод статьи Лиама Хамметта (Liam Hammett): Bitmask Constant Arguments in PHP.
PHP содержит множество стандартных функций, которые принимают аргументы логического типа (boolean) в форме встроенных констант со значениями двоичных чисел.
Эти значения комбинируются в единый аргумент функции с целью передачи нескольких булевых флагов компактным образом.
Они могут работать немного не так, как многие представляют и используют в своем коде, поэтому предлагаю рассмотреть, как на самом деле это устроено.
Как это используется в функциях PHP
PHP 7.2, включая расширения, содержит свыше 1800+ предопределённых констант и часть из них используются как аргументы функций.
Один из примеров такого применения — это новая опция в PHP 7.3, которая позволяет бросать исключение из функции json_encode
при ошибках преобразования.
json_encode($value, JSON_THROW_ON_ERROR);
Используя |
(или) побитовый оператор, несколько аргументов функции работают как один. Пример из PHP документации:
json_encode($value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);
Очень даже ничего.
Но все таки, как же это работает?
Применять побитовые операции, чтобы достичь того же эффекта в мире пользовательских функции на самом деле просто, но это требует как минимум базовых знаний о том, что такое биты и как работают побитовые операции в PHP.
Целые числа могут быть указаны в десятичной (основание 10), шестнадцатеричной (основание 16), восьмеричной (основание 8) или двоичной (основание 2) системе счисления. […]
Для записи в двоичной системе счисления, необходимо поставить перед числом 0b.
— php.net
Примеры констант с разным набором двоичных чисел:
const A = 0b0001; // 1
const B = 0b0010; // 2
const C = 0b0100; // 4
const D = 0b1000; // 8
Обратите внимание на пример и последовательность чисел. Каждое двоичное значение представляет значение в два раза выше для каждого нуля на конце. Нули между 0b и 1 необязательны, но могут помочь выровнять исходный код.
К счастью, нам необходимо понимать, как работают только две побитовые операции.
Побитовое «ИЛИ» (OR)
Не стоит путать оператор |
(побитовое «ИЛИ») с часто используемым оператором ||
(логическое «ИЛИ»), который обычно встречается в конструкциях if else
.
Побитовое «ИЛИ» — это бинарная операция, действие которой эквивалентно применению логического «ИЛИ» к каждой паре битов, находящихся на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 0, двоичный разряд результата равен 0; если же хотя бы один бит из пары равен 1, двоичный разряд результата равен 1.
Пример побитовой операции «ИЛИ»:
const A = 0b0001;
const B = 0b0010;
const C = 0b0100;
const D = 0b1000;
A | B === 0b0011;
A | C | D === 0b1101;
Побитовое «И» (AND)
Аналогичным образом оператор &
(побитовое «И») не стоит путать с часто применяемым оператором &&
(логическое «И»).
Побитовое «И» — это бинарная операция, действие которой эквивалентно применению логического «И» к каждой паре битов, находящихся на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 1, результирующий двоичный разряд равен 1; если же хотя бы один бит из пары равен 0, результирующий двоичный разряд равен 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
Может ли число иметь логический тип (boolean)?
Стоит отметить, что в PHP существует понятие «манипуляции с типами» (type juggling). На языке неспециалистов это значит, что он (PHP) автоматически попытается преобразовать данные одного типа к другому, если это потребуется.
Если вы понимаете, как происходят такие преобразования, то это может быть полезным инструментом.
Например, мы знаем, что число 0
выступает в качестве false
при преобразовании к логическому типу (boolean), в то время как все остальные числа будут true
. Помните, что эти двоичные значения, с которыми мы работаем, на самом деле являются целыми числами?
Подведем итоги!
Теперь мы можем объединить это знание, чтобы создать конструкцию if, код в которой будет выполняться только в том случае, если результат побитовой операции между числами не будет 0b0000
(или 0
, который преобразовывается в false
).
const A = 0b0001;
const B = 0b0010;
function f($arg = 0)
{
if ($arg & A) {
echo 'A';
}
if ($arg & B) {
echo 'B';
}
}
f(); // nothing
f(A); // 'A'
f(B); // 'B'
f(A | B); // 'AB'
По такому же принципу работают другие встроенные функции PHP (например json_encode
).
Стоит ли оно того?
Возможно у вас теперь появилось желание применять такой подход для функций с большим количество аргументов, которые участвуют в условных конструкциях, но существует ряд недостатков в обработке побитовых флагов функции:
- Вы не можете передавать не булевые значения через аргумент.
- Нет стандартного способа документировать аргументы с помощью docblocks.
- Вы теряете общие подсказки и поддержку большинства IDE.
- Вместо этого вы можете передать ассоциативный массив в качестве аргумента, чтобы иметь возможность устанавливать небулевые значения (или даже класс).
- Существуют веские причины, по которым вам следует избегать использования логических «флагов функций» в качестве аргументов и вместо этого использовать другую функцию или метод для изменяемой функциональности.
Тем не менее, теперь вы знаете как это делается.
Чтение длинного списка определения констант может быть многословным и трудным, поэтому вот вспомогательная функция упрощающая их определение.
Автор: dimaborisovsky