ソースを参照

Merge pull request #3116 from AD7six/3.0-config-core

add an instance config trait
Andy Dawson 12 年 前
コミット
c1c64f75c8
2 ファイル変更538 行追加0 行削除
  1. 185 0
      src/Core/InstanceConfigTrait.php
  2. 353 0
      tests/TestCase/Core/InstanceConfigTrait.php

+ 185 - 0
src/Core/InstanceConfigTrait.php

@@ -0,0 +1,185 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+namespace Cake\Core;
+
+use Cake\Error;
+
+/**
+ * A trait for reading and writing instance config
+ *
+ * Implementing objects are expected to declare a `$_defaultConfig` property.
+ */
+trait InstanceConfigTrait {
+
+/**
+ * Runtime config
+ *
+ * @var array
+ */
+	protected $_config = [];
+
+/**
+ * ### Usage
+ *
+ * Reading the whole config:
+ *
+ * `$this->config();`
+ *
+ * Reading a specific value:
+ *
+ * `$this->config('key');`
+ *
+ * Reading a nested value:
+ *
+ * `$this->config('some.nested.key');`
+ *
+ * Setting a specific value:
+ *
+ * `$this->config('key', $value);`
+ *
+ * Setting a nested value:
+ *
+ * `$this->config('some.nested.key', $value);`
+ *
+ * Updating multiple config settings at the same time:
+ *
+ * `$this->config(['one' => 'value', 'another' => 'value']);`
+ *
+ * @param string|array|null $key the key to get/set, or a complete array of configs
+ * @param mixed|null $value the value to set
+ * @return mixed|null null for write, or whatever is in the config on read
+ * @throws \Cake\Error\Exception When trying to set a key that is invalid
+ */
+	public function config($key = null, $value = null) {
+		if ($this->_config === [] && $this->_defaultConfig) {
+			$this->_config = $this->_defaultConfig;
+		}
+
+		if (is_array($key) || func_num_args() === 2) {
+			return $this->_configWrite($key, $value);
+		}
+
+		return $this->_configRead($key);
+	}
+
+/**
+ * Read a config variable
+ *
+ * @param string|null $key
+ * @return mixed
+ */
+	protected function _configRead($key) {
+		if ($key === null) {
+			return $this->_config;
+		}
+
+		if (strpos($key, '.') === false) {
+			return isset($this->_config[$key]) ? $this->_config[$key] : null;
+		}
+
+		$return = $this->_config;
+
+		foreach (explode('.', $key) as $k) {
+			if (!is_array($return) || !isset($return[$k])) {
+				$return = null;
+				break;
+			}
+
+			$return = $return[$k];
+
+		}
+
+		return $return;
+	}
+
+/**
+ * Write a config variable
+ *
+ * @throws Cake\Error\Exception if attempting to clobber existing config
+ * @param string|array $key
+ * @param mixed|null $value
+ * @return void
+ */
+	protected function _configWrite($key, $value = null) {
+		if (is_array($key)) {
+			foreach ($key as $k => $val) {
+				$this->_configWrite($k, $val);
+			}
+			return;
+		}
+
+		if ($value === null) {
+			return $this->_configDelete($key);
+		}
+
+		if (strpos($key, '.') === false) {
+			$this->_config[$key] = $value;
+			return;
+		}
+
+		$update =& $this->_config;
+		$stack = explode('.', $key);
+
+		foreach ($stack as $k) {
+			if (!is_array($update)) {
+				throw new Error\Exception(sprintf('Cannot set %s value', $key));
+			}
+
+			if (!isset($update[$k])) {
+				$update[$k] = [];
+			}
+
+			$update =& $update[$k];
+		}
+
+		$update = $value;
+	}
+
+/**
+ * Delete a single config key
+ *
+ * @throws Cake\Error\Exception if attempting to clobber existing config
+ * @param string $key
+ * @return void
+ */
+	protected function _configDelete($key) {
+		if (strpos($key, '.') === false) {
+			unset($this->_config[$key]);
+			return;
+		}
+
+		$update =& $this->_config;
+		$stack = explode('.', $key);
+		$length = count($stack);
+
+		foreach ($stack as $i => $k) {
+			if (!is_array($update)) {
+				throw new Error\Exception(sprintf('Cannot unset %s value', $key));
+			}
+
+			if (!isset($update[$k])) {
+				break;
+			}
+
+			if ($i === $length - 2) {
+				unset($update[$k]);
+				break;
+			}
+
+			$update =& $update[$k];
+		}
+	}
+
+}

