Complex classes like ModelQueryBuilder 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 ModelQueryBuilder, and based on these observations, apply Extract Interface, too.
1 | <?php declare (strict_types = 1); |
||
51 | class ModelQueryBuilder extends QueryBuilder |
||
52 | { |
||
53 | /** |
||
54 | * Condition joining method. |
||
55 | */ |
||
56 | public const AND = 0; |
||
57 | |||
58 | /** |
||
59 | * Condition joining method. |
||
60 | */ |
||
61 | public const OR = self::AND + 1; |
||
62 | |||
63 | /** |
||
64 | * @var string |
||
65 | */ |
||
66 | private $modelClass; |
||
67 | |||
68 | /** |
||
69 | * @var string |
||
70 | */ |
||
71 | private $mainTableName; |
||
72 | |||
73 | /** |
||
74 | * @var string |
||
75 | */ |
||
76 | private $mainAlias; |
||
77 | |||
78 | /** |
||
79 | * @var Closure |
||
80 | */ |
||
81 | private $columnMapper; |
||
82 | |||
83 | /** |
||
84 | * @var ModelSchemaInfoInterface |
||
85 | */ |
||
86 | private $modelSchemas; |
||
87 | |||
88 | /** |
||
89 | * @var int |
||
90 | */ |
||
91 | private $aliasIdCounter = 0; |
||
92 | |||
93 | /** |
||
94 | * @var array |
||
95 | */ |
||
96 | private $knownAliases = []; |
||
97 | |||
98 | /** |
||
99 | * @var Type|null |
||
100 | */ |
||
101 | private $dateTimeType; |
||
102 | |||
103 | /** |
||
104 | * @param Connection $connection |
||
105 | * @param string $modelClass |
||
106 | * @param ModelSchemaInfoInterface $modelSchemas |
||
107 | * |
||
108 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
109 | */ |
||
110 | 66 | public function __construct(Connection $connection, string $modelClass, ModelSchemaInfoInterface $modelSchemas) |
|
111 | { |
||
112 | 66 | assert(!empty($modelClass)); |
|
113 | |||
114 | 66 | parent::__construct($connection); |
|
115 | |||
116 | 66 | $this->modelSchemas = $modelSchemas; |
|
117 | 66 | $this->modelClass = $modelClass; |
|
118 | |||
119 | 66 | $this->mainTableName = $this->getModelSchemas()->getTable($this->getModelClass()); |
|
120 | 66 | $this->mainAlias = $this->createAlias($this->getTableName()); |
|
121 | |||
122 | 66 | $this->setColumnToDatabaseMapper(Closure::fromCallable([$this, 'quoteDoubleIdentifier'])); |
|
123 | } |
||
124 | |||
125 | /** |
||
126 | * @return string |
||
127 | */ |
||
128 | 66 | public function getModelClass(): string |
|
129 | { |
||
130 | 66 | return $this->modelClass; |
|
131 | } |
||
132 | |||
133 | /** |
||
134 | * @param string|null $tableAlias |
||
135 | * @param string|null $modelClass |
||
136 | * |
||
137 | * @return array |
||
138 | * |
||
139 | * @throws DBALException |
||
140 | */ |
||
141 | 52 | public function getModelColumns(string $tableAlias = null, string $modelClass = null): array |
|
142 | { |
||
143 | 52 | $modelClass = $modelClass ?? $this->getModelClass(); |
|
144 | 52 | $tableAlias = $tableAlias ?? $this->getAlias(); |
|
145 | |||
146 | 52 | $quotedColumns = []; |
|
147 | |||
148 | 52 | $columnMapper = $this->getColumnToDatabaseMapper(); |
|
149 | 52 | $selectedColumns = $this->getModelSchemas()->getAttributes($modelClass); |
|
150 | 52 | foreach ($selectedColumns as $column) { |
|
151 | 52 | $quotedColumns[] = call_user_func($columnMapper, $tableAlias, $column, $this); |
|
152 | } |
||
153 | |||
154 | 52 | $rawColumns = $this->getModelSchemas()->getRawAttributes($modelClass); |
|
155 | 52 | if (empty($rawColumns) === false) { |
|
156 | 1 | $platform = $this->getConnection()->getDatabasePlatform(); |
|
157 | 1 | foreach ($rawColumns as $columnOrCallable) { |
|
158 | 1 | assert(is_string($columnOrCallable) === true || is_callable($columnOrCallable) === true); |
|
159 | 1 | $quotedColumns[] = is_callable($columnOrCallable) === true ? |
|
160 | 1 | call_user_func($columnOrCallable, $tableAlias, $platform) : $columnOrCallable; |
|
161 | } |
||
162 | } |
||
163 | |||
164 | 52 | return $quotedColumns; |
|
165 | } |
||
166 | |||
167 | /** |
||
168 | * Select all fields associated with model. |
||
169 | * |
||
170 | * @param iterable|null $columns |
||
171 | * |
||
172 | * @return self |
||
173 | * |
||
174 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
175 | * |
||
176 | * @throws DBALException |
||
177 | */ |
||
178 | 57 | public function selectModelColumns(iterable $columns = null): self |
|
179 | { |
||
180 | 57 | if ($columns !== null) { |
|
181 | 5 | $quotedColumns = []; |
|
182 | 5 | foreach ($columns as $column) { |
|
183 | 5 | $quotedColumns[] = $this->quoteDoubleIdentifier($this->getAlias(), $column); |
|
184 | } |
||
185 | } else { |
||
186 | 52 | $quotedColumns = $this->getModelColumns(); |
|
187 | } |
||
188 | |||
189 | 57 | $this->select($quotedColumns); |
|
190 | |||
191 | 57 | return $this; |
|
192 | } |
||
193 | |||
194 | /** |
||
195 | * @return self |
||
196 | * |
||
197 | * @throws DBALException |
||
198 | */ |
||
199 | 14 | public function distinct(): self |
|
200 | { |
||
201 | // emulate SELECT DISTINCT with grouping by primary key |
||
202 | 14 | $primaryColumn = $this->getModelSchemas()->getPrimaryKey($this->getModelClass()); |
|
203 | 14 | $this->addGroupBy($this->getQuotedMainAliasColumn($primaryColumn)); |
|
204 | |||
205 | 14 | return $this; |
|
206 | } |
||
207 | |||
208 | /** |
||
209 | * @param Closure $columnMapper |
||
210 | * |
||
211 | * @return self |
||
212 | */ |
||
213 | 66 | public function setColumnToDatabaseMapper(Closure $columnMapper): self |
|
214 | { |
||
215 | 66 | $this->columnMapper = $columnMapper; |
|
216 | |||
217 | 66 | return $this; |
|
218 | } |
||
219 | |||
220 | /** |
||
221 | * @return self |
||
222 | * |
||
223 | * @throws DBALException |
||
224 | */ |
||
225 | 57 | public function fromModelTable(): self |
|
226 | { |
||
227 | 57 | $this->from( |
|
228 | 57 | $this->quoteSingleIdentifier($this->getTableName()), |
|
229 | 57 | $this->quoteSingleIdentifier($this->getAlias()) |
|
230 | ); |
||
231 | |||
232 | 57 | return $this; |
|
233 | } |
||
234 | |||
235 | /** |
||
236 | * @param iterable $attributes |
||
237 | * |
||
238 | * @return self |
||
239 | * |
||
240 | * @throws DBALException |
||
241 | * |
||
242 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
243 | */ |
||
244 | 5 | public function createModel(iterable $attributes): self |
|
245 | { |
||
246 | 5 | $this->insert($this->quoteSingleIdentifier($this->getTableName())); |
|
247 | |||
248 | 5 | $valuesAsParams = []; |
|
249 | 5 | foreach ($this->bindAttributes($this->getModelClass(), $attributes) as $quotedColumn => $parameterName) { |
|
250 | 5 | $valuesAsParams[$quotedColumn] = $parameterName; |
|
251 | } |
||
252 | 5 | $this->values($valuesAsParams); |
|
253 | |||
254 | 5 | return $this; |
|
255 | } |
||
256 | |||
257 | /** |
||
258 | * @param iterable $attributes |
||
259 | * |
||
260 | * @return self |
||
261 | * |
||
262 | * @throws DBALException |
||
263 | * |
||
264 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
265 | */ |
||
266 | 6 | public function updateModels(iterable $attributes): self |
|
267 | { |
||
268 | 6 | $this->update($this->quoteSingleIdentifier($this->getTableName())); |
|
269 | |||
270 | 6 | foreach ($this->bindAttributes($this->getModelClass(), $attributes) as $quotedColumn => $parameterName) { |
|
271 | 6 | $this->set($quotedColumn, $parameterName); |
|
272 | } |
||
273 | |||
274 | 6 | return $this; |
|
275 | } |
||
276 | |||
277 | /** |
||
278 | * @param string $modelClass |
||
279 | * @param iterable $attributes |
||
280 | * |
||
281 | * @return iterable |
||
|
|||
282 | * |
||
283 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
284 | * |
||
285 | * @throws DBALException |
||
286 | */ |
||
287 | 11 | public function bindAttributes(string $modelClass, iterable $attributes): iterable |
|
288 | { |
||
289 | 11 | $dbPlatform = $this->getConnection()->getDatabasePlatform(); |
|
290 | 11 | $types = $this->getModelSchemas()->getAttributeTypes($modelClass); |
|
291 | |||
292 | 11 | foreach ($attributes as $column => $value) { |
|
293 | 11 | assert(is_string($column) && $this->getModelSchemas()->hasAttributeType($this->getModelClass(), $column)); |
|
294 | |||
295 | 11 | $quotedColumn = $this->quoteSingleIdentifier($column); |
|
296 | 11 | $type = $this->getDbalType($types[$column]); |
|
297 | 11 | $pdoValue = $type->convertToDatabaseValue($value, $dbPlatform); |
|
298 | 11 | $parameterName = $this->createNamedParameter($pdoValue, $type->getBindingType()); |
|
299 | |||
300 | 11 | yield $quotedColumn => $parameterName; |
|
301 | } |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * @return self |
||
306 | * |
||
307 | * @throws DBALException |
||
308 | */ |
||
309 | 4 | public function deleteModels(): self |
|
310 | { |
||
311 | 4 | $this->delete($this->quoteSingleIdentifier($this->getTableName())); |
|
312 | |||
313 | 4 | return $this; |
|
314 | } |
||
315 | |||
316 | /** |
||
317 | * @param string $relationshipName |
||
318 | * @param string $identity |
||
319 | * @param string $secondaryIdBindName |
||
320 | * |
||
321 | * @return self |
||
322 | * |
||
323 | * @throws DBALException |
||
324 | */ |
||
325 | 5 | public function prepareCreateInToManyRelationship( |
|
326 | string $relationshipName, |
||
327 | string $identity, |
||
328 | string $secondaryIdBindName |
||
329 | ): self { |
||
330 | list ($intermediateTable, $primaryKey, $secondaryKey) = |
||
331 | 5 | $this->getModelSchemas()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName); |
|
332 | |||
333 | $this |
||
334 | 5 | ->insert($this->quoteSingleIdentifier($intermediateTable)) |
|
335 | 5 | ->values([ |
|
336 | 5 | $this->quoteSingleIdentifier($primaryKey) => $this->createNamedParameter($identity), |
|
337 | 5 | $this->quoteSingleIdentifier($secondaryKey) => $secondaryIdBindName, |
|
338 | ]); |
||
339 | |||
340 | 5 | return $this; |
|
341 | } |
||
342 | |||
343 | /** |
||
344 | * @param string $relationshipName |
||
345 | * @param string $identity |
||
346 | * @param iterable $secondaryIds |
||
347 | * |
||
348 | * @return ModelQueryBuilder |
||
349 | * |
||
350 | * @throws DBALException |
||
351 | */ |
||
352 | 1 | public function prepareDeleteInToManyRelationship( |
|
353 | string $relationshipName, |
||
354 | string $identity, |
||
355 | iterable $secondaryIds |
||
356 | ): self { |
||
357 | list ($intermediateTable, $primaryKey, $secondaryKey) = |
||
358 | 1 | $this->getModelSchemas()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName); |
|
359 | |||
360 | $filters = [ |
||
361 | 1 | $primaryKey => [FilterParameterInterface::OPERATION_EQUALS => [$identity]], |
|
362 | 1 | $secondaryKey => [FilterParameterInterface::OPERATION_IN => $secondaryIds], |
|
363 | ]; |
||
364 | |||
365 | 1 | $addWith = $this->expr()->andX(); |
|
366 | $this |
||
367 | 1 | ->delete($this->quoteSingleIdentifier($intermediateTable)) |
|
368 | 1 | ->applyFilters($addWith, $intermediateTable, $filters); |
|
369 | |||
370 | 1 | $addWith->count() <= 0 ?: $this->andWhere($addWith); |
|
371 | |||
372 | 1 | return $this; |
|
373 | } |
||
374 | |||
375 | /** |
||
376 | * @param string $relationshipName |
||
377 | * @param string $identity |
||
378 | * |
||
379 | * @return self |
||
380 | * |
||
381 | * @throws DBALException |
||
382 | */ |
||
383 | 2 | public function clearToManyRelationship(string $relationshipName, string $identity): self |
|
384 | { |
||
385 | list ($intermediateTable, $primaryKey) = |
||
386 | 2 | $this->getModelSchemas()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName); |
|
387 | |||
388 | 2 | $filters = [$primaryKey => [FilterParameterInterface::OPERATION_EQUALS => [$identity]]]; |
|
389 | 2 | $addWith = $this->expr()->andX(); |
|
390 | $this |
||
391 | 2 | ->delete($this->quoteSingleIdentifier($intermediateTable)) |
|
392 | 2 | ->applyFilters($addWith, $intermediateTable, $filters); |
|
393 | |||
394 | 2 | $addWith->count() <= 0 ?: $this->andWhere($addWith); |
|
395 | |||
396 | 2 | return $this; |
|
397 | } |
||
398 | |||
399 | /** |
||
400 | * @param iterable $filters |
||
401 | * |
||
402 | * @return self |
||
403 | * |
||
404 | * @throws DBALException |
||
405 | */ |
||
406 | 9 | public function addFiltersWithAndToTable(iterable $filters): self |
|
407 | { |
||
408 | 9 | $addWith = $this->expr()->andX(); |
|
409 | 9 | $this->applyFilters($addWith, $this->getTableName(), $filters); |
|
410 | 9 | $addWith->count() <= 0 ?: $this->andWhere($addWith); |
|
411 | |||
412 | 9 | return $this; |
|
413 | } |
||
414 | |||
415 | /** |
||
416 | * @param iterable $filters |
||
417 | * |
||
418 | * @return self |
||
419 | * |
||
420 | * @throws DBALException |
||
421 | */ |
||
422 | 1 | public function addFiltersWithOrToTable(iterable $filters): self |
|
423 | { |
||
424 | 1 | $addWith = $this->expr()->orX(); |
|
425 | 1 | $this->applyFilters($addWith, $this->getTableName(), $filters); |
|
426 | 1 | $addWith->count() <= 0 ?: $this->andWhere($addWith); |
|
427 | |||
428 | 1 | return $this; |
|
429 | } |
||
430 | |||
431 | /** |
||
432 | * @param iterable $filters |
||
433 | * |
||
434 | * @return self |
||
435 | * |
||
436 | * @throws DBALException |
||
437 | */ |
||
438 | 39 | public function addFiltersWithAndToAlias(iterable $filters): self |
|
439 | { |
||
440 | 39 | $addWith = $this->expr()->andX(); |
|
441 | 39 | $this->applyFilters($addWith, $this->getAlias(), $filters); |
|
442 | 38 | $addWith->count() <= 0 ?: $this->andWhere($addWith); |
|
443 | |||
444 | 38 | return $this; |
|
445 | } |
||
446 | |||
447 | /** |
||
448 | * @param iterable $filters |
||
449 | * |
||
450 | * @return self |
||
451 | * |
||
452 | * @throws DBALException |
||
453 | */ |
||
454 | 2 | public function addFiltersWithOrToAlias(iterable $filters): self |
|
455 | { |
||
456 | 2 | $addWith = $this->expr()->orX(); |
|
457 | 2 | $this->applyFilters($addWith, $this->getAlias(), $filters); |
|
458 | 2 | $addWith->count() <= 0 ?: $this->andWhere($addWith); |
|
459 | |||
460 | 2 | return $this; |
|
461 | } |
||
462 | |||
463 | /** |
||
464 | * @param string $relationshipName |
||
465 | * @param iterable|null $relationshipFilters |
||
466 | * @param iterable|null $relationshipSorts |
||
467 | * @param int $joinIndividuals |
||
468 | * @param int $joinRelationship |
||
469 | * |
||
470 | * @return self |
||
471 | * |
||
472 | * @throws DBALException |
||
473 | * |
||
474 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
475 | * @SuppressWarnings(PHPMD.NPathComplexity) |
||
476 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) |
||
477 | */ |
||
478 | 29 | public function addRelationshipFiltersAndSorts( |
|
479 | string $relationshipName, |
||
480 | ?iterable $relationshipFilters, |
||
481 | ?iterable $relationshipSorts, |
||
482 | int $joinIndividuals = self::AND, |
||
483 | int $joinRelationship = self::AND |
||
484 | ): self { |
||
485 | 29 | $targetAlias = null; |
|
486 | |||
487 | 29 | if ($relationshipFilters !== null) { |
|
488 | 29 | $isBelongsTo = $this->getModelSchemas() |
|
489 | 29 | ->getRelationshipType($this->getModelClass(), $relationshipName) === RelationshipTypes::BELONGS_TO; |
|
490 | |||
491 | // it will have non-null value only in a `belongsTo` relationship |
||
492 | 29 | $reversePk = $isBelongsTo === true ? |
|
493 | 29 | $this->getModelSchemas()->getReversePrimaryKey($this->getModelClass(), $relationshipName)[0] : null; |
|
494 | |||
495 | 29 | $addWith = $joinIndividuals === self::AND ? $this->expr()->andX() : $this->expr()->orX(); |
|
496 | |||
497 | 29 | foreach ($relationshipFilters as $columnName => $operationsWithArgs) { |
|
498 | 28 | if ($columnName === $reversePk) { |
|
499 | // We are applying a filter to a primary key in `belongsTo` relationship |
||
500 | // It could be replaced with a filter to a value in main table. Why might we need it? |
||
501 | // Filter could be 'IS NULL' so joining a table will not work because there are no |
||
502 | // related records with 'NULL` key. For plain values it will produce shorter SQL. |
||
503 | $fkName = |
||
504 | 15 | $this->getModelSchemas()->getForeignKey($this->getModelClass(), $relationshipName); |
|
505 | 15 | $fullColumnName = $this->getQuotedMainAliasColumn($fkName); |
|
506 | } else { |
||
507 | // Will apply filters to a joined table. |
||
508 | 19 | $targetAlias = $targetAlias ?: $this->createRelationshipAlias($relationshipName); |
|
509 | 19 | $fullColumnName = $this->quoteDoubleIdentifier($targetAlias, $columnName); |
|
510 | } |
||
511 | |||
512 | 28 | foreach ($operationsWithArgs as $operation => $arguments) { |
|
513 | 28 | assert( |
|
514 | 28 | is_iterable($arguments) === true || is_array($arguments) === true, |
|
515 | 28 | "Operation arguments are missing for `$columnName` column. " . |
|
516 | 28 | 'Use an empty array as an empty argument list.' |
|
517 | ); |
||
518 | 28 | $addWith->add($this->createFilterExpression($fullColumnName, $operation, $arguments)); |
|
519 | } |
||
520 | |||
521 | 28 | if ($addWith->count() > 0) { |
|
522 | 28 | $joinRelationship === self::AND ? $this->andWhere($addWith) : $this->orWhere($addWith); |
|
523 | } |
||
524 | } |
||
525 | } |
||
526 | |||
527 | 29 | if ($relationshipSorts !== null) { |
|
528 | 23 | foreach ($relationshipSorts as $columnName => $isAsc) { |
|
529 | // we join the table only once and only if we have at least one 'sort' or non-belongsToPK filter. |
||
530 | 8 | $targetAlias = $targetAlias ?: $this->createRelationshipAlias($relationshipName); |
|
531 | |||
532 | 8 | assert(is_string($columnName) === true && is_bool($isAsc) === true); |
|
533 | 8 | $fullColumnName = $this->quoteDoubleIdentifier($targetAlias, $columnName); |
|
534 | 8 | $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC'); |
|
535 | } |
||
536 | } |
||
537 | |||
538 | 29 | return $this; |
|
539 | } |
||
540 | |||
541 | /** |
||
542 | * @param iterable $sortParameters |
||
543 | * |
||
544 | * @return self |
||
545 | * |
||
546 | * @throws DBALException |
||
547 | */ |
||
548 | 15 | public function addSorts(iterable $sortParameters): self |
|
549 | { |
||
550 | 15 | return $this->applySorts($this->getAlias(), $sortParameters); |
|
551 | } |
||
552 | |||
553 | /** |
||
554 | * @param string $column |
||
555 | * |
||
556 | * @return string |
||
557 | * |
||
558 | * @throws DBALException |
||
559 | */ |
||
560 | 1 | public function getQuotedMainTableColumn(string $column): string |
|
561 | { |
||
562 | 1 | return $this->quoteDoubleIdentifier($this->getTableName(), $column); |
|
563 | } |
||
564 | |||
565 | /** |
||
566 | * @param string $column |
||
567 | * |
||
568 | * @return string |
||
569 | * |
||
570 | * @throws DBALException |
||
571 | */ |
||
572 | 23 | public function getQuotedMainAliasColumn(string $column): string |
|
576 | |||
577 | /** |
||
578 | * @param string $name |
||
579 | * |
||
580 | * @return string Table alias. |
||
581 | * |
||
582 | * @throws DBALException |
||
583 | */ |
||
584 | 21 | public function createRelationshipAlias(string $name): string |
|
585 | { |
||
586 | 21 | $relationshipType = $this->getModelSchemas()->getRelationshipType($this->getModelClass(), $name); |
|
587 | switch ($relationshipType) { |
||
588 | 21 | case RelationshipTypes::BELONGS_TO: |
|
589 | list($targetColumn, $targetTable) = |
||
590 | 4 | $this->getModelSchemas()->getReversePrimaryKey($this->getModelClass(), $name); |
|
591 | 4 | $targetAlias = $this->innerJoinOneTable( |
|
592 | 4 | $this->getAlias(), |
|
593 | 4 | $this->getModelSchemas()->getForeignKey($this->getModelClass(), $name), |
|
594 | 4 | $targetTable, |
|
595 | 4 | $targetColumn |
|
596 | ); |
||
597 | 4 | break; |
|
598 | |||
599 | 18 | case RelationshipTypes::HAS_MANY: |
|
600 | list($targetColumn, $targetTable) = |
||
601 | 13 | $this->getModelSchemas()->getReverseForeignKey($this->getModelClass(), $name); |
|
602 | 13 | $targetAlias = $this->innerJoinOneTable( |
|
603 | 13 | $this->getAlias(), |
|
604 | 13 | $this->getModelSchemas()->getPrimaryKey($this->getModelClass()), |
|
605 | 13 | $targetTable, |
|
606 | 13 | $targetColumn |
|
607 | ); |
||
608 | 13 | break; |
|
609 | |||
610 | 10 | case RelationshipTypes::BELONGS_TO_MANY: |
|
611 | default: |
||
612 | 10 | assert($relationshipType === RelationshipTypes::BELONGS_TO_MANY); |
|
613 | 10 | $primaryKey = $this->getModelSchemas()->getPrimaryKey($this->getModelClass()); |
|
614 | list ($intermediateTable, $intermediatePk, $intermediateFk) = |
||
615 | 10 | $this->getModelSchemas()->getBelongsToManyRelationship($this->getModelClass(), $name); |
|
616 | list($targetPrimaryKey, $targetTable) = |
||
617 | 10 | $this->getModelSchemas()->getReversePrimaryKey($this->getModelClass(), $name); |
|
618 | |||
619 | 10 | $targetAlias = $this->innerJoinTwoSequentialTables( |
|
620 | 10 | $this->getAlias(), |
|
621 | 10 | $primaryKey, |
|
622 | 10 | $intermediateTable, |
|
623 | 10 | $intermediatePk, |
|
624 | 10 | $intermediateFk, |
|
625 | 10 | $targetTable, |
|
626 | 10 | $targetPrimaryKey |
|
627 | ); |
||
628 | 10 | break; |
|
629 | } |
||
630 | |||
631 | 21 | return $targetAlias; |
|
632 | } |
||
633 | |||
634 | /** |
||
635 | * @return string |
||
636 | */ |
||
637 | 58 | public function getAlias(): string |
|
641 | |||
642 | /** |
||
643 | * @param CompositeExpression $expression |
||
644 | * @param string $tableOrAlias |
||
645 | * @param iterable $filters |
||
646 | * |
||
647 | * @return self |
||
648 | * |
||
649 | * @throws DBALException |
||
650 | * @throws InvalidArgumentException |
||
651 | */ |
||
652 | 46 | public function applyFilters(CompositeExpression $expression, string $tableOrAlias, iterable $filters): self |
|
653 | { |
||
654 | 46 | foreach ($filters as $columnName => $operationsWithArgs) { |
|
655 | 46 | assert( |
|
656 | 46 | is_string($columnName) === true && empty($columnName) === false, |
|
657 | 46 | "Haven't you forgotten to specify a column name in a relationship that joins `$tableOrAlias` table?" |
|
658 | ); |
||
659 | 46 | $fullColumnName = $this->quoteDoubleIdentifier($tableOrAlias, $columnName); |
|
660 | 46 | foreach ($operationsWithArgs as $operation => $arguments) { |
|
661 | 46 | assert( |
|
662 | 46 | is_iterable($arguments) === true || is_array($arguments) === true, |
|
663 | 46 | "Operation arguments are missing for `$columnName` column. " . |
|
664 | 46 | 'Use an empty array as an empty argument list.' |
|
665 | ); |
||
666 | 46 | $expression->add($this->createFilterExpression($fullColumnName, $operation, $arguments)); |
|
667 | } |
||
668 | } |
||
669 | |||
670 | 45 | return $this; |
|
671 | } |
||
672 | |||
673 | /** |
||
674 | * @param string $tableOrAlias |
||
675 | * @param iterable $sorts |
||
676 | * |
||
677 | * @return self |
||
678 | * |
||
679 | * @throws DBALException |
||
680 | */ |
||
681 | 15 | public function applySorts(string $tableOrAlias, iterable $sorts): self |
|
682 | { |
||
683 | 15 | foreach ($sorts as $columnName => $isAsc) { |
|
684 | 15 | assert(is_string($columnName) === true && is_bool($isAsc) === true); |
|
685 | 15 | $fullColumnName = $this->quoteDoubleIdentifier($tableOrAlias, $columnName); |
|
686 | 15 | $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC'); |
|
687 | } |
||
688 | |||
689 | 15 | return $this; |
|
690 | } |
||
691 | |||
692 | /** |
||
693 | * @param string $tableOrColumn |
||
694 | * |
||
695 | * @return string |
||
696 | * |
||
697 | * @throws DBALException |
||
698 | */ |
||
699 | 65 | public function quoteSingleIdentifier(string $tableOrColumn): string |
|
703 | |||
704 | /** |
||
705 | * @param string $tableOrAlias |
||
706 | * @param string $column |
||
707 | * |
||
708 | * @return string |
||
709 | * |
||
710 | * @throws DBALException |
||
711 | */ |
||
712 | 64 | public function quoteDoubleIdentifier(string $tableOrAlias, string $column): string |
|
718 | |||
719 | /** |
||
720 | * @param $value |
||
721 | * |
||
722 | * @return string |
||
723 | * |
||
724 | * @throws DBALException |
||
725 | */ |
||
726 | 58 | public function createSingleValueNamedParameter($value): string |
|
727 | { |
||
728 | 58 | $paramName = $this->createNamedParameter($this->getPdoValue($value), $this->getPdoType($value)); |
|
729 | |||
730 | 58 | return $paramName; |
|
731 | } |
||
732 | |||
733 | /** |
||
734 | * @param iterable $values |
||
735 | * |
||
736 | * @return array |
||
737 | * |
||
738 | * @throws DBALException |
||
739 | */ |
||
740 | 18 | public function createArrayValuesNamedParameter(iterable $values): array |
|
741 | { |
||
742 | 18 | $names = []; |
|
743 | |||
750 | |||
751 | /** |
||
752 | * @param string $tableName |
||
753 | * |
||
754 | * @return string |
||
755 | */ |
||
756 | 66 | public function createAlias(string $tableName): string |
|
763 | |||
764 | /** |
||
765 | * @param string $fromAlias |
||
766 | * @param string $fromColumn |
||
767 | * @param string $targetTable |
||
768 | * @param string $targetColumn |
||
769 | * |
||
770 | * @return string |
||
771 | * |
||
772 | * @throws DBALException |
||
773 | */ |
||
774 | 21 | public function innerJoinOneTable( |
|
793 | |||
794 | /** |
||
795 | * @param string $fromAlias |
||
796 | * @param string $fromColumn |
||
797 | * @param string $intTable |
||
798 | * @param string $intToFromColumn |
||
799 | * @param string $intToTargetColumn |
||
800 | * @param string $targetTable |
||
801 | * @param string $targetColumn |
||
802 | * |
||
803 | * @return string |
||
804 | * |
||
805 | * @throws DBALException |
||
806 | */ |
||
807 | 10 | public function innerJoinTwoSequentialTables( |
|
821 | |||
822 | /** |
||
823 | * @param string $name |
||
824 | * |
||
825 | * @return Type |
||
826 | * |
||
827 | * @throws DBALException |
||
828 | * |
||
829 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
830 | */ |
||
831 | 11 | protected function getDbalType(string $name): Type |
|
838 | |||
839 | /** |
||
840 | * @return string |
||
841 | */ |
||
842 | 66 | private function getTableName(): string |
|
846 | |||
847 | /** |
||
848 | * @return ModelSchemaInfoInterface |
||
849 | */ |
||
850 | 66 | private function getModelSchemas(): ModelSchemaInfoInterface |
|
854 | |||
855 | /** |
||
856 | * @param string $fullColumnName |
||
857 | * @param int $operation |
||
858 | * @param iterable $arguments |
||
859 | * |
||
860 | * @return string |
||
861 | * |
||
862 | * @throws DBALException |
||
863 | * @throws InvalidArgumentException |
||
864 | * |
||
865 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
866 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) |
||
867 | */ |
||
868 | 59 | private function createFilterExpression(string $fullColumnName, int $operation, iterable $arguments): string |
|
923 | |||
924 | /** |
||
925 | * @param iterable $arguments |
||
926 | * |
||
927 | * @return mixed |
||
928 | * |
||
929 | * @throws InvalidArgumentException |
||
930 | */ |
||
931 | 55 | private function firstValue(iterable $arguments) |
|
940 | |||
941 | /** |
||
942 | * @return Closure |
||
943 | */ |
||
944 | 52 | private function getColumnToDatabaseMapper(): Closure |
|
948 | |||
949 | /** |
||
950 | * @param mixed $value |
||
951 | * |
||
952 | * @return mixed |
||
953 | * |
||
954 | * @throws DBALException |
||
955 | */ |
||
956 | 58 | private function getPdoValue($value) |
|
960 | |||
961 | /** |
||
962 | * @param DateTimeInterface $dateTime |
||
963 | * |
||
964 | * @return string |
||
965 | * |
||
966 | * @throws DBALException |
||
967 | */ |
||
968 | 1 | private function convertDataTimeToDatabaseFormat(DateTimeInterface $dateTime): string |
|
975 | |||
976 | /** |
||
977 | * @param mixed $value |
||
978 | * |
||
979 | * @return int |
||
980 | * |
||
981 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
982 | */ |
||
983 | 58 | private function getPdoType($value): int |
|
1003 | |||
1004 | /** |
||
1005 | * @return Type |
||
1006 | * |
||
1007 | * @throws DBALException |
||
1008 | * |
||
1009 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
1010 | */ |
||
1011 | 1 | private function getDateTimeType(): Type |
|
1019 | } |
||
1020 |
This check compares the return type specified in the
@return
annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.