HCE Project DC service web UI  0.2
Hierarchical Cluster Engine DC service web UI
 All Classes Namespaces Files Functions Variables Pages
StatesBinaryFieldBehavior.php
Go to the documentation of this file.
1 <?php
13 /*
14 Usage:
15 
16  ARModel:
17 
18  public function behaviors()
19  {
20  return array(
21  'StatesBinaryField'=>array(
22  'class'=>'path.to.StatesBinaryFieldBehavior',
23  'attribute'=>'states',
24  'data'=>array(
25  'state1'=>'First model state',
26  'state2'=>'Second model state',
27  'state3'=>'Third model state',
28  ),
29  )
30  );
31  }
32 
33  View file:
34 
35 <?php
36 $model=ARModel::model()->findByPK(1);
37 
38 echo CHtml::beginForm();
39 echo CHtml::activeCheckBoxList($model, 'states', $model->statesData);
40 echo CHtml::submitButton();
41 echo CHtml::endForm();
42 ?>
43 
44  Checking state (in any attribute format [int or arr]):
45 if ($model->checkState('state1')) ...
46 
47  Force conversion of attribute:
48 $model->convertAttribute() or $model->convertAttribute('auto') - converts to opposite format
49 $model->convertAttribute('int') - coverts to database int representation
50 $model->convertAttribute('arr') - converts to output array representation
51 
52 
53  Selecting models:
54 Model::model()->statesIncluded(array('state1','state3'))->findAll();
55  Will select records:
56  ('state1','state2','state3')
57  ('state1','state3')
58  Not:
59  ('state1','state2')
60 
61 Model::model()->statesExact(array('state1','state3'))->findAll();
62  Will select records:
63  ('state1','state3')
64  Not:
65  ('state1','state2','state3')
66  ('state1')
67 
68 Model::model()->statesAtLeast(array('state1','state3'))->findAll();
69  Will select records:
70  ('state1','state2','state3')
71  ('state1')
72  ('state3')
73  Not:
74  ('state2')
75 
76  Using out of ARModel for forming sql WHERE statement:
77 $states=array('state1','state3');
78 $data=array(
79  'state1'=>'First model state',
80  'state2'=>'Second model state',
81  'state3'=>'Third model state',
82 );
83 $bitmask=StatesBinaryFieldBehavior::createBitMask($states,$data); // Returns integer: 5
84 $dbColumn='table.column'; // Database table column alias
85 $where_Included ="$dbColumn & $bitmask = $bitmask";
86 $where_Exact ="$dbColumn = $bitmask";
87 $where_AtLeast ="$dbColumn & $bitmask != 0";
88 
89 
90  Other usage moments:
91 
92 * Massive attributes assignment available.
93 
94 * Model saving at controller makes as ususal.
95 
96 * No need for attribute validation, just set it as save-attribute.
97 
98 * Normaly attribute contains an array of selected(setted) states,
99  like array('state1','state3')
100  and converts it to int representation only before saving.
101 
102 * Order of state keys at requested $states doesn't matter
103  Example:
104  Model::model()->statesAtLeast(array('state1','state3'))
105  equal to
106  Model::model()->statesAtLeast(array('state3','state1'))
107 
108 * Only one field per model available.
109 
110 * You a free to rename keys and labels of source $data without affecting to
111  database records, BUT NOT THEIR ORDER.
112 
113 */
114 
115 class StatesBinaryFieldBehavior extends CActiveRecordBehavior
116 {
120  public $attribute = 'states';
121 
138  public $data = array();
139 
145  public $convertBeforeSave = true;
146 
152  public $convertAfterSave = true;
153 
160  public $convertAfterFind = true;
161 
167  public function beforeSave($event)
168  {
169  if ($this->convertBeforeSave) {
170  $this->toDatabaseInt();
171  }
172  }
173 
178  private function toDatabaseInt()
179  {
180  $states = $this->getOwner()->{$this->attribute};
181  if (!is_array($states)) {
182  return;
183  }
184  $this->getOwner()->{$this->attribute} = $this->convertStates($states);
185  }
186 
194  private function convertStates($states)
195  {
196  if (is_int($states)) {
197  return $states;
198  }
199  if (!is_array($states)) {
200  throw new CException('Wrong states format given. Must be an array.');
201  }
202 
203  return self::createBitMask($states, $this->data);
204  }
205 
224  public static function createBitMask($states, $data)
225  {
226  $keys = array_keys($data);
227  $new_states = 0;
228  foreach ($states as $state) {
229  if (isset($data[$state])) {
230  $pos = array_search($state, $keys);
231  $new_states = 1 << $pos | $new_states;
232  }
233  }
234 
235  return $new_states;
236  }
237 
243  public function afterSave($event)
244  {
245  if ($this->convertAfterSave) {
246  $this->toOutputArray();
247  }
248  }
249 
256  private function toOutputArray()
257  {
258  $states = $this->getOwner()->{$this->attribute};
259  if (!is_int($states)) {
260  return;
261  }
262  $keys = array_keys($this->data);
263  $new_states = array();
264  foreach ($keys as $pos => $k) {
265  if ($states >> $pos & 1) {
266  $new_states[] = $k;
267  }
268  }
269  $this->getOwner()->{$this->attribute} = $new_states;
270  }
271 
277  public function afterFind($event)
278  {
279  // INT values from databases stores as STRING type at attributes after data load
280  $this->getOwner()->{$this->attribute} = (int)$this->getOwner()->{$this->attribute};
281 
282  if ($this->convertAfterFind) {
283  $this->toOutputArray();
284  }
285  }
286 
299  public function checkState($states)
300  {
301  $data = $this->getOwner()->{$this->attribute};
302  if (empty($data)) {
303  return false;
304  }
305  $keys = array_keys($this->data);
306  if (is_string($states)) {
307  $states = array($states);
308  }
309 
310  if (is_int($data)) { // Attribute in INT format
311  foreach ($states as $state) {
312  $pos = array_search($state, $keys);
313  if ($pos === false || ($data >> $pos & 1) === 0) {
314  return false;
315  }
316  }
317 
318  return true;
319  } elseif (is_array($data)) {
320  // Attribute in ARRAY format
321 
322  foreach ($states as $state) {
323  if (array_search($state, $data) === false) {
324  return false;
325  }
326  }
327 
328  return true;
329  }
330 
331  return false;
332  }
333 
344  public function convertAttribute($type = 'auto')
345  {
346  if ($type === 'int') {
347  $this->toDatabaseInt();
348  } elseif ($type === 'arr') {
349  $this->toOutputArray();
350  } elseif ($type === 'auto') {
351  $attribute = $this->getOwner()->{$this->attribute};
352  if (is_array($attribute)) {
353  $this->toDatabaseInt();
354  } elseif (is_int($attribute)) {
355  $this->toOutputArray();
356  }
357  }
358  }
359 
365  public function getStatesData()
366  {
367  return $this->data;
368  }
369 
377  public function statesIncluded($states)
378  {
379  $owner = $this->getOwner();
380  $db = $owner->getDbConnection();
381  $criteria = $owner->getDbCriteria();
382  $bitmask = $this->convertStates($states);
383  $column = $db->quoteColumnName($owner->getTableAlias()) . '.' . $db->quoteColumnName($this->attribute);
384  $criteria->mergeWith(array(
385  'condition' => $column . " & {$bitmask} = {$bitmask}",
386  ));
387 
388  return $owner;
389  }
390 
398  public function statesExact($states)
399  {
400  $owner = $this->getOwner();
401  $db = $owner->getDbConnection();
402  $criteria = $owner->getDbCriteria();
403  $bitmask = $this->convertStates($states);
404  $column = $db->quoteColumnName($owner->getTableAlias()) . '.' . $db->quoteColumnName($this->attribute);
405  $criteria->mergeWith(array(
406  'condition' => $column . " = " . $bitmask,
407  ));
408 
409  return $owner;
410  }
411 
419  public function statesAtLeast($states)
420  {
421  $owner = $this->getOwner();
422  $db = $owner->getDbConnection();
423  $criteria = $owner->getDbCriteria();
424  $bitmask = $this->convertStates($states);
425  $column = $db->quoteColumnName($owner->getTableAlias()) . '.' . $db->quoteColumnName($this->attribute);
426  $criteria->mergeWith(array(
427  'condition' => $column . " & {$bitmask} != 0",
428  ));
429 
430  return $owner;
431  }
432 
433  /*
434  * @TODO Create states validation
435  */
436 
437  public function validateStates($states)
438  {
439  }
440 }