# 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 ##### 标志位插入到第 `n` 位,默认值为 `k` 插入公式: `(value >> n << (n + 1)) + (value & ((1 << n) - 1)) + (k << n)` 删除公式: `(value >> (n + 1) << n) + (value & ((1 << n) - 1))` migration: ```php 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))")]); } } ```