Test Failed
Branch feature/dynamodb (3e8935)
by Csaba
02:57
created

RestObject::validateUniqueFields()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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