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


Привет, Хабр! Представляю вашему вниманию перевод статьи Лиама Хамметта (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.
  • Вместо этого вы можете передать ассоциативный массив в качестве аргумента, чтобы иметь возможность устанавливать небулевые значения (или даже класс).
  • Существуют веские причины, по которым вам следует избегать использования логических «флагов функций» в качестве аргументов и вместо этого использовать другую функцию или метод для изменяемой функциональности.

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




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

Вы можете помочь и перевести немного средств на развитие сайта

Теги:



Комментарии (18):

  1. 3JIoDeu
    /#19109491

    Спасибо за обзор. Хоть практического применения у битовых операций в php не так уже и много. На ум приходит только конфигурирование и система прав.
    Было бы интересно почитать еще про трюки с побитовыми сдвигами. Они, в свою очередь, могут быть использованы в некоторых алгоритмах c выигрышем производительности.

    • rjhdby
      /#19110819

      Если в вашем алгоритме нужны битовые операции, то вы будете их использовать, если нет, то нет. И от используемого языка это не зависит.

      • VolCh
        /#19113335

        Для передачи булевых флагов в алгоритм есть много способов, битовые операции лишь один из них.

        • rjhdby
          /#19113423

          А при чем здесь передача булевых флагов? Других вариантов применения битовых операций разве нет?

    • maximw
      /#19111353 / -1

      Если в задаче побитовые операции дают заметный прирост производительности, то, вероятно, для решения этой задачи не стоит использовать PHP (стоит использовать не PHP)

      • rjhdby
        /#19111781

        А при чем тут прирост производительности?

        • maximw
          /#19112043

          См коммент выше. Там речь о производительности.

          • rjhdby
            /#19112059

            Упс, прошу прощения. Почему-то показалось, что это был ответ на мой коммент.

    • zagayevskiy
      /#19112025 / -1

      Неужели интерпретатор пхп настолько убог, что не умеет сам оптимизировать умножение в сдвиги?

  2. FanatPHP
    /#19109561 / -1

    Исходная статья полезная, но пожалуйста, если русский язык для вас родной, вычитайте этот текст на свежую голову, и исправьте хотя бы согласование членов предложения. Глаза болят читать все эти "жонглирование типа", "поддержку, которое". Про смысловые ляпы я уж молчу. Понять смысл предложения "Это может пригодиться, когда вы понимаете, как происходят преобразования" можно только мысленно переведя обратно на английский.


    Если же не родной, то, возможно, вам пока рано заниматься переводами на русский.

  3. SemaIVVV
    /#19109597 / -3

    Спасибо автор, йо-хо! Я даже не заметил этого нововведения, наконец-то пхп дорос до visual basic 6.0! (в шестом басике именно так реализованы битовые и натуральные константы для передачи в функции).
    С одной стороны, в старом басике, который писали просто супер спецы, много чего было реализовано гораздо продуманнее, чем во многих, даже современных, языках. Но, с другой стороны, его писали многие лета назад, и все-таки, он во многом устарел для современных реалий (использования на современных устройствах).
    Но вот передача побитовыми операциями в функции, как по мне, была написано очень даже правильно (и работает очень быстро) — поэтому и для пхп сгодится нынешнего.

    • Hardcoin
      /#19110641

      Не заметили этого нововведения в php4? А где вы были с тех пор?

      • Akdmeh
        /#19111079

        Ну типичный представитель тех, кто ненавидит PHP, хотя в последний раз он видел код на PHP 4 в году эдак 2009-м.
        ООП, PDO + prepared statements, traits, namespaces, filter_input, синтаксический сахар, грамотные фреймворки, PHP-FPM, Composer, оптимизация движка 7.0, возможность указания типа для функций (пусть и со своими особенностями)?
        Нет, не слышали. У них дальше PHP плохой, не то что ____ (подставить свой любимый язык).
        Я не говорю, что в PHP нет проблем, но появилось и много хорошего.

  4. Akdmeh
    /#19110175

    Кстати, на Хабре есть статья об этой же теме, написанная в 2011 году:
    habr.com/post/134557

  5. rjhdby
    /#19110661

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

    Гхм… Нет, ну я конечно допускаю, что для кого-то оно будет откровением.

  6. fhabr
    /#19112761

    Обалдеть, статья про побитовые операции! Казалось бы, при чём тут вообще php?

  7. VolCh
    /#19113347

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