Passed
Branch feature/dynamodb (595a00)
by Csaba
03:06
created

RestObject::generatePromises()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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