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.

5.2 KiB

BitFlag 位标志

Configuration

添加文件: BitFlagBehavior.php
<?php
namespace {yourApp}\behaviors;

use yii\base\Behavior;
use yii\db\ActiveRecord;
use yii\db\Expression;

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

    public function events()
    {
        return [
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate',
            ActiveRecord::EVENT_AFTER_FIND => '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()
    {
        $owner = $this->owner;
        foreach ($this->bitFlags as $bitFlag => $flags) {
            $bitFlagValue = $owner->$bitFlag;
            foreach ($flags as $pos => $flag) {
                $owner->$flag = ($bitFlagValue & 1 << $pos) >> $pos;
            }
        }
    }

    /**
     * 设置位标志
     * @param string $bitFlag 位标志
     * @param string[] $flags 标志
     */
    public function setBitFlag($bitFlag, $flags)
    {
        $owner = $this->owner;
        foreach ($flags as $pos => $flag) {
            $flagValue = $owner->$flag;
            if ($flagValue === null) {
                continue;
            }
            $owner->$bitFlag = $this->getBitFlagUpdate($owner->$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;
    }
}
在需要的Model里添加Behavior
<?php

namespace {yourApp}\models;

use {yourApp}\behaviors\BitFlagBehavior;
 
class MyModel extends base\MyModel
{
    /**
     * @var int 状态
     */
    public $s0;
    public $s1;
    public $s2;
    public $s3;
    public $s5;
    public $s6;
    public $s7;
    public $s8;

    /**
     * 位标志映射表:
     * [
     *     位标志名 => [
     *         标志位 => 标志名
     *     ]
     *     ...
     * ]
     */
    const BIT_FLAGS = [
        'status' => [
            's0',
            's1',
            's2',
            's3',
        ],
        // 'flags' => [
        //     's5', 's6', 's7', 's8'
        // ]
    ];

    // 其他Model代码......

    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);
    }
}
添加文件 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();