1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Doctrine\ORM\Persisters\Collection; |
6
|
|
|
|
7
|
|
|
use Doctrine\Common\Collections\Criteria; |
8
|
|
|
use Doctrine\ORM\Mapping\ClassMetadata; |
9
|
|
|
use Doctrine\ORM\Mapping\FieldMetadata; |
10
|
|
|
use Doctrine\ORM\Mapping\JoinColumnMetadata; |
11
|
|
|
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata; |
12
|
|
|
use Doctrine\ORM\Mapping\ToManyAssociationMetadata; |
13
|
|
|
use Doctrine\ORM\Mapping\ToOneAssociationMetadata; |
14
|
|
|
use Doctrine\ORM\PersistentCollection; |
15
|
|
|
use Doctrine\ORM\Persisters\SqlValueVisitor; |
16
|
|
|
use Doctrine\ORM\Query; |
17
|
|
|
use Doctrine\ORM\Utility\PersisterHelper; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Persister for many-to-many collections. |
21
|
|
|
*/ |
22
|
|
|
class ManyToManyPersister extends AbstractCollectionPersister |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* {@inheritdoc} |
26
|
|
|
*/ |
27
|
17 |
|
public function delete(PersistentCollection $collection) |
28
|
|
|
{ |
29
|
17 |
|
$association = $collection->getMapping(); |
30
|
|
|
|
31
|
17 |
|
if (! $association->isOwningSide()) { |
32
|
|
|
return; // ignore inverse side |
33
|
|
|
} |
34
|
|
|
|
35
|
17 |
|
$class = $this->em->getClassMetadata($association->getSourceEntity()); |
36
|
17 |
|
$joinTable = $association->getJoinTable(); |
37
|
17 |
|
$types = []; |
38
|
|
|
|
39
|
17 |
|
foreach ($joinTable->getJoinColumns() as $joinColumn) { |
40
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
41
|
17 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
42
|
|
|
|
43
|
17 |
|
if (! $joinColumn->getType()) { |
44
|
|
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em)); |
45
|
|
|
} |
46
|
|
|
|
47
|
17 |
|
$types[] = $joinColumn->getType(); |
48
|
|
|
} |
49
|
|
|
|
50
|
17 |
|
$sql = $this->getDeleteSQL($collection); |
51
|
17 |
|
$params = $this->getDeleteSQLParameters($collection); |
52
|
|
|
|
53
|
17 |
|
$this->conn->executeUpdate($sql, $params, $types); |
54
|
17 |
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* {@inheritdoc} |
58
|
|
|
*/ |
59
|
326 |
|
public function update(PersistentCollection $collection) |
60
|
|
|
{ |
61
|
326 |
|
$association = $collection->getMapping(); |
62
|
|
|
|
63
|
326 |
|
if (! $association->isOwningSide()) { |
64
|
235 |
|
return; // ignore inverse side |
65
|
|
|
} |
66
|
|
|
|
67
|
325 |
|
list($deleteSql, $deleteTypes) = $this->getDeleteRowSQL($collection); |
68
|
325 |
|
list($insertSql, $insertTypes) = $this->getInsertRowSQL($collection); |
69
|
|
|
|
70
|
325 |
|
foreach ($collection->getDeleteDiff() as $element) { |
71
|
10 |
|
$this->conn->executeUpdate( |
72
|
10 |
|
$deleteSql, |
73
|
10 |
|
$this->getDeleteRowSQLParameters($collection, $element), |
74
|
10 |
|
$deleteTypes |
75
|
|
|
); |
76
|
|
|
} |
77
|
|
|
|
78
|
325 |
|
foreach ($collection->getInsertDiff() as $element) { |
79
|
325 |
|
$this->conn->executeUpdate( |
80
|
325 |
|
$insertSql, |
81
|
325 |
|
$this->getInsertRowSQLParameters($collection, $element), |
82
|
325 |
|
$insertTypes |
83
|
|
|
); |
84
|
|
|
} |
85
|
325 |
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* {@inheritdoc} |
89
|
|
|
*/ |
90
|
3 |
|
public function get(PersistentCollection $collection, $index) |
91
|
|
|
{ |
92
|
3 |
|
$association = $collection->getMapping(); |
93
|
|
|
|
94
|
3 |
|
if (! ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy())) { |
95
|
|
|
throw new \BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); |
96
|
|
|
} |
97
|
|
|
|
98
|
3 |
|
$persister = $this->uow->getEntityPersister($association->getTargetEntity()); |
99
|
3 |
|
$mappedKey = $association->isOwningSide() |
100
|
2 |
|
? $association->getInversedBy() |
101
|
3 |
|
: $association->getMappedBy() |
102
|
|
|
; |
103
|
|
|
|
104
|
|
|
$criteria = [ |
105
|
3 |
|
$mappedKey => $collection->getOwner(), |
106
|
3 |
|
$association->getIndexedBy() => $index, |
107
|
|
|
]; |
108
|
|
|
|
109
|
3 |
|
return $persister->load($criteria, null, $association, [], 0, 1); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* {@inheritdoc} |
114
|
|
|
*/ |
115
|
17 |
|
public function count(PersistentCollection $collection) |
116
|
|
|
{ |
117
|
17 |
|
$conditions = []; |
118
|
17 |
|
$params = []; |
119
|
17 |
|
$types = []; |
120
|
17 |
|
$association = $collection->getMapping(); |
121
|
17 |
|
$identifier = $this->uow->getEntityIdentifier($collection->getOwner()); |
122
|
17 |
|
$sourceClass = $this->em->getClassMetadata($association->getSourceEntity()); |
123
|
17 |
|
$targetClass = $this->em->getClassMetadata($association->getTargetEntity()); |
124
|
17 |
|
$owningAssociation = ! $association->isOwningSide() |
125
|
4 |
|
? $targetClass->getProperty($association->getMappedBy()) |
126
|
17 |
|
: $association |
127
|
|
|
; |
128
|
|
|
|
129
|
17 |
|
$joinTable = $owningAssociation->getJoinTable(); |
130
|
17 |
|
$joinTableName = $joinTable->getQuotedQualifiedName($this->platform); |
131
|
17 |
|
$joinColumns = $association->isOwningSide() |
132
|
13 |
|
? $joinTable->getJoinColumns() |
133
|
17 |
|
: $joinTable->getInverseJoinColumns() |
134
|
|
|
; |
135
|
|
|
|
136
|
17 |
|
foreach ($joinColumns as $joinColumn) { |
137
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
138
|
17 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
139
|
17 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
140
|
|
|
|
141
|
17 |
|
if (! $joinColumn->getType()) { |
142
|
1 |
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em)); |
143
|
|
|
} |
144
|
|
|
|
145
|
17 |
|
$conditions[] = sprintf('t.%s = ?', $quotedColumnName); |
146
|
17 |
|
$params[] = $identifier[$sourceClass->fieldNames[$referencedColumnName]]; |
147
|
17 |
|
$types[] = $joinColumn->getType(); |
148
|
|
|
} |
149
|
|
|
|
150
|
17 |
|
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($association); |
151
|
|
|
|
152
|
17 |
|
if ($filterSql) { |
153
|
3 |
|
$conditions[] = $filterSql; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
// If there is a provided criteria, make part of conditions |
157
|
|
|
// @todo Fix this. Current SQL returns something like: |
158
|
|
|
// |
159
|
|
|
/*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) { |
|
|
|
|
160
|
|
|
// A join is needed on the target entity |
161
|
|
|
$targetTableName = $targetClass->table->getQuotedQualifiedName($this->platform); |
162
|
|
|
$targetJoinSql = ' JOIN ' . $targetTableName . ' te' |
163
|
|
|
. ' ON' . implode(' AND ', $this->getOnConditionSQL($association)); |
164
|
|
|
|
165
|
|
|
// And criteria conditions needs to be added |
166
|
|
|
$persister = $this->uow->getEntityPersister($targetClass->getClassName()); |
167
|
|
|
$visitor = new SqlExpressionVisitor($persister, $targetClass); |
168
|
|
|
$conditions[] = $visitor->dispatch($expression); |
169
|
|
|
|
170
|
|
|
$joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL; |
171
|
|
|
}*/ |
172
|
|
|
|
173
|
|
|
$sql = 'SELECT COUNT(*)' |
174
|
17 |
|
. ' FROM ' . $joinTableName . ' t' |
175
|
17 |
|
. $joinTargetEntitySQL |
176
|
17 |
|
. ' WHERE ' . implode(' AND ', $conditions); |
177
|
|
|
|
178
|
17 |
|
return $this->conn->fetchColumn($sql, $params, 0, $types); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* {@inheritDoc} |
183
|
|
|
*/ |
184
|
8 |
|
public function slice(PersistentCollection $collection, $offset, $length = null) |
185
|
|
|
{ |
186
|
8 |
|
$association = $collection->getMapping(); |
187
|
8 |
|
$persister = $this->uow->getEntityPersister($association->getTargetEntity()); |
188
|
|
|
|
189
|
8 |
|
return $persister->getManyToManyCollection($association, $collection->getOwner(), $offset, $length); |
190
|
|
|
} |
191
|
|
|
/** |
192
|
|
|
* {@inheritdoc} |
193
|
|
|
*/ |
194
|
7 |
|
public function containsKey(PersistentCollection $collection, $key) |
195
|
|
|
{ |
196
|
7 |
|
$association = $collection->getMapping(); |
197
|
|
|
|
198
|
7 |
|
if (! ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy())) { |
199
|
|
|
throw new \BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); |
200
|
|
|
} |
201
|
|
|
|
202
|
7 |
|
list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictionsWithKey($collection, $key, true); |
203
|
|
|
|
204
|
7 |
|
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); |
205
|
|
|
|
206
|
7 |
|
return (bool) $this->conn->fetchColumn($sql, $params, 0, $types); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* {@inheritDoc} |
211
|
|
|
*/ |
212
|
7 |
|
public function contains(PersistentCollection $collection, $element) |
213
|
|
|
{ |
214
|
7 |
|
if (! $this->isValidEntityState($element)) { |
215
|
2 |
|
return false; |
216
|
|
|
} |
217
|
|
|
|
218
|
7 |
|
list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, true); |
219
|
|
|
|
220
|
7 |
|
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); |
221
|
|
|
|
222
|
7 |
|
return (bool) $this->conn->fetchColumn($sql, $params, 0, $types); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* {@inheritDoc} |
227
|
|
|
*/ |
228
|
2 |
|
public function removeElement(PersistentCollection $collection, $element) |
229
|
|
|
{ |
230
|
2 |
|
if (! $this->isValidEntityState($element)) { |
231
|
2 |
|
return false; |
232
|
|
|
} |
233
|
|
|
|
234
|
2 |
|
list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, false); |
235
|
|
|
|
236
|
2 |
|
$sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); |
237
|
|
|
|
238
|
2 |
|
return (bool) $this->conn->executeUpdate($sql, $params, $types); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* {@inheritDoc} |
243
|
|
|
*/ |
244
|
12 |
|
public function loadCriteria(PersistentCollection $collection, Criteria $criteria) |
245
|
|
|
{ |
246
|
12 |
|
$association = $collection->getMapping(); |
247
|
12 |
|
$owner = $collection->getOwner(); |
248
|
12 |
|
$ownerMetadata = $this->em->getClassMetadata(get_class($owner)); |
249
|
12 |
|
$identifier = $this->uow->getEntityIdentifier($owner); |
250
|
12 |
|
$targetClass = $this->em->getClassMetadata($association->getTargetEntity()); |
251
|
12 |
|
$onConditions = $this->getOnConditionSQL($association); |
252
|
12 |
|
$whereClauses = $params = $types = []; |
253
|
|
|
|
254
|
12 |
|
if (! $association->isOwningSide()) { |
255
|
1 |
|
$association = $targetClass->getProperty($association->getMappedBy()); |
256
|
1 |
|
$joinColumns = $association->getJoinTable()->getInverseJoinColumns(); |
257
|
|
|
} else { |
258
|
11 |
|
$joinColumns = $association->getJoinTable()->getJoinColumns(); |
259
|
|
|
} |
260
|
|
|
|
261
|
12 |
|
foreach ($joinColumns as $joinColumn) { |
262
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
263
|
12 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
264
|
12 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
265
|
|
|
|
266
|
12 |
|
if (! $joinColumn->getType()) { |
267
|
|
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $ownerMetadata, $this->em)); |
268
|
|
|
} |
269
|
|
|
|
270
|
12 |
|
$whereClauses[] = sprintf('t.%s = ?', $quotedColumnName); |
271
|
12 |
|
$params[] = $identifier[$ownerMetadata->fieldNames[$referencedColumnName]]; |
272
|
12 |
|
$types[] = $joinColumn->getType(); |
273
|
|
|
} |
274
|
|
|
|
275
|
12 |
|
$parameters = $this->expandCriteriaParameters($criteria); |
276
|
|
|
|
277
|
12 |
|
foreach ($parameters as $parameter) { |
278
|
7 |
|
[$name, $value, $operator] = $parameter; |
279
|
|
|
|
280
|
7 |
|
$property = $targetClass->getProperty($name); |
281
|
7 |
|
$columnName = $this->platform->quoteIdentifier($property->getColumnName()); |
282
|
|
|
|
283
|
7 |
|
$whereClauses[] = sprintf('te.%s %s ?', $columnName, $operator); |
284
|
7 |
|
$params[] = $value; |
285
|
7 |
|
$types[] = $property->getType(); |
286
|
|
|
} |
287
|
|
|
|
288
|
12 |
|
$tableName = $targetClass->table->getQuotedQualifiedName($this->platform); |
289
|
12 |
|
$joinTableName = $association->getJoinTable()->getQuotedQualifiedName($this->platform); |
290
|
12 |
|
$resultSetMapping = new Query\ResultSetMappingBuilder($this->em); |
291
|
|
|
|
292
|
12 |
|
$resultSetMapping->addRootEntityFromClassMetadata($targetClass->getClassName(), 'te'); |
293
|
|
|
|
294
|
12 |
|
$sql = 'SELECT ' . $resultSetMapping->generateSelectClause() |
295
|
12 |
|
. ' FROM ' . $tableName . ' te' |
296
|
12 |
|
. ' JOIN ' . $joinTableName . ' t ON' |
297
|
12 |
|
. implode(' AND ', $onConditions) |
298
|
12 |
|
. ' WHERE ' . implode(' AND ', $whereClauses); |
299
|
|
|
|
300
|
12 |
|
$sql .= $this->getOrderingSql($criteria, $targetClass); |
301
|
12 |
|
$sql .= $this->getLimitSql($criteria); |
302
|
|
|
|
303
|
12 |
|
$stmt = $this->conn->executeQuery($sql, $params, $types); |
304
|
|
|
|
305
|
12 |
|
return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $resultSetMapping); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Generates the filter SQL for a given mapping. |
310
|
|
|
* |
311
|
|
|
* This method is not used for actually grabbing the related entities |
312
|
|
|
* but when the extra-lazy collection methods are called on a filtered |
313
|
|
|
* association. This is why besides the many to many table we also |
314
|
|
|
* have to join in the actual entities table leading to additional |
315
|
|
|
* JOIN. |
316
|
|
|
* |
317
|
|
|
* @return string[] ordered tuple: |
318
|
|
|
* - JOIN condition to add to the SQL |
319
|
|
|
* - WHERE condition to add to the SQL |
320
|
|
|
*/ |
321
|
31 |
|
public function getFilterSql(ManyToManyAssociationMetadata $association) |
322
|
|
|
{ |
323
|
31 |
|
$targetClass = $this->em->getClassMetadata($association->getTargetEntity()); |
324
|
31 |
|
$rootClass = $this->em->getClassMetadata($targetClass->getRootClassName()); |
325
|
31 |
|
$filterSql = $this->generateFilterConditionSQL($rootClass, 'te'); |
326
|
|
|
|
327
|
31 |
|
if ($filterSql === '') { |
328
|
31 |
|
return ['', '']; |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
// A join is needed if there is filtering on the target entity |
332
|
6 |
|
$tableName = $rootClass->table->getQuotedQualifiedName($this->platform); |
333
|
6 |
|
$joinSql = ' JOIN ' . $tableName . ' te' |
334
|
6 |
|
. ' ON' . implode(' AND ', $this->getOnConditionSQL($association)); |
335
|
|
|
|
336
|
6 |
|
return [$joinSql, $filterSql]; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Generates the filter SQL for a given entity and table alias. |
341
|
|
|
* |
342
|
|
|
* @param ClassMetadata $targetEntity Metadata of the target entity. |
343
|
|
|
* @param string $targetTableAlias The table alias of the joined/selected table. |
344
|
|
|
* |
345
|
|
|
* @return string The SQL query part to add to a query. |
346
|
|
|
*/ |
347
|
31 |
|
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) |
348
|
|
|
{ |
349
|
31 |
|
$filterClauses = []; |
350
|
|
|
|
351
|
31 |
|
foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { |
352
|
6 |
|
$filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias); |
353
|
|
|
|
354
|
6 |
|
if ($filterExpr) { |
355
|
6 |
|
$filterClauses[] = '(' . $filterExpr . ')'; |
356
|
|
|
} |
357
|
|
|
} |
358
|
|
|
|
359
|
31 |
|
if (! $filterClauses) { |
360
|
31 |
|
return ''; |
361
|
|
|
} |
362
|
|
|
|
363
|
6 |
|
$filterSql = implode(' AND ', $filterClauses); |
364
|
|
|
|
365
|
6 |
|
return isset($filterClauses[1]) |
366
|
|
|
? '(' . $filterSql . ')' |
367
|
6 |
|
: $filterSql |
368
|
|
|
; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Generate ON condition |
373
|
|
|
* |
374
|
|
|
* @return string[] |
375
|
|
|
*/ |
376
|
18 |
|
protected function getOnConditionSQL(ManyToManyAssociationMetadata $association) |
377
|
|
|
{ |
378
|
18 |
|
$targetClass = $this->em->getClassMetadata($association->getTargetEntity()); |
379
|
18 |
|
$owningAssociation = ! $association->isOwningSide() |
380
|
3 |
|
? $targetClass->getProperty($association->getMappedBy()) |
381
|
18 |
|
: $association; |
382
|
|
|
|
383
|
18 |
|
$joinTable = $owningAssociation->getJoinTable(); |
384
|
18 |
|
$joinColumns = $association->isOwningSide() |
385
|
15 |
|
? $joinTable->getInverseJoinColumns() |
386
|
18 |
|
: $joinTable->getJoinColumns() |
387
|
|
|
; |
388
|
|
|
|
389
|
18 |
|
$conditions = []; |
390
|
|
|
|
391
|
18 |
|
foreach ($joinColumns as $joinColumn) { |
392
|
18 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
393
|
18 |
|
$quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName()); |
394
|
|
|
|
395
|
18 |
|
$conditions[] = ' t.' . $quotedColumnName . ' = te.' . $quotedReferencedColumnName; |
396
|
|
|
} |
397
|
|
|
|
398
|
18 |
|
return $conditions; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* {@inheritdoc} |
403
|
|
|
* |
404
|
|
|
* @override |
405
|
|
|
*/ |
406
|
17 |
|
protected function getDeleteSQL(PersistentCollection $collection) |
407
|
|
|
{ |
408
|
17 |
|
$association = $collection->getMapping(); |
409
|
17 |
|
$joinTable = $association->getJoinTable(); |
410
|
17 |
|
$joinTableName = $joinTable->getQuotedQualifiedName($this->platform); |
411
|
17 |
|
$columns = []; |
412
|
|
|
|
413
|
17 |
|
foreach ($joinTable->getJoinColumns() as $joinColumn) { |
414
|
17 |
|
$columns[] = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
415
|
|
|
} |
416
|
|
|
|
417
|
17 |
|
return 'DELETE FROM ' . $joinTableName . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* {@inheritdoc} |
422
|
|
|
* |
423
|
|
|
* Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql. |
424
|
|
|
* @override |
425
|
|
|
*/ |
426
|
17 |
|
protected function getDeleteSQLParameters(PersistentCollection $collection) |
427
|
|
|
{ |
428
|
17 |
|
$association = $collection->getMapping(); |
429
|
17 |
|
$identifier = $this->uow->getEntityIdentifier($collection->getOwner()); |
430
|
17 |
|
$joinTable = $association->getJoinTable(); |
431
|
17 |
|
$joinColumns = $joinTable->getJoinColumns(); |
432
|
|
|
|
433
|
|
|
// Optimization for single column identifier |
434
|
17 |
|
if (count($joinColumns) === 1) { |
435
|
15 |
|
return [reset($identifier)]; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
// Composite identifier |
439
|
2 |
|
$sourceClass = $this->em->getClassMetadata($association->getSourceEntity()); |
440
|
2 |
|
$params = []; |
441
|
|
|
|
442
|
2 |
|
foreach ($joinColumns as $joinColumn) { |
443
|
2 |
|
$params[] = $identifier[$sourceClass->fieldNames[$joinColumn->getReferencedColumnName()]]; |
444
|
|
|
} |
445
|
|
|
|
446
|
2 |
|
return $params; |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Gets the SQL statement used for deleting a row from the collection. |
451
|
|
|
* |
452
|
|
|
* @return string[]|string[][] ordered tuple containing the SQL to be executed and an array |
453
|
|
|
* of types for bound parameters |
454
|
|
|
*/ |
455
|
325 |
|
protected function getDeleteRowSQL(PersistentCollection $collection) |
456
|
|
|
{ |
457
|
325 |
|
$association = $collection->getMapping(); |
458
|
325 |
|
$class = $this->em->getClassMetadata($association->getSourceEntity()); |
459
|
325 |
|
$targetClass = $this->em->getClassMetadata($association->getTargetEntity()); |
460
|
325 |
|
$columns = []; |
461
|
325 |
|
$types = []; |
462
|
|
|
|
463
|
325 |
|
$joinTable = $association->getJoinTable(); |
464
|
325 |
|
$joinTableName = $joinTable->getQuotedQualifiedName($this->platform); |
465
|
|
|
|
466
|
325 |
|
foreach ($joinTable->getJoinColumns() as $joinColumn) { |
467
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
468
|
325 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
469
|
325 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
470
|
|
|
|
471
|
325 |
|
if (! $joinColumn->getType()) { |
472
|
34 |
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em)); |
473
|
|
|
} |
474
|
|
|
|
475
|
325 |
|
$columns[] = $quotedColumnName; |
476
|
325 |
|
$types[] = $joinColumn->getType(); |
477
|
|
|
} |
478
|
|
|
|
479
|
325 |
|
foreach ($joinTable->getInverseJoinColumns() as $joinColumn) { |
480
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
481
|
325 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
482
|
325 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
483
|
|
|
|
484
|
325 |
|
if (! $joinColumn->getType()) { |
485
|
34 |
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em)); |
486
|
|
|
} |
487
|
|
|
|
488
|
325 |
|
$columns[] = $quotedColumnName; |
489
|
325 |
|
$types[] = $joinColumn->getType(); |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
return [ |
493
|
325 |
|
sprintf('DELETE FROM %s WHERE %s = ?', $joinTableName, implode(' = ? AND ', $columns)), |
494
|
325 |
|
$types, |
495
|
|
|
]; |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
/** |
499
|
|
|
* Gets the SQL parameters for the corresponding SQL statement to delete the given |
500
|
|
|
* element from the given collection. |
501
|
|
|
* |
502
|
|
|
* Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql. |
503
|
|
|
* |
504
|
|
|
* @param mixed $element |
505
|
|
|
* |
506
|
|
|
* @return mixed[] |
507
|
|
|
*/ |
508
|
10 |
|
protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element) |
509
|
|
|
{ |
510
|
10 |
|
return $this->collectJoinTableColumnParameters($collection, $element); |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* Gets the SQL statement used for inserting a row in the collection. |
515
|
|
|
* |
516
|
|
|
* @return string[]|string[][] ordered tuple containing the SQL to be executed and an array |
517
|
|
|
* of types for bound parameters |
518
|
|
|
*/ |
519
|
325 |
|
protected function getInsertRowSQL(PersistentCollection $collection) |
520
|
|
|
{ |
521
|
325 |
|
$association = $collection->getMapping(); |
522
|
325 |
|
$class = $this->em->getClassMetadata($association->getSourceEntity()); |
523
|
325 |
|
$targetClass = $this->em->getClassMetadata($association->getTargetEntity()); |
524
|
325 |
|
$columns = []; |
525
|
325 |
|
$types = []; |
526
|
|
|
|
527
|
325 |
|
$joinTable = $association->getJoinTable(); |
528
|
325 |
|
$joinTableName = $joinTable->getQuotedQualifiedName($this->platform); |
529
|
|
|
|
530
|
325 |
|
foreach ($joinTable->getJoinColumns() as $joinColumn) { |
531
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
532
|
325 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
533
|
325 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
534
|
|
|
|
535
|
325 |
|
if (! $joinColumn->getType()) { |
536
|
|
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em)); |
537
|
|
|
} |
538
|
|
|
|
539
|
325 |
|
$columns[] = $quotedColumnName; |
540
|
325 |
|
$types[] = $joinColumn->getType(); |
541
|
|
|
} |
542
|
|
|
|
543
|
325 |
|
foreach ($joinTable->getInverseJoinColumns() as $joinColumn) { |
544
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
545
|
325 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
546
|
325 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
547
|
|
|
|
548
|
325 |
|
if (! $joinColumn->getType()) { |
549
|
|
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em)); |
550
|
|
|
} |
551
|
|
|
|
552
|
325 |
|
$columns[] = $quotedColumnName; |
553
|
325 |
|
$types[] = $joinColumn->getType(); |
554
|
|
|
} |
555
|
|
|
|
556
|
325 |
|
$columnNamesAsString = implode(', ', $columns); |
557
|
325 |
|
$columnValuesAsString = implode(', ', array_fill(0, count($columns), '?')); |
558
|
|
|
|
559
|
|
|
return [ |
560
|
325 |
|
sprintf('INSERT INTO %s (%s) VALUES (%s)', $joinTableName, $columnNamesAsString, $columnValuesAsString), |
561
|
325 |
|
$types, |
562
|
|
|
]; |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
/** |
566
|
|
|
* Gets the SQL parameters for the corresponding SQL statement to insert the given |
567
|
|
|
* element of the given collection into the database. |
568
|
|
|
* |
569
|
|
|
* Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql. |
570
|
|
|
* |
571
|
|
|
* @param mixed $element |
572
|
|
|
* |
573
|
|
|
* @return mixed[] |
574
|
|
|
*/ |
575
|
325 |
|
protected function getInsertRowSQLParameters(PersistentCollection $collection, $element) |
576
|
|
|
{ |
577
|
325 |
|
return $this->collectJoinTableColumnParameters($collection, $element); |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
/** |
581
|
|
|
* Collects the parameters for inserting/deleting on the join table in the order |
582
|
|
|
* of the join table columns. |
583
|
|
|
* |
584
|
|
|
* @param object $element |
585
|
|
|
* |
586
|
|
|
* @return mixed[] |
587
|
|
|
*/ |
588
|
325 |
|
private function collectJoinTableColumnParameters(PersistentCollection $collection, $element) |
589
|
|
|
{ |
590
|
325 |
|
$params = []; |
591
|
325 |
|
$association = $collection->getMapping(); |
592
|
325 |
|
$owningClass = $this->em->getClassMetadata(get_class($collection->getOwner())); |
593
|
325 |
|
$targetClass = $collection->getTypeClass(); |
594
|
325 |
|
$owningIdentifier = $this->uow->getEntityIdentifier($collection->getOwner()); |
595
|
325 |
|
$targetIdentifier = $this->uow->getEntityIdentifier($element); |
596
|
325 |
|
$joinTable = $association->getJoinTable(); |
597
|
|
|
|
598
|
325 |
|
foreach ($joinTable->getJoinColumns() as $joinColumn) { |
599
|
325 |
|
$fieldName = $owningClass->fieldNames[$joinColumn->getReferencedColumnName()]; |
600
|
|
|
|
601
|
325 |
|
$params[] = $owningIdentifier[$fieldName]; |
602
|
|
|
} |
603
|
|
|
|
604
|
325 |
|
foreach ($joinTable->getInverseJoinColumns() as $joinColumn) { |
605
|
325 |
|
$fieldName = $targetClass->fieldNames[$joinColumn->getReferencedColumnName()]; |
606
|
|
|
|
607
|
325 |
|
$params[] = $targetIdentifier[$fieldName]; |
608
|
|
|
} |
609
|
|
|
|
610
|
325 |
|
return $params; |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
/** |
614
|
|
|
* @param string $key |
615
|
|
|
* @param bool $addFilters Whether the filter SQL should be included or not. |
616
|
|
|
* |
617
|
|
|
* @return mixed[] ordered vector: |
618
|
|
|
* - quoted join table name |
619
|
|
|
* - where clauses to be added for filtering |
620
|
|
|
* - parameters to be bound for filtering |
621
|
|
|
* - types of the parameters to be bound for filtering |
622
|
|
|
*/ |
623
|
7 |
|
private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters) |
624
|
|
|
{ |
625
|
7 |
|
$association = $collection->getMapping(); |
626
|
7 |
|
$owningAssociation = $association; |
627
|
7 |
|
$indexBy = $owningAssociation->getIndexedBy(); |
628
|
7 |
|
$identifier = $this->uow->getEntityIdentifier($collection->getOwner()); |
629
|
7 |
|
$sourceClass = $this->em->getClassMetadata($owningAssociation->getSourceEntity()); |
630
|
7 |
|
$targetClass = $this->em->getClassMetadata($owningAssociation->getTargetEntity()); |
631
|
|
|
|
632
|
7 |
|
if (! $owningAssociation->isOwningSide()) { |
633
|
3 |
|
$owningAssociation = $targetClass->getProperty($owningAssociation->getMappedBy()); |
634
|
3 |
|
$joinTable = $owningAssociation->getJoinTable(); |
635
|
3 |
|
$joinColumns = $joinTable->getJoinColumns(); |
636
|
3 |
|
$inverseJoinColumns = $joinTable->getInverseJoinColumns(); |
637
|
|
|
} else { |
638
|
4 |
|
$joinTable = $owningAssociation->getJoinTable(); |
639
|
4 |
|
$joinColumns = $joinTable->getInverseJoinColumns(); |
640
|
4 |
|
$inverseJoinColumns = $joinTable->getJoinColumns(); |
641
|
|
|
} |
642
|
|
|
|
643
|
7 |
|
$joinTableName = $joinTable->getQuotedQualifiedName($this->platform); |
644
|
7 |
|
$quotedJoinTable = $joinTableName . ' t'; |
645
|
7 |
|
$whereClauses = []; |
646
|
7 |
|
$params = []; |
647
|
7 |
|
$types = []; |
648
|
7 |
|
$joinNeeded = ! in_array($indexBy, $targetClass->identifier, true); |
649
|
|
|
|
650
|
7 |
|
if ($joinNeeded) { // extra join needed if indexBy is not a @id |
651
|
3 |
|
$joinConditions = []; |
652
|
|
|
|
653
|
3 |
|
foreach ($joinColumns as $joinColumn) { |
654
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
655
|
3 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
656
|
3 |
|
$quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName()); |
657
|
|
|
|
658
|
3 |
|
$joinConditions[] = ' t.' . $quotedColumnName . ' = tr.' . $quotedReferencedColumnName; |
659
|
|
|
} |
660
|
|
|
|
661
|
3 |
|
$tableName = $targetClass->table->getQuotedQualifiedName($this->platform); |
662
|
3 |
|
$quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions); |
663
|
3 |
|
$indexByProperty = $targetClass->getProperty($indexBy); |
664
|
|
|
|
665
|
|
|
switch (true) { |
666
|
3 |
|
case ($indexByProperty instanceof FieldMetadata): |
667
|
3 |
|
$quotedColumnName = $this->platform->quoteIdentifier($indexByProperty->getColumnName()); |
668
|
|
|
|
669
|
3 |
|
$whereClauses[] = sprintf('tr.%s = ?', $quotedColumnName); |
670
|
3 |
|
$params[] = $key; |
671
|
3 |
|
$types[] = $indexByProperty->getType(); |
672
|
3 |
|
break; |
673
|
|
|
|
674
|
|
|
case ($indexByProperty instanceof ToOneAssociationMetadata && $indexByProperty->isOwningSide()): |
675
|
|
|
// Cannot be supported because PHP does not accept objects as keys. =( |
676
|
|
|
break; |
677
|
|
|
} |
678
|
|
|
} |
679
|
|
|
|
680
|
7 |
|
foreach ($inverseJoinColumns as $joinColumn) { |
681
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
682
|
7 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
683
|
7 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
684
|
|
|
|
685
|
7 |
|
if (! $joinColumn->getType()) { |
686
|
|
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em)); |
687
|
|
|
} |
688
|
|
|
|
689
|
7 |
|
$whereClauses[] = sprintf('t.%s = ?', $quotedColumnName); |
690
|
7 |
|
$params[] = $identifier[$sourceClass->fieldNames[$joinColumn->getReferencedColumnName()]]; |
691
|
7 |
|
$types[] = $joinColumn->getType(); |
692
|
|
|
} |
693
|
|
|
|
694
|
7 |
|
if (! $joinNeeded) { |
695
|
4 |
|
foreach ($joinColumns as $joinColumn) { |
696
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
697
|
4 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
698
|
4 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
699
|
|
|
|
700
|
4 |
|
if (! $joinColumn->getType()) { |
701
|
|
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em)); |
702
|
|
|
} |
703
|
|
|
|
704
|
4 |
|
$whereClauses[] = sprintf('t.%s = ?', $quotedColumnName); |
705
|
4 |
|
$params[] = $key; |
706
|
4 |
|
$types[] = $joinColumn->getType(); |
707
|
|
|
} |
708
|
|
|
} |
709
|
|
|
|
710
|
7 |
|
if ($addFilters) { |
711
|
7 |
|
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($association); |
712
|
|
|
|
713
|
7 |
|
if ($filterSql) { |
714
|
|
|
$quotedJoinTable .= ' ' . $joinTargetEntitySQL; |
715
|
|
|
$whereClauses[] = $filterSql; |
716
|
|
|
} |
717
|
|
|
} |
718
|
|
|
|
719
|
7 |
|
return [$quotedJoinTable, $whereClauses, $params, $types]; |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
/** |
723
|
|
|
* @param object $element |
724
|
|
|
* @param bool $addFilters Whether the filter SQL should be included or not. |
725
|
|
|
* |
726
|
|
|
* @return mixed[] ordered vector: |
727
|
|
|
* - quoted join table name |
728
|
|
|
* - where clauses to be added for filtering |
729
|
|
|
* - parameters to be bound for filtering |
730
|
|
|
* - types of the parameters to be bound for filtering |
731
|
|
|
*/ |
732
|
9 |
|
private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters) |
733
|
|
|
{ |
734
|
9 |
|
$association = $collection->getMapping(); |
735
|
9 |
|
$owningAssociation = $association; |
736
|
|
|
|
737
|
9 |
|
if (! $association->isOwningSide()) { |
738
|
4 |
|
$sourceClass = $this->em->getClassMetadata($association->getTargetEntity()); |
739
|
4 |
|
$targetClass = $this->em->getClassMetadata($association->getSourceEntity()); |
740
|
4 |
|
$sourceIdentifier = $this->uow->getEntityIdentifier($element); |
741
|
4 |
|
$targetIdentifier = $this->uow->getEntityIdentifier($collection->getOwner()); |
742
|
|
|
|
743
|
4 |
|
$owningAssociation = $sourceClass->getProperty($association->getMappedBy()); |
744
|
|
|
} else { |
745
|
5 |
|
$sourceClass = $this->em->getClassMetadata($association->getSourceEntity()); |
746
|
5 |
|
$targetClass = $this->em->getClassMetadata($association->getTargetEntity()); |
747
|
5 |
|
$sourceIdentifier = $this->uow->getEntityIdentifier($collection->getOwner()); |
748
|
5 |
|
$targetIdentifier = $this->uow->getEntityIdentifier($element); |
749
|
|
|
} |
750
|
|
|
|
751
|
9 |
|
$joinTable = $owningAssociation->getJoinTable(); |
752
|
9 |
|
$joinTableName = $joinTable->getQuotedQualifiedName($this->platform); |
753
|
9 |
|
$quotedJoinTable = $joinTableName; |
754
|
9 |
|
$whereClauses = []; |
755
|
9 |
|
$params = []; |
756
|
9 |
|
$types = []; |
757
|
|
|
|
758
|
9 |
|
foreach ($joinTable->getJoinColumns() as $joinColumn) { |
759
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
760
|
9 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
761
|
9 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
762
|
|
|
|
763
|
9 |
|
if (! $joinColumn->getType()) { |
764
|
|
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em)); |
765
|
|
|
} |
766
|
|
|
|
767
|
9 |
|
$whereClauses[] = ($addFilters ? 't.' : '') . $quotedColumnName . ' = ?'; |
768
|
9 |
|
$params[] = $sourceIdentifier[$sourceClass->fieldNames[$referencedColumnName]]; |
769
|
9 |
|
$types[] = $joinColumn->getType(); |
770
|
|
|
} |
771
|
|
|
|
772
|
9 |
|
foreach ($joinTable->getInverseJoinColumns() as $joinColumn) { |
773
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
774
|
9 |
|
$quotedColumnName = $this->platform->quoteIdentifier($joinColumn->getColumnName()); |
775
|
9 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
776
|
|
|
|
777
|
9 |
|
if (! $joinColumn->getType()) { |
778
|
|
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em)); |
779
|
|
|
} |
780
|
|
|
|
781
|
9 |
|
$whereClauses[] = ($addFilters ? 't.' : '') . $quotedColumnName . ' = ?'; |
782
|
9 |
|
$params[] = $targetIdentifier[$targetClass->fieldNames[$referencedColumnName]]; |
783
|
9 |
|
$types[] = $joinColumn->getType(); |
784
|
|
|
} |
785
|
|
|
|
786
|
9 |
|
if ($addFilters) { |
787
|
7 |
|
$quotedJoinTable .= ' t'; |
788
|
|
|
|
789
|
7 |
|
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($association); |
790
|
|
|
|
791
|
7 |
|
if ($filterSql) { |
792
|
3 |
|
$quotedJoinTable .= ' ' . $joinTargetEntitySQL; |
793
|
3 |
|
$whereClauses[] = $filterSql; |
794
|
|
|
} |
795
|
|
|
} |
796
|
|
|
|
797
|
9 |
|
return [$quotedJoinTable, $whereClauses, $params, $types]; |
798
|
|
|
} |
799
|
|
|
|
800
|
|
|
/** |
801
|
|
|
* Expands Criteria Parameters by walking the expressions and grabbing all |
802
|
|
|
* parameters and types from it. |
803
|
|
|
* |
804
|
|
|
* @return mixed[] |
805
|
|
|
*/ |
806
|
12 |
|
private function expandCriteriaParameters(Criteria $criteria) |
807
|
|
|
{ |
808
|
12 |
|
$expression = $criteria->getWhereExpression(); |
809
|
|
|
|
810
|
12 |
|
if ($expression === null) { |
811
|
5 |
|
return []; |
812
|
|
|
} |
813
|
|
|
|
814
|
7 |
|
$valueVisitor = new SqlValueVisitor(); |
815
|
|
|
|
816
|
7 |
|
$valueVisitor->dispatch($expression); |
817
|
|
|
|
818
|
7 |
|
list(, $types) = $valueVisitor->getParamsAndTypes(); |
819
|
|
|
|
820
|
7 |
|
return $types; |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
/** |
824
|
|
|
* @return string |
825
|
|
|
*/ |
826
|
12 |
|
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass) |
827
|
|
|
{ |
828
|
12 |
|
$orderings = $criteria->getOrderings(); |
829
|
|
|
|
830
|
12 |
|
if ($orderings) { |
831
|
3 |
|
$orderBy = []; |
832
|
|
|
|
833
|
3 |
|
foreach ($orderings as $name => $direction) { |
834
|
3 |
|
$property = $targetClass->getProperty($name); |
835
|
3 |
|
$columnName = $this->platform->quoteIdentifier($property->getColumnName()); |
836
|
|
|
|
837
|
3 |
|
$orderBy[] = $columnName . ' ' . $direction; |
838
|
|
|
} |
839
|
|
|
|
840
|
3 |
|
return ' ORDER BY ' . implode(', ', $orderBy); |
841
|
|
|
} |
842
|
9 |
|
return ''; |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
/** |
846
|
|
|
* @return string |
847
|
|
|
* @throws \Doctrine\DBAL\DBALException |
848
|
|
|
*/ |
849
|
12 |
|
private function getLimitSql(Criteria $criteria) |
850
|
|
|
{ |
851
|
12 |
|
$limit = $criteria->getMaxResults(); |
852
|
12 |
|
$offset = $criteria->getFirstResult(); |
853
|
12 |
|
if ($limit !== null || $offset !== null) { |
854
|
3 |
|
return $this->platform->modifyLimitQuery('', $limit, $offset); |
855
|
|
|
} |
856
|
9 |
|
return ''; |
857
|
|
|
} |
858
|
|
|
} |
859
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.