+ 353 - 0
tests/TestCase/Core/InstanceConfigTrait.php

@@ -0,0 +1,353 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+namespace Cake\Test\TestCase\Core;
+
+use Cake\Core\InstanceConfigTrait;
+use Cake\TestSuite\TestCase;
+
+/**
+ * TestInstanceConfig
+ */
+class TestInstanceConfig {
+
+	use InstanceConfigTrait;
+
+/**
+ * _defaultConfig
+ *
+ * Some default config
+ *
+ * @var array
+ */
+	protected $_defaultConfig = [
+		'some' => 'string',
+		'a' => ['nested' => 'value']
+	];
+}
+
+/**
+ * ReadOnlyTestInstanceConfig
+ */
+class ReadOnlyTestInstanceConfig {
+
+	use InstanceConfigTrait;
+
+/**
+ * _defaultConfig
+ *
+ * Some default config
+ *
+ * @var array
+ */
+	protected $_defaultConfig = [
+		'some' => 'string',
+		'a' => ['nested' => 'value']
+	];
+
+/**
+ * Example of how to prevent modifying config at run time
+ *
+ * @throws \Exception
+ * @param mixed $key
+ * @param mixed $value
+ * @return void
+ */
+	protected function _configWrite($key, $value = null) {
+		throw new \Exception('This Instance is readonly');
+	}
+
+}
+
+/**
+ * InstanceConfigTraitTest
+ *
+ */
+class InstanceConfigTraitTest extends TestCase {
+
+/**
+ * setUp method
+ *
+ * @return void
+ */
+	public function setUp() {
+		parent::setUp();
+		$this->object = new TestInstanceConfig();
+	}
+
+/**
+ * testDefaultsAreSet
+ *
+ * @return void
+ */
+	public function testDefaultsAreSet() {
+		$this->assertSame(
+			[
+				'some' => 'string',
+				'a' => ['nested' => 'value']
+			],
+			$this->object->config(),
+			'runtime config should match the defaults if not overriden'
+		);
+	}
+
+/**
+ * testGetSimple
+ *
+ * @return void
+ */
+	public function testGetSimple() {
+		$this->assertSame(
+			'string',
+			$this->object->config('some'),
+			'should return the key value only'
+		);
+
+		$this->assertSame(
+			['nested' => 'value'],
+			$this->object->config('a'),
+			'should return the key value only'
+		);
+	}
+
+/**
+ * testGetDot
+ *
+ * @return void
+ */
+	public function testGetDot() {
+		$this->assertSame(
+			'value',
+			$this->object->config('a.nested'),
+			'should return the nested value only'
+		);
+	}
+
+/**
+ * testSetSimple
+ *
+ * @return void
+ */
+	public function testSetSimple() {
+		$this->object->config('foo', 'bar');
+		$this->assertSame(
+			'bar',
+			$this->object->config('foo'),
+			'should return the same value just set'
+		);
+
+		$this->object->config('some', 'zum');
+		$this->assertSame(
+			'zum',
+			$this->object->config('some'),
+			'should return the overritten value'
+		);
+
+		$this->assertSame(
+			[
+				'some' => 'zum',
+				'a' => ['nested' => 'value'],
+				'foo' => 'bar',
+			],
+			$this->object->config(),
+			'updates should be merged with existing config'
+		);
+	}
+
+/**
+ * testSetNested
+ *
+ * @return void
+ */
+	public function testSetNested() {
+		$this->object->config('new.foo', 'bar');
+		$this->assertSame(
+			'bar',
+			$this->object->config('new.foo'),
+			'should return the same value just set'
+		);
+
+		$this->object->config('a.nested', 'zum');
+		$this->assertSame(
+			'zum',
+			$this->object->config('a.nested'),
+			'should return the overritten value'
+		);
+
+		$this->assertSame(
+			[
+				'some' => 'string',
+				'a' => ['nested' => 'zum'],
+				'new' => ['foo' => 'bar']
+			],
+			$this->object->config(),
+			'updates should be merged with existing config'
+		);
+	}
+
+/**
+ * testSetNested
+ *
+ * @return void
+ */
+	public function testSetArray() {
+		$this->object->config(['foo' => 'bar']);
+		$this->assertSame(
+			'bar',
+			$this->object->config('foo'),
+			'should return the same value just set'
+		);
+
+		$this->assertSame(
+			[
+				'some' => 'string',
+				'a' => ['nested' => 'value'],
+				'foo' => 'bar',
+			],
+			$this->object->config(),
+			'updates should be merged with existing config'
+		);
+
+		$this->object->config(['new.foo' => 'bar']);
+		$this->assertSame(
+			'bar',
+			$this->object->config('new.foo'),
+			'should return the same value just set'
+		);
+
+		$this->assertSame(
+			[
+				'some' => 'string',
+				'a' => ['nested' => 'value'],
+				'foo' => 'bar',
+				'new' => ['foo' => 'bar']
+			],
+			$this->object->config(),
+			'updates should be merged with existing config'
+		);
+
+		$this->object->config(['multiple' => 'different', 'a.values.to' => 'set']);
+
+		$this->assertSame(
+			[
+				'some' => 'string',
+				'a' => ['nested' => 'value', 'values' => ['to' => 'set']],
+				'foo' => 'bar',
+				'new' => ['foo' => 'bar'],
+				'multiple' => 'different'
+			],
+			$this->object->config(),
+			'updates should be merged with existing config'
+		);
+	}
+
+/**
+ * testSetClobber
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage Cannot set a.nested.value
+ * @return void
+ */
+	public function testSetClobber() {
+		$this->object->config(['a.nested.value' => 'not possible']);
+	}
+
+/**
+ * testReadOnlyConfig
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage This Instance is readonly
+ * @return void
+ */
+	public function testReadOnlyConfig() {
+		$object = new ReadOnlyTestInstanceConfig();
+
+		$this->assertSame(
+			[
+				'some' => 'string',
+				'a' => ['nested' => 'value']
+			],
+			$object->config(),
+			'default config should be returned'
+		);
+
+		$object->config('throw.me', 'an exception');
+	}
+
+/**
+ * testDeleteSimple
+ *
+ * @return void
+ */
+	public function testDeleteSimple() {
+		$this->object->config('foo', null);
+		$this->assertNull(
+			$this->object->config('foo'),
+			'setting a new key to null should have no effect'
+		);
+
+		$this->object->config('some', null);
+		$this->assertNull(
+			$this->object->config('some'),
+			'should delete the existing value'
+		);
+
+		$this->assertSame(
+			[
+				'a' => ['nested' => 'value'],
+			],
+			$this->object->config(),
+			'deleted keys should not be present'
+		);
+	}
+
+/**
+ * testDeleteNested
+ *
+ * @return void
+ */
+	public function testDeleteNested() {
+		$this->object->config('new.foo', null);
+		$this->assertNull(
+			$this->object->config('new.foo'),
+			'setting a new key to null should have no effect'
+		);
+
+		$this->object->config('a.nested', null);
+		$this->assertNull(
+			$this->object->config('a.nested'),
+			'should delete the existing value'
+		);
+
+		$this->assertSame(
+			[
+				'some' => 'string',
+			],
+			$this->object->config(),
+			'deleted keys should not be present'
+		);
+	}
+
+/**
+ * testSetClobber
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage Cannot unset a.nested.value.whoops
+ * @return void
+ */
+	public function testDeleteClobber() {
+		$this->object->config('a.nested.value.whoops', null);
+	}
+
+}