|
|
# BitFlag 位标志
### Configuration
#### 添加文件: `BitFlagBehavior.php`
```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 <?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 <?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
#### 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 <?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->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); } }
```
|