ソースを参照

Merge pull request #172 from bancer/bitmasked-multi-columns

Bitmasked multi columns
Mark Sch 9 年 前
コミット
a6f2a87505

+ 1 - 1
.gitignore

@@ -1,4 +1,4 @@
 # Eclipse paths
 /.project
 /.buildpath
-/.settings/
+/.settings/

+ 138 - 76
Model/Behavior/BitmaskedBehavior.php

@@ -37,14 +37,49 @@ class BitmaskedBehavior extends ModelBehavior {
 
 	/**
 	 * Behavior configuration
+	 * Setup example:
+	 * <code>
+	 * public $actsAs = array(
+	 * 	'Tools.Bitmasked' => [
+	 * 		[
+	 * 			'mappedField' => 'weekdays',
+	 * 			'field' => 'weekday'
+	 * 		],
+	 * 		[
+	 * 			'mappedField' => 'monthdays',
+	 * 			'field' => 'monthday'
+	 * 		]
+	 * 	]
+	 * ];
+	 * </code>
 	 *
 	 * @param Model $Model
 	 * @param array $config
 	 * @return void
 	 */
 	public function setup(Model $Model, $config = []) {
-		$config += $this->_defaultConfig;
+		if (is_array(reset($config))) {
+			foreach ($config as $fieldConfig) {
+				$fieldConfig += $this->_defaultConfig;
+				$fieldName = $fieldConfig['field'];
+				$this->settings[$Model->alias][$fieldName] = $this->_getFieldConfig($Model, $fieldConfig);
+			}
+		} else {
+			$config += $this->_defaultConfig;
+			$fieldName = $config['field'];
+			$this->settings[$Model->alias][$fieldName] = $this->_getFieldConfig($Model, $config);
+		}
+	}
 
+	/**
+	 * Generates settings array for a single bitmasked field.
+	 *
+	 * @param Model $Model
+	 * @param array $config configuration of a single bitmasked field.
+	 * @throws InternalErrorException
+	 * @return array
+	 */
+	protected function _getFieldConfig(Model $Model, $config) {
 		if (empty($config['bits'])) {
 			$config['bits'] = Inflector::pluralize($config['field']);
 		}
@@ -59,8 +94,7 @@ class BitmaskedBehavior extends ModelBehavior {
 			throw new InternalErrorException('Bits not found');
 		}
 		ksort($config['bits'], SORT_NUMERIC);
-
-		$this->settings[$Model->alias] = $config;
+		return $config;
 	}
 
 	/**
@@ -69,12 +103,9 @@ class BitmaskedBehavior extends ModelBehavior {
 	 * @return array
 	 */
 	public function beforeFind(Model $Model, $query) {
-		$field = $this->settings[$Model->alias]['field'];
-
 		if (isset($query['conditions']) && is_array($query['conditions'])) {
 			$query['conditions'] = $this->encodeBitmaskConditions($Model, $query['conditions']);
 		}
-
 		return $query;
 	}
 
@@ -85,17 +116,19 @@ class BitmaskedBehavior extends ModelBehavior {
 	 * @return array
 	 */
 	public function afterFind(Model $Model, $results, $primary = false) {
-		$field = $this->settings[$Model->alias]['field'];
-		if (!($mappedField = $this->settings[$Model->alias]['mappedField'])) {
-			$mappedField = $field;
-		}
-
-		foreach ($results as $key => $result) {
-			if (isset($result[$Model->alias][$field])) {
-				$results[$key][$Model->alias][$mappedField] = $this->decodeBitmask($Model, $result[$Model->alias][$field]);
+		foreach ($this->settings[$Model->alias] as $fieldConfig) {
+			$field = $fieldConfig['field'];
+			if (empty($fieldConfig['mappedField'])) {
+				$mappedField = $field;
+			} else {
+				$mappedField = $fieldConfig['mappedField'];
+			}
+			foreach ($results as $key => $result) {
+				if (isset($result[$Model->alias][$field])) {
+					$results[$key][$Model->alias][$mappedField] = $this->decodeBitmask($Model, $result[$Model->alias][$field], $field);
+				}
 			}
 		}
-
 		return $results;
 	}
 
@@ -105,10 +138,11 @@ class BitmaskedBehavior extends ModelBehavior {
 	 * @return bool Success
 	 */
 	public function beforeValidate(Model $Model, $options = []) {
-		if ($this->settings[$Model->alias]['before'] !== 'validate') {
-			return true;
+		foreach ($this->settings[$Model->alias] as $fieldConfig) {
+			if ($fieldConfig['before'] === 'validate') {
+				$this->encodeBitmaskData($Model);
+			}
 		}
-		$this->encodeBitmaskData($Model);
 		return true;
 	}
 
@@ -118,22 +152,38 @@ class BitmaskedBehavior extends ModelBehavior {
 	 * @return bool Success
 	 */
 	public function beforeSave(Model $Model, $options = []) {
-		if ($this->settings[$Model->alias]['before'] !== 'save') {
-			return true;
+		foreach ($this->settings[$Model->alias] as $fieldConfig) {
+			if ($fieldConfig['before'] === 'save') {
+				$this->encodeBitmaskData($Model);
+			}
 		}
-		$this->encodeBitmaskData($Model);
 		return true;
 	}
 
 	/**
+	 * Gets the name of the first field name.
+	 *
+	 * @param Model $Model
+	 * @return string
+	 */
+	protected function _getFieldName(Model $Model) {
+		$firstField = reset($this->settings[$Model->alias]);
+		return $firstField['field'];
+	}
+
+	/**
 	 * @param Model $Model
 	 * @param int $value Bitmask.
+	 * @param string|null $fieldName field name.
 	 * @return array Bitmask array (from DB to APP).
 	 */
-	public function decodeBitmask(Model $Model, $value) {
+	public function decodeBitmask(Model $Model, $value, $fieldName = null) {
+		if (empty($fieldName)) {
+			$fieldName = $this->_getFieldName($Model);
+		}
 		$res = [];
 		$value = (int)$value;
-		foreach ($this->settings[$Model->alias]['bits'] as $key => $val) {
+		foreach ($this->settings[$Model->alias][$fieldName]['bits'] as $key => $val) {
 			$val = (($value & $key) !== 0) ? true : false;
 			if ($val) {
 				$res[] = $key;
@@ -145,7 +195,7 @@ class BitmaskedBehavior extends ModelBehavior {
 	/**
 	 * @param Model $Model
 	 * @param array $value Bitmask array.
-	 * @param array $defaultValue Default bitmask array.
+	 * @param array|null $defaultValue Default bitmask array.
 	 * @return int Bitmask (from APP to DB).
 	 */
 	public function encodeBitmask(Model $Model, $value, $defaultValue = null) {
@@ -168,29 +218,33 @@ class BitmaskedBehavior extends ModelBehavior {
 	 * @return array Conditions.
 	 */
 	public function encodeBitmaskConditions(Model $Model, $conditions) {
-		$field = $this->settings[$Model->alias]['field'];
-		if (!($mappedField = $this->settings[$Model->alias]['mappedField'])) {
-			$mappedField = $field;
-		}
-
-		foreach ($conditions as $key => $val) {
-			if ($key === $mappedField) {
-				$conditions[$field] = $this->encodeBitmask($Model, $val);
-				if ($field !== $mappedField) {
-					unset($conditions[$mappedField]);
+		foreach ($this->settings[$Model->alias] as $fieldConfig) {
+			$field = $fieldConfig['field'];
+			if (empty($fieldConfig['mappedField'])) {
+				$mappedField = $field;
+			} else {
+				$mappedField = $fieldConfig['mappedField'];
+			}
+			foreach ($conditions as $key => $val) {
+				if ($key === $mappedField) {
+					$conditions[$field] = $this->encodeBitmask($Model, $val);
+					if ($field !== $mappedField) {
+						unset($conditions[$mappedField]);
+					}
+					continue;
 				}
-				continue;
-			} elseif ($key === $Model->alias . '.' . $mappedField) {
-				$conditions[$Model->alias . '.' . $field] = $this->encodeBitmask($Model, $val);
-				if ($field !== $mappedField) {
-					unset($conditions[$Model->alias . '.' . $mappedField]);
+				if ($key === $Model->alias . '.' . $mappedField) {
+					$conditions[$Model->alias . '.' . $field] = $this->encodeBitmask($Model, $val);
+					if ($field !== $mappedField) {
+						unset($conditions[$Model->alias . '.' . $mappedField]);
+					}
+					continue;
 				}
-				continue;
-			}
-			if (!is_array($val)) {
-				continue;
+				if (!is_array($val)) {
+					continue;
+				}
+				$conditions[$key] = $this->encodeBitmaskConditions($Model, $val);
 			}
-			$conditions[$key] = $this->encodeBitmaskConditions($Model, $val);
 		}
 		return $conditions;
 	}
@@ -200,80 +254,88 @@ class BitmaskedBehavior extends ModelBehavior {
 	 * @return void
 	 */
 	public function encodeBitmaskData(Model $Model) {
-		$field = $this->settings[$Model->alias]['field'];
-		if (!($mappedField = $this->settings[$Model->alias]['mappedField'])) {
-			$mappedField = $field;
-		}
-		$default = null;
-		$schema = $Model->schema($field);
-		if ($schema && isset($schema['default'])) {
-			$default = $schema['default'];
-		}
-		if ($this->settings[$Model->alias]['defaultValue'] !== null) {
-			$default = $this->settings[$Model->alias]['defaultValue'];
-		}
-
-		if (isset($Model->data[$Model->alias][$mappedField])) {
-			$Model->data[$Model->alias][$field] = $this->encodeBitmask($Model, $Model->data[$Model->alias][$mappedField], $default);
-		}
-		if ($field !== $mappedField) {
-			unset($Model->data[$Model->alias][$mappedField]);
+		foreach ($this->settings[$Model->alias] as $fieldConfig) {
+			$field = $fieldConfig['field'];
+			if (!($mappedField = $fieldConfig['mappedField'])) {
+				$mappedField = $field;
+			}
+			$default = null;
+			$schema = $Model->schema($field);
+			if ($schema && isset($schema['default'])) {
+				$default = $schema['default'];
+			}
+			if ($fieldConfig['defaultValue'] !== null) {
+				$default = $fieldConfig['defaultValue'];
+			}
+			if (isset($Model->data[$Model->alias][$mappedField])) {
+				$Model->data[$Model->alias][$field] = $this->encodeBitmask($Model, $Model->data[$Model->alias][$mappedField], $default);
+			}
+			if ($field !== $mappedField) {
+				unset($Model->data[$Model->alias][$mappedField]);
+			}
 		}
 	}
 
 	/**
 	 * @param Model $Model
 	 * @param mixed $bits (int, array)
+	 * @param string|null $fieldName field name.
 	 * @return array SQL snippet.
 	 */
-	public function isBit(Model $Model, $bits) {
+	public function isBit(Model $Model, $bits, $fieldName = null) {
+		if (empty($fieldName)) {
+			$fieldName = $this->_getFieldName($Model);
+		}
 		$bits = (array)$bits;
 		$bitmask = $this->encodeBitmask($Model, $bits);
-
-		$field = $this->settings[$Model->alias]['field'];
-		return [$Model->alias . '.' . $field => $bitmask];
+		return [$Model->alias . '.' . $fieldName => $bitmask];
 	}
 
 	/**
 	 * @param Model $Model
 	 * @param mixed $bits (int, array)
+	 * @param string|null $fieldName field name.
 	 * @return array SQL snippet.
 	 */
-	public function isNotBit(Model $Model, $bits) {
-		return ['NOT' => $this->isBit($Model, $bits)];
+	public function isNotBit(Model $Model, $bits, $fieldName = null) {
+		return ['NOT' => $this->isBit($Model, $bits, $fieldName)];
 	}
 
 	/**
 	 * @param Model $Model
 	 * @param mixed $bits (int, array)
+	 * @param string|null $fieldName field name.
 	 * @return array SQL snippet.
 	 */
-	public function containsBit(Model $Model, $bits) {
-		return $this->_containsBit($Model, $bits);
+	public function containsBit(Model $Model, $bits, $fieldName = null) {
+		return $this->_containsBit($Model, $bits, $fieldName);
 	}
 
 	/**
 	 * @param Model $Model
 	 * @param mixed $bits (int, array)
+	 * @param string|null $fieldName field name.
 	 * @return array SQL snippet.
 	 */
-	public function containsNotBit(Model $Model, $bits) {
-		return $this->_containsBit($Model, $bits, false);
+	public function containsNotBit(Model $Model, $bits, $fieldName = null) {
+		return $this->_containsBit($Model, $bits, $fieldName, false);
 	}
 
 	/**
 	 * @param Model $Model
 	 * @param mixed $bits (int, array)
+	 * @param string $fieldName field name.
 	 * @param bool $contain
 	 * @return array SQL snippet.
 	 */
-	protected function _containsBit(Model $Model, $bits, $contain = true) {
+	protected function _containsBit(Model $Model, $bits, $fieldName, $contain = true) {
+		if (empty($fieldName)) {
+			$fieldName = $this->_getFieldName($Model);
+		}
 		$bits = (array)$bits;
 		$bitmask = $this->encodeBitmask($Model, $bits);
-
-		$field = $this->settings[$Model->alias]['field'];
 		$contain = $contain ? ' & ? = ?' : ' & ? != ?';
-		return ['(' . $Model->alias . '.' . $field . $contain . ')' => [$bitmask, $bitmask]];
+		return ['(' . $Model->alias . '.' . $fieldName . $contain . ')' => [$bitmask, $bitmask]];
 	}
 
 }

+ 25 - 0
Test/Case/Model/Behavior/BitmaskedBehaviorTest.php

@@ -110,6 +110,7 @@ class BitmaskedBehaviorTest extends MyCakeTestCase {
 	 * Assert that you can manually trigger "notEmpty" rule with null instead of 0 for "not null" db fields
 	 */
 	public function testSaveWithDefaultValue() {
+		$this->Comment->Behaviors->unload('Bitmasked');
 		$this->Comment->Behaviors->load('Tools.Bitmasked', ['mappedField' => 'statuses', 'defaultValue' => '']);
 		$data = [
 			'comment' => 'test save',
@@ -177,4 +178,28 @@ class BitmaskedBehaviorTest extends MyCakeTestCase {
 		$this->assertTrue(!empty($res) && count($res) === 5);
 	}
 
+	public function testSaveMultiFields() {
+		$this->Comment->Behaviors->unload('Bitmasked');
+		$this->Comment->Behaviors->load('Tools.Bitmasked', [
+			['mappedField' => 'types', 'field' => 'type'],
+			['mappedField' => 'statuses', 'field' => 'status'],
+		]);
+		$data = [
+			'comment' => 'test save',
+			'types' => [
+				BitmaskedComment::TYPE_COMPLAINT,
+				BitmaskedComment::TYPE_RFC,
+			],
+			'statuses' => [
+				BitmaskedComment::STATUS_ACTIVE,
+				BitmaskedComment::STATUS_APPROVED,
+			],
+		];
+		$this->Comment->create();
+		$result = $this->Comment->save($data);
+		$expectedStatus = BitmaskedComment::STATUS_ACTIVE | BitmaskedComment::STATUS_APPROVED;
+		$this->assertEquals($expectedStatus, $result['BitmaskedComment']['status']);
+		$expectedType = BitmaskedComment::TYPE_COMPLAINT | BitmaskedComment::TYPE_RFC;
+		$this->assertEquals($expectedType, $result['BitmaskedComment']['type']);
+	}
 }

+ 15 - 0
Test/test_app/Model/BitmaskedComment.php

@@ -12,6 +12,16 @@ class BitmaskedComment extends MyModel {
 		]
 	];
 
+	public static function types($value = null) {
+		$options = [
+			static::TYPE_BUG => 'Bug',
+			static::TYPE_COMPLAINT => 'Complaint',
+			static::TYPE_DISCUSSION => 'Discussion',
+			static::TYPE_RFC => 'Request for change',
+		];
+		return static::enum($value, $options);
+	}
+
 	public static function statuses($value = null) {
 		$options = [
 			static::STATUS_ACTIVE => __d('tools', 'Active'),
@@ -23,6 +33,11 @@ class BitmaskedComment extends MyModel {
 		return static::enum($value, $options);
 	}
 
+	const TYPE_BUG = 0;
+	const TYPE_COMPLAINT = 1;
+	const TYPE_DISCUSSION = 2;
+	const TYPE_RFC = 4;
+
 	const STATUS_NONE = 0;
 	const STATUS_ACTIVE = 1;
 	const STATUS_PUBLISHED = 2;