1
0
mirror of https://github.com/danog/parallel.git synced 2024-11-30 04:39:01 +01:00

LocalStorage class for storing unsafe data inside a thread

This commit is contained in:
coderstephen 2015-08-03 16:07:58 -05:00
parent e2577a6ab1
commit a372328534
3 changed files with 277 additions and 1 deletions

View File

@ -1,5 +1,5 @@
<?php
namespace Icicle\Stream\Exception;
namespace Icicle\Concurrent\Exception;
class InvalidArgumentError extends Error
{

View File

@ -0,0 +1,140 @@
<?php
namespace Icicle\Concurrent\Threading;
use Icicle\Concurrent\Exception\InvalidArgumentError;
/**
* A storage container that stores data in the local thread only.
*
* The storage container acts like a dictionary, and can store any type of value,
* including arrays and non-serializable or non-thread-safe objects. Numbers and
* strings are valid keys.
*/
class LocalStorage implements \Countable, \IteratorAggregate, \ArrayAccess
{
/**
* @var string The key where this local storage's data is stored.
*/
private $storageKey;
/**
* Creates a new local storage container.
*/
public function __construct()
{
global $__localStorage;
$this->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]);
}
}

View File

@ -0,0 +1,136 @@
<?php
namespace Icicle\Tests\Concurrent\Threading;
use Icicle\Concurrent\Threading\LocalStorage;
use Icicle\Promise\Promise;
use Icicle\Tests\Concurrent\TestCase;
/**
* @group threading
*/
class LocalStorageTest extends TestCase
{
private $localStorage;
public function setUp()
{
$this->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();
}
}