SelectFacade   B
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 380
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 125
c 4
b 0
f 0
dl 0
loc 380
rs 8.72
wmc 46

17 Methods

Rating   Name   Duplication   Size   Complexity  
A shouldEncodeRowId() 0 6 5
A getResultHeader() 0 21 3
A getSelectData() 0 4 1
A getResultHeaders() 0 18 3
A hasGroupsInFields() 0 4 1
A getRowIdValue() 0 18 4
A getRowColumns() 0 9 3
A execSelect() 0 24 2
A getRowIds() 0 18 3
A getRowIdMd5Key() 0 5 3
A getResultHeaderItem() 0 13 4
A executeSelect() 0 20 5
A getUniqueIds() 0 14 4
A countSelect() 0 11 2
A __construct() 0 5 1
A getRowColumn() 0 14 1
A setSelectEntity() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like SelectFacade often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SelectFacade, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Lagdo\DbAdmin\Db\Facades;
4
5
use Exception;
6
use Lagdo\DbAdmin\Db\Facades\Select\SelectEntity;
7
use Lagdo\DbAdmin\Db\Facades\Select\SelectQuery;
8
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
9
use Lagdo\Facades\Logger;
0 ignored issues
show
Bug introduced by
The type Lagdo\Facades\Logger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
11
use function array_map;
12
use function compact;
13
use function count;
14
use function current;
15
use function is_string;
16
use function key;
17
use function max;
18
use function md5;
19
use function microtime;
20
use function next;
21
use function preg_match;
22
use function strlen;
23
use function strpos;
24
use function trim;
25
26
/**
27
 * Facade to table select functions
28
 */
