Failed Conditions
Pull Request — master (#7842)
by
unknown
09:15
created

ManyToManyPersister::loadCriteria()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 62
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 5.0098

Importance

Changes 0
Metric Value
cc 5
eloc 41
c 0
b 0
f 0
nc 12
nop 2
dl 0
loc 62
rs 8.9528
ccs 38
cts 41
cp 0.9268
crap 5.0098

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Persisters\Collection;
6
7
use BadMethodCallException;
8
use Doctrine\Common\Collections\Criteria;
9
use Doctrine\DBAL\DBALException;
10
use Doctrine\ORM\Mapping\ClassMetadata;
11
use Doctrine\ORM\Mapping\FieldMetadata;
12
use Doctrine\ORM\Mapping\JoinColumnMetadata;
13
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
14
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
15
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
16
use Doctrine\ORM\PersistentCollection;
17
use Doctrine\ORM\Persisters\SqlValueVisitor;
18
use Doctrine\ORM\Query;
19
use Doctrine\ORM\Utility\PersisterHelper;
20
use function array_fill;
21
use function count;
22
use function get_class;
23
use function implode;
24
use function in_array;
25
use function reset;
26
use function sprintf;
27
28
/**
29
 * Persister for many-to-many collections.
30
 */
31
class ManyToManyPersister extends AbstractCollectionPersister
32
{
33
    /**
34
     * {@inheritdoc}
35
     */
36 13
    public function delete(PersistentCollection $collection)
37
    {
38 13
        $association = $collection->getMapping();
39
40 13
        if (! $association->isOwningSide()) {
41
            return; // ignore inverse side
42
        }
43
44 13
        $class     = $this->em->getClassMetadata($association->getSourceEntity());
45 13
        $joinTable = $association->getJoinTable();
0 ignored issues
show
Bug introduced by
The method getJoinTable() does not exist on Doctrine\ORM\Mapping\ToManyAssociationMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\ToManyAssociationMetadata such as Doctrine\ORM\Mapping\ManyToManyAssociationMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

45
        /** @scrutinizer ignore-call */ 
46
        $joinTable = $association->getJoinTable();
Loading history...
46 13
        $types     = [];
47
48 13
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
49
            /** @var JoinColumnMetadata $joinColumn */
50 13
            $referencedColumnName = $joinColumn->getReferencedColumnName();
51
52 13
            if (! $joinColumn->getType()) {
53
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em));
54
            }
55
56 13
            $types[] = $joinColumn->getType();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $types[] is correct as $joinColumn->getType() targeting Doctrine\ORM\Mapping\ColumnMetadata::getType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
57
        }
58
59 13
        $sql    = $this->getDeleteSQL($collection);
60 13
        $params = $this->getDeleteSQLParameters($collection);
61
62 13
        $this->conn->executeUpdate($sql, $params, $types);
63 13
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68 292
    public function update(PersistentCollection $collection)
