QueryRelationManagerBase::withSingle()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 17
c 1
b 0
f 0
nc 1
nop 8
dl 0
loc 32
rs 9.7

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Smoren\QueryRelationManager\Base;
4
5
use Smoren\QueryRelationManager\Base\Structs\JoinCondition;
6
use Smoren\QueryRelationManager\Base\Structs\JoinConditionCollection;
7
use Smoren\QueryRelationManager\Base\Structs\Table;
8
use Smoren\QueryRelationManager\Base\Structs\TableCollection;
9
10
/**
11
 * Base class for making select-query for getting data from several referenced tables and parsing result to the tree
12
 * @author Smoren <[email protected]>
13
 */
14
abstract class QueryRelationManagerBase
15
{
16
    /**
17
     * @var QueryWrapperInterface query builder wrapper
18
     */
19
    protected QueryWrapperInterface $query;
20
21
    /**
22
     * @var JoinConditionCollection collection of join conditions used in query
23
     */
24
    protected JoinConditionCollection $joinConditionCollection;
25
26
    /**
27
     * @var TableCollection collection of tables used in query
28
     */
29
    protected TableCollection $tableCollection;
30
31
    /**
32
     * @var array<callable> list of closures for query modifying
33
     */
34
    protected array $filters = [];
35
36
    /**
37
     * @var array<string, callable> list of closures indexed by table alias for it's parts of result tree modifying
38
     */
39
    protected array $modifierMap = [];
40
41
    /**
42
     * Starts the query
43
     * @param string $className ORM-model class name to use in "from" section of select-query
44
     * @param string $tableAlias table alias
45
     * @return static new instance of relation manager
46
     * @throws QueryRelationManagerException
47
     */
48
    public static function select(string $className, string $tableAlias): self
49
    {
50
        return new static($className, $tableAlias);
51
    }
52
53
    /**
54
     * Adds "join" reference of type "one to one" with another ORM-model
55
     * @param string $containerFieldAlias container key of parent item to put the current item to
56
     * @param string $className name of the ORM-model class to use in "join" section
57
     * @param string $joinAs alias of joined table
58
     * @param string $joinTo alias of table to join to
59
     * @param array<string, string> $joinCondition main join condition
60
     * @param string $joinType join type ("inner", "left", "right")
61
     * @param string|null $extraJoinCondition extra join conditions
62
     * @param array<string, scalar> $extraJoinParams values of dynamic extra params
63
     * @return $this
64
     * @throws QueryRelationManagerException
65
     */
66
    public function withSingle(
67
        string $containerFieldAlias,
68
        string $className,
69
        string $joinAs,
70
        string $joinTo,
71
        array $joinCondition,
72
        string $joinType = 'left',
73
        ?string $extraJoinCondition = null,
74
        array $extraJoinParams = []
75
    ): self {
76
        $table = new Table(
77
            $className,
78
            $this->getTableName($className),
79
            $joinAs,
80
            $this->getTableFields($className),
81
            $this->getPrimaryKey($className),
82
            $containerFieldAlias
83
        );
84
85
        $this->tableCollection->add($table);
86
87
        $this->joinConditionCollection->add(new JoinCondition(
88
            JoinCondition::TYPE_SINGLE,
89
            $table,
90
            $this->tableCollection->byAlias($joinTo),
91
            $joinCondition,
92
            $joinType,
93
            $extraJoinCondition,
94
            $extraJoinParams
95
        ));
96
97
        return $this;
98
    }
99
100
    /**
101
     * Adds "join" reference of type "one to many" with another ORM-model
102
     * @param string $containerFieldAlias container key of parent item to put the current item to
103
     * @param string $className name of the ORM-model class to use in "join" section
104
     * @param string $joinAs alias of joined table
105
     * @param string $joinTo alias of table to join to
106
     * @param array<string, string> $joinCondition main join condition
107
     * @param string $joinType join type ("inner", "left", "right")
108
     * @param string|null $extraJoinCondition extra join conditions
109
     * @param array<string, scalar> $extraJoinParams values of dynamic extra params
110
     * @return $this
111
     * @throws QueryRelationManagerException
112
     */
113
    public function withMultiple(
114
        string $containerFieldAlias,
115
        string $className,
116
        string $joinAs,
117
        string $joinTo,
118
        array $joinCondition,
119
        string $joinType = 'left',
120
        ?string $extraJoinCondition = null,
121
        array $extraJoinParams = []
122
    ): self {
123
        $table = new Table(
124
            $className,
125
            $this->getTableName($className),
126
            $joinAs,
127
            $this->getTableFields($className),
128
            $this->getPrimaryKey($className),
129
            $containerFieldAlias
130
        );
131
132
        $this->tableCollection->add($table);
133
134
        $this->joinConditionCollection->add(new JoinCondition(
135
            JoinCondition::TYPE_MULTIPLE,
136
            $table,
137
            $this->tableCollection->byAlias($joinTo),
138
            $joinCondition,
139
            $joinType,
140
            $extraJoinCondition,
141
            $extraJoinParams
142
        ));
143
144
        return $this;
145
    }
146
147
    /**
148
     * Adds closure for query modifying
149
     * @param callable $filter query modifier closure
150
     * @return $this
151
     */
152
    public function filter(callable $filter): self
153
    {
154
        $this->filters[] = $filter;
155
        return $this;
156
    }
157
158
    /**
159
     * Adds closure for result tree modifying by table alias
160
     * @param string $tableAlias table alias
161
     * @param callable $modifier result modifier closure
162
     * @return $this
163
     */
164
    public function modify(string $tableAlias, callable $modifier): self
165
    {
166
        $this->modifierMap[$tableAlias] = $modifier;
167
        return $this;
168
    }
169
170
    /**
171
     * Executes query, builds tree and return it
172
     * @param mixed|null $db DB connection object
173
     * @return array<array<mixed>> result tree
174
     * @throws QueryRelationManagerException
175
     */
176
    public function all($db = null): array
177
    {
178
        $this->prepare();
179
180
        $rows = $this->query->all($db);
181
182
        $map = [];
183
        foreach($this->tableCollection as $table) {
184
            $map[$table->alias] = [];
185
        }
186
187
        $bufMap = [];
188
189
        foreach($rows as $row) {
190
            foreach($this->tableCollection as $table) {
191
                if(!$table->issetDataInRow($row)) {
192
                    continue;
193
                }
194
195
                [$item, $pkValue, $alias, $aliasTo, $fkValue, $containerFieldAlias, $type]
196
                    = $table->getDataFromRow($row, $this->joinConditionCollection);
197
198
                if(!isset($map[$alias][$pkValue])) {
199
                    $map[$alias][$pkValue] = &$item;
200
                }
201
202
                if($aliasTo !== null) {
203
                    $bufMapKey = implode('-', [$aliasTo, $fkValue, $containerFieldAlias, $pkValue]);
204
                    /** @var string $type */
205
                    switch($type) {
206
                        case JoinCondition::TYPE_SINGLE:
207
                            if(!isset($bufMap[$bufMapKey])) {
208
                                /** @var mixed[][][] $map */
209
                                $map[$aliasTo][$fkValue][$containerFieldAlias] = &$item;
210
                                $bufMap[$bufMapKey] = 1;
211
                            }
212
                            break;
213
                        case JoinCondition::TYPE_MULTIPLE:
214
                            if(!isset($bufMap[$bufMapKey])) {
215
                                /** @var mixed[][][][] $map */
216
                                $map[$aliasTo][$fkValue][$containerFieldAlias][] = &$item;
217
                                $bufMap[$bufMapKey] = 1;
218
                            }
219
                            break;
220
                        default:
221
                            throw new QueryRelationManagerException("unknown condition type '{$type}'");
222
                    }
223
                }
224
                unset($item);
225
            }
226
        }
227
228
        foreach($this->modifierMap as $alias => $modifier) {
229
            foreach($map[$alias] as $pk => &$item) {
230
                ($modifier)($item);
231
            }
232
            unset($item);
233
        }
234
235
        return array_values($map[$this->tableCollection->getMainTable()->alias]);
236
    }
237
238
    /**
239
     * Builds query
240
     * @return QueryWrapperInterface
241
     * @throws QueryRelationManagerException
242
     */
243
    public function prepare(): QueryWrapperInterface
244
    {
245
        foreach($this->tableCollection as $table) {
246
            $table->setPkFieldChain(
247
                $this->tableCollection->getPkFieldChain($table->alias, $this->joinConditionCollection)
248
            );
249
        }
250
251
        $this->query = $this->createQuery();
252
253
        $arSelect = [];
254
        foreach($this->tableCollection as $table) {
255
            foreach($table->getFieldMap() as $fieldName => $fieldNamePrefixed) {
256
                $arSelect[$fieldNamePrefixed] = $fieldName;
257
            }
258
        }
259
260
        $mainTable = $this->tableCollection->getMainTable();
261
262
        $this->query
263
            ->select($arSelect)
264
            ->from([$mainTable->alias => $mainTable->name]);
265
266
        foreach($this->joinConditionCollection as $cond) {
267
            $this->query->join(
268
                $cond->joinType,
269
                [$cond->table->alias => $cond->table->name],
270
                $cond->stringify(),
271
                $cond->extraJoinParams
272
            );
273
        }
274
275
        foreach($this->filters as $modifier) {
276
            $modifier($this->query->getQuery());
277
        }
278
279
        return $this->query;
280
    }
281
282
    /**
283
     * Returns collection of tables used in query
284
     * Возвращает коллекцию объектов таблиц, участвующих в запросе
285
     * @return TableCollection
286
     */
287
    public function getTableCollection(): TableCollection
288
    {
289
        return $this->tableCollection;
290
    }
291
292
    /**
293
     * Returns raw SQL query string
294
     * @return string текст SQL-запроса
295
     * @throws QueryRelationManagerException
296
     */
297
    public function getRawSql(): string
298
    {
299
        $this->prepare();
300
301
        return $this->query->getRawSql();
302
    }
303
304
    /**
305
     * Return table name by ORM-model class name
306
     * @param string $className ORM-model class name
307
     * @return string table name
308
     * @throws QueryRelationManagerException
309
     */
310
    abstract protected function getTableName(string $className): string;
311
312
    /**
313
     * Returns list of names of the table fields by ORM-model class name
314
     * @param string $className ORM-model class name
315
     * @return array<string>
316
     */
317
    abstract protected function getTableFields(string $className): array;
318
319
    /**
320
     * Returns list of names of the primary key fields by ORM-model class name
321
     * @param string $className ORM-model class name
322
     * @return array<string>
323
     */
324
    abstract protected function getPrimaryKey(string $className): array;
325
326
    /**
327
     * Creates ORM query wrapper object
328
     * @return QueryWrapperInterface
329
     */
330
    abstract protected function createQuery(): QueryWrapperInterface;
331
332
    /**
333
     * Magic method for cloning
334
     */
335
    public function __clone()
336
    {
337
        $this->joinConditionCollection = clone $this->joinConditionCollection;
338
        $this->tableCollection = clone $this->tableCollection;
339
    }
340
341
    /**
342
     * QueryRelationManager constructor.
343
     * @param string $className ORM-model class name
344
     * @param string $alias DB table alias
345
     * @throws QueryRelationManagerException
346
     */
347
    final protected function __construct(string $className, string $alias)
348
    {
349
        $this->tableCollection = new TableCollection();
350
        $this->joinConditionCollection = new JoinConditionCollection();
351
352
        $this->tableCollection->add(new Table(
353
            $className,
354
            $this->getTableName($className),
355
            $alias,
356
            $this->getTableFields($className),
357
            $this->getPrimaryKey($className)
358
        ));
359
    }
360
}
361