1
|
|
|
<?php declare (strict_types = 1); |
2
|
|
|
|
3
|
|
|
namespace Limoncello\Flute\Adapters; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Copyright 2015-2019 [email protected] |
7
|
|
|
* |
8
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
9
|
|
|
* you may not use this file except in compliance with the License. |
10
|
|
|
* You may obtain a copy of the License at |
11
|
|
|
* |
12
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
13
|
|
|
* |
14
|
|
|
* Unless required by applicable law or agreed to in writing, software |
15
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
16
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
17
|
|
|
* See the License for the specific language governing permissions and |
18
|
|
|
* limitations under the License. |
19
|
|
|
*/ |
20
|
|
|
|
21
|
|
|
use Closure; |
22
|
|
|
use DateTimeInterface; |
23
|
|
|
use Doctrine\DBAL\Connection; |
24
|
|
|
use Doctrine\DBAL\DBALException; |
25
|
|
|
use Doctrine\DBAL\Query\Expression\CompositeExpression; |
26
|
|
|
use Doctrine\DBAL\Query\QueryBuilder; |
27
|
|
|
use Doctrine\DBAL\Types\DateTimeType; |
28
|
|
|
use Doctrine\DBAL\Types\Type; |
29
|
|
|
use Limoncello\Contracts\Data\ModelSchemaInfoInterface; |
30
|
|
|
use Limoncello\Contracts\Data\RelationshipTypes; |
31
|
|
|
use Limoncello\Flute\Contracts\Http\Query\FilterParameterInterface; |
32
|
|
|
use Limoncello\Flute\Exceptions\InvalidArgumentException; |
33
|
|
|
use PDO; |
34
|
|
|
use function assert; |
35
|
|
|
use function call_user_func; |
36
|
|
|
use function is_array; |
37
|
|
|
use function is_bool; |
38
|
|
|
use function is_callable; |
39
|
|
|
use function is_int; |
40
|
|
|
use function is_iterable; |
41
|
|
|
use function is_string; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @package Limoncello\Flute |
45
|
|
|
* |
46
|
|
|
* @SuppressWarnings(PHPMD.TooManyMethods) |
47
|
|
|
* @SuppressWarnings(PHPMD.TooManyPublicMethods) |
48
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
49
|
|
|
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity) |
50
|
|
|
*/ |
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
|
66 |
|
|
103
|
|
|
/** |
104
|
66 |
|
* @param Connection $connection |
105
|
|
|
* @param string $modelClass |
106
|
66 |
|
* @param ModelSchemaInfoInterface $modelSchemas |
107
|
|
|
* |
108
|
66 |
|
* @SuppressWarnings(PHPMD.StaticAccess) |
109
|
66 |
|
*/ |
110
|
|
|
public function __construct(Connection $connection, string $modelClass, ModelSchemaInfoInterface $modelSchemas) |
111
|
66 |
|
{ |
112
|
66 |
|
assert(!empty($modelClass)); |
113
|
|
|
|
114
|
66 |
|
parent::__construct($connection); |
115
|
|
|
|
116
|
|
|
$this->modelSchemas = $modelSchemas; |
117
|
|
|
$this->modelClass = $modelClass; |
118
|
|
|
|
119
|
|
|
$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
|
|
|
public function getModelClass(): string |
129
|
|
|
{ |
130
|
|
|
return $this->modelClass; |
131
|
52 |
|
} |
132
|
|
|
|
133
|
52 |
|
/** |
134
|
52 |
|
* @param string|null $tableAlias |
135
|
|
|
* @param string|null $modelClass |
136
|
52 |
|
* |
137
|
|
|
* @return array |
138
|
52 |
|
* |
139
|
52 |
|
* @throws DBALException |
140
|
52 |
|
*/ |
141
|
52 |
|
public function getModelColumns(string $tableAlias = null, string $modelClass = null): array |
142
|
|
|
{ |
143
|
|
|
$modelClass = $modelClass ?? $this->getModelClass(); |
144
|
52 |
|
$tableAlias = $tableAlias ?? $this->getAlias(); |
145
|
52 |
|
|
146
|
1 |
|
$quotedColumns = []; |
147
|
1 |
|
|
148
|
|
|
$columnMapper = $this->getColumnToDatabaseMapper(); |
149
|
|
|
$selectedColumns = $this->getModelSchemas()->getAttributes($modelClass); |
150
|
52 |
|
foreach ($selectedColumns as $column) { |
151
|
|
|
$quotedColumns[] = call_user_func($columnMapper, $tableAlias, $column, $this); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$rawColumns = $this->getModelSchemas()->getRawAttributes($modelClass); |
155
|
|
|
if (empty($rawColumns) === false) { |
156
|
|
|
$platform = $this->getConnection()->getDatabasePlatform(); |
157
|
|
|
foreach ($rawColumns as $columnOrCallable) { |
158
|
|
|
assert(is_string($columnOrCallable) === true || is_callable($columnOrCallable) === true); |
159
|
|
|
$quotedColumns[] = is_callable($columnOrCallable) === true ? |
160
|
|
|
call_user_func($columnOrCallable, $tableAlias, $platform) : $columnOrCallable; |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
57 |
|
return $quotedColumns; |
165
|
|
|
} |
166
|
57 |
|
|
167
|
5 |
|
/** |
168
|
5 |
|
* Select all fields associated with model. |
169
|
5 |
|
* |
170
|
|
|
* @param iterable|null $columns |
171
|
|
|
* |
172
|
52 |
|
* @return self |
173
|
|
|
* |
174
|
|
|
* @SuppressWarnings(PHPMD.ElseExpression) |
175
|
57 |
|
* |
176
|
|
|
* @throws DBALException |
177
|
57 |
|
*/ |
178
|
|
|
public function selectModelColumns(iterable $columns = null): self |
179
|
|
|
{ |
180
|
|
|
if ($columns !== null) { |
181
|
|
|
$quotedColumns = []; |
182
|
|
|
foreach ($columns as $column) { |
183
|
|
|
$quotedColumns[] = $this->quoteDoubleIdentifier($this->getAlias(), $column); |
184
|
|
|
} |
185
|
14 |
|
} else { |
186
|
|
|
$quotedColumns = $this->getModelColumns(); |
187
|
|
|
} |
188
|
14 |
|
|
189
|
14 |
|
$this->select($quotedColumns); |
190
|
|
|
|
191
|
14 |
|
return $this; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* @return self |
196
|
|
|
* |
197
|
|
|
* @throws DBALException |
198
|
|
|
*/ |
199
|
66 |
|
public function distinct(): self |
200
|
|
|
{ |
201
|
66 |
|
// emulate SELECT DISTINCT with grouping by primary key |
202
|
|
|
$primaryColumn = $this->getModelSchemas()->getPrimaryKey($this->getModelClass()); |
203
|
66 |
|
$this->addGroupBy($this->getQuotedMainAliasColumn($primaryColumn)); |
204
|
|
|
|
205
|
|
|
return $this; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* @param Closure $columnMapper |
210
|
|
|
* |
211
|
57 |
|
* @return self |
212
|
|
|
*/ |
213
|
57 |
|
public function setColumnToDatabaseMapper(Closure $columnMapper): self |
214
|
57 |
|
{ |
215
|
57 |
|
$this->columnMapper = $columnMapper; |
216
|
|
|
|
217
|
|
|
return $this; |
218
|
57 |
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* @return self |
222
|
|
|
* |
223
|
|
|
* @throws DBALException |
224
|
|
|
*/ |
225
|
|
|
public function fromModelTable(): self |
226
|
|
|
{ |
227
|
|
|
$this->from( |
228
|
|
|
$this->quoteSingleIdentifier($this->getTableName()), |
229
|
|
|
$this->quoteSingleIdentifier($this->getAlias()) |
230
|
5 |
|
); |
231
|
|
|
|
232
|
5 |
|
return $this; |
233
|
|
|
} |
234
|
5 |
|
|
235
|
5 |
|
/** |
236
|
5 |
|
* @param iterable $attributes |
237
|
|
|
* |
238
|
5 |
|
* @return self |
239
|
|
|
* |
240
|
5 |
|
* @throws DBALException |
241
|
|
|
* |
242
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
243
|
|
|
*/ |
244
|
|
|
public function createModel(iterable $attributes): self |
245
|
|
|
{ |
246
|
|
|
$this->insert($this->quoteSingleIdentifier($this->getTableName())); |
247
|
|
|
|
248
|
|
|
$valuesAsParams = []; |
249
|
|
|
foreach ($this->bindAttributes($this->getModelClass(), $attributes) as $quotedColumn => $parameterName) { |
250
|
|
|
$valuesAsParams[$quotedColumn] = $parameterName; |
251
|
|
|
} |
252
|
6 |
|
$this->values($valuesAsParams); |
253
|
|
|
|
254
|
6 |
|
return $this; |
255
|
|
|
} |
256
|
6 |
|
|
257
|
6 |
|
/** |
258
|
|
|
* @param iterable $attributes |
259
|
|
|
* |
260
|
6 |
|
* @return self |
261
|
|
|
* |
262
|
|
|
* @throws DBALException |
263
|
|
|
* |
264
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
265
|
|
|
*/ |
266
|
|
|
public function updateModels(iterable $attributes): self |
267
|
|
|
{ |
268
|
|
|
$this->update($this->quoteSingleIdentifier($this->getTableName())); |
269
|
|
|
|
270
|
|
|
foreach ($this->bindAttributes($this->getModelClass(), $attributes) as $quotedColumn => $parameterName) { |
271
|
|
|
$this->set($quotedColumn, $parameterName); |
272
|
|
|
} |
273
|
11 |
|
|
274
|
|
|
return $this; |
275
|
11 |
|
} |
276
|
11 |
|
|
277
|
|
|
/** |
278
|
11 |
|
* @param string $modelClass |
279
|
11 |
|
* @param iterable $attributes |
280
|
|
|
* |
281
|
11 |
|
* @return iterable |
|
|
|
|
282
|
11 |
|
* |
283
|
11 |
|
* @SuppressWarnings(PHPMD.StaticAccess) |
284
|
11 |
|
* |
285
|
|
|
* @throws DBALException |
286
|
11 |
|
*/ |
287
|
|
|
public function bindAttributes(string $modelClass, iterable $attributes): iterable |
288
|
|
|
{ |
289
|
|
|
$dbPlatform = $this->getConnection()->getDatabasePlatform(); |
290
|
|
|
$types = $this->getModelSchemas()->getAttributeTypes($modelClass); |
291
|
|
|
|
292
|
|
|
foreach ($attributes as $column => $value) { |
293
|
|
|
assert(is_string($column) && $this->getModelSchemas()->hasAttributeType($this->getModelClass(), $column)); |
294
|
|
|
|
295
|
4 |
|
$quotedColumn = $this->quoteSingleIdentifier($column); |
296
|
|
|
$type = $this->getDbalType($types[$column]); |
297
|
4 |
|
$pdoValue = $type->convertToDatabaseValue($value, $dbPlatform); |
298
|
|
|
$parameterName = $this->createNamedParameter($pdoValue, $type->getBindingType()); |
299
|
4 |
|
|
300
|
|
|
yield $quotedColumn => $parameterName; |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* @return self |
306
|
|
|
* |
307
|
|
|
* @throws DBALException |
308
|
|
|
*/ |
309
|
|
|
public function deleteModels(): self |
310
|
|
|
{ |
311
|
5 |
|
$this->delete($this->quoteSingleIdentifier($this->getTableName())); |
312
|
|
|
|
313
|
|
|
return $this; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
5 |
|
* @param string $relationshipName |
318
|
|
|
* @param string $identity |
319
|
|
|
* @param string $secondaryIdBindName |
320
|
5 |
|
* |
321
|
5 |
|
* @return self |
322
|
5 |
|
* |
323
|
5 |
|
* @throws DBALException |
324
|
|
|
*/ |
325
|
|
|
public function prepareCreateInToManyRelationship( |
326
|
5 |
|
string $relationshipName, |
327
|
|
|
string $identity, |
328
|
|
|
string $secondaryIdBindName |
329
|
|
|
): self { |
330
|
|
|
list ($intermediateTable, $primaryKey, $secondaryKey) = |
331
|
|
|
$this->getModelSchemas()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName); |
332
|
|
|
|
333
|
|
|
$this |
334
|
|
|
->insert($this->quoteSingleIdentifier($intermediateTable)) |
335
|
|
|
->values([ |
336
|
|
|
$this->quoteSingleIdentifier($primaryKey) => $this->createNamedParameter($identity), |
337
|
|
|
$this->quoteSingleIdentifier($secondaryKey) => $secondaryIdBindName, |
338
|
1 |
|
]); |
339
|
|
|
|
340
|
|
|
return $this; |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
1 |
|
* @param string $relationshipName |
345
|
|
|
* @param string $identity |
346
|
|
|
* @param iterable $secondaryIds |
347
|
1 |
|
* |
348
|
1 |
|
* @return ModelQueryBuilder |
349
|
|
|
* |
350
|
|
|
* @throws DBALException |
351
|
1 |
|
*/ |
352
|
|
|
public function prepareDeleteInToManyRelationship( |
353
|
1 |
|
string $relationshipName, |
354
|
1 |
|
string $identity, |
355
|
|
|
iterable $secondaryIds |
356
|
1 |
|
): self { |
357
|
|
|
list ($intermediateTable, $primaryKey, $secondaryKey) = |
358
|
1 |
|
$this->getModelSchemas()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName); |
359
|
|
|
|
360
|
|
|
$filters = [ |
361
|
|
|
$primaryKey => [FilterParameterInterface::OPERATION_EQUALS => [$identity]], |
362
|
|
|
$secondaryKey => [FilterParameterInterface::OPERATION_IN => $secondaryIds], |
363
|
|
|
]; |
364
|
|
|
|
365
|
|
|
$addWith = $this->expr()->andX(); |
366
|
|
|
$this |
367
|
|
|
->delete($this->quoteSingleIdentifier($intermediateTable)) |
368
|
|
|
->applyFilters($addWith, $intermediateTable, $filters); |
|
|
|
|
369
|
2 |
|
|
370
|
|
|
$addWith->count() <= 0 ?: $this->andWhere($addWith); |
371
|
|
|
|
372
|
2 |
|
return $this; |
373
|
|
|
} |
374
|
2 |
|
|
375
|
2 |
|
/** |
376
|
|
|
* @param string $relationshipName |
377
|
2 |
|
* @param string $identity |
378
|
2 |
|
* |
379
|
|
|
* @return self |
380
|
2 |
|
* |
381
|
|
|
* @throws DBALException |
382
|
2 |
|
*/ |
383
|
|
|
public function clearToManyRelationship(string $relationshipName, string $identity): self |
384
|
|
|
{ |
385
|
|
|
list ($intermediateTable, $primaryKey) = |
386
|
|
|
$this->getModelSchemas()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName); |
387
|
|
|
|
388
|
|
|
$filters = [$primaryKey => [FilterParameterInterface::OPERATION_EQUALS => [$identity]]]; |
389
|
|
|
$addWith = $this->expr()->andX(); |
390
|
|
|
$this |
391
|
|
|
->delete($this->quoteSingleIdentifier($intermediateTable)) |
392
|
9 |
|
->applyFilters($addWith, $intermediateTable, $filters); |
|
|
|
|
393
|
|
|
|
394
|
9 |
|
$addWith->count() <= 0 ?: $this->andWhere($addWith); |
395
|
9 |
|
|
396
|
9 |
|
return $this; |
397
|
|
|
} |
398
|
9 |
|
|
399
|
|
|
/** |
400
|
|
|
* @param iterable $filters |
401
|
|
|
* |
402
|
|
|
* @return self |
403
|
|
|
* |
404
|
|
|
* @throws DBALException |
405
|
|
|
*/ |
406
|
|
|
public function addFiltersWithAndToTable(iterable $filters): self |
407
|
|
|
{ |
408
|
1 |
|
$addWith = $this->expr()->andX(); |
409
|
|
|
$this->applyFilters($addWith, $this->getTableName(), $filters); |
410
|
1 |
|
$addWith->count() <= 0 ?: $this->andWhere($addWith); |
411
|
1 |
|
|
412
|
1 |
|
return $this; |
413
|
|
|
} |
414
|
1 |
|
|
415
|
|
|
/** |
416
|
|
|
* @param iterable $filters |
417
|
|
|
* |
418
|
|
|
* @return self |
419
|
|
|
* |
420
|
|
|
* @throws DBALException |
421
|
|
|
*/ |
422
|
|
|
public function addFiltersWithOrToTable(iterable $filters): self |
423
|
|
|
{ |
424
|
39 |
|
$addWith = $this->expr()->orX(); |
425
|
|
|
$this->applyFilters($addWith, $this->getTableName(), $filters); |
426
|
39 |
|
$addWith->count() <= 0 ?: $this->andWhere($addWith); |
427
|
39 |
|
|
428
|
38 |
|
return $this; |
429
|
|
|
} |
430
|
38 |
|
|
431
|
|
|
/** |
432
|
|
|
* @param iterable $filters |
433
|
|
|
* |
434
|
|
|
* @return self |
435
|
|
|
* |
436
|
|
|
* @throws DBALException |
437
|
|
|
*/ |
438
|
|
|
public function addFiltersWithAndToAlias(iterable $filters): self |
439
|
|
|
{ |
440
|
2 |
|
$addWith = $this->expr()->andX(); |
441
|
|
|
$this->applyFilters($addWith, $this->getAlias(), $filters); |
442
|
2 |
|
$addWith->count() <= 0 ?: $this->andWhere($addWith); |
443
|
2 |
|
|
444
|
2 |
|
return $this; |
445
|
|
|
} |
446
|
2 |
|
|
447
|
|
|
/** |
448
|
|
|
* @param iterable $filters |
449
|
|
|
* |
450
|
|
|
* @return self |
451
|
|
|
* |
452
|
|
|
* @throws DBALException |
453
|
|
|
*/ |
454
|
|
|
public function addFiltersWithOrToAlias(iterable $filters): self |
455
|
|
|
{ |
456
|
|
|
$addWith = $this->expr()->orX(); |
457
|
|
|
$this->applyFilters($addWith, $this->getAlias(), $filters); |
458
|
|
|
$addWith->count() <= 0 ?: $this->andWhere($addWith); |
459
|
|
|
|
460
|
|
|
return $this; |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
/** |
464
|
29 |
|
* @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
|
29 |
|
* |
472
|
|
|
* @throws DBALException |
473
|
29 |
|
* |
474
|
29 |
|
* @SuppressWarnings(PHPMD.ElseExpression) |
475
|
29 |
|
* @SuppressWarnings(PHPMD.NPathComplexity) |
476
|
|
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) |
477
|
|
|
*/ |
478
|
29 |
|
public function addRelationshipFiltersAndSorts( |
479
|
29 |
|
string $relationshipName, |
480
|
|
|
?iterable $relationshipFilters, |
481
|
29 |
|
?iterable $relationshipSorts, |
482
|
|
|
int $joinIndividuals = self::AND, |
483
|
29 |
|
int $joinRelationship = self::AND |
484
|
28 |
|
): self { |
485
|
|
|
$targetAlias = null; |
486
|
|
|
|
487
|
|
|
if ($relationshipFilters !== null) { |
488
|
|
|
$isBelongsTo = $this->getModelSchemas() |
489
|
|
|
->getRelationshipType($this->getModelClass(), $relationshipName) === RelationshipTypes::BELONGS_TO; |
490
|
15 |
|
|
491
|
15 |
|
// it will have non-null value only in a `belongsTo` relationship |
492
|
|
|
$reversePk = $isBelongsTo === true ? |
493
|
|
|
$this->getModelSchemas()->getReversePrimaryKey($this->getModelClass(), $relationshipName)[0] : null; |
494
|
19 |
|
|
495
|
19 |
|
$addWith = $joinIndividuals === self::AND ? $this->expr()->andX() : $this->expr()->orX(); |
496
|
|
|
|
497
|
|
|
foreach ($relationshipFilters as $columnName => $operationsWithArgs) { |
498
|
28 |
|
if ($columnName === $reversePk) { |
499
|
28 |
|
// We are applying a filter to a primary key in `belongsTo` relationship |
500
|
28 |
|
// It could be replaced with a filter to a value in main table. Why might we need it? |
501
|
28 |
|
// Filter could be 'IS NULL' so joining a table will not work because there are no |
502
|
28 |
|
// related records with 'NULL` key. For plain values it will produce shorter SQL. |
503
|
|
|
$fkName = |
504
|
28 |
|
$this->getModelSchemas()->getForeignKey($this->getModelClass(), $relationshipName); |
505
|
|
|
$fullColumnName = $this->getQuotedMainAliasColumn($fkName); |
506
|
|
|
} else { |
507
|
28 |
|
// Will apply filters to a joined table. |
508
|
28 |
|
$targetAlias = $targetAlias ?: $this->createRelationshipAlias($relationshipName); |
509
|
|
|
$fullColumnName = $this->quoteDoubleIdentifier($targetAlias, $columnName); |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
foreach ($operationsWithArgs as $operation => $arguments) { |
513
|
29 |
|
assert( |
514
|
23 |
|
is_iterable($arguments) === true || is_array($arguments) === true, |
515
|
|
|
"Operation arguments are missing for `$columnName` column. " . |
516
|
8 |
|
'Use an empty array as an empty argument list.' |
517
|
|
|
); |
518
|
8 |
|
$addWith->add($this->createFilterExpression($fullColumnName, $operation, $arguments)); |
519
|
8 |
|
} |
520
|
8 |
|
|
521
|
|
|
if ($addWith->count() > 0) { |
522
|
|
|
$joinRelationship === self::AND ? $this->andWhere($addWith) : $this->orWhere($addWith); |
523
|
|
|
} |
524
|
29 |
|
} |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
if ($relationshipSorts !== null) { |
528
|
|
|
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
|
|
|
$targetAlias = $targetAlias ?: $this->createRelationshipAlias($relationshipName); |
531
|
|
|
|
532
|
|
|
assert(is_string($columnName) === true && is_bool($isAsc) === true); |
533
|
|
|
$fullColumnName = $this->quoteDoubleIdentifier($targetAlias, $columnName); |
534
|
8 |
|
$this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC'); |
535
|
|
|
} |
536
|
8 |
|
} |
537
|
|
|
|
538
|
|
|
return $this; |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
/** |
542
|
|
|
* @param iterable $sortParameters |
543
|
|
|
* |
544
|
|
|
* @return self |
545
|
|
|
* |
546
|
1 |
|
* @throws DBALException |
547
|
|
|
*/ |
548
|
1 |
|
public function addSorts(iterable $sortParameters): self |
549
|
|
|
{ |
550
|
|
|
return $this->applySorts($this->getAlias(), $sortParameters); |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
/** |
554
|
|
|
* @param string $column |
555
|
|
|
* |
556
|
|
|
* @return string |
557
|
|
|
* |
558
|
23 |
|
* @throws DBALException |
559
|
|
|
*/ |
560
|
23 |
|
public function getQuotedMainTableColumn(string $column): string |
561
|
|
|
{ |
562
|
|
|
return $this->quoteDoubleIdentifier($this->getTableName(), $column); |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
/** |
566
|
|
|
* @param string $column |
567
|
|
|
* |
568
|
|
|
* @return string |
569
|
|
|
* |
570
|
21 |
|
* @throws DBALException |
571
|
|
|
*/ |
572
|
21 |
|
public function getQuotedMainAliasColumn(string $column): string |
573
|
|
|
{ |
574
|
21 |
|
return $this->quoteDoubleIdentifier($this->getAlias(), $column); |
575
|
|
|
} |
576
|
4 |
|
|
577
|
4 |
|
/** |
578
|
4 |
|
* @param string $name |
579
|
4 |
|
* |
580
|
4 |
|
* @return string Table alias. |
581
|
4 |
|
* |
582
|
|
|
* @throws DBALException |
583
|
4 |
|
*/ |
584
|
|
|
public function createRelationshipAlias(string $name): string |
585
|
18 |
|
{ |
586
|
|
|
$relationshipType = $this->getModelSchemas()->getRelationshipType($this->getModelClass(), $name); |
587
|
13 |
|
switch ($relationshipType) { |
588
|
13 |
|
case RelationshipTypes::BELONGS_TO: |
589
|
13 |
|
list($targetColumn, $targetTable) = |
590
|
13 |
|
$this->getModelSchemas()->getReversePrimaryKey($this->getModelClass(), $name); |
591
|
13 |
|
$targetAlias = $this->innerJoinOneTable( |
592
|
13 |
|
$this->getAlias(), |
593
|
|
|
$this->getModelSchemas()->getForeignKey($this->getModelClass(), $name), |
594
|
13 |
|
$targetTable, |
595
|
|
|
$targetColumn |
596
|
10 |
|
); |
597
|
|
|
break; |
598
|
10 |
|
|
599
|
10 |
|
case RelationshipTypes::HAS_MANY: |
600
|
|
|
list($targetColumn, $targetTable) = |
601
|
10 |
|
$this->getModelSchemas()->getReverseForeignKey($this->getModelClass(), $name); |
602
|
|
|
$targetAlias = $this->innerJoinOneTable( |
603
|
10 |
|
$this->getAlias(), |
604
|
|
|
$this->getModelSchemas()->getPrimaryKey($this->getModelClass()), |
605
|
10 |
|
$targetTable, |
606
|
10 |
|
$targetColumn |
607
|
10 |
|
); |
608
|
10 |
|
break; |
609
|
10 |
|
|
610
|
10 |
|
case RelationshipTypes::BELONGS_TO_MANY: |
611
|
10 |
|
default: |
612
|
10 |
|
assert($relationshipType === RelationshipTypes::BELONGS_TO_MANY); |
613
|
|
|
$primaryKey = $this->getModelSchemas()->getPrimaryKey($this->getModelClass()); |
614
|
10 |
|
list ($intermediateTable, $intermediatePk, $intermediateFk) = |
615
|
|
|
$this->getModelSchemas()->getBelongsToManyRelationship($this->getModelClass(), $name); |
616
|
|
|
list($targetPrimaryKey, $targetTable) = |
617
|
21 |
|
$this->getModelSchemas()->getReversePrimaryKey($this->getModelClass(), $name); |
618
|
|
|
|
619
|
|
|
$targetAlias = $this->innerJoinTwoSequentialTables( |
620
|
|
|
$this->getAlias(), |
621
|
|
|
$primaryKey, |
622
|
|
|
$intermediateTable, |
623
|
58 |
|
$intermediatePk, |
624
|
|
|
$intermediateFk, |
625
|
58 |
|
$targetTable, |
626
|
|
|
$targetPrimaryKey |
627
|
|
|
); |
628
|
|
|
break; |
629
|
|
|
} |
630
|
|
|
|
631
|
|
|
return $targetAlias; |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
/** |
635
|
|
|
* @return string |
636
|
|
|
*/ |
637
|
|
|
public function getAlias(): string |
638
|
46 |
|
{ |
639
|
|
|
return $this->mainAlias; |
640
|
46 |
|
} |
641
|
46 |
|
|
642
|
46 |
|
/** |
643
|
46 |
|
* @param CompositeExpression $expression |
644
|
|
|
* @param string $tableOrAlias |
645
|
46 |
|
* @param iterable $filters |
646
|
46 |
|
* |
647
|
46 |
|
* @return self |
648
|
46 |
|
* |
649
|
46 |
|
* @throws DBALException |
650
|
46 |
|
* @throws InvalidArgumentException |
651
|
|
|
*/ |
652
|
46 |
|
public function applyFilters(CompositeExpression $expression, string $tableOrAlias, iterable $filters): self |
653
|
|
|
{ |
654
|
|
|
foreach ($filters as $columnName => $operationsWithArgs) { |
655
|
|
|
assert( |
656
|
45 |
|
is_string($columnName) === true && empty($columnName) === false, |
657
|
|
|
"Haven't you forgotten to specify a column name in a relationship that joins `$tableOrAlias` table?" |
658
|
|
|
); |
659
|
|
|
$fullColumnName = $this->quoteDoubleIdentifier($tableOrAlias, $columnName); |
660
|
|
|
foreach ($operationsWithArgs as $operation => $arguments) { |
661
|
|
|
assert( |
662
|
|
|
is_iterable($arguments) === true || is_array($arguments) === true, |
663
|
|
|
"Operation arguments are missing for `$columnName` column. " . |
664
|
|
|
'Use an empty array as an empty argument list.' |
665
|
|
|
); |
666
|
|
|
$expression->add($this->createFilterExpression($fullColumnName, $operation, $arguments)); |
667
|
8 |
|
} |
668
|
|
|
} |
669
|
8 |
|
|
670
|
8 |
|
return $this; |
671
|
8 |
|
} |
672
|
8 |
|
|
673
|
|
|
/** |
674
|
|
|
* @param string $tableOrAlias |
675
|
8 |
|
* @param iterable $sorts |
676
|
|
|
* |
677
|
|
|
* @return self |
678
|
|
|
* |
679
|
|
|
* @throws DBALException |
680
|
|
|
*/ |
681
|
|
|
public function applySorts(string $tableOrAlias, iterable $sorts): self |
682
|
|
|
{ |
683
|
|
|
foreach ($sorts as $columnName => $isAsc) { |
684
|
|
|
assert(is_string($columnName) === true && is_bool($isAsc) === true); |
685
|
65 |
|
$fullColumnName = $this->quoteDoubleIdentifier($tableOrAlias, $columnName); |
686
|
|
|
$this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC'); |
687
|
65 |
|
} |
688
|
|
|
|
689
|
|
|
return $this; |
690
|
|
|
} |
691
|
|
|
|
692
|
|
|
/** |
693
|
|
|
* @param string $tableOrColumn |
694
|
|
|
* |
695
|
|
|
* @return string |
696
|
|
|
* |
697
|
|
|
* @throws DBALException |
698
|
64 |
|
*/ |
699
|
|
|
public function quoteSingleIdentifier(string $tableOrColumn): string |
700
|
64 |
|
{ |
701
|
|
|
return $this->getConnection()->getDatabasePlatform()->quoteSingleIdentifier($tableOrColumn); |
702
|
64 |
|
} |
703
|
|
|
|
704
|
|
|
/** |
705
|
|
|
* @param string $tableOrAlias |
706
|
|
|
* @param string $column |
707
|
|
|
* |
708
|
|
|
* @return string |
709
|
|
|
* |
710
|
|
|
* @throws DBALException |
711
|
|
|
*/ |
712
|
58 |
|
public function quoteDoubleIdentifier(string $tableOrAlias, string $column): string |
713
|
|
|
{ |
714
|
58 |
|
$platform = $this->getConnection()->getDatabasePlatform(); |
715
|
|
|
|
716
|
58 |
|
return $platform->quoteSingleIdentifier($tableOrAlias) . '.' . $platform->quoteSingleIdentifier($column); |
717
|
|
|
} |
718
|
|
|
|
719
|
|
|
/** |
720
|
|
|
* @param $value |
721
|
|
|
* |
722
|
|
|
* @return string |
723
|
|
|
* |
724
|
|
|
* @throws DBALException |
725
|
|
|
*/ |
726
|
18 |
|
public function createSingleValueNamedParameter($value): string |
727
|
|
|
{ |
728
|
18 |
|
$paramName = $this->createNamedParameter($this->getPdoValue($value), $this->getPdoType($value)); |
729
|
|
|
|
730
|
18 |
|
return $paramName; |
731
|
18 |
|
} |
732
|
|
|
|
733
|
|
|
/** |
734
|
18 |
|
* @param iterable $values |
735
|
|
|
* |
736
|
|
|
* @return array |
737
|
|
|
* |
738
|
|
|
* @throws DBALException |
739
|
|
|
*/ |
740
|
|
|
public function createArrayValuesNamedParameter(iterable $values): array |
741
|
|
|
{ |
742
|
66 |
|
$names = []; |
743
|
|
|
|
744
|
66 |
|
foreach ($values as $value) { |
745
|
66 |
|
$names[] = $this->createSingleValueNamedParameter($value); |
746
|
|
|
} |
747
|
66 |
|
|
748
|
|
|
return $names; |
749
|
|
|
} |
750
|
|
|
|
751
|
|
|
/** |
752
|
|
|
* @param string $tableName |
753
|
|
|
* |
754
|
|
|
* @return string |
755
|
|
|
*/ |
756
|
|
|
public function createAlias(string $tableName): string |
757
|
|
|
{ |
758
|
|
|
$alias = $tableName . (++$this->aliasIdCounter); |
759
|
|
|
$this->knownAliases[$tableName] = $alias; |
760
|
21 |
|
|
761
|
|
|
return $alias; |
762
|
|
|
} |
763
|
|
|
|
764
|
|
|
/** |
765
|
|
|
* @param string $fromAlias |
766
|
21 |
|
* @param string $fromColumn |
767
|
21 |
|
* @param string $targetTable |
768
|
21 |
|
* @param string $targetColumn |
769
|
|
|
* |
770
|
21 |
|
* @return string |
771
|
21 |
|
* |
772
|
21 |
|
* @throws DBALException |
773
|
21 |
|
*/ |
774
|
21 |
|
public function innerJoinOneTable( |
775
|
|
|
string $fromAlias, |
776
|
|
|
string $fromColumn, |
777
|
21 |
|
string $targetTable, |
778
|
|
|
string $targetColumn |
779
|
|
|
): string { |
780
|
|
|
$targetAlias = $this->createAlias($targetTable); |
781
|
|
|
$joinCondition = $this->quoteDoubleIdentifier($fromAlias, $fromColumn) . '=' . |
782
|
|
|
$this->quoteDoubleIdentifier($targetAlias, $targetColumn); |
783
|
|
|
|
784
|
|
|
$this->innerJoin( |
785
|
|
|
$this->quoteSingleIdentifier($fromAlias), |
786
|
|
|
$this->quoteSingleIdentifier($targetTable), |
787
|
|
|
$this->quoteSingleIdentifier($targetAlias), |
788
|
|
|
$joinCondition |
789
|
|
|
); |
790
|
|
|
|
791
|
|
|
return $targetAlias; |
792
|
|
|
} |
793
|
10 |
|
|
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
|
10 |
|
* |
803
|
10 |
|
* @return string |
804
|
|
|
* |
805
|
10 |
|
* @throws DBALException |
806
|
|
|
*/ |
807
|
|
|
public function innerJoinTwoSequentialTables( |
808
|
|
|
string $fromAlias, |
809
|
|
|
string $fromColumn, |
810
|
|
|
string $intTable, |
811
|
|
|
string $intToFromColumn, |
812
|
|
|
string $intToTargetColumn, |
813
|
|
|
string $targetTable, |
814
|
|
|
string $targetColumn |
815
|
|
|
): string { |
816
|
|
|
$intAlias = $this->innerJoinOneTable($fromAlias, $fromColumn, $intTable, $intToFromColumn); |
817
|
11 |
|
$targetAlias = $this->innerJoinOneTable($intAlias, $intToTargetColumn, $targetTable, $targetColumn); |
818
|
|
|
|
819
|
11 |
|
return $targetAlias; |
820
|
11 |
|
} |
821
|
|
|
|
822
|
11 |
|
/** |
823
|
|
|
* @param string $name |
824
|
|
|
* |
825
|
|
|
* @return Type |
826
|
|
|
* |
827
|
|
|
* @throws DBALException |
828
|
66 |
|
* |
829
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
830
|
66 |
|
*/ |
831
|
|
|
protected function getDbalType(string $name): Type |
832
|
|
|
{ |
833
|
|
|
assert(Type::hasType($name), "Type `$name` either do not exist or registered."); |
834
|
|
|
$type = Type::getType($name); |
835
|
|
|
|
836
|
66 |
|
return $type; |
837
|
|
|
} |
838
|
66 |
|
|
839
|
|
|
/** |
840
|
|
|
* @return string |
841
|
|
|
*/ |
842
|
|
|
private function getTableName(): string |
843
|
|
|
{ |
844
|
|
|
return $this->mainTableName; |
845
|
|
|
} |
846
|
|
|
|
847
|
|
|
/** |
848
|
|
|
* @return ModelSchemaInfoInterface |
849
|
|
|
*/ |
850
|
|
|
private function getModelSchemas(): ModelSchemaInfoInterface |
851
|
|
|
{ |
852
|
|
|
return $this->modelSchemas; |
853
|
|
|
} |
854
|
59 |
|
|
855
|
|
|
/** |
856
|
|
|
* @param string $fullColumnName |
857
|
59 |
|
* @param int $operation |
858
|
48 |
|
* @param iterable $arguments |
859
|
47 |
|
* |
860
|
47 |
|
* @return string |
861
|
30 |
|
* |
862
|
1 |
|
* @throws DBALException |
863
|
1 |
|
* @throws InvalidArgumentException |
864
|
1 |
|
* |
865
|
30 |
|
* @SuppressWarnings(PHPMD.StaticAccess) |
866
|
6 |
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) |
867
|
6 |
|
*/ |
868
|
6 |
|
private function createFilterExpression(string $fullColumnName, int $operation, iterable $arguments): string |
869
|
30 |
|
{ |
870
|
7 |
|
switch ($operation) { |
871
|
7 |
|
case FilterParameterInterface::OPERATION_EQUALS: |
872
|
7 |
|
$parameter = $this->createSingleValueNamedParameter($this->firstValue($arguments)); |
873
|
29 |
|
$expression = $this->expr()->eq($fullColumnName, $parameter); |
874
|
2 |
|
break; |
875
|
2 |
|
case FilterParameterInterface::OPERATION_NOT_EQUALS: |
876
|
2 |
|
$parameter = $this->createSingleValueNamedParameter($this->firstValue($arguments)); |
877
|
28 |
|
$expression = $this->expr()->neq($fullColumnName, $parameter); |
878
|
6 |
|
break; |
879
|
6 |
|
case FilterParameterInterface::OPERATION_LESS_THAN: |
880
|
6 |
|
$parameter = $this->createSingleValueNamedParameter($this->firstValue($arguments)); |
881
|
23 |
|
$expression = $this->expr()->lt($fullColumnName, $parameter); |
882
|
9 |
|
break; |
883
|
9 |
|
case FilterParameterInterface::OPERATION_LESS_OR_EQUALS: |
884
|
9 |
|
$parameter = $this->createSingleValueNamedParameter($this->firstValue($arguments)); |
885
|
18 |
|
$expression = $this->expr()->lte($fullColumnName, $parameter); |
886
|
1 |
|
break; |
887
|
1 |
|
case FilterParameterInterface::OPERATION_GREATER_THAN: |
888
|
1 |
|
$parameter = $this->createSingleValueNamedParameter($this->firstValue($arguments)); |
889
|
18 |
|
$expression = $this->expr()->gt($fullColumnName, $parameter); |
890
|
18 |
|
break; |
891
|
18 |
|
case FilterParameterInterface::OPERATION_GREATER_OR_EQUALS: |
892
|
18 |
|
$parameter = $this->createSingleValueNamedParameter($this->firstValue($arguments)); |
893
|
1 |
|
$expression = $this->expr()->gte($fullColumnName, $parameter); |
894
|
1 |
|
break; |
895
|
1 |
|
case FilterParameterInterface::OPERATION_LIKE: |
896
|
1 |
|
$parameter = $this->createSingleValueNamedParameter($this->firstValue($arguments)); |
897
|
1 |
|
$expression = $this->expr()->like($fullColumnName, $parameter); |
898
|
1 |
|
break; |
899
|
1 |
|
case FilterParameterInterface::OPERATION_NOT_LIKE: |
900
|
1 |
|
$parameter = $this->createSingleValueNamedParameter($this->firstValue($arguments)); |
901
|
|
|
$expression = $this->expr()->notLike($fullColumnName, $parameter); |
902
|
1 |
|
break; |
903
|
1 |
|
case FilterParameterInterface::OPERATION_IN: |
904
|
1 |
|
$parameters = $this->createArrayValuesNamedParameter($arguments); |
905
|
|
|
$expression = $this->expr()->in($fullColumnName, $parameters); |
906
|
|
|
break; |
907
|
58 |
|
case FilterParameterInterface::OPERATION_NOT_IN: |
908
|
|
|
$parameters = $this->createArrayValuesNamedParameter($arguments); |
909
|
|
|
$expression = $this->expr()->notIn($fullColumnName, $parameters); |
910
|
|
|
break; |
911
|
|
|
case FilterParameterInterface::OPERATION_IS_NULL: |
912
|
|
|
$expression = $this->expr()->isNull($fullColumnName); |
913
|
|
|
break; |
914
|
|
|
case FilterParameterInterface::OPERATION_IS_NOT_NULL: |
915
|
|
|
default: |
916
|
|
|
assert($operation === FilterParameterInterface::OPERATION_IS_NOT_NULL); |
917
|
55 |
|
$expression = $this->expr()->isNotNull($fullColumnName); |
918
|
|
|
break; |
919
|
55 |
|
} |
920
|
54 |
|
|
921
|
|
|
return $expression; |
922
|
|
|
} |
923
|
|
|
|
924
|
1 |
|
/** |
925
|
|
|
* @param iterable $arguments |
926
|
|
|
* |
927
|
|
|
* @return mixed |
928
|
|
|
* |
929
|
|
|
* @throws InvalidArgumentException |
930
|
52 |
|
*/ |
931
|
|
|
private function firstValue(iterable $arguments) |
932
|
52 |
|
{ |
933
|
|
|
foreach ($arguments as $argument) { |
934
|
|
|
return $argument; |
935
|
|
|
} |
936
|
|
|
|
937
|
|
|
// arguments are empty |
938
|
|
|
throw new InvalidArgumentException(); |
939
|
|
|
} |
940
|
|
|
|
941
|
|
|
/** |
942
|
58 |
|
* @return Closure |
943
|
|
|
*/ |
944
|
58 |
|
private function getColumnToDatabaseMapper(): Closure |
945
|
|
|
{ |
946
|
|
|
return $this->columnMapper; |
947
|
|
|
} |
948
|
|
|
|
949
|
|
|
/** |
950
|
|
|
* @param mixed $value |
951
|
|
|
* |
952
|
|
|
* @return mixed |
953
|
|
|
* |
954
|
1 |
|
* @throws DBALException |
955
|
|
|
*/ |
956
|
1 |
|
private function getPdoValue($value) |
957
|
1 |
|
{ |
958
|
1 |
|
return $value instanceof DateTimeInterface ? $this->convertDataTimeToDatabaseFormat($value) : $value; |
959
|
|
|
} |
960
|
|
|
|
961
|
|
|
/** |
962
|
|
|
* @param DateTimeInterface $dateTime |
963
|
|
|
* |
964
|
|
|
* @return string |
965
|
|
|
* |
966
|
|
|
* @throws DBALException |
967
|
|
|
*/ |
968
|
|
|
private function convertDataTimeToDatabaseFormat(DateTimeInterface $dateTime): string |
969
|
58 |
|
{ |
970
|
|
|
return $this->getDateTimeType()->convertToDatabaseValue( |
971
|
58 |
|
$dateTime, |
972
|
37 |
|
$this->getConnection()->getDatabasePlatform() |
973
|
38 |
|
); |
974
|
1 |
|
} |
975
|
37 |
|
|
976
|
1 |
|
/** |
977
|
|
|
* @param mixed $value |
978
|
36 |
|
* |
979
|
36 |
|
* @return int |
980
|
|
|
* |
981
|
36 |
|
* @SuppressWarnings(PHPMD.ElseExpression) |
982
|
|
|
*/ |
983
|
36 |
|
private function getPdoType($value): int |
984
|
36 |
|
{ |
985
|
|
|
if (is_int($value) === true) { |
986
|
|
|
$type = PDO::PARAM_INT; |
987
|
58 |
|
} elseif (is_bool($value)) { |
988
|
|
|
$type = PDO::PARAM_BOOL; |
989
|
|
|
} elseif ($value instanceof DateTimeInterface) { |
990
|
|
|
$type = PDO::PARAM_STR; |
991
|
|
|
} else { |
992
|
|
|
assert( |
993
|
|
|
$value !== null, |
994
|
|
|
'It seems you are trying to use `null` with =, >, <, or etc operator. ' . |
995
|
|
|
'Use `is null` or `not null` instead.' |
996
|
|
|
); |
997
|
1 |
|
assert(is_string($value), "Only strings, booleans and integers are supported."); |
998
|
|
|
$type = PDO::PARAM_STR; |
999
|
1 |
|
} |
1000
|
1 |
|
|
1001
|
|
|
return $type; |
1002
|
|
|
} |
1003
|
1 |
|
|
1004
|
|
|
/** |
1005
|
|
|
* @return Type |
1006
|
|
|
* |
1007
|
|
|
* @throws DBALException |
1008
|
|
|
* |
1009
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
1010
|
|
|
*/ |
1011
|
|
|
private function getDateTimeType(): Type |
1012
|
|
|
{ |
1013
|
|
|
if ($this->dateTimeType === null) { |
1014
|
|
|
$this->dateTimeType = Type::getType(DateTimeType::DATETIME); |
1015
|
|
|
} |
1016
|
|
|
|
1017
|
|
|
return $this->dateTimeType; |
1018
|
|
|
} |
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.