69
    {
70 292
        $association = $collection->getMapping();
71
72 292
        if (! $association->isOwningSide()) {
73 222
            return; // ignore inverse side
74
        }
75
76 291
        [$deleteSql, $deleteTypes] = $this->getDeleteRowSQL($collection);
77 291
        [$insertSql, $insertTypes] = $this->getInsertRowSQL($collection);
78
79 291
        foreach ($collection->getDeleteDiff() as $element) {
80 10
            $this->conn->executeUpdate(
81 10
                $deleteSql,
82 10
                $this->getDeleteRowSQLParameters($collection, $element),
83 10
                $deleteTypes
84
            );
85
        }
86
87 291
        foreach ($collection->getInsertDiff() as $element) {
88 291
            $this->conn->executeUpdate(
89 291
                $insertSql,
90 291
                $this->getInsertRowSQLParameters($collection, $element),
91 291
                $insertTypes
92
            );
93
        }
94 291
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99 3
    public function get(PersistentCollection $collection, $index)
100
    {
101 3
        $association = $collection->getMapping();
102
103 3
        if (! ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy())) {
104
            throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
105
        }
106
107 3
        $persister = $this->uow->getEntityPersister($association->getTargetEntity());
108 3
        $mappedKey = $association->isOwningSide()
109 2
            ? $association->getInversedBy()
110 3
            : $association->getMappedBy();
111
112
        $criteria = [
113 3
            $mappedKey                   => $collection->getOwner(),
114 3
            $association->getIndexedBy() => $index,
115
        ];
116
117 3
        return $persister->load($criteria, null, $association, [], 0, 1);
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123 17
    public function count(PersistentCollection $collection)
124
    {
125 17
        $conditions        = [];
126 17
        $params            = [];
127 17
        $types             = [];
128 17
        $association       = $collection->getMapping();
129 17
        $identifier        = $this->uow->getEntityIdentifier($collection->getOwner());
130 17
        $sourceClass       = $this->em->getClassMetadata($association->getSourceEntity());
131 17
        $targetClass       = $this->em->getClassMetadata($association->getTargetEntity());
132 17
        $owningAssociation = ! $association->isOwningSide()
133 3
            ? $targetClass->getProperty($association->getMappedBy())
0 ignored issues
show
Bug introduced by
The method getProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

133
            ? $targetClass->/** @scrutinizer ignore-call */ getProperty($association->getMappedBy())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
134 17
            : $association;
135
136 17
        $joinTable     = $owningAssociation->getJoinTable();
137 17
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
138 17
        $joinColumns   = $association->isOwningSide()
139 14
            ? $joinTable->getJoinColumns()
140 17
            : $joinTable->getInverseJoinColumns();
141
142 17
        foreach ($joinColumns as $joinColumn) {
143
            /** @var JoinColumnMetadata $joinColumn */
144 17
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
145 17
            $referencedColumnName = $joinColumn->getReferencedColumnName();
146
147 17
            if (! $joinColumn->getType()) {
148 1
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em));
149
            }
150
151 17
            $conditions[] = sprintf('t.%s = ?', $quotedColumnName);
152 17
            $params[]     = $identifier[$sourceClass->fieldNames[$referencedColumnName]];
0 ignored issues
show
Bug introduced by
Accessing fieldNames on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
153 17
            $types[]      = $joinColumn->getType();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $types[] is correct as $joinColumn->getType() targeting Doctrine\ORM\Mapping\ColumnMetadata::getType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
154
        }
155
156 17
        [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($association);
157
158 17
        if ($filterSql) {
159 3
            $conditions[] = $filterSql;
160
        }
161
162
        // If there is a provided criteria, make part of conditions
163
        // @todo Fix this. Current SQL returns something like:
164
        /*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) {
165
            // A join is needed on the target entity
166
            $targetTableName = $targetClass->table->getQuotedQualifiedName($this->platform);
167
            $targetJoinSql   = ' JOIN ' . $targetTableName . ' te'
168
                . ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
169
170
            // And criteria conditions needs to be added
171
            $persister    = $this->uow->getEntityPersister($targetClass->getClassName());
172
            $visitor      = new SqlExpressionVisitor($persister, $targetClass);
173
            $conditions[] = $visitor->dispatch($expression);
174
175
            $joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL;
176
        }*/
177
178
        $sql = 'SELECT COUNT(*)'
179 17
            . ' FROM ' . $joinTableName . ' t'
180 17
            . $joinTargetEntitySQL
181 17
            . ' WHERE ' . implode(' AND ', $conditions);
182
183 17
        return $this->conn->fetchColumn($sql, $params, 0, $types);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->fetc...ql, $params, 0, $types) also could return the type false which is incompatible with the return type mandated by Doctrine\ORM\Persisters\...ctionPersister::count() of integer.
Loading history...
184
    }
185
186
    /**
187
     * {@inheritDoc}
188
     */
189 8
    public function slice(PersistentCollection $collection, $offset, $length = null)
