# BitFlag 位标志 ### Configuration #### 添加文件: `BitFlagBehavior.php` ```php [ * 标志位 => 标志名 * ] * ... * ] * @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 [ * 标志位 => 标志名 * ] * ... * ] */ 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 [ * 标志位 => 标志名 * ] * ... * ] * @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 #### Search ```php $myModels = MyModel::find() ->andWhereBitFlags(['s0' => 1, 's1' => 0]) ->orWhereBitFlags(['s2' => 0, 's3' => 1]) ->all(); ``` #### Migration ##### 从最高位添加标志位,默认值为 `1` (默认值为 `0` 无操作) 添加公式: `value + (1 << n)` 删除公式: `value - (1 << n)` migration: ```php update('{table}', ['{column}' => new \yii\db\Expression('{column} + (1 << {n})')]); } /** * {@inheritdoc} */ public function safeDown() { $this->update('{table}', ['{column}' => new \yii\db\Expression('{column} - (1 << {n})')]); } } ``` ##### 标志位插入到第 `n` 位,默认值为 `k` 插入公式: `(value >> n << (n + 1)) + (value & ((1 << n) - 1)) + (k << n)` 删除公式: `(value >> (n + 1) << n) + (value & ((1 << n) - 1))` migration: ```php update('{table}', ['{column}' => new \yii\db\Expression('({column} >> {n} << ({n} + 1)) + ({column} & ((1 << {n}) - 1)) + ({k} << {n})')]); } /** * {@inheritdoc} */ public function safeDown() { $this->update('{table}', ['{column}' => new \yii\db\Expression('({column} >> ({n} + 1) << {n}) + ({column} & ((1 << {n}) - 1))')]); } } ``` 封装: ```php /** * @param string $column * @param int $pos * @param string $operate * @param int $defaultValue * @return \yii\db\Expression */ public function getBitFlagExpression($column, $pos, $operate, $defaultValue = 0) { if ($operate == 'insert') { $expresstion = "($column >> $pos << ($pos + 1)) + ($column & ((1 << $pos) - 1)) + ($defaultValue << $pos)"; } elseif ($operate == 'delete') { $expresstion = "($column >> ($pos + 1) << $pos) + ($column & ((1 << $pos) - 1))"; } else { throw new \InvalidArgumentException('$operate: '.$operate); } return new \yii\db\Expression($expresstion); } ``` migration: ```php update('{table}', ['{column}' => $this->getBitFlagExpression('{column}', 1, 'insert', {k})]); } /** * {@inheritdoc} */ public function safeDown() { $this->update('{table}', ['{column}' => $this->getBitFlagExpression('{column}', 1, 'delete')]); } /** * @param string $column * @param int $pos * @param string $operate * @param int $defaultValue * @return \yii\db\Expression */ public function getBitFlagExpression($column, $pos, $operate, $defaultValue = 0) { if ($operate == 'insert') { $expresstion = "($column >> $pos << ($pos + 1)) + ($column & ((1 << $pos) - 1)) + ($defaultValue << $pos)"; } elseif ($operate == 'delete') { $expresstion = "($column >> ($pos + 1) << $pos) + ($column & ((1 << $pos) - 1))"; } else { throw new \InvalidArgumentException('$operate: '.$operate); } return new \yii\db\Expression($expresstion); } } ```