Passed
Push — master ( 300fb2...9a33a3 )
by Smoren
01:41
created

QueryRelationManagerBase::withSingle()   A

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