You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
LIERLIER a69b14755c migration 1 year ago
readme.md migration 1 year ago

readme.md

BitFlag 位标志

Configuration

添加文件: BitFlagBehavior.php

<?php
namespace {yourApp}\behaviors;

use yii\base\Behavior;
use yii\db\ActiveRecord;
use yii\helpers\ArrayHelper;

class BitFlagBehavior extends Behavior
{
    /**
     * [
     *     位标志名 => [
     *         标志位 => 标志名
     *     ]
     *     ...
     * ]
     * @var array[] $bitFlags 位标志映射表
     */
    public $bitFlags = [];
    /**
     * @var array
     */
    private $_attributes = [];

    /**
     * {@inheritdoc}
     */
    public function init()
    {
        parent::init();
        $this->initAttributes();
    }

    /**
     * 初始化属性数组
     */
    private function initAttributes()
    {
        $keys = array_merge(...array_values($this->bitFlags));
        $this->_attributes = array_fill_keys($keys, null);
    }

    public function events()
    {
        return [
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate',
            ActiveRecord::EVENT_AFTER_FIND => 'afterFind',
            ActiveRecord::EVENT_AFTER_REFRESH => 'afterFind',
        ];
    }

    public function beforeSave()
    {
        foreach ($this->bitFlags as $bitFlag => $flags) {
            $this->owner->$bitFlag = 0;
            $this->setBitFlag($bitFlag, $flags);
        }
    }

    public function beforeUpdate()
    {
        foreach ($this->bitFlags as $bitFlag => $flags) {
            $this->setBitFlag($bitFlag, $flags);
        }
    }

    public function afterFind()
    {
        valuewner = $this->owner;
        foreach ($this->bitFlags as $bitFlag => $flags) {
            $bitFlagValue = valuewner->$bitFlag;
            foreach ($flags as $pos => $flag) {
                valuewner->$flag = ($bitFlagValue & 1 << $pos) >> $pos;
            }
        }
    }

    /**
     * 设置位标志
     * @param string $bitFlag 位标志
     * @param string[] $flags 标志
     */
    public function setBitFlag($bitFlag, $flags)
    {
        valuewner = $this->owner;
        foreach ($flags as $pos => $flag) {
            $flagValue = valuewner->$flag;
            if ($flagValue === null) {
                continue;
            }
            valuewner->$bitFlag = $this->getBitFlagUpdate(valuewner->$bitFlag, $pos, $flagValue);
        }
    }

    /**
     * 获得更新后的位标志值
     * @param int $bitFlagValue 位标志值
     * @param int $pos 状态位置
     * @param bool|int $flagValue 标志值
     * @return int
     */
    public function getBitFlagUpdate($bitFlagValue, $pos, $flagValue)
    {
        if ($flagValue) {
            $bitFlagValue = $bitFlagValue | 1 << $pos;
        } else {
            $bitFlagValue = $bitFlagValue & ~(1 << $pos);
        }
        return $bitFlagValue;
    }

    /**
     * {@inheritdoc}
     */
    public function __get(name)
    {
        return $this->_attributes[name];
    }

    /**
     * {@inheritdoc}
     */
    public function __set(name, $value)
    {
        $this->_attributes[name] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function canGetProperty(name, $checkVars = true)
    {
        return parent::canGetProperty(name, $checkVars) || ArrayHelper::keyExists(name, $this->_attributes);
    }

    /**
     * {@inheritdoc}
     */
    public function canSetProperty(name, $checkVars = true)
    {
        return parent::canSetProperty(name, $checkVars) || ArrayHelper::keyExists(name, $this->_attributes);
    }
}

在需要的Model里添加Behavior

<?php

namespace {yourApp}\models;

use {yourApp}\behaviors\BitFlagBehavior;
 
class MyModel extends base\MyModel
{
    /**
     * 位标志映射表:
     * [
     *     位标志名 => [
     *         标志位 => 标志名
     *     ]
     *     ...
     * ]
     */
    const BIT_FLAGS = [
        'status' => [
            's0',
            's1',
            's2',
            's3',
        ],
        // 'flags' => [
        //     's5', 's6', 's7', 's8'
        // ]
    ];