190
    {
191 8
        $association = $collection->getMapping();
192 8
        $persister   = $this->uow->getEntityPersister($association->getTargetEntity());
193
194 8
        return $persister->getManyToManyCollection($association, $collection->getOwner(), $offset, $length);
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200 7
    public function containsKey(PersistentCollection $collection, $key)
201
    {
202 7
        $association = $collection->getMapping();
203
204 7
        if (! ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy())) {
205
            throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
206
        }
207
208 7
        [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictionsWithKey($collection, $key, true);
209
210 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
211
212 7
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
213
    }
214
215
    /**
216
     * {@inheritDoc}
217
     */
218 7
    public function contains(PersistentCollection $collection, $element)
219
    {
220 7
        if (! $this->isValidEntityState($element)) {
221 2
            return false;
222
        }
223
224 7
        [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictions($collection, $element, true);
225
226 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
227
228 7
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234 2
    public function removeElement(PersistentCollection $collection, $element)
235
    {
236 2
        if (! $this->isValidEntityState($element)) {
237 2
            return false;
238
        }
239
240 2
        [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictions($collection, $element, false);
241
242 2
        $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
243
244 2
        return (bool) $this->conn->executeUpdate($sql, $params, $types);
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250 6
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
251
    {
252 6
        $association   = $collection->getMapping();
253 6
        $owner         = $collection->getOwner();
254 6
        $ownerMetadata = $this->em->getClassMetadata(get_class($owner));
255 6
        $identifier    = $this->uow->getEntityIdentifier($owner);
256 6
        $targetClass   = $this->em->getClassMetadata($association->getTargetEntity());
257 6
        $onConditions  = $this->getOnConditionSQL($association);
258 6
        $whereClauses  = $params = $types = [];
259
260 6
        if (! $association->isOwningSide()) {
261
            $association = $targetClass->getProperty($association->getMappedBy());
262
            $joinColumns = $association->getJoinTable()->getInverseJoinColumns();
263
        } else {
264 6
            $joinColumns = $association->getJoinTable()->getJoinColumns();
265
        }
266
267 6
        foreach ($joinColumns as $joinColumn) {
268
            /** @var JoinColumnMetadata $joinColumn */
269 6
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
270 6
            $referencedColumnName = $joinColumn->getReferencedColumnName();
271
272 6
            if (! $joinColumn->getType()) {
273
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $ownerMetadata, $this->em));
274
            }
275
276 6
            $whereClauses[] = sprintf('t.%s = ?', $quotedColumnName);
277 6
            $params[]       = $identifier[$ownerMetadata->fieldNames[$referencedColumnName]];
0 ignored issues
show
Bug introduced by
Accessing fieldNames on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
278 6
            $types[]        = $joinColumn->getType();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $types[] is correct as $joinColumn->getType() targeting Doctrine\ORM\Mapping\ColumnMetadata::getType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
279
        }
280
281 6
        $parameters = $this->expandCriteriaParameters($criteria);
282
283 6
        foreach ($parameters as $parameter) {
284 1
            [$name, $value, $operator] = $parameter;
285
286 1
            $property   = $targetClass->getProperty($name);
287 1
            $columnName = $this->platform->quoteIdentifier($property->getColumnName());
288
289 1
            $whereClauses[] = sprintf('te.%s %s ?', $columnName, $operator);
290 1
            $params[]       = $value;
291 1
            $types[]        = $property->getType();
292
        }
293
294 6
        $tableName        = $targetClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
295 6
        $joinTableName    = $association->getJoinTable()->getQuotedQualifiedName($this->platform);
296 6
        $resultSetMapping = new Query\ResultSetMappingBuilder($this->em);
297
298 6
        $resultSetMapping->addRootEntityFromClassMetadata($targetClass->getClassName(), 'te');
0 ignored issues
show
Bug introduced by
The method getClassName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

298
        $resultSetMapping->addRootEntityFromClassMetadata($targetClass->/** @scrutinizer ignore-call */ getClassName(), 'te');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
299
300 6
        $sql = 'SELECT ' . $resultSetMapping->generateSelectClause()
301 6
            . ' FROM ' . $tableName . ' te'
302 6
            . ' JOIN ' . $joinTableName . ' t ON'
303 6
            . implode(' AND ', $onConditions)
304 6
            . ' WHERE ' . implode(' AND ', $whereClauses);
305
306 6
        $sql .= $this->getOrderingSql($criteria, $targetClass);
307 6
        $sql .= $this->getLimitSql($criteria);
308
309 6
        $stmt = $this->conn->executeQuery($sql, $params, $types);
310
311 6
        return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $resultSetMapping);
312
    }
313
314
    /**
315
     * Generates the filter SQL for a given mapping.
316
     *
317
     * This method is not used for actually grabbing the related entities
318
     * but when the extra-lazy collection methods are called on a filtered
319
     * association. This is why besides the many to many table we also
320
     * have to join in the actual entities table leading to additional
321
     * JOIN.
322
     *
323
     * @return string[] ordered tuple:
324
     *                   - JOIN condition to add to the SQL
325
     *                   - WHERE condition to add to the SQL
326
     */
327 31
    public function getFilterSql(ManyToManyAssociationMetadata $association)
328
    {
329 31
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
330 31
        $rootClass   = $this->em->getClassMetadata($targetClass->getRootClassName());
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

330
        $rootClass   = $this->em->getClassMetadata($targetClass->/** @scrutinizer ignore-call */ getRootClassName());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
331 31
        $filterSql   = $this->generateFilterConditionSQL($rootClass, 'te');
332
333 31
        if ($filterSql === '') {
334 31
            return ['', ''];
335
        }
336
337
        // A join is needed if there is filtering on the target entity
338 6
        $tableName = $rootClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
339 6
        $joinSql   = ' JOIN ' . $tableName . ' te'
340 6
            . ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
341
342 6
        return [$joinSql, $filterSql];
343
    }
344
345
    /**
346
     * Generates the filter SQL for a given entity and table alias.
347
     *
348
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
349
     * @param string        $targetTableAlias The table alias of the joined/selected table.
350
     *
351
     * @return string The SQL query part to add to a query.
352
     */
353 31
    protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
354
    {
355 31
        $filterClauses = [];
356
357 31
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
358 6
            $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
359
360 6
            if ($filterExpr) {
361 6
                $filterClauses[] = '(' . $filterExpr . ')';
362
            }
363
        }
364
365 31
        if (! $filterClauses) {
366 31
            return '';
367
        }
368
369 6
        $filterSql = implode(' AND ', $filterClauses);
370
371 6
        return isset($filterClauses[1])
372
            ? '(' . $filterSql . ')'
373 6
            : $filterSql;
374
    }
375
376
    /**
377
     * Generate ON condition
378
     *
379
     * @return string[]
380
     */
381 12
    protected function getOnConditionSQL(ManyToManyAssociationMetadata $association)
382
    {
383 12
        $targetClass       = $this->em->getClassMetadata($association->getTargetEntity());
384 12
        $owningAssociation = ! $association->isOwningSide()
385 2
            ? $targetClass->getProperty($association->getMappedBy())
386 12
            : $association;
387
388 12
        $joinTable   = $owningAssociation->getJoinTable();
389 12
        $joinColumns = $association->isOwningSide()
390 10
            ? $joinTable->getInverseJoinColumns()
391 12
            : $joinTable->getJoinColumns();
392
393 12
        $conditions = [];
394
395 12
        foreach ($joinColumns as $joinColumn) {
396 12
            $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
397 12
            $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
398
399 12
            $conditions[] = ' t.' . $quotedColumnName . ' = te.' . $quotedReferencedColumnName;
400
        }
401
402 12
        return $conditions;
403
    }
404
405
    /**
406
     * {@inheritdoc}
407
     *
408
     * @override
409
     */
410 13
    protected function getDeleteSQL(PersistentCollection $collection)
411
    {
412 13
        $association   = $collection->getMapping();
413 13
        $joinTable     = $association->getJoinTable();
414 13
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
415 13
        $columns       = [];
416
417 13
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
418 13
            $columns[] = $this->platform->quoteIdentifier($joinColumn->getColumnName());
419
        }
420
421 13
        return 'DELETE FROM ' . $joinTableName . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
422
    }
