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