Records::checkCondition()   C
last analyzed

Complexity

Conditions 14
Paths 14

Size

Total Lines 31
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 14

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 29
c 1
b 0
f 0
nc 14
nop 3
dl 0
loc 31
ccs 15
cts 15
cp 1
crap 14
rs 6.2666

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
3
namespace kalanis\kw_mapper\Search\Connector;
4
5
6
use kalanis\kw_mapper\Interfaces\IQueryBuilder;
7
use kalanis\kw_mapper\MapperException;
8
use kalanis\kw_mapper\Records\ARecord;
9
use kalanis\kw_mapper\Storage;
10
11
12
/**
13
 * Class Records
14
 * @package kalanis\kw_mapper\Search
15
 * Connect records behaving as datasource
16
 * Behave only as advanced filtering
17
 */
18
class Records extends AConnector
19
{
20
    /** @var ARecord[] */
21
    protected array $initialRecords = [];
22
    protected ?Storage\Shared\QueryBuilder\Condition $condition = null;
23
    protected ?Storage\Shared\QueryBuilder\Order $sortingOrder = null;
24
25
    /**
26
     * @param ARecord $record
27
     * @throws MapperException
28
     */
29 65
    public function __construct(ARecord $record)
30
    {
31 65
        $this->basicRecord = $record;
32 65
        $this->initRecordLookup($record); // correct column
33 65
        $this->queryBuilder = $this->initQueryBuilder();
34 65
        $this->queryBuilder->setBaseTable($record->getMapper()->getAlias());
35
    }
36
37 65
    protected function initQueryBuilder(): Storage\Shared\QueryBuilder
38
    {
39 65
        return new Storage\Shared\QueryBuilder();
40
    }
41
42
    /**
43
     * @param ARecord[] $initialRecords
44
     */
45 2
    public function setInitialRecords(array $initialRecords): void
46
    {
47 2
        $this->initialRecords = array_filter($initialRecords, [$this, 'filterInitial']);
48
    }
49
50
    /**
51
     * @param mixed $record
52
     * @return bool
53
     */
54 2
    public function filterInitial($record): bool
55
    {
56 2
        $class = get_class($this->basicRecord);
57 2
        return is_object($record) && ($record instanceof $class);
58
    }
59
60
    /**
61
     * @param string $table
62
     * @return string
63
     * The table here represents a unknown entity, in which it's saved - can even be no storage and the whole can be
64
     * initialized on-the-fly. So return no table. Also good for files where the storage points to the whole file path.
65
     * In fact in some SQL engines it's also real file on volume.
66
     */
67 18
    protected function correctTable(string $table): string
68
    {
69 18
        return '';
70
    }
71
72
    /**
73
     * @param string $table
74
     * @param string $column
75
     * @throws MapperException
76
     * @return string
77
     */
78 18
    protected function correctColumn(string $table, string $column)
79
    {
80 18
        $relations = $this->basicRecord->getMapper()->getRelations();
81 18
        if (!isset($relations[$column])) {
82 1
            throw new MapperException(sprintf('Unknown relation key *%s* in mapper for table *%s*', $column, $this->basicRecord->getMapper()->getAlias()));
83
        }
84 17
        return $column;
85
    }
86
87 1
    public function child(string $childAlias, string $joinType = IQueryBuilder::JOIN_LEFT, string $parentAlias = '', string $customAlias = ''): parent
88
    {
89 1
        throw new MapperException('Cannot make relations over already loaded records!');
90
    }
91
92 1
    public function childNotExist(string $childAlias, string $table, string $column, string $parentAlias = ''): parent
93
    {
94 1
        throw new MapperException('Cannot make relations over already loaded records!');
95
    }
96
97 18
    public function getCount(): int
98
    {
99 18
        return count($this->getResults(false));
100
    }
101
102
    /**
103
     * @param bool $limited
104
     * @throws MapperException
105
     * @return ARecord[]
106
     */
107 18
    public function getResults(bool $limited = true): array
108
    {
109 18
        $results = $this->sortResults( // sorting
110 18
                $this->filterResults( // filters after grouping
111 18
                $this->groupResults( // grouping
112 18
                    $this->filterResults( // basic filters
113 18
                        $this->getInitialRecords(), // all records
114 18
                        $this->queryBuilder->getConditions()
115 18
                    ),
116 18
                    $this->queryBuilder->getGrouping()
117 18
                ),
118 18
                $this->queryBuilder->getHavingCondition()
119 18
            ),
120 18
            $this->queryBuilder->getOrdering()
121 18
        );
122
123
        // paging
124 18
        return $limited
125 4
            ? array_slice($results, intval($this->queryBuilder->getOffset()), $this->queryBuilder->getLimit())
126 18
            : $results ;
127
    }
128
129
    /**
130
     * @return ARecord[]
131
     */
132 1
    protected function getInitialRecords(): array
133
    {
134 1
        return $this->initialRecords;
135
    }
136
137
    /**
138
     * @param ARecord[] $records
139
     * @param Storage\Shared\QueryBuilder\Condition[] $conditions
140
     * @return ARecord[]
141
     * @todo: add compare option for OR (at least one condition OK)
142
     */
143 18
    protected function filterResults(array $records, array $conditions): array
144
    {
145 18
        foreach ($conditions as $condition) {
146 15
            $this->condition = $condition;
147 15
            $records = array_filter($records, [$this, 'filterCondition']);
148
        }
149 18
        $this->condition = null;
150 18
        return $records;
151
    }
152
153
    /**
154
     * @param ARecord[] $records
155
     * @param Storage\Shared\QueryBuilder\Group[] $grouping
156
     * @throws MapperException
157
     * @return ARecord[]
158
     * Each one in group must have the same value; one difference = another group
159
     */
160 18
    protected function groupResults(array $records, array $grouping): array
161
    {
162
        // no groups - no process
163 18
        if (empty($grouping)) {
164 17
            return $records;
165
        }
166
        // get indexes of groups
167 1
        $indexes = [];
168 1
        foreach ($grouping as $group) {
169 1
            $indexes[] = $group->getColumnName();
170
        }
171 1
        $keys = [];
172
        // over records...
173 1
        foreach ($records as $record) {
174 1
            $key = [];
175
            // get value of each element wanted for grouping
176 1
            foreach ($indexes as $index) {
177 1
                $key[] = strval($record->offsetGet($index));
178
            }
179
            // create key which represents that element from the angle of view of groups
180 1
            $expected = implode('__', $key);
181
            // and check if already exists - add if not
182 1
            if (!isset($keys[$expected])) {
183 1
                $keys[$expected] = $record;
184
            }
185
        }
186
        // here stays only the first one
187 1
        return $keys;
188
    }
189
190
    /**
191
     * @param ARecord[] $records
192
     * @param Storage\Shared\QueryBuilder\Order[] $ordering
193
     * @return ARecord[]
194
     */
195 19
    protected function sortResults(array $records, array $ordering): array
196
    {
197 19
        foreach ($ordering as $order) {
198 2
            $this->sortingOrder = $order;
199 2
            usort($records, [$this, 'sortOrder']);
200
        }
201 19
        $this->sortingOrder = null;
202 19
        return $records;
203
    }
204
205
    /**
206
     * @param ARecord $result
207
     * @throws MapperException
208
     * @return bool
209
     */
210 17
    public function filterCondition(ARecord $result): bool
211
    {
212 17
        if (empty($this->condition)) {
213 1
            throw new MapperException('You must set conditions first!');
214
        }
215
216 16
        $columnKey = $this->condition->getColumnKey();
217 16
        return $this->condition->isRaw()
218 1
            ? (is_callable($this->condition->getRaw())
219 1
                ? $this->condition->getRaw()($result)
220 1
                : false
221 16
            )
222 15
            : (is_array($columnKey)
223 2
                ? $this->filterFromManyValues(
224 2
                    $this->condition->getOperation(),
225 2
                    $result->offsetGet($this->condition->getColumnName()),
226 2
                    $this->queryBuilder->getParams(),
227 2
                    $columnKey
228 2
                )
229 16
                : $this->checkCondition(
230 16
                    $this->condition->getOperation(),
231 16
                    $result->offsetGet($this->condition->getColumnName()),
232 16
                    $this->queryBuilder->getParams()[$columnKey] ?? null
233 16
                )
234 16
            )
235 16
        ;
236
    }
237
238
    /**
239
     * @param string $operation
240
     * @param mixed $value
241
     * @param array<string, int|string|float|null> $params
242
     * @param string[] $columnKeys
243
     * @throws MapperException
244
     * @return bool
245
     */
246 2
    protected function filterFromManyValues(string $operation, $value, array $params, array $columnKeys): bool
247
    {
248 2
        $values = [];
249 2
        foreach ($columnKeys as $columnKey) {
250 2
            $values[$columnKey] = $params[$columnKey];
251
        }
252 2
        return $this->checkCondition($operation, $value, $values);
253
    }
254
255
    /**
256
     * @param string $operation
257
     * @param mixed $value
258
     * @param mixed $expected
259
     * @throws MapperException
260
     * @return bool
261
     */
262 5
    protected function checkCondition(string $operation, $value, $expected): bool
263
    {
264
        switch ($operation) {
265
            case IQueryBuilder::OPERATION_NULL:
266 3
                return is_null($value);
267
            case IQueryBuilder::OPERATION_NNULL:
268 3
                return !is_null($value);
269
            case IQueryBuilder::OPERATION_EQ:
270 5
                return $expected == $value;
271
            case IQueryBuilder::OPERATION_NEQ:
272 5
                return $expected != $value;
273
            case IQueryBuilder::OPERATION_GT:
274 4
                return $expected < $value;
275
            case IQueryBuilder::OPERATION_GTE:
276 5
                return $expected <= $value;
277
            case IQueryBuilder::OPERATION_LT:
278 4
                return $expected > $value;
279
            case IQueryBuilder::OPERATION_LTE:
280 5
                return $expected >= $value;
281
            case IQueryBuilder::OPERATION_LIKE:
282 5
                return false !== mb_strpos($value, $expected);
283
            case IQueryBuilder::OPERATION_NLIKE:
284 4
                return false === mb_strpos($value, $expected);
285
            case IQueryBuilder::OPERATION_REXP:
286 1
                return 1 === preg_match($expected, $value);
287
            case IQueryBuilder::OPERATION_IN:
288 3
                return in_array($value, (array) $expected);
289
            case IQueryBuilder::OPERATION_NIN:
290 3
                return !in_array($value, (array) $expected);
291
            default:
292 3
                throw new MapperException(sprintf('Unknown operation *%s* for comparation.', $operation));
293
        }
294
    }
295
296
    /**
297
     * @param ARecord $resultA
298
     * @param ARecord $resultB
299
     * @throws MapperException
300
     * @return int
301
     */
302 3
    public function sortOrder(ARecord $resultA, ARecord $resultB): int
303
    {
304 3
        if (empty($this->sortingOrder)) {
305 1
            throw new MapperException('You must set how it will be sorted!');
306
        }
307 2
        $sortingDirection = empty($this->sortingOrder->getDirection()) ? IQueryBuilder::ORDER_ASC : $this->sortingOrder->getDirection();
308 2
        $a = $resultA->offsetGet($this->sortingOrder->getColumnName());
309 2
        $b = $resultB->offsetGet($this->sortingOrder->getColumnName());
310
311 2
        return (IQueryBuilder::ORDER_ASC == $sortingDirection) ? $a <=> $b : $b <=> $a ;
312
    }
313
}
314