mirror of
https://github.com/danog/parallel.git
synced 2024-11-26 20:34:40 +01:00
LocalStorage class for storing unsafe data inside a thread
This commit is contained in:
parent
e2577a6ab1
commit
a372328534
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Icicle\Stream\Exception;
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class InvalidArgumentError extends Error
|
||||
{
|
||||
|
140
src/Threading/LocalStorage.php
Normal file
140
src/Threading/LocalStorage.php
Normal 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]);
|
||||
}
|
||||
}
|
136
tests/Threading/LocalStorageTest.php
Normal file
136
tests/Threading/LocalStorageTest.php
Normal 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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user