BitmaskedBehavior.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <?php
  2. /**
  3. * BitmaskedBehavior
  4. *
  5. * An implementation of bitwise masks for row-level operations.
  6. * You can submit/register flags in different ways. The easiest way is using a static model function.
  7. * It should contain the bits like so (starting with 1):
  8. * 1 => w, 2 => x, 4 => y, 8 => z, ... (bits as keys - names as values)
  9. * The order shoudn't matter, as long as no bit is used twice.
  10. *
  11. * The theoretical limit for a 64-bit integer would be 64 bits (2^64).
  12. * But if you actually seem to need more than a hand full you
  13. * obviously do something wrong and should better use a joined table etc.
  14. *
  15. * @author Mark Scherer
  16. * @cake 2.x
  17. * @license MIT
  18. * @uses ModelBehavior
  19. * 2012-02-24 ms
  20. */
  21. class BitmaskedBehavior extends ModelBehavior {
  22. /**
  23. * settings defaults
  24. */
  25. protected $_defaults = array(
  26. 'field' => 'status',
  27. 'mappedField' => null, # NULL = same as above
  28. //'mask' => null,
  29. 'bits' => null,
  30. 'before' => 'validate', // on: save or validate
  31. );
  32. /**
  33. * Behavior configuration
  34. *
  35. * @param Model $Model
  36. * @param array $config
  37. * @return void
  38. */
  39. public function setup(Model $Model, $config = array()) {
  40. $config = array_merge($this->_defaults, $config);
  41. if (empty($config['bits'])) {
  42. $config['bits'] = Inflector::pluralize($config['field']);
  43. }
  44. if (is_string($config['bits'])) {
  45. if (method_exists($Model, $config['bits'])) {
  46. $config['bits'] = $Model->{$config['bits']}();
  47. } else {
  48. $config['bits'] = false;
  49. }
  50. }
  51. if (empty($config['bits'])) {
  52. throw new InternalErrorException('Bits not found');
  53. }
  54. /*
  55. if (Set::numeric(array_keys($config['bits']))) {
  56. $last = 1;
  57. $bits = array();
  58. foreach ($config['bits'] as $flag) {
  59. $bits[$flag] = $last = $last * 2;
  60. }
  61. $config['bits'] = $bits;
  62. }
  63. */
  64. $this->settings[$Model->alias] = $config;
  65. }
  66. public function beforeFind(Model $Model, $query) {
  67. $field = $this->settings[$Model->alias]['field'];
  68. if (isset($query['conditions']) && is_array($query['conditions'])) {
  69. $query['conditions'] = $this->encodeConditions($Model, $query['conditions']);
  70. }
  71. return $query;
  72. }
  73. public function afterFind(Model $Model, $results, $primary) {
  74. $field = $this->settings[$Model->alias]['field'];
  75. if (!($mappedField = $this->settings[$Model->alias]['mappedField'])) {
  76. $mappedField = $field;
  77. }
  78. foreach ($results as $key => $result) {
  79. if (isset($result[$Model->alias][$field])) {
  80. $results[$key][$Model->alias][$mappedField] = $this->decode($Model, $result[$Model->alias][$field]);
  81. }
  82. }
  83. return $results;
  84. }
  85. public function beforeValidate(Model $Model) {
  86. if ($this->settings[$Model->alias]['before'] != 'validate') {
  87. return true;
  88. }
  89. $this->encodeData($Model);
  90. return true;
  91. }
  92. public function beforeSave(Model $Model) {
  93. if ($this->settings[$Model->alias]['before'] != 'save') {
  94. return true;
  95. }
  96. $this->encodeData($Model);
  97. return true;
  98. }
  99. /**
  100. * @param int $bitmask
  101. * @return array $bitmaskArray
  102. * from DB to APP
  103. */
  104. public function decode(Model $Model, $value) {
  105. $res = array();
  106. $i = 0;
  107. $value = (int) $value;
  108. foreach ($this->settings[$Model->alias]['bits'] as $key => $val) {
  109. $val = (($value & pow(2, $i)) != 0) ? true : false;
  110. if ($val) {
  111. $res[] = $key;
  112. }
  113. $i++;
  114. }
  115. return $res;
  116. }
  117. /**
  118. * @param array $bitmaskArrayk
  119. * @return int $bitmask
  120. * from APP to DB
  121. */
  122. public function encode(Model $Model, $value) {
  123. $res = 0;
  124. foreach ($value as $key => $val) {
  125. $res |= (int) $val;
  126. }
  127. if ($res === 0) {
  128. return null; # make sure notEmpty validation rule triggers
  129. }
  130. return $res;
  131. }
  132. public function encodeConditions(Model $Model, $conditions) {
  133. $field = $this->settings[$Model->alias]['field'];
  134. if (!($mappedField = $this->settings[$Model->alias]['mappedField'])) {
  135. $mappedField = $field;
  136. }
  137. foreach ($conditions as $key => $val) {
  138. if ($key === $mappedField) {
  139. $conditions[$field] = $this->encode($Model, $val);
  140. if ($field != $mappedField) {
  141. unset($conditions[$mappedField]);
  142. }
  143. continue;
  144. } elseif ($key === $Model->alias . '.' . $mappedField) {
  145. $conditions[$Model->alias . '.' .$field] = $this->encode($Model, $val);
  146. if ($field != $mappedField) {
  147. unset($conditions[$Model->alias . '.' .$mappedField]);
  148. }
  149. continue;
  150. }
  151. if (!is_array($val)) {
  152. continue;
  153. }
  154. $conditions[$key] = $this->encodeConditions($Model, $val);
  155. }
  156. return $conditions;
  157. }
  158. public function encodeData(Model $Model) {
  159. $field = $this->settings[$Model->alias]['field'];
  160. if (!($mappedField = $this->settings[$Model->alias]['mappedField'])) {
  161. $mappedField = $field;
  162. }
  163. if (isset($Model->data[$Model->alias][$mappedField])) {
  164. $Model->data[$Model->alias][$field] = $this->encode($Model, $Model->data[$Model->alias][$mappedField]);
  165. }
  166. if ($field != $mappedField) {
  167. unset($Model->data[$Model->alias][$mappedField]);
  168. }
  169. }
  170. /**
  171. * @param mixed bits (int, array)
  172. * @return array $sqlSnippet
  173. */
  174. public function containsBit(Model $Model, $bits) {
  175. $bits = (array) $bits;
  176. $res = array();
  177. foreach ($bits as $bit) {
  178. $res[]['('.$Model->alias.'.'.$this->settings[$Model->alias]['field'].' & ? = ?)'] = array($bit, $bit);
  179. }
  180. if (count($res) > 1) {
  181. return array('AND'=>$res);
  182. }
  183. return $res[0];
  184. }
  185. /**
  186. * @param mixed bits (int, array)
  187. * @return array $sqlSnippet
  188. */
  189. public function containsNotBit(Model $Model, $bits) {
  190. $bits = (array) $bits;
  191. $res = array();
  192. foreach ($bits as $bit) {
  193. $res[]['('.$Model->alias.'.'.$this->settings[$Model->alias]['field'].' & ? != ?)'] = array($bit, $bit);
  194. }
  195. if (count($res) > 1) {
  196. return array('AND'=>$res);
  197. }
  198. return $res[0];
  199. }
  200. }