Passed
Push — master ( d9f030...e2368e )
by Fran
03:25
created

NOSQLQuery::parseCriteria()   B

Complexity

Conditions 10
Paths 48

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 21
c 1
b 0
f 0
dl 0
loc 36
rs 7.6666
cc 10
nc 48
nop 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace NOSQL\Models;
3
4
use MongoDB\BSON\ObjectId;
5
use MongoDB\Collection;
6
use MongoDB\Database;
7
use NOSQL\Services\Base\NOSQLBase;
8
use NOSQL\Dto\Model\ResultsetDto;
9
use NOSQL\Models\base\NOSQLParserTrait;
10
use PSFS\base\config\Config;
11
use PSFS\base\exception\ApiException;
12
use PSFS\base\types\Api;
13
14
final class NOSQLQuery {
15
    const NOSQL_COLLATION_FIELD = '__collation';
16
    const NOSQL_IN_OPERATOR = '$in';
17
    const NOSQL_NOT_NULL_OPERATOR = '$ne';
18
    const NOSQL_EQUAL_OPERATOR = '$eq';
19
20
    /**
21
     * @param $pk
22
     * @param Database|null $con
23
     * @return mixed
24
     * @throws ApiException
25
     */
26
    public static function findPk($modelName, $pk, Database $con = null) {
27
        $model = new $modelName();
28
        $con = NOSQLParserTrait::initConnection($model, $con);
29
        $collection = $con->selectCollection($model->getSchema()->name);
30
        $result = $collection->findOne(['_id' => new ObjectId($pk)]);
31
        if(null !== $result) {
32
            $model->feed($result->getArrayCopy());
33
        } else {
34
            throw new ApiException(t('Document not found'), 404);
35
        }
36
        return $model;
37
    }
38
39
    /**
40
     * @param string $modelName
41
     * @param array $criteria
42
     * @param Database|null $con
43
     * @return ResultsetDto
44
     * @throws \NOSQL\Exceptions\NOSQLValidationException
45
     * @throws \PSFS\base\exception\GeneratorException
46
     */
47
    public static function find($modelName, array $criteria, Database $con = null) {
48
        /** @var NOSQLActiveRecord $model */
49
        $model = new $modelName();
50
        $con = NOSQLParserTrait::initConnection($model, $con);
51
        $collection = $con->selectCollection($model->getSchema()->name);
52
        $resultSet = new ResultsetDto(false);
53
        // TODO create Query model for it
54
        [$filters, $nosqlOptions] = self::parseCriteria($criteria, $model, $collection);
55
56
        $resultSet->count = $collection->countDocuments($filters, $nosqlOptions);
57
58
        $nosqlOptions["limit"] = (integer)(array_key_exists(Api::API_LIMIT_FIELD, $criteria) ? $criteria[Api::API_LIMIT_FIELD] : Config::getParam('pagination.limit', 50));
59
        $page = (integer)(array_key_exists(Api::API_PAGE_FIELD, $criteria) ? $criteria[Api::API_PAGE_FIELD] : 1);
60
        $nosqlOptions["skip"] = ($page === 1) ? 0 : ($page - 1) * $nosqlOptions["limit"];
61
62
        if ((array_key_exists(Api::API_ORDER_FIELD, $criteria)) && (is_array($criteria[Api::API_ORDER_FIELD]))) {
63
            $nosqlOptions["sort"] = [];
64
            foreach ($criteria[Api::API_ORDER_FIELD] as $field => $direction) {
65
                $nosqlOptions["sort"][$field] = (abs($direction) === 1)  ? $direction : 1;
66
            }
67
        }
68
69
        $results = $collection->find($filters, $nosqlOptions);
70
        /** @var  $result */
71
        $items = $results->toArray();
72
        foreach($items as $item) {
73
            $model->feed($item->getArrayCopy(), true);
74
            $resultSet->items[] = $model->getDtoCopy(true);
75
        }
76
        return $resultSet;
77
    }
78
79
    /**
80
     * @param array $criteria
81
     * @param NOSQLActiveRecord $model
82
     * @return array
83
     */
84
    private static function parseCriteria(array $criteria, NOSQLActiveRecord $model, Collection $collection)
85
    {
86
        $filters = [];
87
        if (array_key_exists(Api::API_COMBO_FIELD, $criteria)) {
88
            $filters['$text'] = ['$search' => $criteria[Api::API_COMBO_FIELD]];
89
        }
90
91
        foreach ($model->getSchema()->properties as $property) {
92
            if (array_key_exists($property->name, $criteria)) {
93
                $filterValue = self::composeFilter($criteria, $property);
94
                $filters[$property->name] = $filterValue;
95
            }
96
        }
97
98
        // Check index collation
99
        $options = [];
100
        $indexes = $collection->listIndexes();
101
        foreach($indexes as $index) {
102
            $indexInfo = $index->__debugInfo();
103
            if (empty(array_diff(array_keys($index["key"]), array_keys($filters)))) {
104
                if (array_key_exists("collation", $indexInfo)) {
105
                    $collation = $indexInfo["collation"];
106
                    $options["collation"] = ["locale" => $collation["locale"], "strength" => $collation["strength"]];
107
                    break;
108
                }
109
            }
110
        }
111
112
        if (array_key_exists("collation", $options)) {
113
            foreach($filters as $key=>$filter) {
114
                if (is_string($criteria[$key])) {
115
                    $filters[$key] = $criteria[$key];
116
                }
117
            }
118
        }
119
        return [$filters, $options];
120
    }
121
122
    /**
123
     * @param array $criteria
124
     * @param \NOSQL\Dto\PropertyDto $property
125
     * @return array|bool|float|int|mixed
126
     */
127
    private static function composeFilter(array $criteria, \NOSQL\Dto\PropertyDto $property)
128
    {
129
        $filterValue = $criteria[$property->name];
130
        $matchOperator = is_array($filterValue) ? $filterValue[0] : self::NOSQL_EQUAL_OPERATOR;
131
        if (is_array($filterValue)) {
132
            if(in_array($matchOperator, [
133
                self::NOSQL_NOT_NULL_OPERATOR,
134
                self::NOSQL_IN_OPERATOR,
135
            ], true)) {
136
                $operator = array_shift($filterValue);
137
                $value = array_shift($filterValue);
138
                $filterValue = [
139
                    $operator => $value,
140
                ];
141
            } else {
142
                // Default case for back compatibility
143
                $filterValue = [
144
                    self::NOSQL_IN_OPERATOR => $filterValue,
145
                ];
146
            }
147
        } elseif(in_array($filterValue, [
148
            self::NOSQL_NOT_NULL_OPERATOR,
149
        ], true)) {
150
            $filterValue = [
151
                $filterValue => null,
152
            ];
153
        } elseif (in_array($property->type, [
154
            NOSQLBase::NOSQL_TYPE_BOOLEAN,
155
            NOSQLBase::NOSQL_TYPE_INTEGER,
156
            NOSQLBase::NOSQL_TYPE_DOUBLE,
157
            NOSQLBase::NOSQL_TYPE_LONG], true)) {
158
            if ($property->type === NOSQLBase::NOSQL_TYPE_BOOLEAN) {
159
                switch ($filterValue) {
160
                    case '1':
161
                    case 1:
162
                    case 'true':
163
                    case true:
164
                        $filterValue = true;
165
                        break;
166
                    default:
167
                        $filterValue = false;
168
                        break;
169
                }
170
            } elseif (NOSQLBase::NOSQL_TYPE_INTEGER === $property->type) {
171
                $filterValue = (integer)$filterValue;
172
            } else {
173
                $filterValue = (float)$filterValue;
174
            }
175
            $filterValue = [
176
                self::NOSQL_EQUAL_OPERATOR => $filterValue,
177
            ];
178
        } else {
179
            $filterValue = [
180
                '$regex' => $filterValue,
181
                '$options' => 'i'
182
            ];
183
        }
184
        return $filterValue;
185
    }
186
}
187