From a372328534735e6fbfda7eeb6317b110849f2808 Mon Sep 17 00:00:00 2001 From: coderstephen Date: Mon, 3 Aug 2015 16:07:58 -0500 Subject: [PATCH] LocalStorage class for storing unsafe data inside a thread --- src/Exception/InvalidArgumentError.php | 2 +- src/Threading/LocalStorage.php | 140 +++++++++++++++++++++++++ tests/Threading/LocalStorageTest.php | 136 ++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 src/Threading/LocalStorage.php create mode 100644 tests/Threading/LocalStorageTest.php diff --git a/src/Exception/InvalidArgumentError.php b/src/Exception/InvalidArgumentError.php index f8566f0..028be9e 100644 --- a/src/Exception/InvalidArgumentError.php +++ b/src/Exception/InvalidArgumentError.php @@ -1,5 +1,5 @@ storageKey = spl_object_hash($this); + $__localStorage[$this->storageKey] = []; + } + + /** + * Counts the number of items in the local storage. + * + * @return int The number of items. + */ + public function count() + { + global $__localStorage; + return count($__localStorage[$this->storageKey]); + } + + /** + * Gets an iterator for iterating over all the items in the local storage. + * + * @return \Traversable A new iterator. + */ + public function getIterator() + { + global $__localStorage; + return new \ArrayIterator($__localStorage[$this->storageKey]); + } + + /** + * Removes all items from the local storage. + */ + public function clear() + { + global $__localStorage; + $__localStorage[$this->storageKey] = []; + } + + /** + * Checks if a given item exists. + * + * @param int|string $key The key of the item to check. + * + * @return bool True if the item exists, otherwise false. + */ + public function offsetExists($key) + { + global $__localStorage; + + if (!is_int($key) && !is_string($key)) { + throw new InvalidArgumentError('Key must be an integer or string.'); + } + return isset($__localStorage[$this->storageKey][$key]); + } + + /** + * Gets an item's value from the local storage. + * + * @param int|string $key The key of the item to get. + * + * @return mixed The item value. + */ + public function offsetGet($key) + { + global $__localStorage; + + if (!is_int($key) && !is_string($key)) { + throw new InvalidArgumentError('Key must be an integer or string.'); + } + + if (!isset($__localStorage[$this->storageKey][$key])) { + throw new InvalidArgumentError("The key '{$key}' does not exist."); + } + + return $__localStorage[$this->storageKey][$key]; + } + + /** + * Sets the value of an item. + * + * @param int|string $key The key of the item to set. + * @param mixed $value The value to set. + */ + public function offsetSet($key, $value) + { + global $__localStorage; + + if (!is_int($key) && !is_string($key)) { + throw new InvalidArgumentError('Key must be an integer or string.'); + } + $__localStorage[$this->storageKey][$key] = $value; + } + + /** + * Removes an item from the local storage. + * + * @param int|string $key The key of the item to remove. + */ + public function offsetUnset($key) + { + global $__localStorage; + + if (!is_int($key) && !is_string($key)) { + throw new InvalidArgumentError('Key must be an integer or string.'); + } + unset($__localStorage[$this->storageKey][$key]); + } + + /** + * Removes the local storage from the current thread's memory. + */ + public function __destruct() + { + global $__localStorage; + unset($__localStorage[$this->storageKey]); + } +} diff --git a/tests/Threading/LocalStorageTest.php b/tests/Threading/LocalStorageTest.php new file mode 100644 index 0000000..2329e0d --- /dev/null +++ b/tests/Threading/LocalStorageTest.php @@ -0,0 +1,136 @@ +localStorage = new LocalStorage(); + } + + public function testCount() + { + $this->localStorage['a'] = 'Apple'; + $this->localStorage['b'] = 'Banana'; + $this->localStorage['c'] = 'Cherry'; + + $this->assertCount(3, $this->localStorage); + } + + public function testIterate() + { + $this->localStorage['a'] = 'Apple'; + $this->localStorage['b'] = 'Banana'; + $this->localStorage['c'] = 'Cherry'; + + foreach ($this->localStorage as $key => $value) { + switch ($key) { + case 'a': + $this->assertEquals('Apple', $value); + break; + case 'b': + $this->assertEquals('Banana', $value); + break; + case 'c': + $this->assertEquals('Cherry', $value); + break; + default: + $this->fail('Invalid key returned from iterator.'); + } + } + } + + public function testClear() + { + $this->localStorage['a'] = 'Apple'; + $this->localStorage['b'] = 'Banana'; + $this->localStorage['c'] = 'Cherry'; + + $this->localStorage->clear(); + $this->assertCount(0, $this->localStorage); + $this->assertEmpty($this->localStorage); + } + + public function testIsset() + { + $this->localStorage['foo'] = 'bar'; + + $this->assertTrue(isset($this->localStorage['foo'])); + $this->assertFalse(isset($this->localStorage['baz'])); + } + + public function testGetSet() + { + $this->localStorage['foo'] = 'bar'; + $this->assertEquals('bar', $this->localStorage['foo']); + } + + public function testUnset() + { + $this->localStorage['foo'] = 'bar'; + unset($this->localStorage['foo']); + + $this->assertFalse(isset($this->localStorage['foo'])); + } + + /** + * @expectedException \Icicle\Concurrent\Exception\InvalidArgumentError + */ + public function testGetInvalidKeyThrowsError() + { + $value = $this->localStorage['does not exist']; + } + + /** + * @expectedException \Icicle\Concurrent\Exception\InvalidArgumentError + */ + public function testAppendThrowsError() + { + $this->localStorage[] = 'value'; + } + + /** + * @expectedException \Icicle\Concurrent\Exception\InvalidArgumentError + */ + public function testSetFloatKeyThrowsError() + { + $this->localStorage[1.1] = 'value'; + } + + /** + * @expectedException \Icicle\Concurrent\Exception\InvalidArgumentError + */ + public function testSetObjectKeyThrowsError() + { + $this->localStorage[new \stdClass()] = 'value'; + } + + public function testClosure() + { + $this->localStorage['foo'] = function () { + return 'Hello, World!'; + }; + + $this->assertInstanceOf('Closure', $this->localStorage['foo']); + } + + public function testThreadedPromise() + { + $t = $thread = \Thread::from(function () { + $storage = new LocalStorage(); + $storage['promise'] = new Promise(); + }); + + $thread->start(PTHREADS_INHERIT_INI); + $thread->join(); + } +}