RestObject::scanUniqueFields()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 17
ccs 0
cts 11
cp 0
rs 9.2
cc 4
eloc 10
nc 3
nop 1
crap 20
1
<?php
2
namespace Fathomminds\Rest\Database\DynamoDb;
3
4
use Aws\DynamoDb\Marshaler;
5
use Fathomminds\Rest\Exceptions\RestException;
6
use Fathomminds\Rest\Objects\RestObject as CoreRestObject;
7
use GuzzleHttp\Promise as PromiseFunctions;
8
use GuzzleHttp\Promise\Promise as Promise;
9
10
class RestObject extends CoreRestObject
11
{
12
    protected $primaryKey = '_id';
13
    protected $databaseClass = Database::class;
14
15
    public function find($client = null)
16
    {
17
        if ($client === null) {
18
            $client = $this->getClient();
19
        }
20
        return (new Finder($client))
21
            ->database($this->getDatabaseName())
22
            ->from($this->getResourceName());
23
    }
24
25
    public function validateUniqueFields()
26
    {
27
        $uniqueFields = $this->getUniqueFields();
28
        $uniqueFields = array_diff($uniqueFields, [$this->primaryKey]); //Secondary indexes only
29
        if ($this->allUniqueFieldHasIndex($uniqueFields)) {
30
            return $this->queryUniqueFields($uniqueFields);
31
        }
32
        return $this->scanUniqueFields($uniqueFields);
33
    }
34
35
    protected function allUniqueFieldHasIndex($fields)
36
    {
37
        $indexes = array_keys($this->indexNames);
38
        $existingFields = array_keys(get_object_vars($this->resource));
39
        $uniqueFields = array_diff(
40
            array_intersect($fields, $existingFields), //Is unique and value is set
41
            [$this->primaryKey] //Collect only secondary indexes
42
        );
43
        $indexedFields = array_intersect($uniqueFields, $indexes); //Is unique and value is set and is indexed
44
        sort($uniqueFields);
45
        sort($indexedFields);
46
        return $uniqueFields === $indexedFields;
47
    }
48
49
    protected function scanUniqueFields($fields)
50
    {
51
        $client = $this->getClient();
52
        $query = new Scan($client, $this->generateScan($fields));
53
        while ($res = $query->next()) {
54
            if ($res !== null && $res['Count'] !== 0) {
55
                throw new RestException(
56
                    'Unique constraint violation',
57
                    [
58
                        'resourceName' => $this->resourceName,
59
                        'confilct' => $res,
60
                        'mode' => 'scan',
61
                    ]
62
                );
63
            }
64
        }
65
    }
66
67
    protected function generateScan($fields)
68
    {
69
        $filter = $this->generateScanFilter($fields);
70
        if (property_exists($this->resource, $this->primaryKey)) {
71
            $marshaler = new Marshaler;
72
            $filter['FilterExpression'] = '(' . $filter['FilterExpression'] . ') AND #pk<>:pk';
73
            $filter['ExpressionAttributeNames']['#pk'] = $this->primaryKey;
74
            $filter['ExpressionAttributeValues'][':pk'] = $marshaler->marshalValue(
75
                $this->resource->{$this->primaryKey}
76
            );
77
        }
78
        return [
79
            'TableName' => $this->database->getDatabaseName() . '-' . $this->resourceName,
80
            'FilterExpression' => $filter['FilterExpression'],
81
            'ExpressionAttributeNames' => $filter['ExpressionAttributeNames'],
82
            'ExpressionAttributeValues' => $filter['ExpressionAttributeValues'],
83
        ];
84
    }
85
86
    protected function generateScanFilter($fields)
87
    {
88
        $marshaler = new Marshaler;
89
        $ret = [
90
            'FilterExpression' => '',
91
            'ExpressionAttributeNames' => [],
92
            'ExpressionAttributeValues' => [],
93
        ];
94
        foreach ($fields as $field) {
95
            if (property_exists($this->resource, $field)) {
96
                $ret['FilterExpression'] .= '#' . $field . '=:' . $field . ' OR ';
97
                $ret['ExpressionAttributeNames']['#' . $field] = $field;
98
                $ret['ExpressionAttributeValues'][':' . $field] = $marshaler->marshalValue(
99
                    $this->resource->{$field}
100
                );
101
            }
102
        }
103
        $ret['FilterExpression'] = trim($ret['FilterExpression'], ' OR ');
104
        return $ret;
105
    }
106
107
    protected function queryUniqueFields($fields)
108
    {
109
        $queries = $this->generateQueries($fields);
110
        $promises = $this->generatePromises($queries);
111
        $results = PromiseFunctions\unwrap($promises);
112
        foreach ($results as $result) {
113
            if ($result !== null && $result['Count'] !== 0) {
114
                throw new RestException(
115
                    'Unique constraint violation',
116
                    [
117
                        'resourceName' => $this->resourceName,
118
                        'confilct' => $result,
119
                        'mode' => 'query',
120
                    ]
121
                );
122
            }
123
        }
124
    }
125
126
    protected function generateQueries($fields)
127
    {
128
        $queries = [];
129
        foreach ($fields as $field) {
130
            if (property_exists($this->resource, $field)) {
131
                $queries[] = $this->generateQuery($field);
132
            }
133
        }
134
        return $queries;
135
    }
136
137
    protected function generateQuery($field)
138
    {
139
        $marshaler = new Marshaler;
140
        $query = [
141
            'TableName' => $this->database->getDatabaseName() . '-' . $this->resourceName,
142
            'KeyConditionExpression' => '#' . $field . '=:' . $field,
143
            'IndexName' => $this->indexNames[$field],
144
            'ExpressionAttributeNames' => ['#' . $field => $field],
145
            'ExpressionAttributeValues' => [':' . $field =>  $marshaler->marshalValue(
146
                $this->resource->{$field}
147
            )],
148
        ];
149
        if (property_exists($this->resource, $this->primaryKey)) {
150
            $query['FilterExpression'] = '#pk<>:pk';
151
            $query['ExpressionAttributeNames']['#pk'] = $this->primaryKey;
152
            $query['ExpressionAttributeValues'][':pk'] = $marshaler->marshalValue(
153
                $this->resource->{$this->primaryKey}
154
            );
155
        }
156
        return $query;
157
    }
158
159
    protected function generatePromises($queries)
160
    {
161
        $client = $this->getClient();
162
        $promises = [];
163
        foreach ($queries as $query) {
164
            $promises[] = $this->generatePromise($client, $query);
165
        }
166
        return $promises;
167
    }
168
169
    protected function generatePromise($client, $request)
170
    {
171
        $promise = new Promise(
172
            function() use (&$promise, $client, $request) {
173
                $query = new Query($client, $request);
174
                while ($res = $query->next()) {
175
                    if ($res['Count'] !== 0) {
176
                        $promise->resolve($res);
177
                        return;
178
                    }
179
                }
180
                $promise->resolve(null);
181
            }
182
        );
183
        return $promise;
184
    }
185
186
    public function query()
187
    {
188
        return null;
189
    }
190
}
191