diff --git a/.travis.yml b/.travis.yml index a809ab6932d1..dd6ee9cc55d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ matrix: fast_finish: true before_script: - - pecl install grpc || echo 'Failed to install grpc' + - pecl install grpc-1.4.1 || echo 'Failed to install grpc' - composer install - if [[ $TRAVIS_PHP_VERSION =~ ^hhvm ]]; then composer --no-interaction --dev remove google/protobuf google/gax google/proto-client; fi - ./dev/sh/system-test-credentials diff --git a/src/Datastore/DatastoreSessionHandler.php b/src/Datastore/DatastoreSessionHandler.php index d1793db25ce4..423845921f6a 100644 --- a/src/Datastore/DatastoreSessionHandler.php +++ b/src/Datastore/DatastoreSessionHandler.php @@ -130,6 +130,9 @@ class DatastoreSessionHandler implements SessionHandlerInterface /* @var Transaction */ private $transaction; + /* @var array */ + private $options; + /** * Create a custom session handler backed by Cloud Datastore. * @@ -137,14 +140,38 @@ class DatastoreSessionHandler implements SessionHandlerInterface * @param int $gcLimit [optional] A number of entities to delete in the * garbage collection. Defaults to 0 which means it does nothing. * The value larger than 1000 will be cut down to 1000. + * @param array $options [optional] { + * Configuration Options + * + * @type array $entityOptions Default options to be passed to the + * {@see \Google\Cloud\Datastore\DatastoreClient::entity()} method when writing session data to Datastore. + * If not specified, defaults to `['excludeFromIndexes' => ['data']]`. + * } */ public function __construct( DatastoreClient $datastore, - $gcLimit = self::DEFAULT_GC_LIMIT + $gcLimit = self::DEFAULT_GC_LIMIT, + array $options = [] ) { $this->datastore = $datastore; // Cut down to 1000 $this->gcLimit = min($gcLimit, 1000); + + if (!isset($options['entityOptions'])) { + $options['entityOptions'] = [ + 'excludeFromIndexes' => ['data'] + ]; + } + if (!is_array($options['entityOptions'])) { + throw new InvalidArgumentException( + 'Optional argument `entityOptions` must be an array, got ' . + (is_object($options['entityOptions']) + ? get_class($options['entityOptions']) + : gettype($options['entityOptions'])) + ); + } + + $this->options = $options; } /** @@ -204,6 +231,11 @@ public function read($id) /** * Write the session data to Cloud Datastore. + * + * @param string $id Identifier used to construct a {@see \Google\Cloud\Datastore\Key} + * for the {@see \Google\Cloud\Datastore\Entity} to be written. + * @param string $data The session data to write to the {@see \Google\Cloud\Datastore\Entity}. + * @return bool */ public function write($id, $data) { @@ -218,7 +250,8 @@ public function write($id, $data) [ 'data' => $data, 't' => time() - ] + ], + $this->options['entityOptions'] ); $this->transaction->upsert($entity); $this->transaction->commit(); diff --git a/tests/unit/Datastore/DatastoreSessionHandlerTest.php b/tests/unit/Datastore/DatastoreSessionHandlerTest.php index 55be63a759c3..fa994a39e66c 100644 --- a/tests/unit/Datastore/DatastoreSessionHandlerTest.php +++ b/tests/unit/Datastore/DatastoreSessionHandlerTest.php @@ -190,7 +190,7 @@ public function testWrite() ->shouldBeCalledTimes(1) ->willReturn($key); $that = $this; - $this->datastore->entity($key, Argument::type('array')) + $this->datastore->entity($key, Argument::type('array'), Argument::type('array')) ->will(function($args) use ($that, $key, $entity) { $that->assertEquals($key, $args[0]); $that->assertEquals('sessiondata', $args[1]['data']); @@ -198,6 +198,7 @@ public function testWrite() $that->assertTrue(time() >= $args[1]['t']); // 2 seconds grace period should be enough $that->assertTrue(time() - $args[1]['t'] <= 2); + $that->assertEquals(['excludeFromIndexes' => ['data']], $args[2]); return $entity; }); $datastoreSessionHandler = new DatastoreSessionHandler( @@ -234,7 +235,7 @@ public function testWriteWithException() ->shouldBeCalledTimes(1) ->willReturn($key); $that = $this; - $this->datastore->entity($key, Argument::type('array')) + $this->datastore->entity($key, Argument::type('array'), Argument::type('array')) ->will(function($args) use ($that, $key, $entity) { $that->assertEquals($key, $args[0]); $that->assertEquals('sessiondata', $args[1]['data']); @@ -242,6 +243,7 @@ public function testWriteWithException() $that->assertTrue(time() >= $args[1]['t']); // 2 seconds grace period should be enough $that->assertTrue(time() - $args[1]['t'] <= 2); + $that->assertEquals(['excludeFromIndexes' => ['data']], $args[2]); return $entity; }); @@ -254,6 +256,123 @@ public function testWriteWithException() $this->assertEquals(false, $ret); } + public function testWriteWithEntityOptions() + { + $data = 'sessiondata'; + $key = new Key('projectid'); + $key->pathElement(self::KIND, 'sessionid'); + $datastoreSessionHandlerOptions = [ + 'entityOptions' => ['excludeFromIndexes' => ['data', 'additional']], + ]; + $entity = new Entity($key, ['data' => $data]); + $this->transaction->upsert($entity) + ->shouldBeCalledTimes(1); + $this->transaction->commit() + ->shouldBeCalledTimes(1); + $this->datastore->transaction() + ->shouldBeCalledTimes(1) + ->willReturn($this->transaction->reveal()); + $this->datastore->key( + self::KIND, + 'sessionid', + ['namespaceId' => self::NAMESPACE_ID] + ) + ->shouldBeCalledTimes(1) + ->willReturn($key); + $that = $this; + $this->datastore->entity($key, Argument::type('array'), Argument::type('array')) + ->will(function($args) use ($that, $key, $entity) { + $that->assertEquals($key, $args[0]); + $that->assertEquals('sessiondata', $args[1]['data']); + $that->assertInternalType('int', $args[1]['t']); + $that->assertTrue(time() >= $args[1]['t']); + // 2 seconds grace period should be enough + $that->assertTrue(time() - $args[1]['t'] <= 2); + $that->assertEquals(['excludeFromIndexes' => ['data', 'additional']], $args[2]); + return $entity; + }); + $datastoreSessionHandler = new DatastoreSessionHandler( + $this->datastore->reveal(), + DatastoreSessionHandler::DEFAULT_GC_LIMIT, + $datastoreSessionHandlerOptions + ); + $datastoreSessionHandler->open(self::NAMESPACE_ID, self::KIND); + $ret = $datastoreSessionHandler->write('sessionid', $data); + + $this->assertEquals(true, $ret); + } + + public function testWriteWithEmptyEntityOptions() + { + $data = 'sessiondata'; + $key = new Key('projectid'); + $key->pathElement(self::KIND, 'sessionid'); + $datastoreSessionHandlerOptions = [ + 'entityOptions' => [], + ]; + $entity = new Entity($key, ['data' => $data]); + $this->transaction->upsert($entity) + ->shouldBeCalledTimes(1); + $this->transaction->commit() + ->shouldBeCalledTimes(1); + $this->datastore->transaction() + ->shouldBeCalledTimes(1) + ->willReturn($this->transaction->reveal()); + $this->datastore->key( + self::KIND, + 'sessionid', + ['namespaceId' => self::NAMESPACE_ID] + ) + ->shouldBeCalledTimes(1) + ->willReturn($key); + $that = $this; + $this->datastore->entity($key, Argument::type('array'), Argument::type('array')) + ->will(function($args) use ($that, $key, $entity) { + $that->assertEquals($key, $args[0]); + $that->assertEquals('sessiondata', $args[1]['data']); + $that->assertInternalType('int', $args[1]['t']); + $that->assertTrue(time() >= $args[1]['t']); + // 2 seconds grace period should be enough + $that->assertTrue(time() - $args[1]['t'] <= 2); + $that->assertEquals([], $args[2]); + return $entity; + }); + $datastoreSessionHandler = new DatastoreSessionHandler( + $this->datastore->reveal(), + DatastoreSessionHandler::DEFAULT_GC_LIMIT, + $datastoreSessionHandlerOptions + ); + $datastoreSessionHandler->open(self::NAMESPACE_ID, self::KIND); + $ret = $datastoreSessionHandler->write('sessionid', $data); + + $this->assertEquals(true, $ret); + } + + /** + * @dataProvider invalidEntityOptions + * @expectedException InvalidArgumentException + */ + public function testInvalidEntityOptions($datastoreSessionHandlerOptions) + { + new DatastoreSessionHandler( + $this->datastore->reveal(), + DatastoreSessionHandler::DEFAULT_GC_LIMIT, + $datastoreSessionHandlerOptions + ); + } + + public function invalidEntityOptions() + { + return [ + [ + ['entityOptions' => 1] + ], + [ + ['entityOptions' => new \stdClass()] + ], + ]; + } + public function testDestroy() { $key = new Key('projectid');