Skip to content

Commit

Permalink
Convert blobValue back to a resource on fetch (#126)
Browse files Browse the repository at this point in the history
* Convert blobValue back to a resource on fetch

* Add test coverage to property and exclude mapper

* Add Blob class and client factories

* Store blob values as resource
  • Loading branch information
jdpedrie authored Sep 5, 2016
1 parent 560274a commit 58ea47a
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 7 deletions.
1 change: 1 addition & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"defaultService": "servicebuilder",
"markdown": "php",
"versions": [
"v0.7.1",
"v0.7.0",
"v0.6.0",
"v0.5.1",
Expand Down
79 changes: 79 additions & 0 deletions src/Datastore/Blob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
/**
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Datastore;

use GuzzleHttp\Psr7;
use Psr\Http\Message\StreamInterface;

/**
* Represents a Blob value.
*
* Blobs can be used to store binary data in Google Cloud Datastore.
*
* Example:
* ```
* $blob = $datastore->blob(file_get_contents(__DIR__ .'/family-photo.jpg'));
* ```
*/
class Blob
{
/**
* @var mixed
*/
private $value;

/**
* Create a blob
*
* @param string|resource|StreamInterface $value The blob value
*/
public function __construct($value)
{
$this->value = Psr7\stream_for($value);
}

/**
* Get the blob contents as a stream
*
* Example:
* ```
* $value = $blob->value();
* ```
*
* @returns StreamInterface
*/
public function get()
{
return $this->value;
}

/**
* Cast the blob to a string
*
* Example:
* ```
* echo (string) $blob->value();
* ```
*
* @return string
*/
public function __toString()
{
return (string) $this->value;
}
}
40 changes: 40 additions & 0 deletions src/Datastore/DatastoreClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,46 @@ public function entity($key, array $entity = [], array $options = [])
return $this->operation->entity($key, $entity, $options);
}

/**
* Create a new GeoPoint
*
* Example:
* ```
* $geoPoint = $datastore->geoPoint(37.4220, -122.0841);
* ```
*
* @see https://cloud.google.com/datastore/reference/rest/Shared.Types/LatLng LatLng
*
* @param float $latitude The latitude
* @param float $longitude The longitude
* @return GeoPoint
*/
public function geoPoint($latitude, $longitude)
{
return new GeoPoint($latitude, $longitude);
}

/**
* Create a new Blob
*
* Example:
* ```
* $blob = $datastore->blob('hello world');
* ```
*
* ```
* // Blobs can be used to store binary data
* $blob = $datastore->blob(file_get_contents(__DIR__ .'/family-photo.jpg'));
* ```
*
* @param string|resource|StreamInterface $value
* @return Blob
*/
public function blob($value)
{
return new Blob($value);
}

/**
* Allocates an available ID to a given incomplete key
*
Expand Down
20 changes: 20 additions & 0 deletions src/Datastore/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@
* Entity implements PHP's [ArrayAccess](http://php.net/arrayaccess), allowing
* access via the array syntax (example below).
*
* Properties are mapped automatically to their corresponding Datastore value
* types. Refer to the table below for a guide to how types are stored.
*
* | **PHP Type** | **Datastore Value Type** |
* |--------------------------------------------|--------------------------------------|
* | `\DateTimeInterface` | `timestampValue` |
* | {@see Google\Cloud\Datastore\Key} | `keyValue` |
* | {@see Google\Cloud\Datastore\GeoPoint} | `geoPointValue` |
* | {@see Google\Cloud\Datastore\Entity} | `entityValue` |
* | {@see Google\Cloud\Datastore\Blob} | `blobValue` |
* | Associative Array | `entityValue` (No Key) |
* | Non-Associative Array | `arrayValue` |
* | `float` | `doubleValue` |
* | `int` | `integerValue` |
* | `string` | `stringValue` |
* | `resource` | `blobValue` |
* | `NULL` | `nullValue` |
* | `bool` | `booleanValue` |
* | `object` (Outside types specified above) | **ERROR** `InvalidArgumentException` |
*
* Example:
* ```
* use Google\Cloud\ServiceBuilder;
Expand Down
51 changes: 45 additions & 6 deletions src/Datastore/EntityMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ public function responseToExcludeFromIndexes(array $entityData)
$excludes = [];

foreach ($entityData as $key => $property) {
$type = key($property);

if (isset($property['excludeFromIndexes']) && $property['excludeFromIndexes']) {
$excludes[] = $key;
}
Expand Down Expand Up @@ -205,6 +203,15 @@ public function convertValue($type, $value)

break;

case 'blobValue':
if ($this->isEncoded($value)) {
$value = base64_decode($value);
}

$result = new Blob($value);

break;

default:
$result = $value;
break;
Expand Down Expand Up @@ -316,16 +323,25 @@ public function valueObject($value, $exclude = false)
public function objectProperty($value)
{
switch (true) {
case $value instanceof Blob:
return [
'blobValue' => ($this->encode)
? base64_encode((string) $value)
: (string) $value
];

break;

case $value instanceof \DateTimeInterface:
return [
'timestampValue' => $value->format(\DateTime::RFC3339)
];

break;

case $value instanceof Key:
case $value instanceof Entity:
return [
'keyValue' => $value->keyObject()
'entityValue' => $this->objectToRequest($value)
];

break;
Expand All @@ -337,11 +353,13 @@ public function objectProperty($value)

break;

case $value instanceof Entity:
case $value instanceof Key:
return [
'entityValue' => $this->objectToRequest($value)
'keyValue' => $value->keyObject()
];

break;

default:
throw new InvalidArgumentException(
sprintf('Value of type `%s` could not be serialized', get_class($value))
Expand Down Expand Up @@ -390,4 +408,25 @@ private function convertArrayToEntityValue(array $value)
]
];
}

private function isEncoded($value)
{
// Check if there are valid base64 characters
if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $value)) {
return false;
}

// Decode the string in strict mode and check the results
$decoded = base64_decode($value, true);
if ($decoded == false) {
return false;
}

// Encode the string again
if (base64_encode($decoded) != $value) {
return false;
}

return true;
}
}
2 changes: 1 addition & 1 deletion src/ServiceBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
*/
class ServiceBuilder
{
const VERSION = '0.7.0';
const VERSION = '0.7.1';

/**
* @var array Configuration options to be used between clients.
Expand Down
56 changes: 56 additions & 0 deletions tests/Datastore/BlobTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
/**
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Tests\Datastore;

use Google\Cloud\Datastore\Blob;
use GuzzleHttp\Psr7;

/**
* @group datastore
*/
class BlobTest extends \PHPUnit_Framework_TestCase
{
public function testBlobString()
{
$blob = new Blob('hello world');
$this->assertEquals('hello world', (string) $blob);
}

public function testBlobResource()
{
$string = 'hello world';
$stream = fopen('php://memory','r+');
fwrite($stream, $string);
rewind($stream);

$blob = new Blob($stream);
$this->assertEquals('hello world', (string) $blob);
}

public function testBlobStreamInterface()
{
$blob = new Blob(Psr7\stream_for('hello world'));
$this->assertEquals('hello world', (string) $blob);
}

public function testToString()
{
$blob = new Blob('hello world');
$this->assertEquals((string)$blob->get(), (string) $blob);
}
}
19 changes: 19 additions & 0 deletions tests/Datastore/DatastoreClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

namespace Google\Cloud\Tests\Datastore;

use Google\Cloud\Datastore\Blob;
use Google\Cloud\Datastore\Connection\ConnectionInterface;
use Google\Cloud\Datastore\DatastoreClient;
use Google\Cloud\Datastore\Entity;
use Google\Cloud\Datastore\GeoPoint;
use Google\Cloud\Datastore\Key;
use Google\Cloud\Datastore\Operation;
use Google\Cloud\Datastore\Query\GqlQuery;
Expand Down Expand Up @@ -144,6 +146,23 @@ public function testEntity()
$this->assertEquals($entity['foo'], 'bar');
}

public function testBlob()
{
$blob = $this->datastore->blob('foo');
$this->assertInstanceOf(Blob::class, $blob);
$this->assertEquals('foo', (string) $blob);
}

public function testGeoPoint()
{
$point = $this->datastore->geoPoint(1.1, 0.1);
$this->assertInstanceOf(GeoPoint::class, $point);
$this->assertEquals($point->point(), [
'latitude' => 1.1,
'longitude' => 0.1
]);
}

public function testAllocateId()
{
$datastore = new DatastoreClientStubNoService;
Expand Down
Loading

0 comments on commit 58ea47a

Please sign in to comment.