29
class SelectFacade extends AbstractFacade
30
{
31
    /**
32
     * @var SelectQuery
33
     */
34
    private SelectQuery $selectQuery;
35
36
    /**
37
     * @var SelectEntity|null
38
     */
39
    private SelectEntity|null $selectEntity = null;
40
41
    /**
42
     * @param AbstractFacade $dbFacade
43
     */
44
    public function __construct(AbstractFacade $dbFacade)
45
    {
46
        parent::__construct($dbFacade);
47
48
        $this->selectQuery = new SelectQuery($dbFacade);
49
    }
50
51
    /**
52
     * @param string $table The table name
53
     * @param array $queryOptions The query options
54
     *
55
     * @return void
56
     * @throws Exception
57
     */
58
    private function setSelectEntity(string $table, array $queryOptions = []): void
59
    {
60
        $tableStatus = $this->driver->tableStatusOrName($table);
61
        $tableName = $this->admin->tableName($tableStatus);
62
        $this->selectEntity = new SelectEntity($table,
63
            $tableName, $tableStatus, $queryOptions);
64
        $this->selectQuery->prepareSelect($this->selectEntity);
65
    }
66
67
    /**
68
     * Get required data for create/update on tables
69
     *
70
     * @param string $table The table name
71
     * @param array $queryOptions The query options
72
     *
73
     * @return SelectEntity
74
     * @throws Exception
75
     */
76
    public function getSelectData(string $table, array $queryOptions = []): SelectEntity
77
    {
78
        $this->setSelectEntity($table, $queryOptions);
79
        return $this->selectEntity;
80
    }
81
82
    /**
83
     * @return void
84
     */
85
    private function executeSelect(): void
86
    {
87
        // From driver.inc.php
88
        $startTimestamp = microtime(true);
89
        $statement = $this->driver->execute($this->selectEntity->query);
90
        $this->selectEntity->duration = max(0, microtime(true) - $startTimestamp);
91
92
        // From adminer.inc.php
93
        if (!$statement) {
94
            $this->selectEntity = $this->driver->error();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->driver->error() of type string is incompatible with the declared type Lagdo\DbAdmin\Db\Facades\Select\SelectEntity|null of property $selectEntity.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
95
            return;
96
        }
97
98
        // From select.inc.php
99
        $this->selectEntity->rows = [];
100
        while (($row = $statement->fetchAssoc())) {
101
            if ($this->selectEntity->page && $this->driver->jush() == "oracle") {
102
                unset($row["RNUM"]);
103
            }
104
            $this->selectEntity->rows[] = $row;
105
        }
106
    }
107
108
    /**
109
     * @param string $key
110
     * @param int $rank
111
     *
112
     * @return array
113
     */
114
    private function getResultHeaderItem(string $key, int $rank): array
115
    {
116
        $valueKey = key($this->selectEntity->select);
117
        $value = $this->selectEntity->queryOptions["columns"][$valueKey] ?? [];
118
119
        $fun = $value["fun"] ?? '';
120
        $fieldKey = !$this->selectEntity->select ? $key :
121
            ($value["col"] ?? current($this->selectEntity->select));
122
        $field = $this->selectEntity->fields[$fieldKey];
123
        $name = !$field ? ($fun ? "*" : $key) :
124
            $this->admin->fieldName($field, $rank);
125
126
        return [$fun, $name, $field];
127
    }
128
129
    /**
130
     * @param string $key
131
     * @param mixed $value
132
     * @param int $rank
133
     *
134
     * @return array
135
     */
136
    private function getResultHeader(string $key, $value, int $rank): array
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

136
    private function getResultHeader(string $key, /** @scrutinizer ignore-unused */ $value, int $rank): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
137
    {
138
        if (isset($this->selectEntity->unselected[$key])) {
139
            return [];
140
        }
141
142
        [$fun, $name, $field] = $this->getResultHeaderItem($key, $rank);
143
        $header = compact('field', 'name');
144
        if ($name != "") {
145
            $this->selectEntity->names[$key] = $name;
146
            // $href = remove_from_uri('(order|desc)[^=]*|page') . '&order%5B0%5D=' . urlencode($key);
147
            // $desc = "&desc%5B0%5D=1";
148
            $header['column'] = $this->driver->escapeId($key);
149
            // $header['key'] = $this->utils->str
150
            //     ->html($this->driver->bracketEscape($key));
151
            //! columns looking like functions
152
            $header['title'] = $this->selectQuery->applySqlFunction($fun, $name);
153
        }
154
        // $functions[$key] = $fun;
155
        next($this->selectEntity->select);
156
        return $header;
157
    }
158
159
    /**
160
     * Get the result headers from the first result row
161
     * @return void
162
     */
163
    private function getResultHeaders(): void
164
    {
165
        // Results headers
166
        $this->selectEntity->headers = [
167
            '', // !$group && $select ? '' : lang('Modify');
168
        ];
169
        $this->selectEntity->names = [];
170
        // $this->selectEntity->functions = [];
171
        reset($this->selectEntity->select);
172
173
        $rank = 1;
174
        $firstResultRow = $this->selectEntity->rows[0];
175
        foreach ($firstResultRow as $key => $value) {
176
            $header = $this->getResultHeader($key, $value, $rank);
177
            if ($header['name'] ?? '' !== '') {
178
                $rank++;
179
            }
180
            $this->selectEntity->headers[] = $header;
181
        }
182
    }
183
184
    /**
185
     * @param array $rows
186
     * @param array $queryOptions
187
     *
188
     * @return array
189
     */
190
    /*private function getValuesLengths(array $rows, array $queryOptions): array
191
    {
192
        $lengths = [];
193
        if($queryOptions["modify"])
194
        {
195
            foreach($rows as $row)
196
            {
197
                foreach($row as $key => $value)
198
                {
199
                    $lengths[$key] = \max($lengths[$key], \min(40, strlen(\utf8_decode($value))));
200
                }
201
            }
202
        }
203
        return $lengths;
204
    }*/
205
206
    /**
207
     * @param array $row
208
     *
209
     * @return array
210
     */
211
    private function getUniqueIds(array $row): array
212
    {
213
        $uniqueIds = $this->admin->uniqueIds($row, $this->selectEntity->indexes);
214
        if (empty($uniqueIds)) {
215
            $pattern = '~^(COUNT\((\*|(DISTINCT )?`(?:[^`]|``)+`)\)' .
216
                '|(AVG|GROUP_CONCAT|MAX|MIN|SUM)\(`(?:[^`]|``)+`\))$~';
217
            foreach ($row as $key => $value) {
218
                if (!preg_match($pattern, $key)) {
219
                    //! columns looking like functions
220
                    $uniqueIds[$key] = $value;
221
                }
222
            }
223
        }
224
        return $uniqueIds;
225
    }
226
227
    /**
228
     * @param string $type
229
     * @param mixed $value
230
     *
231
     * @return bool
232
     */
233
    private function shouldEncodeRowId(string $type, $value): bool
234
    {
235
        $jush = $this->driver->jush();
236
        return ($jush === "sql" || $jush === "pgsql") &&
237
            is_string($value) && strlen($value) > 64 &&
238
            preg_match('~char|text|enum|set~', $type);
239
    }
240
241
    /**
242
     * @param string $key
243
     * @param string $collation
244
     *
245
     * @return string
246
     */
247
    private function getRowIdMd5Key(string $key, string $collation): string
248
    {
249
        return $this->driver->jush() != 'sql' ||
250
            preg_match("~^utf8~", $collation) ? $key :
251
                "CONVERT($key USING " . $this->driver->charset() . ")";
252
    }
253
254
    /**
255
     * @param string $key
256
     * @param mixed $value
257
     *
258
     * @return array
259
     */
260
    private function getRowIdValue(string $key, $value): array
261
    {
262
        $key = trim($key);
263
        $type = '';
264
        $collation = '';
265
        if (isset($this->selectEntity->fields[$key])) {
266
            $type = $this->selectEntity->fields[$key]->type;
267
            $collation = $this->selectEntity->fields[$key]->collation;
268
        }
269
        if ($this->shouldEncodeRowId($type, $value)) {
270
            if (!strpos($key, '(')) {
271
                //! columns looking like functions
272
                $key = $this->driver->escapeId($key);
273
            }
274
            $key = "MD5(" . $this->getRowIdMd5Key($key, $collation) . ")";
275
            $value = md5($value);
276
        }
277
        return [$key, $value];
278
    }
279
280
    /**
281
     * @param array $row
282
     *
283
     * @return array
284
     */
285
    private function getRowIds(array $row): array
286
    {
287
        $uniqueIds = $this->getUniqueIds($row);
288
        // Unique identifier to edit returned data.
289
        // $unique_idf = "";
290
        $rowIds = ['where' => [], 'null' => []];
291
        foreach ($uniqueIds as $key => $value) {
292
            [$key, $value] = $this->getRowIdValue($key, $value);
293
            // $unique_idf .= "&" . ($value !== null ? \urlencode("where[" .
294
            // $this->driver->bracketEscape($key) . "]") . "=" .
295
            // \urlencode($value) : \urlencode("null[]") . "=" . \urlencode($key));
296
            if ($value === null) {
297
                $rowIds['null'][] = $this->driver->bracketEscape($key);
298
                continue;
299
            }
300
            $rowIds['where'][$this->driver->bracketEscape($key)] = $value;
301
        }
302
        return $rowIds;
303
    }
304
305
    /**
306
     * @param string $key
307
     * @param mixed $value
308
     *
309
     * @return array
310
     */
311
    private function getRowColumn(string $key, $value): array
312
    {
313
        $field = $this->selectEntity->fields[$key] ?? new TableFieldEntity();
314
        $value = $this->driver->value($value, $field);
315
        /*if ($value != "" && (!isset($email_fields[$key]) || $email_fields[$key] != "")) {
316
            //! filled e-mails can be contained on other pages
317
            $email_fields[$key] = ($this->admin->isMail($value) ? $names[$key] : "");
318
        }*/
319
        $length = $this->selectEntity->textLength;
320
        $value = $this->admin->selectValue($field, $value, $length);
321
        return [
322
            // 'id',
323
            'text' => preg_match('~text|lob~', $field->type),
324
            'value' => $value,
325
            // 'editable' => false,
326
        ];
327
    }
328
329
    /**
330
     * @param array $row
331
     *
332
     * @return array
333
     */
334
    private function getRowColumns(array $row): array
335
    {
336
        $cols = [];
337
        foreach ($row as $key => $value) {
338
            if (isset($this->selectEntity->names[$key])) {
339
                $cols[] = $this->getRowColumn($key, $value);
340
            }
341
        }
342
        return $cols;
343
    }
344
345
    /**
346
     * @return bool
347
     */
348
    private function hasGroupsInFields(): bool
349
    {
350
        return count($this->selectEntity->group) <
351
            count($this->selectEntity->select);
352
    }
353
354
    /**
355
     * Get required data for create/update on tables
356
     *
357
     * @param string $table The table name
358
     * @param array $queryOptions The query options
359
     *
360
     * @return int
361
     */
362
    public function countSelect(string $table, array $queryOptions): int
363
    {
364
        $this->setSelectEntity($table, $queryOptions);
365
366
        try {
367
            $query = $this->driver->getRowCountQuery($table,
368
                $this->selectEntity->where, $this->hasGroupsInFields(),
369
                $this->selectEntity->group);
370
            return (int)$this->driver->result($query);
371
        } catch(Exception $_) {
372
            return -1;
373
        }
374
    }
375
376
    /**
377
     * Get required data for create/update on tables
378
     *
379
     * @param string $table The table name
380
     * @param array $queryOptions The query options
381
     *
382
     * @return array
383
     * @throws Exception
384
     */
385
    public function execSelect(string $table, array $queryOptions): array
386
    {
387
        $this->setSelectEntity($table, $queryOptions);
388
389
        $this->executeSelect();
390
        if (!$this->selectEntity->rows) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->selectEntity->rows of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
391
            return ['message' => $this->utils->trans->lang('No rows.')];
392
        }
393
        // $backward_keys = $this->driver->backwardKeys($table, $tableName);
394
        // lengths = $this->getValuesLengths($rows, $this->selectEntity->queryOptions);
395
396
        $this->getResultHeaders();
397
398
        return [
399
            'headers' => $this->selectEntity->headers,
400
            'query' => $this->selectEntity->query,
401
            'limit' => $this->selectEntity->limit,
402
            'duration' => $this->selectEntity->duration,
403
            'message' => null,
404
            'rows' => array_map(fn($row) => [
405
                // Unique identifier to edit returned data.
406
                'ids' => $this->getRowIds($row),
407
                'cols' => $this->getRowColumns($row),
408
            ], $this->selectEntity->rows),
409
        ];
410
    }
411
}
412