    public function fields()
    {
        $fields = parent::fields();
        $flagFields = [];
        foreach (self::BIT_FLAGS as $bitFlag => $flags) {
            $flagFields = ArrayHelper::merge($flagFields, array_combine($flags, $flags));
            unset($fields[$bitFlag]);
        }
        return ArrayHelper::merge($fields, $flagFields);
    }

    public function behaviors()
    {
        return [
            // 其他Behavior

            'bitFlagBehavior' => [
                'class' => BitFlagBehavior::class,
                'bitFlags' => self::BIT_FLAGS,
            ],
        ];
    }

    /**
     * {@inheritdoc}
     * 重写find()返回自定义查询类
     */
    public static function find()
    {
        $config = ['bitFlags' => self::BIT_FLAGS];
        return new MyModelQuery(get_called_class(), $config);
    }
    
    // 其他Model代码......
}

添加文件 MyModelQuery

<?php
namespace {yourApp}\models;

use yii\db\ActiveQuery;
use yii\db\Expression;

class MyModelQuery extends ActiveQuery
{
    /**
     * [
     *     位标志名 => [
     *         标志位 => 标志名
     *     ]
     *     ...
     * ]
     * @var array[] $bitFlags 融合状态表
     */
    public $bitFlags;

    /**
     * @param string[] $conditions
     * @return MyModelQuery
     */
    public function andWhereBitFlags($conditions = [])
    {
        return $this->andWhere($this->getBitFlagsCondition($conditions));
    }

    /**
     * @param string[] $conditions
     * @return MyModelQuery
     */
    public function orWhereBitFlags($conditions = [])
    {
        return $this->orWhere($this->getBitFlagsCondition($conditions));
    }

    /**
     * @param array $conditions
     * @return Expression
     */
    public function getBitFlagsCondition($conditions = [])
    {
        $conditionsKeys = array_keys($conditions);
        foreach ($this->bitFlags as $bitFlag => $flags) {
            $mask = $res = 0;
            foreach ($flags as $pos => $flag) {
                if (in_array($flag, $conditionsKeys)) {
                    $mask += 1 << $pos;
                    $res += $conditions[$flag] << $pos;
                }
            }
            if ($mask != 0) {
                $bitFlagsConditions[] = "$bitFlag & $mask = $res";
            }
        }
        return new Expression(implode(' and ', $bitFlagsConditions));
    }
}

Usage

$myModels = MyModel::find()
            ->andWhereBitFlags(['s0' => 1, 's1' => 0])
            ->orWhereBitFlags(['s2' => 0, 's3' => 1])
            ->all();

Migration

标志位插入到第 n 位,默认值为 k

插入公式: (value >> n << (n + 1)) + (value & ((1 << n) - 1)) + (k << n)

删除公式: (value >> (n + 1) << n) + (value & ((1 << n) - 1))

migration:

<?php

use yii\db\Migration;

/**
 * Class mxxxxxx_xxxxxx_update_{table}_table_{column}_column
 */
class mxxxxxx_xxxxxx_update_{table}_table_{column}_column extends Migration
{
    /**
     * {@inheritdoc}
     */
    public function safeUp()
    {
        $this->addBitFlag('{table}', '{column}', {pos}, {defaultValue});
    }

    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
        $this->deleteBitFlag('{table}', '{column}', {pos});
    }

    /**
     * 添加标志位
     * @param string $table
     * @param string $column
     * @param int $pos
     * @param int $defaultValue
     */
    public function addBitFlag($table, $column, $pos, $defaultValue = 0)
    {
        $this->update($table, [$column => new \yii\db\Expression("($column >> $pos << ($pos + 1)) + ($column & ((1 << $pos) - 1)) + ($defaultValue << $pos)")]);
    }

    /**
     * 删除标志位
     * @param string $table
     * @param string $column
     * @param int $pos
     */
    public function deleteBitFlag($table, $column, $pos)
    {
        $this->update($table, [$column => new \yii\db\Expression("($column >> ($pos + 1) << $pos) + ($column & ((1 << $pos) - 1))")]);
    }
}