浏览代码

KeyValue Behavior

euromark 13 年之前
父节点
当前提交
449b9599ad

+ 25 - 0
Config/Schema/key_value.php

@@ -0,0 +1,25 @@
+<?php 
+class KeyValueSchema extends CakeSchema {
+
+	public function before($event = array()) {
+		return true;
+	}
+
+	public function after($event = array()) {
+	}
+
+	public $key_values = array(
+		'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'key' => 'primary'),
+		'foreign_id' => array('type' => 'string', 'null' => false, 'default' => null, 'length' => 36, 'key' => 'index', 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'),
+		'model' => array('type' => 'string', 'null' => false, 'default' => null, 'length' => 30, 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'),
+		'key' => array('type' => 'string', 'null' => false, 'default' => null, 'length' => 30, 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8'),
+		'value' => array('type' => 'string', 'null' => false, 'default' => null, 'collate' => 'utf8_unicode_ci', 'comment' => 'option setting', 'charset' => 'utf8'),
+		'created' => array('type' => 'datetime', 'null' => false, 'default' => null),
+		'modified' => array('type' => 'datetime', 'null' => false, 'default' => null),
+		'indexes' => array(
+			'PRIMARY' => array('column' => 'id', 'unique' => 1),
+			'foreign_id' => array('column' => 'foreign_id', 'unique' => 0)
+		),
+		'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_unicode_ci', 'engine' => 'MyISAM')
+	);
+}

+ 6 - 2
Lib/Bootstrap/MyBootstrap.php

@@ -63,6 +63,9 @@ define('CURRENT_DAY', date('d'));
 define('FILES', APP . 'files' . DS);
 define('LOCALE', APP . 'locale' . DS);
 
+if (!defined('CLASS_USER')) {
+	define('CLASS_USER', 'User');
+}
 
 # Validation ## (minus should be "hyphen")
 /** Valid characters: letters only */
@@ -530,13 +533,14 @@ function uid($default = null) {
 		$default;
 }
 
-
-
 /**
  * own shutdown function - also logs fatal errors (necessary until cake2.2)
  * 2010-10-17 ms
  */
 function shutDownFunction() {
+	if (Configure::version() >= 2.3) {
+		return;
+	}
 	$error = error_get_last();
 	if (empty($error)) {
 		return;

+ 207 - 0
Model/Behavior/KeyValueBehavior.php

@@ -0,0 +1,207 @@
+<?php
+App::uses('ModelBehavior', 'Model');
+
+/**
+ * KeyValue Behavior
+ *
+ * TODO: long text? separate table or not at all?
+ * TODO: caching
+ *
+ * @license MIT
+ * @modified Mark Scherer
+ */
+class KeyValueBehavior extends ModelBehavior {
+
+	/**
+	 * Settings
+	 *
+	 * @var mixed
+	 */
+	public $settings = array();
+
+	/**
+	 * Storage model for all key value pairs
+	 */
+	public $KeyValue = null;
+
+	/**
+	 * Default settings
+	 *
+	 * @var array
+	 */
+	protected $_defaults = array(
+		'foreignKeyField' => 'foreign_id',
+		'keyField' => 'key',
+		'valueField' => 'value',
+		'defaults' => null, // looks for `public $keyValueDefaults` property in the model,
+		'validate' => null, // looks for `public $keyValueValidate` property in the model
+		'defaultOnEmpty' => false, // if nothing is posted, delete (0 is not nothing)
+		'deleteIfDefault' => false, // if default value is posted, delete
+	);
+
+	/**
+	 * Setup
+	 *
+	 * @param object AppModel
+	 * @param array $config
+	 */
+	public function setup(Model $Model, $config = array()) {
+		$settings = array_merge($this->_defaults, $config);
+		$this->settings[$Model->alias] = $settings;
+		if (!$this->KeyValue) {
+			$this->KeyValue = ClassRegistry::init('Tools.KeyValue');
+		}
+		/*
+		if ($this->settings[$Model->alias]['validate']) {
+			foreach ($this->settings[$Model->alias]['validate'] as $key => $validate) {
+				$this->KeyValue->validate[$key] = $validate;
+			}
+		}
+		*/
+	}
+
+	/**
+	 * Returns details for named section
+	 *
+	 * @var string
+	 * @var string
+	 * @return mixed
+	 */
+	public function getSection(Model $Model, $foreignKey, $section = null, $key = null) {
+		extract($this->settings[$Model->alias]);
+		$results = $this->KeyValue->find('all', array(
+			'recursive' => -1,
+			'conditions' => array($foreignKeyField => $foreignKey),
+			'fields' => array('key', 'value')
+		));
+
+		$defaultValues = $this->defaultValues($Model);
+
+		$detailArray = array();
+		foreach ($results as $value) {
+			$keyArray = preg_split('/\./', $value[$this->KeyValue->alias]['key'], 2);
+			$detailArray[$keyArray[0]][$keyArray[1]] = $value[$this->KeyValue->alias]['value'];
+		}
+		$detailArray = Set::merge($defaultValues, $detailArray);
+		if ($section === null) {
+			return $detailArray;
+		}
+		if ($key === null) {
+			return $detailArray[$section];
+		}
+		if (!isset($detailArray[$section][$key])) {
+			return null;
+		}
+		return $detailArray[$section][$key];
+	}
+
+	/**
+	 * Save details for named section
+	 *
+	 * TODO: validate
+	 *
+	 * @var string
+	 * @var array
+	 * @var string
+	 * @return bool $success
+	 */
+	public function saveSection(Model $Model, $foreignKey, $data, $section = null) {
+		if (!$this->validateSection($Model, $data)) {
+			return false;
+		}
+
+		extract($this->settings[$Model->alias]);
+		foreach ($data as $model => $details) {
+			foreach ($details as $field => $value) {
+				$newDetail = array();
+				$section = $section ? $section : $model;
+				$key = $section . '.' . $field;
+
+				if ($defaultOnEmpty && (String)$value === '' || $deleteIfDefault && (String)$value === (String)$this->defaultValues($Model, $section, $field)) {
+					return $this->resetSection($Model, $foreignKey, $section, $field);
+				}
+
+				$tmp = $this->KeyValue->find('first', array(
+					'recursive' => -1,
+					'conditions' => array($foreignKeyField => $foreignKey, $keyField => $key),
+					'fields' => array('id')));
+				$newDetail[$this->KeyValue->alias]['id'] = $tmp[$this->KeyValue->alias]['id'];
+				$newDetail[$this->KeyValue->alias][$foreignKeyField] = $foreignKey;
+				$newDetail[$this->KeyValue->alias][$keyField] = $key;
+				$newDetail[$this->KeyValue->alias][$valueField] = $value;
+				$this->KeyValue->save($newDetail, false);
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * @return bool $success
+	 */
+	public function validateSection(Model $Model, $data) {
+		$validate = $this->settings[$Model->alias]['validate'];
+		if ($validate === null) {
+			$validate = 'keyValueValidate';
+		}
+		if (empty($Model->{$validate})) {
+			return true;
+		}
+		$rules = $Model->keyValueValidate;
+		$res = true;
+		foreach ($data as $model => $array) {
+			if (empty($rules[$model])) {
+				continue;
+			}
+			$this->KeyValue->{$model} = ClassRegistry::init(array('class'=>'AppModel', 'alias'=>$model));
+			$this->KeyValue->{$model}->validate = $rules[$model];
+			$this->KeyValue->{$model}->set($array);
+			$res = $res && $this->KeyValue->{$model}->validates();
+		}
+		return $res;
+	}
+
+	public function defaultValues(Model $Model, $section = null, $key = null) {
+		$defaults = $this->settings[$Model->alias]['defaults'];
+		if ($defaults === null) {
+			$defaults = 'keyValueDefaults';
+		}
+		$defaultValues = array();
+		if (!empty($Model->{$defaults})) {
+			$defaultValues = $Model->{$defaults};
+		}
+		if ($section !== null) {
+			if ($key !== null) {
+				return isset($defaultValues[$section][$key]) ? $defaultValues[$section][$key] : null;
+			}
+			return isset($defaultValues[$section]) ? $defaultValues[$section] : null;
+		}
+		return $defaultValues;
+	}
+
+	/**
+	 * resets the custom data for the specific domains (model, foreign_id)
+	 * careful: passing both null values will result in a complete truncate command
+	 *
+	 * @return bool $success
+	 * 2012-08-08 ms
+	 */
+	public function resetSection(Model $Model, $foreignKey = null, $section = null, $key = null) {
+		extract($this->settings[$Model->alias]);
+		$conditions = array();
+		if ($foreignKey !== null) {
+			$conditions[$foreignKeyField] = $foreignKey;
+		}
+		if ($section !== null) {
+			if ($key !== null) {
+				$conditions[$keyField] = $section . '.'. $key;
+			} else {
+				$conditions[$keyField.' LIKE'] = $section.'.%';
+			}
+		}
+		if (empty($conditions)) {
+			return $this->KeyValue->truncate();
+		}
+		return (bool)$this->KeyValue->deleteAll($conditions, false);
+	}
+
+}

+ 28 - 0
Model/KeyValue.php

@@ -0,0 +1,28 @@
+<?php
+App::uses('ToolsAppModel', 'Tools.Model');
+/**
+ * KeyValue Model
+ *
+ */
+class KeyValue extends ToolsAppModel {
+
+	public $displayField = 'value';
+
+	public $order = array();
+
+	public $validate = array(
+		'foreign_id' => array(
+			'notEmpty' => array(
+				'rule' => array('notEmpty'),
+				'message' => 'valErrMandatoryField',
+			),
+		),
+		'key' => array(
+			'notEmpty' => array(
+				'rule' => array('notEmpty'),
+				'message' => 'valErrMandatoryField',
+			),
+		),
+	);
+
+}

+ 129 - 0
Test/Case/Model/Behavior/KeyValueBehaviorTest.php

@@ -0,0 +1,129 @@
+<?php
+
+App::uses('KeyValueBehavior', 'Tools.Model/Behavior');
+App::uses('MyCakeTestCase', 'Tools.Lib');
+
+class KeyValueBehaviorTest extends MyCakeTestCase {
+
+	public $fixtures = array('plugin.tools.key_value', 'core.user');
+
+	public $KeyValueBehavior;
+
+	public $Model;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->KeyValueBehavior = new KeyValueBehavior();
+		$this->Model = ClassRegistry::init('User');
+		$this->Model->Behaviors->attach('Tools.KeyValue');
+	}
+
+	public function testObject() {
+		$this->assertTrue(is_object($this->KeyValueBehavior));
+		$this->assertIsA($this->KeyValueBehavior, 'KeyValueBehavior');
+	}
+
+	public function testValidate() {
+		$res = $this->Model->validateSection(array('User'=>array('x'=>1, 'y'=>'')));
+		$this->assertTrue(!empty($res));
+
+		$this->Model->keyValueValidate = array(
+			'User' => array('y' => 'notEmpty'),
+		);
+		$res = $this->Model->validateSection(array('User'=>array('x'=>1, 'y'=>'')));
+		$this->assertFalse($res);
+
+		$res = $this->Model->validateSection(array('User'=>array('x'=>1, 'y'=>'1')));
+		$this->assertTrue(!empty($res));
+	}
+
+	public function testSaveAndGet() {
+		$this->Model->saveSection(1, array('User'=>array('x'=>1, 'y'=>'z')));
+
+		$res = $this->Model->getSection(2);
+		$this->assertTrue(empty($res));
+
+		$res = $this->Model->getSection(1);
+		$this->assertTrue(!empty($res['User']));
+		$this->assertEquals('z', $res['User']['y']);
+
+
+		$this->Model->saveSection(2, array('User'=>array('x'=>1, 'y'=>'z')), 'Profile');
+
+		$res = $this->Model->getSection(2);
+		$this->assertTrue(!empty($res['Profile']));
+		//debug($res); ob_flush();
+	}
+
+	public function testDefaults() {
+		$this->Model->keyValueDefaults = array(
+			'User' => array(
+				'x' => 0,
+				'y' => '',
+				'z' => '123',
+			)
+		);
+		$this->Model->Behaviors->detach('KeyValue');
+		$this->Model->Behaviors->attach('Tools.KeyValue');
+
+		$this->Model->saveSection(0, array('User'=>array('x'=>1, 'y'=>'z')));
+
+		$res = $this->Model->getSection(0, 'User');
+		$this->assertEquals(array('x'=>1, 'y'=>'z', 'z' => 123), $res);
+
+		$res = $this->Model->getSection(0, 'User', 'y');
+		$this->assertEquals('z', $res);
+
+		$res = $this->Model->getSection(0, 'User', 'a');
+		$this->assertEquals(null, $res);
+
+		$res = $this->Model->getSection(0, 'User', 'z');
+		$this->assertEquals('123', $res);
+	}
+
+	public function testReset() {
+		$this->Model->saveSection(0, array('User'=>array('x'=>1, 'y'=>'z')));
+		$res = $this->Model->Behaviors->KeyValue->KeyValue->find('count');
+		$this->assertEquals(3, $res);
+
+		$res = $this->Model->resetSection(0, 'User', 'y');
+		$this->assertTrue($res);
+		$res = $this->Model->Behaviors->KeyValue->KeyValue->find('count');
+		$this->assertEquals(2, $res);
+
+		$this->Model->Behaviors->detach('KeyValue');
+		$this->Model->Behaviors->attach('Tools.KeyValue', array('defaultOnEmpty' => true));
+		$this->Model->keyValueDefaults = array(
+			'User' => array(
+				'x' => 0,
+				'y' => '',
+				'z' => '123',
+			)
+		);
+		$this->Model->saveSection(0, array('User'=>array('x'=>1, 'y'=>'z', 'z' => 123)));
+		$res = $this->Model->Behaviors->KeyValue->KeyValue->find('count');
+		$this->assertEquals(4, $res);
+
+		$res = $this->Model->saveSection(0, array('User'=>array('x'=>0, 'y'=>null)));
+		$this->assertTrue($res);
+		$res = $this->Model->Behaviors->KeyValue->KeyValue->find('count');
+		$this->assertEquals(3, $res);
+
+		$this->Model->Behaviors->detach('KeyValue');
+		$this->Model->Behaviors->attach('Tools.KeyValue', array('deleteIfDefault' => true));
+		$res = $this->Model->saveSection(0, array('User'=>array('z'=>'123')));
+		$this->assertTrue($res);
+		$res = $this->Model->Behaviors->KeyValue->KeyValue->find('count');
+		$this->assertEquals(2, $res);
+
+		$res = $this->Model->resetSection(1);
+		$res = $this->Model->Behaviors->KeyValue->KeyValue->find('count');
+		$this->assertEquals(1, $res);
+
+		$res = $this->Model->resetSection();
+		$res = $this->Model->Behaviors->KeyValue->KeyValue->find('count');
+		$this->assertEquals(0, $res);
+	}
+
+}

+ 1 - 1
Test/Case/Model/Behavior/SluggedBehaviorTest.php

@@ -147,7 +147,7 @@ class SluggedTest extends CakeTestCase {
 	}
 
 /**
- * Test slug generation/update based on trigger
+ * Test slug generation/update based on scope
  *
  * @access public
  * @return void

+ 44 - 0
Test/Case/Model/KeyValueTest.php

@@ -0,0 +1,44 @@
+<?php
+App::uses('KeyValue', 'Tools.Model');
+App::uses('MyCakeTestCase', 'Tools.Lib');
+
+/**
+ * KeyValue Test Case
+ *
+ */
+class KeyValueTest extends MyCakeTestCase {
+
+	public $KeyValue;
+
+	/**
+	 * Fixtures
+	 *
+	 * @var array
+	 */
+	public $fixtures = array('plugin.tools.key_value');
+
+	/**
+	 * setUp method
+	 *
+	 * @return void
+	 */
+	public function setUp() {
+		parent::setUp();
+
+		$this->KeyValue = ClassRegistry::init('Tools.KeyValue');
+	}
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+	public function tearDown() {
+		unset($this->KeyValue);
+
+		parent::tearDown();
+	}
+
+}
+
+