1
|
|
|
<?php |
2
|
|
|
namespace Fathomminds\Rest\Database\DynamoDb; |
3
|
|
|
|
4
|
|
|
use Aws\Sdk; |
5
|
|
|
use Aws\DynamoDb\DynamoDbClient as Client; |
6
|
|
|
use Aws\DynamoDb\Marshaler; |
7
|
|
|
use Fathomminds\Rest\Exceptions\RestException; |
8
|
|
|
use Fathomminds\Rest\Contracts\IResource; |
9
|
|
|
use Fathomminds\Rest\Helpers\Uuid; |
10
|
|
|
use Aws\DynamoDb\Exception\DynamoDbException; |
11
|
|
|
|
12
|
|
|
class Resource implements IResource |
13
|
|
|
{ |
14
|
|
|
protected $client; |
15
|
|
|
protected $databaseName; |
16
|
|
|
protected $collection; |
17
|
|
|
protected $fullTableName; |
18
|
|
|
protected $resourceName; |
19
|
|
|
protected $primaryKey; |
20
|
|
|
protected $marshaler; |
21
|
|
|
|
22
|
|
|
public function __construct($resourceName, $primaryKey, Client $client = null, $databaseName = null) |
23
|
|
|
{ |
24
|
|
|
$this->resourceName = $resourceName; |
25
|
|
|
$this->primaryKey = $primaryKey; |
26
|
|
|
$this->client = $client === null ? $this->createClient() : $client; |
27
|
|
|
$this->databaseName = $databaseName === null ? $this->getFullDatabaseName() : $databaseName; |
28
|
|
|
$this->fullTableName = $this->databaseName . '-' . $this->resourceName; |
29
|
|
|
$this->marshaler = new Marshaler; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
private function createClient() |
33
|
|
|
{ |
34
|
|
|
$sdk = new Sdk([ |
35
|
|
|
'region' => getenv('AWS_SDK_REGION'), |
36
|
|
|
'version' => getenv('AWS_SDK_VERSION'), |
37
|
|
|
'http' => [ |
38
|
|
|
'verify' => getenv('AWS_SDK_HTTP_VERIFY') === 'false' ? false : getenv('AWS_SDK_HTTP_VERIFY'), |
39
|
|
|
] |
40
|
|
|
]); |
41
|
|
|
return $sdk->createDynamoDb(); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
private function getFullDatabaseName() |
45
|
|
|
{ |
46
|
|
|
return getenv('AWS_DYNAMODB_NAMESPACE') . '-' . getenv('AWS_DYNAMODB_DATABASE'); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
public function get($resourceId = null) |
50
|
|
|
{ |
51
|
|
|
if ($resourceId !== null) { |
52
|
|
|
return $this->getOne($resourceId); |
53
|
|
|
} |
54
|
|
|
return $this->getAll(); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
protected function getOne($resourceId) |
58
|
|
|
{ |
59
|
|
|
$query = $this->createGetQuery($resourceId); |
60
|
|
|
$res = $this->client->getItem($query); |
61
|
|
|
return $this->unmarshalItem($res['Item']); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
protected function createGetQuery($resourceId) |
65
|
|
|
{ |
66
|
|
|
$query = [ |
67
|
|
|
'TableName' => $this->fullTableName, |
68
|
|
|
'Key' => $this->marshalItem([$this->primaryKey => $resourceId]), |
69
|
|
|
]; |
70
|
|
|
return $query; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
protected function getAll() |
74
|
|
|
{ |
75
|
|
|
$query = $this->createScanQuery(); |
76
|
|
|
$res = $this->client->scan($query); |
77
|
|
|
return $this->unmarshalBatch($res['Items']); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
protected function createScanQuery() |
81
|
|
|
{ |
82
|
|
|
$query = [ |
83
|
|
|
'TableName' => $this->fullTableName, |
84
|
|
|
]; |
85
|
|
|
return $query; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
protected function throwAwsPostError($ex) |
89
|
|
|
{ |
90
|
|
|
switch ($ex->getAwsErrorCode()) { |
91
|
|
|
case 'ConditionalCheckFailedException': |
92
|
|
|
throw new RestException( |
93
|
|
|
'Primary key collision', |
94
|
|
|
['exception' => $ex] |
95
|
|
|
); |
96
|
|
|
break; |
|
|
|
|
97
|
|
|
} |
98
|
|
|
throw new RestException($ex->getMessage(), ['exception' => $ex]); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
public function post($newResource) |
102
|
|
|
{ |
103
|
|
|
try { |
104
|
|
|
$query = $this->createPostQuery($newResource); |
105
|
|
|
$this->client->putItem($query); |
106
|
|
|
return $newResource; |
107
|
|
|
} catch (DynamoDbException $ex) { |
108
|
|
|
$this->throwAwsPostError($ex); |
109
|
|
|
} catch (\Exception $ex) { |
110
|
|
|
throw new RestException($ex->getMessage(), ['exception' => $ex]); |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
protected function createPostQuery($newResource) |
115
|
|
|
{ |
116
|
|
|
$query = [ |
117
|
|
|
'TableName' => $this->fullTableName, |
118
|
|
|
'Item' => $this->marshalItem($newResource), |
119
|
|
|
'ConditionExpression' => 'attribute_not_exists(#pk)', |
120
|
|
|
"ExpressionAttributeNames" => ["#pk" => $this->primaryKey], |
121
|
|
|
]; |
122
|
|
|
return $query; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
protected function throwAwsPutError($ex) |
126
|
|
|
{ |
127
|
|
|
switch ($ex->getAwsErrorCode()) { |
128
|
|
|
case 'ConditionalCheckFailedException': |
129
|
|
|
throw new RestException( |
130
|
|
|
'Resource does not exist', |
131
|
|
|
['exception' => $ex] |
132
|
|
|
); |
133
|
|
|
break; |
|
|
|
|
134
|
|
|
} |
135
|
|
|
throw new RestException($ex->getMessage(), ['exception' => $ex]); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
public function put($resourceId, $newResource) |
139
|
|
|
{ |
140
|
|
|
try { |
141
|
|
|
$newResource->{$this->primaryKey} = $resourceId; |
142
|
|
|
$query = $this->createPutQuery($newResource); |
143
|
|
|
$res = $this->client->putItem($query); |
144
|
|
|
return $newResource; |
145
|
|
|
} catch (DynamoDbException $ex) { |
146
|
|
|
$this->throwAwsPutError($ex); |
147
|
|
|
} catch (\Exception $ex) { |
148
|
|
|
throw new RestException($ex->getMessage(), ['result'=>empty($res)?null:$res]); |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
protected function createPutQuery($newResource) |
153
|
|
|
{ |
154
|
|
|
$query = [ |
155
|
|
|
'TableName' => $this->fullTableName, |
156
|
|
|
'Item' => $this->marshalItem($newResource), |
157
|
|
|
'ConditionExpression' => 'attribute_exists(#pk)', |
158
|
|
|
"ExpressionAttributeNames" => ["#pk" => $this->primaryKey], |
159
|
|
|
]; |
160
|
|
|
return $query; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
public function delete($resourceId) |
164
|
|
|
{ |
165
|
|
|
try { |
166
|
|
|
$query = $this->createDeleteQuery($resourceId); |
167
|
|
|
$res = $this->client->deleteItem($query); |
168
|
|
|
return $resourceId; |
169
|
|
|
} catch (\Exception $ex) { |
170
|
|
|
throw new RestException($ex->getMessage(), ['result'=>empty($res)?null:$res]); |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
protected function createDeleteQuery($resourceId) |
175
|
|
|
{ |
176
|
|
|
$query = [ |
177
|
|
|
'TableName' => $this->fullTableName, |
178
|
|
|
'Key' => $this->marshalItem([$this->primaryKey => $resourceId]), |
179
|
|
|
]; |
180
|
|
|
return $query; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
protected function marshalItem($resource) |
184
|
|
|
{ |
185
|
|
|
return $this->marshaler->marshalItem($resource); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
protected function unmarshalItem($item, $mapAsObject = true) |
189
|
|
|
{ |
190
|
|
|
if ($item === null) { |
191
|
|
|
return new \StdClass; |
192
|
|
|
} |
193
|
|
|
return $this->marshaler->unmarshalItem($item, $mapAsObject); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
protected function unmarshalBatch($itemList, $mapAsObject = true) |
197
|
|
|
{ |
198
|
|
|
if ($itemList === null) { |
199
|
|
|
return []; |
200
|
|
|
} |
201
|
|
|
$list = []; |
202
|
|
|
foreach ($itemList as $item) { |
203
|
|
|
$list[] = $this->unmarshalItem($item, $mapAsObject); |
204
|
|
|
} |
205
|
|
|
return $list; |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.