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
|
19 |
|
public function __construct($resourceName, $primaryKey, Client $client = null, $databaseName = null) |
23
|
|
|
{ |
24
|
19 |
|
$this->resourceName = $resourceName; |
25
|
19 |
|
$this->primaryKey = $primaryKey; |
26
|
19 |
|
$this->client = $client === null ? $this->createClient() : $client; |
27
|
19 |
|
$this->databaseName = $databaseName === null ? $this->getFullDatabaseName() : $databaseName; |
28
|
19 |
|
$this->fullTableName = $this->databaseName . '-' . $this->resourceName; |
29
|
19 |
|
$this->marshaler = new Marshaler; |
30
|
19 |
|
} |
31
|
|
|
|
32
|
1 |
|
private function createClient() |
33
|
|
|
{ |
34
|
1 |
|
$sdk = new Sdk([ |
35
|
1 |
|
'region' => getenv('AWS_SDK_REGION'), |
36
|
1 |
|
'version' => getenv('AWS_SDK_VERSION'), |
37
|
|
|
'http' => [ |
38
|
1 |
|
'verify' => getenv('AWS_SDK_HTTP_VERIFY') === 'false' ? false : getenv('AWS_SDK_HTTP_VERIFY'), |
39
|
|
|
] |
40
|
|
|
]); |
41
|
1 |
|
return $sdk->createDynamoDb(); |
42
|
|
|
} |
43
|
|
|
|
44
|
1 |
|
private function getFullDatabaseName() |
45
|
|
|
{ |
46
|
1 |
|
return getenv('AWS_DYNAMODB_NAMESPACE') . '-' . getenv('AWS_DYNAMODB_DATABASE'); |
47
|
|
|
} |
48
|
|
|
|
49
|
2 |
|
public function get($resourceId = null) |
50
|
|
|
{ |
51
|
2 |
|
if ($resourceId !== null) { |
52
|
1 |
|
return $this->getOne($resourceId); |
53
|
|
|
} |
54
|
1 |
|
return $this->getAll(); |
55
|
|
|
} |
56
|
|
|
|
57
|
1 |
|
protected function getOne($resourceId) |
58
|
|
|
{ |
59
|
1 |
|
$query = $this->createGetQuery($resourceId); |
60
|
1 |
|
$res = $this->client->getItem($query); |
61
|
1 |
|
return $this->unmarshalItem($res['Item']); |
62
|
|
|
} |
63
|
|
|
|
64
|
1 |
|
protected function createGetQuery($resourceId) |
65
|
|
|
{ |
66
|
|
|
$query = [ |
67
|
1 |
|
'TableName' => $this->fullTableName, |
68
|
1 |
|
'Key' => $this->marshalItem([$this->primaryKey => $resourceId]), |
69
|
|
|
]; |
70
|
1 |
|
return $query; |
71
|
|
|
} |
72
|
|
|
|
73
|
1 |
|
protected function getAll() |
74
|
|
|
{ |
75
|
1 |
|
$query = $this->createScanQuery(); |
76
|
1 |
|
$res = $this->client->scan($query); |
77
|
1 |
|
return $this->unmarshalBatch($res['Items']); |
78
|
|
|
} |
79
|
|
|
|
80
|
1 |
|
protected function createScanQuery() |
81
|
|
|
{ |
82
|
|
|
$query = [ |
83
|
1 |
|
'TableName' => $this->fullTableName, |
84
|
|
|
]; |
85
|
1 |
|
return $query; |
86
|
|
|
} |
87
|
|
|
|
88
|
2 |
|
protected function throwAwsPostError($exception) |
89
|
|
|
{ |
90
|
2 |
|
switch ($exception->getAwsErrorCode()) { |
91
|
2 |
|
case 'ConditionalCheckFailedException': |
92
|
1 |
|
throw new RestException( |
93
|
1 |
|
'Primary key collision', |
94
|
1 |
|
['exception' => $exception] |
95
|
|
|
); |
96
|
|
|
} |
97
|
1 |
|
throw new RestException($exception->getMessage(), ['exception' => $exception]); |
98
|
|
|
} |
99
|
|
|
|
100
|
6 |
|
public function post($newResource) |
101
|
|
|
{ |
102
|
|
|
try { |
103
|
6 |
|
$query = $this->createPostQuery($newResource); |
104
|
6 |
|
$this->client->putItem($query); |
105
|
4 |
|
} catch (DynamoDbException $ex) { |
106
|
2 |
|
$this->throwAwsPostError($ex); |
107
|
2 |
|
} catch (\Exception $ex) { |
108
|
2 |
|
throw new RestException($ex->getMessage(), ['exception' => $ex]); |
109
|
|
|
} |
110
|
2 |
|
return $newResource; |
111
|
|
|
} |
112
|
|
|
|
113
|
6 |
|
protected function createPostQuery($newResource) |
114
|
|
|
{ |
115
|
|
|
$query = [ |
116
|
6 |
|
'TableName' => $this->fullTableName, |
117
|
6 |
|
'Item' => $this->marshalItem($newResource), |
118
|
6 |
|
'ConditionExpression' => 'attribute_not_exists(#pk)', |
119
|
6 |
|
"ExpressionAttributeNames" => ["#pk" => $this->primaryKey], |
120
|
|
|
]; |
121
|
6 |
|
return $query; |
122
|
|
|
} |
123
|
|
|
|
124
|
2 |
|
protected function throwAwsPutError($exception) |
125
|
|
|
{ |
126
|
2 |
|
switch ($exception->getAwsErrorCode()) { |
127
|
2 |
|
case 'ConditionalCheckFailedException': |
128
|
1 |
|
throw new RestException( |
129
|
1 |
|
'Resource does not exist', |
130
|
1 |
|
['exception' => $exception] |
131
|
|
|
); |
132
|
|
|
} |
133
|
1 |
|
throw new RestException($exception->getMessage(), ['exception' => $exception]); |
134
|
|
|
} |
135
|
|
|
|
136
|
5 |
|
public function put($resourceId, $newResource) |
137
|
|
|
{ |
138
|
|
|
try { |
139
|
5 |
|
$newResource->{$this->primaryKey} = $resourceId; |
140
|
5 |
|
$query = $this->createPutQuery($newResource); |
141
|
5 |
|
$res = $this->client->putItem($query); |
142
|
3 |
|
} catch (DynamoDbException $ex) { |
143
|
2 |
|
$this->throwAwsPutError($ex); |
144
|
1 |
|
} catch (\Exception $ex) { |
145
|
1 |
|
throw new RestException($ex->getMessage(), ['result'=>empty($res)?null:$res]); |
146
|
|
|
} |
147
|
2 |
|
return $newResource; |
148
|
|
|
} |
149
|
|
|
|
150
|
5 |
|
protected function createPutQuery($newResource) |
151
|
|
|
{ |
152
|
|
|
$query = [ |
153
|
5 |
|
'TableName' => $this->fullTableName, |
154
|
5 |
|
'Item' => $this->marshalItem($newResource), |
155
|
5 |
|
'ConditionExpression' => 'attribute_exists(#pk)', |
156
|
5 |
|
"ExpressionAttributeNames" => ["#pk" => $this->primaryKey], |
157
|
|
|
]; |
158
|
5 |
|
return $query; |
159
|
|
|
} |
160
|
|
|
|
161
|
2 |
|
public function delete($resourceId) |
162
|
|
|
{ |
163
|
|
|
try { |
164
|
2 |
|
$query = $this->createDeleteQuery($resourceId); |
165
|
2 |
|
$res = $this->client->deleteItem($query); |
166
|
1 |
|
return $resourceId; |
167
|
1 |
|
} catch (\Exception $ex) { |
168
|
1 |
|
throw new RestException($ex->getMessage(), ['result'=>empty($res)?null:$res]); |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
|
172
|
2 |
|
protected function createDeleteQuery($resourceId) |
173
|
|
|
{ |
174
|
|
|
$query = [ |
175
|
2 |
|
'TableName' => $this->fullTableName, |
176
|
2 |
|
'Key' => $this->marshalItem([$this->primaryKey => $resourceId]), |
177
|
|
|
]; |
178
|
2 |
|
return $query; |
179
|
|
|
} |
180
|
|
|
|
181
|
14 |
|
protected function marshalItem($resource) |
182
|
|
|
{ |
183
|
14 |
|
return $this->marshaler->marshalItem($resource); |
184
|
|
|
} |
185
|
|
|
|
186
|
3 |
|
protected function unmarshalItem($item) |
187
|
|
|
{ |
188
|
3 |
|
if ($item === null) { |
189
|
1 |
|
return new \StdClass; |
190
|
|
|
} |
191
|
2 |
|
return $this->marshaler->unmarshalItem($item, true); |
192
|
|
|
} |
193
|
|
|
|
194
|
2 |
|
protected function unmarshalBatch($itemList) |
195
|
|
|
{ |
196
|
2 |
|
if ($itemList === null) { |
197
|
1 |
|
return []; |
198
|
|
|
} |
199
|
1 |
|
$list = []; |
200
|
1 |
|
foreach ($itemList as $item) { |
201
|
1 |
|
$list[] = $this->unmarshalItem($item, true); |
|
|
|
|
202
|
|
|
} |
203
|
1 |
|
return $list; |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.