Использование битовых масок и побитовых операторов для хранения, оценки и управления системными настройками в PHP

Некоторое время назад я разговаривал с коллегой об использовании битовых масок для хранения данных системных флагов. Он сказал, что это пустая трата времени и усилий.

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

Некоторые разработчики могут хранить каждую выбранную опцию как отдельные значения (отдельные столбцы/строки таблицы), в то время как мой коллега предложил мне просто хранить ее в одном столбце в виде строки из единиц и нулей, где 1 представляет выбранную опцию, а 0 — невыбранную опцию.

При таком подходе набор параметров выше будет сохранен как 1111, а набор параметров ниже сохранен как 1001.

Первый метод — это не то, чем я хотел заниматься, а второй метод не казался очень эффективным.

Вместо этих двух методов, чтобы узнать что-то новое, я решил исследовать альтернативу, включающую битовые маски и побитовые операторы.

Итак, чему я научился?…

Битовая маска против побитовой

Во-первых, бит (binary digitit) — это одно целое число, например единица или ноль, а байт — это объединение восьми битов. Например, 6, представленное в 8-битном двоичном формате, равно 00000110.

Как это шесть? Ну:

  • 1 is 00000001
  • 2 is 00000010
  • 4 is 00000100
  • делая 6 комбинацией 2 и 400000110

Битовые маски и побитовые значения являются связанными, но разными понятиями. Побитовые операции оценивают и манипулируют определенными «битами» в двоичном представлении, а битовая маска — это значение, представляющее шаблон/набор битов.

Наше 00000110 выше — это двоичное представление 6, но это также и битовая маска для 2 и 4.

Использование битовых масок и побитовых операторов для настроек

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

Что такое побитовые операторы?

В документации PHP.net перечислены все доступные побитовые операторы, но в данном случае нас интересуют только операторы И, Или и Не.

Присвоение постоянных значений

В этом примере у нас есть три класса: Employee, TimsheetSettings и BitmaskFlags.

Объект Employee будет иметь свойство employment_type и константы класса для каждого возможного типа занятости. Каждому типу/константе занятости присваивается целое число, удваивающее предыдущее.

Эти целые числа являются представлениями двоичного значения, как указано в комментариях.

<?php

class Employee
{
    /**
     * Employment type options
     */
    const TYPE_CONTRACTOR = 1;   // 00000001
    const TYPE_CASUAL = 2;       // 00000010
    const TYPE_PARTTIME = 4;     // 00000100
    const TYPE_FULLTIME = 8;     // 00001000
    const TYPE_PROJECT = 16;     // 00010000

}

Сохранение значения битовой маски

Значение, представляющее набор выбранных опций типа занятости, будет сохранено в модели TimesheetSettings для свойства с именем import_template_for_types.

Получение и оценка битовой маски

При оценке или изменении значения параметра создайте новый класс BitmaskFlags и передайте текущее значение параметра конструктору.

$flags = new BitmaskFlags($settings->import_template_for_types);

Для целей следующих примеров текущее сохраненное значение import_template_for_types равно 12 (были включены только полный и неполный рабочий день).

<?php

class BitmaskFlags {

    /**
     * Hold all values
     *
     * @var integer
     */
    protected $flags;


    /**
     * Initialise with the current value of the flag option set
     *
     * @param integer $bitwise
     */
    public function __construct(int $bitmask = 0)
    {
        $this->flags = $bitmask;
    }

}

Чтобы проверить, установлен ли конкретный тип занятости внутри флагов, мы используем побитовый оператор & (And). Оператор & выполнит оценку, чтобы определить, установлены ли биты в обоих значениях (текущем наборе параметров и оцениваемом типе занятости).

$flags->checkFlag(Employee::TYPE_FULLTIME); // true

$flags->checkFlag(Employee::TYPE_CASUAL); // false

<?php

class BitmaskFlags {

    protected $flags;


    /**
     * Check if a particular flag is set within the flag option set
     *
     * @param integer $flag
     * @return boolean
     */
    public function checkFlag(int $flag): bool
    {
        return $this->flags & $flag;
    }

}

Управление битовой маской

Если вам нужно манипулировать битовой маской, чтобы включить настройки, используйте побитовый оператор | (Или). Этот оператор установит любые биты, отсутствующие в свойстве $flags.

$flags->setFlag(Employee::STATUS_PROJECT, true);

Свойство $flags теперь будет иметь значение 28, так как оно содержит значение настройки для полного рабочего дня, неполного рабочего дня и проекта.

Чтобы отключить параметр, используйте оператор & (И) в сочетании с побитовым оператором ~ (Не). оператор. Оператор Не не будет включать (или сбрасывать) значение параметра из свойства $flags.

Мы можем использовать один и тот же метод как для ON, так и для OFF, передав логическое состояние и выполнив условную побитовую операцию над свойством $flags.

$flags->setFlag(Employee::STATUS_PROJECT, false);

Свойство $flags теперь вернулось к 12.

<?php

class BitmaskFlags {

    protected $flags;


    /**
     * Update flag option within the option set,
     * using state to set whether the option is on or off
     *
     * @param integer $flag
     * @param boolean $state
     * @return integer
     */
    public function setFlag(int $flag, bool $state): int
    {
        return $state
            ? $this->flags |= $flag
            : $this->flags &= ~$flag;
    }

}

В моем классе BitmaskFlags также есть два других метода, которые могут быть полезны. Сброс и геттер.

<?php

class BitmaskFlags {

    protected $flags;


    /**
     * Get current value of flag option set
     *
     * @return integer
     */
    public function getFlags()
    {
        return $this->flags;
    }


    /**
     * Reset flag option set
     *
     * @return void
     */
    public function reset()
    {
        return $this->flags = 0;
    }

}

Какие еще приложения?

Хотя это и забавно, использование битовых масок и побитового кода может быть слишком «умным» или сложным в проекте с несколькими разработчиками. При просмотре значения import_template_for_types в базе данных разработчику может быть не сразу ясно, какие параметры выбрал пользователь.

Я знаю другое приложение, которое использует битовые маски для хранения статуса счета-фактуры (черновик, одобрено, недействительно, удалено, обрабатывается, оплачено, отправлено по электронной почте и т. д.). Это, без сомнения, еще больше сбивает с толку разработчиков.

Возможно, более логично, что сам PHP использует битовые маски для настройки error_reporting php.ini. «Чтобы отобразить все ошибки, кроме уведомлений, в инструкциях к файлу php.ini указано использовать: E_ALL & ~E_NOTICE».

Подробнее об этом можно прочитать в Документации по побитовым операторам php.net.

Хочу больше?

Не обязательно побитовый или битовая маска, но если вам нужна информация о счете в двоичном формате, загляните на эту страницу, и если вы супер гик, вы можете научиться считать в двоичном формате на руках. Моя любимая 4.