423
424
    /**
425
     * {@inheritdoc}
426
     *
427
     * {@internal Order of the parameters must be the same as the order of the columns in getDeleteSql. }}
428
     *
429
     * @override
430
     */
431 13
    protected function getDeleteSQLParameters(PersistentCollection $collection)
432
    {
433 13
        $association = $collection->getMapping();
434 13
        $identifier  = $this->uow->getEntityIdentifier($collection->getOwner());
435 13
        $joinTable   = $association->getJoinTable();
436 13
        $joinColumns = $joinTable->getJoinColumns();
437
438
        // Optimization for single column identifier
439 13
        if (count($joinColumns) === 1) {
440 10
            return [reset($identifier)];
441
        }
442
443
        // Composite identifier
444 3
        $sourceClass = $this->em->getClassMetadata($association->getSourceEntity());
445 3
        $params      = [];
446
447 3
        foreach ($joinColumns as $joinColumn) {
448 3
            $params[] = $identifier[$sourceClass->fieldNames[$joinColumn->getReferencedColumnName()]];
0 ignored issues
show
Bug introduced by
Accessing fieldNames on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
449
        }
450
451 3
        return $params;
452
    }
453
454
    /**
455
     * Gets the SQL statement used for deleting a row from the collection.
456
     *
457
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
458
     *                             of types for bound parameters
459
     */
460 291
    protected function getDeleteRowSQL(PersistentCollection $collection)
461
    {
462 291
        $association = $collection->getMapping();
463 291
        $class       = $this->em->getClassMetadata($association->getSourceEntity());
464 291
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
465 291
        $columns     = [];
466 291
        $types       = [];
467
468 291
        $joinTable     = $association->getJoinTable();
469 291
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
470
471 291
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
472
            /** @var JoinColumnMetadata $joinColumn */
473 291
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
474 291
            $referencedColumnName = $joinColumn->getReferencedColumnName();
475
476 291
            if (! $joinColumn->getType()) {
477 33
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em));
478
            }
479
480 291
            $columns[] = $quotedColumnName;
481 291
            $types[]   = $joinColumn->getType();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $types[] is correct as $joinColumn->getType() targeting Doctrine\ORM\Mapping\ColumnMetadata::getType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
482
        }
483
484 291
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
485
            /** @var JoinColumnMetadata $joinColumn */
486 291
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
487 291
            $referencedColumnName = $joinColumn->getReferencedColumnName();
488
489 291
            if (! $joinColumn->getType()) {
490 33
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
491
            }
492
493 291
            $columns[] = $quotedColumnName;
494 291
            $types[]   = $joinColumn->getType();
495
        }
496
497
        return [
498 291
            sprintf('DELETE FROM %s WHERE %s = ?', $joinTableName, implode(' = ? AND ', $columns)),
499 291
            $types,
500
        ];
501
    }
502
503
    /**
504
     * Gets the SQL parameters for the corresponding SQL statement to delete the given
505
     * element from the given collection.
506
     *
507
     * {@internal Order of the parameters must be the same as the order of the columns in getDeleteRowSql. }}
508
     *
509
     * @param mixed $element
510
     *
511
     * @return mixed[]
512
     */
513 10
    protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element)
514
    {
515 10
        return $this->collectJoinTableColumnParameters($collection, $element);
516
    }
517
518
    /**
519
     * Gets the SQL statement used for inserting a row in the collection.
520
     *
521
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
522
     *                             of types for bound parameters
523
     */
524 291
    protected function getInsertRowSQL(PersistentCollection $collection)
525
    {
526 291
        $association = $collection->getMapping();
527 291
        $class       = $this->em->getClassMetadata($association->getSourceEntity());
528 291
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
529 291
        $columns     = [];
530 291
        $types       = [];
531
532 291
        $joinTable     = $association->getJoinTable();
533 291
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
534
535 291
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
536
            /** @var JoinColumnMetadata $joinColumn */
537 291
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
538 291
            $referencedColumnName = $joinColumn->getReferencedColumnName();
539
540 291
            if (! $joinColumn->getType()) {
541
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em));
542
            }
543
544 291
            $columns[] = $quotedColumnName;
545 291
            $types[]   = $joinColumn->getType();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $types[] is correct as $joinColumn->getType() targeting Doctrine\ORM\Mapping\ColumnMetadata::getType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
546
        }
547
548 291
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
549
            /** @var JoinColumnMetadata $joinColumn */
550 291
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
551 291
            $referencedColumnName = $joinColumn->getReferencedColumnName();
552
553 291
            if (! $joinColumn->getType()) {
554
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
555
            }
556
557 291
            $columns[] = $quotedColumnName;
558 291
            $types[]   = $joinColumn->getType();
559
        }
560
561 291
        $columnNamesAsString  = implode(', ', $columns);
562 291
        $columnValuesAsString = implode(', ', array_fill(0, count($columns), '?'));
563
564
        return [
565 291
            sprintf('INSERT INTO %s (%s) VALUES (%s)', $joinTableName, $columnNamesAsString, $columnValuesAsString),
566 291
            $types,
567
        ];
568
    }
569
570
    /**
571
     * Gets the SQL parameters for the corresponding SQL statement to insert the given
572
     * element of the given collection into the database.
573
     *
574
     * {@internal Order of the parameters must be the same as the order of the columns in getInsertRowSql. }}
575
     *
576
     * @param mixed $element
577
     *
578
     * @return mixed[]
579
     */
580 291
    protected function getInsertRowSQLParameters(PersistentCollection $collection, $element)
581
    {
582 291
        return $this->collectJoinTableColumnParameters($collection, $element);
583
    }
584
585
    /**
586
     * Collects the parameters for inserting/deleting on the join table in the order
587
     * of the join table columns.
588
     *
589
     * @param object $element
590
     *
591
     * @return mixed[]
592
     */
593 291
    private function collectJoinTableColumnParameters(PersistentCollection $collection, $element)
594
    {
595 291
        $params           = [];
596 291
        $association      = $collection->getMapping();
597 291
        $owningClass      = $this->em->getClassMetadata(get_class($collection->getOwner()));
598 291
        $targetClass      = $collection->getTypeClass();
599 291
        $owningIdentifier = $this->uow->getEntityIdentifier($collection->getOwner());
600 291
        $targetIdentifier = $this->uow->getEntityIdentifier($element);
601 291
        $joinTable        = $association->getJoinTable();
602
603 291
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
604 291
            $fieldName = $owningClass->fieldNames[$joinColumn->getReferencedColumnName()];
0 ignored issues
show
Bug introduced by
Accessing fieldNames on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
605
606 291
            $params[] = $owningIdentifier[$fieldName];
607
        }
608
609 291
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
610 291
            $fieldName = $targetClass->fieldNames[$joinColumn->getReferencedColumnName()];
611
612 291
            $params[] = $targetIdentifier[$fieldName];
613
        }
614
615 291
        return $params;
616
    }
617
618
    /**
619
     * @param string $key
620
     * @param bool   $addFilters Whether the filter SQL should be included or not.
621
     *
622
     * @return mixed[] ordered vector:
623
     *                - quoted join table name
624
     *                - where clauses to be added for filtering
625
     *                - parameters to be bound for filtering
626
     *                - types of the parameters to be bound for filtering
627
     */
628 7
    private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters)
629
    {
630 7
        $association       = $collection->getMapping();
631 7
        $owningAssociation = $association;
632 7
        $indexBy           = $owningAssociation->getIndexedBy();
633 7
        $identifier        = $this->uow->getEntityIdentifier($collection->getOwner());
634 7
        $sourceClass       = $this->em->getClassMetadata($owningAssociation->getSourceEntity());
635 7
        $targetClass       = $this->em->getClassMetadata($owningAssociation->getTargetEntity());
636
637 7
        if (! $owningAssociation->isOwningSide()) {
638 3
            $owningAssociation  = $targetClass->getProperty($owningAssociation->getMappedBy());
639 3
            $joinTable          = $owningAssociation->getJoinTable();
640 3
            $joinColumns        = $joinTable->getJoinColumns();
641 3
            $inverseJoinColumns = $joinTable->getInverseJoinColumns();
642
        } else {
643 4
            $joinTable          = $owningAssociation->getJoinTable();
644 4
            $joinColumns        = $joinTable->getInverseJoinColumns();
645 4
            $inverseJoinColumns = $joinTable->getJoinColumns();
646
        }
647
648 7
        $joinTableName   = $joinTable->getQuotedQualifiedName($this->platform);
649 7
        $quotedJoinTable = $joinTableName . ' t';
650 7
        $whereClauses    = [];
651 7
        $params          = [];
652 7
        $types           = [];
653 7
        $joinNeeded      = ! in_array($indexBy, $targetClass->identifier, true);
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
654
655 7
        if ($joinNeeded) { // extra join needed if indexBy is not a @id
656 3
            $joinConditions = [];
657
658 3
            foreach ($joinColumns as $joinColumn) {
659
                /** @var JoinColumnMetadata $joinColumn */
660 3
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
661 3
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
662
663 3
                $joinConditions[] = ' t.' . $quotedColumnName . ' = tr.' . $quotedReferencedColumnName;
664
            }
665
666 3
            $tableName        = $targetClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
667 3
            $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
668 3
            $indexByProperty  = $targetClass->getProperty($indexBy);
669
670
            switch (true) {
671 3
                case $indexByProperty instanceof FieldMetadata:
672 3
                    $quotedColumnName = $this->platform->quoteIdentifier($indexByProperty->getColumnName());
673
674 3
                    $whereClauses[] = sprintf('tr.%s = ?', $quotedColumnName);
675 3
                    $params[]       = $key;
676 3
                    $types[]        = $indexByProperty->getType();
677 3
                    break;
678
679
                case $indexByProperty instanceof ToOneAssociationMetadata && $indexByProperty->isOwningSide():
680
                    // Cannot be supported because PHP does not accept objects as keys. =(
681
                    break;
682
            }
683
        }
684
685 7
        foreach ($inverseJoinColumns as $joinColumn) {
686
            /** @var JoinColumnMetadata $joinColumn */
687 7
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
688 7
            $referencedColumnName = $joinColumn->getReferencedColumnName();
689
690 7
            if (! $joinColumn->getType()) {
691
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em));
692
            }
693
694 7
            $whereClauses[] = sprintf('t.%s = ?', $quotedColumnName);
695 7
            $params[]       = $identifier[$sourceClass->fieldNames[$joinColumn->getReferencedColumnName()]];
0 ignored issues
show
Bug introduced by
Accessing fieldNames on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
696 7
            $types[]        = $joinColumn->getType();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $types[] is correct as $joinColumn->getType() targeting Doctrine\ORM\Mapping\ColumnMetadata::getType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
697
        }
698
699 7
        if (! $joinNeeded) {
700 4
            foreach ($joinColumns as $joinColumn) {
701
                /** @var JoinColumnMetadata $joinColumn */
702 4
                $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
703 4
                $referencedColumnName = $joinColumn->getReferencedColumnName();
704
705 4
                if (! $joinColumn->getType()) {
706
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
707
                }
708
709 4
                $whereClauses[] = sprintf('t.%s = ?', $quotedColumnName);
710 4
                $params[]       = $key;
711 4
                $types[]        = $joinColumn->getType();
712
            }
713
        }
714
715 7
        if ($addFilters) {
716 7
            [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($association);
717
718 7
            if ($filterSql) {
719
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
720
                $whereClauses[]   = $filterSql;
721
            }
722
        }
723
724 7
        return [$quotedJoinTable, $whereClauses, $params, $types];
725
    }
726
727
    /**
728
     * @param object $element
729
     * @param bool   $addFilters Whether the filter SQL should be included or not.
730
     *
731
     * @return mixed[] ordered vector:
732
     *                - quoted join table name
733
     *                - where clauses to be added for filtering
734
     *                - parameters to be bound for filtering
735
     *                - types of the parameters to be bound for filtering
736
     */
737 9
    private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters)
738
    {
739 9
        $association       = $collection->getMapping();
740 9
        $owningAssociation = $association;
741
742 9
        if (! $association->isOwningSide()) {
743 4
            $sourceClass      = $this->em->getClassMetadata($association->getTargetEntity());
744 4
            $targetClass      = $this->em->getClassMetadata($association->getSourceEntity());
745 4
            $sourceIdentifier = $this->uow->getEntityIdentifier($element);
746 4
            $targetIdentifier = $this->uow->getEntityIdentifier($collection->getOwner());
747
748 4
            $owningAssociation = $sourceClass->getProperty($association->getMappedBy());
749
        } else {
750 5
            $sourceClass      = $this->em->getClassMetadata($association->getSourceEntity());
751 5
            $targetClass      = $this->em->getClassMetadata($association->getTargetEntity());
752 5
            $sourceIdentifier = $this->uow->getEntityIdentifier($collection->getOwner());
753 5
            $targetIdentifier = $this->uow->getEntityIdentifier($element);
754
        }
755
756 9
        $joinTable       = $owningAssociation->getJoinTable();
757 9
        $joinTableName   = $joinTable->getQuotedQualifiedName($this->platform);
758 9
        $quotedJoinTable = $joinTableName;
759 9
        $whereClauses    = [];
760 9
        $params          = [];
761 9
        $types           = [];
762
763 9
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
764
            /** @var JoinColumnMetadata $joinColumn */
765 9
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
766 9
            $referencedColumnName = $joinColumn->getReferencedColumnName();
767
768 9
            if (! $joinColumn->getType()) {
769
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em));
770
            }
