Аргументы функций в виде битовых констант в PHP

от автора

Привет, Хабр! Представляю вашему вниманию перевод статьи Лиама Хамметта (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 func($arg = 0) {     if ($arg & A) {         echo 'A';     }     if ($arg & B) {         echo 'B';     } } example();      // nothing example(A);     // 'A' example(B);     // 'B' example(A | B); // 'AB'

По такому же принципу работают другие встроенные функции PHP (например json_encode).

Стоит ли оно того?

Возможно у вас теперь появилось желание применять такой подход для функций с большим количество аргументов, которые участвуют в условных конструкциях, но существует ряд недостатков в обработке побитовых флагов функции:

  • Вы не можете передавать не булевые значения через аргумент.
  • Нет стандартного способа документировать аргументы с помощью docblocks.
  • Вы теряете общие подсказки и поддержку большинства IDE, которое было бы, если бы вы передавали одно логическое значение в качестве аргумента.
  • Вместо этого вы можете передать ассоциативный массив в качестве аргумента, чтобы иметь возможность устанавливать небулевые значения (или даже класс).
  • Существуют веские причины, по которым вам следует избегать использования логических «флагов функций» в качестве аргументов и вместо этого использовать другую функцию или метод для изменяемой функциональности.

Тем не менее, теперь вы знаете, как это делается.


Чтение большого списка определения констант может быть немного многословным и трудным, поэтому вот вспомогательная функция упрощающая их определение.


ссылка на оригинал статьи https://habr.com/post/423257/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *