Test Failed
Branch feature/dynamodb (336f9f)
by Csaba
05:26
created

RestObject::scanUniqueFields()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
c 0
b 0
f 0
rs 9.2
cc 4
eloc 10
nc 3
nop 1
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
        if (empty($uniqueFields)) {
20
            return;
21
        }
22
        if ($this->allUniqueFieldHasIndex($uniqueFields)) {
23
            return $this->queryUniqueFields($uniqueFields);
24
        }
25
        return $this->scanUniqueFields($uniqueFields);
26
    }
27
28
    protected function allUniqueFieldHasIndex($fields)
29
    {
30
        $indexes = array_keys($this->indexNames);
31
        $existingFields = array_keys(get_object_vars($this->resource));
32
        $uniqueAndSet = array_intersect($fields, $existingFields); //Is unique and value is set
33
        $uniqueAndSetAndIndexed = array_intersect($uniqueAndSet, $indexes); //Is unique and value is set and is indexed
34
        sort($uniqueAndSet);
35
        sort($uniqueAndSetAndIndexed);
36
        return $uniqueAndSet===$uniqueAndSetAndIndexed;
37
    }
38
39
    protected function scanUniqueFields($fields)
40
    {
41
        $client = $this->getClient();
42
        $query = new Scan($client, $this->generateScan($fields));
43
        while ($res = $query->next()) {
44
            if ($res !== null && $res['Count'] !== 0) {
45
                throw new RestException(
46
                    'Unique constraint violation',
47
                    [
48
                        'resourceName' => $this->resourceName,
49
                        'confilct' => $res,
50
                        'mode' => 'scan',
51
                    ]
52
                );
53
            }
54
        }
55
    }
56
57
    protected function generateScan($fields)
58
    {
59
        $filter = $this->generateScanFilter($fields);
60
        if (property_exists($this->resource, $this->primaryKey)) {
61
            $marshaler = new Marshaler;
62
            $filter['FilterExpression'] = '('.$filter['FilterExpression'].') AND #pk<>:pk';
63
            $filter['ExpressionAttributeNames']['#pk'] = $this->primaryKey;
64
            $filter['ExpressionAttributeValues'][':pk'] = $marshaler->marshalValue(
65
                $this->resource->{$this->primaryKey}
66
            );
67
        }
68
        return [
69
            'TableName' => $this->database->getDatabaseName() . '-' . $this->resourceName,
70
            'FilterExpression' => $filter['FilterExpression'],
71
            'ExpressionAttributeNames' => $filter['ExpressionAttributeNames'],
72
            'ExpressionAttributeValues' => $filter['ExpressionAttributeValues'],
73
        ];
74
    }
75
76
    protected function generateScanFilter($fields)
77
    {
78
        $marshaler = new Marshaler;
79
        $ret = [
80
            'FilterExpression' => '',
81
            'ExpressionAttributeNames' => [],
82
            'ExpressionAttributeValues' => [],
83
        ];
84
        foreach ($fields as $field) {
85
            if (property_exists($this->resource, $field)) {
86
                $ret['FilterExpression'] .= '#'.$field.'=:'.$field.' OR ';
87
                $ret['ExpressionAttributeNames']['#'.$field] = $field;
88
                $ret['ExpressionAttributeValues'][':'.$field] = $marshaler->marshalValue(
89
                    $this->resource->{$field}
90
                );
91
            }
92
        }
93
        $ret['FilterExpression'] = trim($ret['FilterExpression'], ' OR ');
94
        return $ret;
95
    }
96
97
    protected function queryUniqueFields($fields)
98
    {
99
        $client = $this->getClient();
0 ignored issues
show
Unused Code introduced by
$client is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
100
        $queries = [];
101
        foreach ($fields as $field) {
102
            if (property_exists($this->resource, $field)) {
103
                $queries[] = $this->generateQuery($field);
104
            }
105
        }
106
        $promises = $this->generatePromises($queries);
107
        $results = PromiseFunctions\unwrap($promises);
108
        foreach ($results as $result) {
109
            if ($result !== null && $result['Count'] !== 0) {
110
                throw new RestException(
111
                    'Unique constraint violation',
112
                    [
113
                        'resourceName' => $this->resourceName,
114
                        'confilct' => $result,
115
                        'mode' => 'query',
116
                    ]
117
                );
118
            }
119
        }
120
    }
121
122
    protected function generateQuery($field)
123
    {
124
        $marshaler = new Marshaler;
125
        $query = [
126
            'TableName' => $this->database->getDatabaseName() . '-' . $this->resourceName,
127
            'KeyConditionExpression' => '#'.$field.'=:'.$field,
128
            'IndexName' => $this->indexNames[$field],
129
            'ExpressionAttributeNames' => ['#'.$field => $field],
130
            'ExpressionAttributeValues' => [':'.$field =>  $marshaler->marshalValue(
131
                $this->resource->{$field}
132
            )],
133
        ];
134
        if (property_exists($this->resource, $this->primaryKey)) {
135
            $query['FilterExpression'] = '#pk<>:pk';
136
            $query['ExpressionAttributeNames']['#pk'] = $this->primaryKey;
137
            $query['ExpressionAttributeValues'][':pk'] = $marshaler->marshalValue(
138
                $this->resource->{$this->primaryKey}
139
            );
140
        }
141
        return $query;
142
    }
143
144
    protected function generatePromises($queries)
145
    {
146
        $client = $this->getClient();
147
        $promises = [];
148
        foreach ($queries as $query) {
149
            $promise = new Promise(
150
                function () use (&$promise, $client, $query) {
151
                    $q = new Query($client, $query);
152
                    while ($res = $q->next()) {
153
                        if ($res['Count'] !== 0) {
154
                            $promise->resolve($res);
155
                            return;
156
                        }
157
                    }
158
                    $promise->resolve(null);
159
                }
160
            );
161
            $promises[] = $promise;
162
            unset($promise);
163
        }
164
        return $promises;
165
    }
166
}
167