771
772 9
            $whereClauses[] = ($addFilters ? 't.' : '') . $quotedColumnName . ' = ?';
773 9
            $params[]       = $sourceIdentifier[$sourceClass->fieldNames[$referencedColumnName]];
0 ignored issues
show
Bug introduced by
Accessing fieldNames on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
774 9
            $types[]        = $joinColumn->getType();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $types[] is correct as $joinColumn->getType() targeting Doctrine\ORM\Mapping\ColumnMetadata::getType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
775
        }
776
777 9
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
778
            /** @var JoinColumnMetadata $joinColumn */
779 9
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
780 9
            $referencedColumnName = $joinColumn->getReferencedColumnName();
781
782 9
            if (! $joinColumn->getType()) {
783
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
784
            }
785
786 9
            $whereClauses[] = ($addFilters ? 't.' : '') . $quotedColumnName . ' = ?';
787 9
            $params[]       = $targetIdentifier[$targetClass->fieldNames[$referencedColumnName]];
788 9
            $types[]        = $joinColumn->getType();
789
        }
790
791 9
        if ($addFilters) {
792 7
            $quotedJoinTable .= ' t';
793
794 7
            [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($association);
795
796 7
            if ($filterSql) {
797 3
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
798 3
                $whereClauses[]   = $filterSql;
799
            }
800
        }
801
802 9
        return [$quotedJoinTable, $whereClauses, $params, $types];
803
    }
804
805
    /**
806
     * Expands Criteria Parameters by walking the expressions and grabbing all
807
     * parameters and types from it.
808
     *
809
     * @return mixed[]
810
     */
811 6
    private function expandCriteriaParameters(Criteria $criteria)
812
    {
813 6
        $expression = $criteria->getWhereExpression();
814
815 6
        if ($expression === null) {
816 5
            return [];
817
        }
818
819 1
        $valueVisitor = new SqlValueVisitor();
820
821 1
        $valueVisitor->dispatch($expression);
822
823 1
        [, $types] = $valueVisitor->getParamsAndTypes();
824
825 1
        return $types;
826
    }
827
828
    /**
829
     * @return string
830
     */
831 6
    private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass)
832
    {
833 6
        $orderings = $criteria->getOrderings();
834
835 6
        if ($orderings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $orderings of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
836 3
            $orderBy = [];
837
838 3
            foreach ($orderings as $name => $direction) {
839 3
                $property   = $targetClass->getProperty($name);
840 3
                $columnName = $this->platform->quoteIdentifier($property->getColumnName());
0 ignored issues
show
Bug introduced by
The method getColumnName() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

840
                $columnName = $this->platform->quoteIdentifier($property->/** @scrutinizer ignore-call */ getColumnName());
Loading history...
841
842 3
                $orderBy[] = $columnName . ' ' . $direction;
843
            }
844
845 3
            return ' ORDER BY ' . implode(', ', $orderBy);
846
        }
847
848 3
        return '';
849
    }
850
851
    /**
852
     * @return string
853
     *
854
     * @throws DBALException
855
     */
856 6
    private function getLimitSql(Criteria $criteria)
857
    {
858 6
        $limit  = $criteria->getMaxResults();
859 6
        $offset = $criteria->getFirstResult();
860 6
        if ($limit !== null || $offset !== null) {
861 3
            return $this->platform->modifyLimitQuery('', $limit, $offset ?? 0);
862
        }
863
864 3
        return '';
865
    }
866
}
867