Passed
Pull Request — master (#7448)
by Ilya
14:31
created

ManyToManyPersister::loadCriteria()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 62
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 5.0003

Importance

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

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 18
    public function delete(PersistentCollection $collection)
37
    {
38 18
        $association = $collection->getMapping();
39
40 18
        if (! $association->isOwningSide()) {
41
            return; // ignore inverse side
42
        }
43
44 18
        $class     = $this->em->getClassMetadata($association->getSourceEntity());
45 18
        $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 18
        $types     = [];
47
48 18
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
49
            /** @var JoinColumnMetadata $joinColumn */
50 18
            $referencedColumnName = $joinColumn->getReferencedColumnName();
51
52 18
            if (! $joinColumn->getType()) {
53
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em));
54
            }
55
56 18
            $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 18
        $sql    = $this->getDeleteSQL($collection);
60 18
        $params = $this->getDeleteSQLParameters($collection);
61
62 18
        $this->conn->executeUpdate($sql, $params, $types);
63 18
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68 329
    public function update(PersistentCollection $collection)
69
    {
70 329
        $association = $collection->getMapping();
71
72 329
        if (! $association->isOwningSide()) {
73 236
            return; // ignore inverse side
74
        }
75
76 328
        [$deleteSql, $deleteTypes] = $this->getDeleteRowSQL($collection);
77 328
        [$insertSql, $insertTypes] = $this->getInsertRowSQL($collection);
78
79 328
        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 328
        foreach ($collection->getInsertDiff() as $element) {
88 328
            $this->conn->executeUpdate(
89 328
                $insertSql,
90 328
                $this->getInsertRowSQLParameters($collection, $element),
91 328
                $insertTypes
92
            );
93
        }
94 328
    }
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 18
    public function count(PersistentCollection $collection)
124
    {
125 18
        $conditions        = [];
126 18
        $params            = [];
127 18
        $types             = [];
128 18
        $association       = $collection->getMapping();
129 18
        $identifier        = $this->uow->getEntityIdentifier($collection->getOwner());
130 18
        $sourceClass       = $this->em->getClassMetadata($association->getSourceEntity());
131 18
        $targetClass       = $this->em->getClassMetadata($association->getTargetEntity());
132 18
        $owningAssociation = ! $association->isOwningSide()
133 4
            ? $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 18
            : $association;
135
136 18
        $joinTable     = $owningAssociation->getJoinTable();
137 18
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
138 18
        $joinColumns   = $association->isOwningSide()
139 14
            ? $joinTable->getJoinColumns()
140 18
            : $joinTable->getInverseJoinColumns();
141
142 18
        foreach ($joinColumns as $joinColumn) {
143
            /** @var JoinColumnMetadata $joinColumn */
144 18
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
145 18
            $referencedColumnName = $joinColumn->getReferencedColumnName();
146
147 18
            if (! $joinColumn->getType()) {
148 1
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em));
149
            }
150
151 18
            $conditions[] = sprintf('t.%s = ?', $quotedColumnName);
152 18
            $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 18
            $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 18
        [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($association);
157
158 18
        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 18
            . ' FROM ' . $joinTableName . ' t'
180 18
            . $joinTargetEntitySQL
181 18
            . ' WHERE ' . implode(' AND ', $conditions);
182
183 18
        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) returns the type boolean|string which is incompatible with the return type mandated by Doctrine\ORM\Persisters\...ctionPersister::count() of integer.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
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
     * {@inheritdoc}
198
     */
199 7
    public function containsKey(PersistentCollection $collection, $key)
200
    {
201 7
        $association = $collection->getMapping();
202
203 7
        if (! ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy())) {
204
            throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
205
        }
206
207 7
        [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictionsWithKey($collection, $key, true);
208
209 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
210
211 7
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     */
217 7
    public function contains(PersistentCollection $collection, $element)
218
    {
219 7
        if (! $this->isValidEntityState($element)) {
220 2
            return false;
221
        }
222
223 7
        [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictions($collection, $element, true);
224
225 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
226
227 7
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
228
    }
229
230
    /**
231
     * {@inheritDoc}
232
     */
233 2
    public function removeElement(PersistentCollection $collection, $element)
234
    {
235 2
        if (! $this->isValidEntityState($element)) {
236 2
            return false;
237
        }
238
239 2
        [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictions($collection, $element, false);
240
241 2
        $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
242
243 2
        return (bool) $this->conn->executeUpdate($sql, $params, $types);
244
    }
245
246
    /**
247
     * {@inheritDoc}
248
     */
249 12
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
250
    {
251 12
        $association   = $collection->getMapping();
252 12
        $owner         = $collection->getOwner();
253 12
        $ownerMetadata = $this->em->getClassMetadata(get_class($owner));
254 12
        $identifier    = $this->uow->getEntityIdentifier($owner);
255 12
        $targetClass   = $this->em->getClassMetadata($association->getTargetEntity());
256 12
        $onConditions  = $this->getOnConditionSQL($association);
257 12
        $whereClauses  = $params = $types = [];
258
259 12
        if (! $association->isOwningSide()) {
260 1
            $association = $targetClass->getProperty($association->getMappedBy());
261 1
            $joinColumns = $association->getJoinTable()->getInverseJoinColumns();
262
        } else {
263 11
            $joinColumns = $association->getJoinTable()->getJoinColumns();
264
        }
265
266 12
        foreach ($joinColumns as $joinColumn) {
267
            /** @var JoinColumnMetadata $joinColumn */
268 12
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
269 12
            $referencedColumnName = $joinColumn->getReferencedColumnName();
270
271 12
            if (! $joinColumn->getType()) {
272
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $ownerMetadata, $this->em));
273
            }
274
275 12
            $whereClauses[] = sprintf('t.%s = ?', $quotedColumnName);
276 12
            $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...
277 12
            $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...
278
        }
279
280 12
        $parameters = $this->expandCriteriaParameters($criteria);
281
282 12
        foreach ($parameters as $parameter) {
283 7
            [$name, $value, $operator] = $parameter;
284
285 7
            $property   = $targetClass->getProperty($name);
286 7
            $columnName = $this->platform->quoteIdentifier($property->getColumnName());
287
288 7
            $whereClauses[] = sprintf('te.%s %s ?', $columnName, $operator);
289 7
            $params[]       = $value;
290 7
            $types[]        = $property->getType();
291
        }
292
293 12
        $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...
294 12
        $joinTableName    = $association->getJoinTable()->getQuotedQualifiedName($this->platform);
295 12
        $resultSetMapping = new Query\ResultSetMappingBuilder($this->em);
296
297 12
        $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

297
        $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...
298
299 12
        $sql = 'SELECT ' . $resultSetMapping->generateSelectClause()
300 12
            . ' FROM ' . $tableName . ' te'
301 12
            . ' JOIN ' . $joinTableName . ' t ON'
302 12
            . implode(' AND ', $onConditions)
303 12
            . ' WHERE ' . implode(' AND ', $whereClauses);
304
305 12
        $sql .= $this->getOrderingSql($criteria, $targetClass);
306 12
        $sql .= $this->getLimitSql($criteria);
307
308 12
        $stmt = $this->conn->executeQuery($sql, $params, $types);
309
310 12
        return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $resultSetMapping);
311
    }
312
313
    /**
314
     * Generates the filter SQL for a given mapping.
315
     *
316
     * This method is not used for actually grabbing the related entities
317
     * but when the extra-lazy collection methods are called on a filtered
318
     * association. This is why besides the many to many table we also
319
     * have to join in the actual entities table leading to additional
320
     * JOIN.
321
     *
322
     * @return string[] ordered tuple:
323
     *                   - JOIN condition to add to the SQL
324
     *                   - WHERE condition to add to the SQL
325
     */
326 32
    public function getFilterSql(ManyToManyAssociationMetadata $association)
327
    {
328 32
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
329 32
        $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

329
        $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...
330 32
        $filterSql   = $this->generateFilterConditionSQL($rootClass, 'te');
331
332 32
        if ($filterSql === '') {
333 32
            return ['', ''];
334
        }
335
336
        // A join is needed if there is filtering on the target entity
337 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...
338 6
        $joinSql   = ' JOIN ' . $tableName . ' te'
339 6
            . ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
340
341 6
        return [$joinSql, $filterSql];
342
    }
343
344
    /**
345
     * Generates the filter SQL for a given entity and table alias.
346
     *
347
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
348
     * @param string        $targetTableAlias The table alias of the joined/selected table.
349
     *
350
     * @return string The SQL query part to add to a query.
351
     */
352 32
    protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
353
    {
354 32
        $filterClauses = [];
355
356 32
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
357 6
            $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
358
359 6
            if ($filterExpr) {
360 6
                $filterClauses[] = '(' . $filterExpr . ')';
361
            }
362
        }
363
364 32
        if (! $filterClauses) {
365 32
            return '';
366
        }
367
368 6
        $filterSql = implode(' AND ', $filterClauses);
369
370 6
        return isset($filterClauses[1])
371
            ? '(' . $filterSql . ')'
372 6
            : $filterSql;
373
    }
374
375
    /**
376
     * Generate ON condition
377
     *
378
     * @return string[]
379
     */
380 18
    protected function getOnConditionSQL(ManyToManyAssociationMetadata $association)
381
    {
382 18
        $targetClass       = $this->em->getClassMetadata($association->getTargetEntity());
383 18
        $owningAssociation = ! $association->isOwningSide()
384 3
            ? $targetClass->getProperty($association->getMappedBy())
385 18
            : $association;
386
387 18
        $joinTable   = $owningAssociation->getJoinTable();
388 18
        $joinColumns = $association->isOwningSide()
389 15
            ? $joinTable->getInverseJoinColumns()
390 18
            : $joinTable->getJoinColumns();
391
392 18
        $conditions = [];
393
394 18
        foreach ($joinColumns as $joinColumn) {
395 18
            $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
396 18
            $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
397
398 18
            $conditions[] = ' t.' . $quotedColumnName . ' = te.' . $quotedReferencedColumnName;
399
        }
400
401 18
        return $conditions;
402
    }
403
404
    /**
405
     * {@inheritdoc}
406
     *
407
     * @override
408
     */
409 18
    protected function getDeleteSQL(PersistentCollection $collection)
410
    {
411 18
        $association   = $collection->getMapping();
412 18
        $joinTable     = $association->getJoinTable();
413 18
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
414 18
        $columns       = [];
415
416 18
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
417 18
            $columns[] = $this->platform->quoteIdentifier($joinColumn->getColumnName());
418
        }
419
420 18
        return 'DELETE FROM ' . $joinTableName . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
421
    }
422
423
    /**
424
     * {@inheritdoc}
425
     *
426
     * {@internal Order of the parameters must be the same as the order of the columns in getDeleteSql. }}
427
     *
428
     * @override
429
     */
430 18
    protected function getDeleteSQLParameters(PersistentCollection $collection)
431
    {
432 18
        $association = $collection->getMapping();
433 18
        $identifier  = $this->uow->getEntityIdentifier($collection->getOwner());
434 18
        $joinTable   = $association->getJoinTable();
435 18
        $joinColumns = $joinTable->getJoinColumns();
436
437
        // Optimization for single column identifier
438 18
        if (count($joinColumns) === 1) {
439 15
            return [reset($identifier)];
440
        }
441
442
        // Composite identifier
443 3
        $sourceClass = $this->em->getClassMetadata($association->getSourceEntity());
444 3
        $params      = [];
445
446 3
        foreach ($joinColumns as $joinColumn) {
447 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...
448
        }
449
450 3
        return $params;
451
    }
452
453
    /**
454
     * Gets the SQL statement used for deleting a row from the collection.
455
     *
456
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
457
     *                             of types for bound parameters
458
     */
459 328
    protected function getDeleteRowSQL(PersistentCollection $collection)
460
    {
461 328
        $association = $collection->getMapping();
462 328
        $class       = $this->em->getClassMetadata($association->getSourceEntity());
463 328
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
464 328
        $columns     = [];
465 328
        $types       = [];
466
467 328
        $joinTable     = $association->getJoinTable();
468 328
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
469
470 328
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
471
            /** @var JoinColumnMetadata $joinColumn */
472 328
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
473 328
            $referencedColumnName = $joinColumn->getReferencedColumnName();
474
475 328
            if (! $joinColumn->getType()) {
476 35
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em));
477
            }
478
479 328
            $columns[] = $quotedColumnName;
480 328
            $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...
481
        }
482
483 328
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
484
            /** @var JoinColumnMetadata $joinColumn */
485 328
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
486 328
            $referencedColumnName = $joinColumn->getReferencedColumnName();
487
488 328
            if (! $joinColumn->getType()) {
489 35
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
490
            }
491
492 328
            $columns[] = $quotedColumnName;
493 328
            $types[]   = $joinColumn->getType();
494
        }
495
496
        return [
497 328
            sprintf('DELETE FROM %s WHERE %s = ?', $joinTableName, implode(' = ? AND ', $columns)),
498 328
            $types,
499
        ];
500
    }
501
502
    /**
503
     * Gets the SQL parameters for the corresponding SQL statement to delete the given
504
     * element from the given collection.
505
     *
506
     * {@internal Order of the parameters must be the same as the order of the columns in getDeleteRowSql. }}
507
     *
508
     * @param mixed $element
509
     *
510
     * @return mixed[]
511
     */
512 10
    protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element)
513
    {
514 10
        return $this->collectJoinTableColumnParameters($collection, $element);
515
    }
516
517
    /**
518
     * Gets the SQL statement used for inserting a row in the collection.
519
     *
520
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
521
     *                             of types for bound parameters
522
     */
523 328
    protected function getInsertRowSQL(PersistentCollection $collection)
524
    {
525 328
        $association = $collection->getMapping();
526 328
        $class       = $this->em->getClassMetadata($association->getSourceEntity());
527 328
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
528 328
        $columns     = [];
529 328
        $types       = [];
530
531 328
        $joinTable     = $association->getJoinTable();
532 328
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
533
534 328
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
535
            /** @var JoinColumnMetadata $joinColumn */
536 328
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
537 328
            $referencedColumnName = $joinColumn->getReferencedColumnName();
538
539 328
            if (! $joinColumn->getType()) {
540
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em));
541
            }
542
543 328
            $columns[] = $quotedColumnName;
544 328
            $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...
545
        }
546
547 328
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
548
            /** @var JoinColumnMetadata $joinColumn */
549 328
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
550 328
            $referencedColumnName = $joinColumn->getReferencedColumnName();
551
552 328
            if (! $joinColumn->getType()) {
553
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
554
            }
555
556 328
            $columns[] = $quotedColumnName;
557 328
            $types[]   = $joinColumn->getType();
558
        }
559
560 328
        $columnNamesAsString  = implode(', ', $columns);
561 328
        $columnValuesAsString = implode(', ', array_fill(0, count($columns), '?'));
562
563
        return [
564 328
            sprintf('INSERT INTO %s (%s) VALUES (%s)', $joinTableName, $columnNamesAsString, $columnValuesAsString),
565 328
            $types,
566
        ];
567
    }
568
569
    /**
570
     * Gets the SQL parameters for the corresponding SQL statement to insert the given
571
     * element of the given collection into the database.
572
     *
573
     * {@internal Order of the parameters must be the same as the order of the columns in getInsertRowSql. }}
574
     *
575
     * @param mixed $element
576
     *
577
     * @return mixed[]
578
     */
579 328
    protected function getInsertRowSQLParameters(PersistentCollection $collection, $element)
580
    {
581 328
        return $this->collectJoinTableColumnParameters($collection, $element);
582
    }
583
584
    /**
585
     * Collects the parameters for inserting/deleting on the join table in the order
586
     * of the join table columns.
587
     *
588
     * @param object $element
589
     *
590
     * @return mixed[]
591
     */
592 328
    private function collectJoinTableColumnParameters(PersistentCollection $collection, $element)
593
    {
594 328
        $params           = [];
595 328
        $association      = $collection->getMapping();
596 328
        $owningClass      = $this->em->getClassMetadata(get_class($collection->getOwner()));
597 328
        $targetClass      = $collection->getTypeClass();
598 328
        $owningIdentifier = $this->uow->getEntityIdentifier($collection->getOwner());
599 328
        $targetIdentifier = $this->uow->getEntityIdentifier($element);
600 328
        $joinTable        = $association->getJoinTable();
601
602 328
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
603 328
            $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...
604
605 328
            $params[] = $owningIdentifier[$fieldName];
606
        }
607
608 328
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
609 328
            $fieldName = $targetClass->fieldNames[$joinColumn->getReferencedColumnName()];
610
611 328
            $params[] = $targetIdentifier[$fieldName];
612
        }
613
614 328
        return $params;
615
    }
616
617
    /**
618
     * @param string $key
619
     * @param bool   $addFilters Whether the filter SQL should be included or not.
620
     *
621
     * @return mixed[] ordered vector:
622
     *                - quoted join table name
623
     *                - where clauses to be added for filtering
624
     *                - parameters to be bound for filtering
625
     *                - types of the parameters to be bound for filtering
626
     */
627 7
    private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters)
628
    {
629 7
        $association       = $collection->getMapping();
630 7
        $owningAssociation = $association;
631 7
        $indexBy           = $owningAssociation->getIndexedBy();
632 7
        $identifier        = $this->uow->getEntityIdentifier($collection->getOwner());
633 7
        $sourceClass       = $this->em->getClassMetadata($owningAssociation->getSourceEntity());
634 7
        $targetClass       = $this->em->getClassMetadata($owningAssociation->getTargetEntity());
635
636 7
        if (! $owningAssociation->isOwningSide()) {
637 3
            $owningAssociation  = $targetClass->getProperty($owningAssociation->getMappedBy());
638 3
            $joinTable          = $owningAssociation->getJoinTable();
639 3
            $joinColumns        = $joinTable->getJoinColumns();
640 3
            $inverseJoinColumns = $joinTable->getInverseJoinColumns();
641
        } else {
642 4
            $joinTable          = $owningAssociation->getJoinTable();
643 4
            $joinColumns        = $joinTable->getInverseJoinColumns();
644 4
            $inverseJoinColumns = $joinTable->getJoinColumns();
645
        }
646
647 7
        $joinTableName   = $joinTable->getQuotedQualifiedName($this->platform);
648 7
        $quotedJoinTable = $joinTableName . ' t';
649 7
        $whereClauses    = [];
650 7
        $params          = [];
651 7
        $types           = [];
652 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...
653
654 7
        if ($joinNeeded) { // extra join needed if indexBy is not a @id
655 3
            $joinConditions = [];
656
657 3
            foreach ($joinColumns as $joinColumn) {
658
                /** @var JoinColumnMetadata $joinColumn */
659 3
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
660 3
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
661
662 3
                $joinConditions[] = ' t.' . $quotedColumnName . ' = tr.' . $quotedReferencedColumnName;
663
            }
664
665 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...
666 3
            $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
667 3
            $indexByProperty  = $targetClass->getProperty($indexBy);
668
669
            switch (true) {
670 3
                case $indexByProperty instanceof FieldMetadata:
671 3
                    $quotedColumnName = $this->platform->quoteIdentifier($indexByProperty->getColumnName());
672
673 3
                    $whereClauses[] = sprintf('tr.%s = ?', $quotedColumnName);
674 3
                    $params[]       = $key;
675 3
                    $types[]        = $indexByProperty->getType();
676 3
                    break;
677
678
                case $indexByProperty instanceof ToOneAssociationMetadata && $indexByProperty->isOwningSide():
679
                    // Cannot be supported because PHP does not accept objects as keys. =(
680
                    break;
681
            }
682
        }
683
684 7
        foreach ($inverseJoinColumns as $joinColumn) {
685
            /** @var JoinColumnMetadata $joinColumn */
686 7
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
687 7
            $referencedColumnName = $joinColumn->getReferencedColumnName();
688
689 7
            if (! $joinColumn->getType()) {
690
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em));
691
            }
692
693 7
            $whereClauses[] = sprintf('t.%s = ?', $quotedColumnName);
694 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...
695 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...
696
        }
697
698 7
        if (! $joinNeeded) {
699 4
            foreach ($joinColumns as $joinColumn) {
700
                /** @var JoinColumnMetadata $joinColumn */
701 4
                $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
702 4
                $referencedColumnName = $joinColumn->getReferencedColumnName();
703
704 4
                if (! $joinColumn->getType()) {
705
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
706
                }
707
708 4
                $whereClauses[] = sprintf('t.%s = ?', $quotedColumnName);
709 4
                $params[]       = $key;
710 4
                $types[]        = $joinColumn->getType();
711
            }
712
        }
713
714 7
        if ($addFilters) {
715 7
            [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($association);
716
717 7
            if ($filterSql) {
718
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
719
                $whereClauses[]   = $filterSql;
720
            }
721
        }
722
723 7
        return [$quotedJoinTable, $whereClauses, $params, $types];
724
    }
725
726
    /**
727
     * @param object $element
728
     * @param bool   $addFilters Whether the filter SQL should be included or not.
729
     *
730
     * @return mixed[] ordered vector:
731
     *                - quoted join table name
732
     *                - where clauses to be added for filtering
733
     *                - parameters to be bound for filtering
734
     *                - types of the parameters to be bound for filtering
735
     */
736 9
    private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters)
737
    {
738 9
        $association       = $collection->getMapping();
739 9
        $owningAssociation = $association;
740
741 9
        if (! $association->isOwningSide()) {
742 4
            $sourceClass      = $this->em->getClassMetadata($association->getTargetEntity());
743 4
            $targetClass      = $this->em->getClassMetadata($association->getSourceEntity());
744 4
            $sourceIdentifier = $this->uow->getEntityIdentifier($element);
745 4
            $targetIdentifier = $this->uow->getEntityIdentifier($collection->getOwner());
746
747 4
            $owningAssociation = $sourceClass->getProperty($association->getMappedBy());
748
        } else {
749 5
            $sourceClass      = $this->em->getClassMetadata($association->getSourceEntity());
750 5
            $targetClass      = $this->em->getClassMetadata($association->getTargetEntity());
751 5
            $sourceIdentifier = $this->uow->getEntityIdentifier($collection->getOwner());
752 5
            $targetIdentifier = $this->uow->getEntityIdentifier($element);
753
        }
754
755 9
        $joinTable       = $owningAssociation->getJoinTable();
756 9
        $joinTableName   = $joinTable->getQuotedQualifiedName($this->platform);
757 9
        $quotedJoinTable = $joinTableName;
758 9
        $whereClauses    = [];
759 9
        $params          = [];
760 9
        $types           = [];
761
762 9
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
763
            /** @var JoinColumnMetadata $joinColumn */
764 9
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
765 9
            $referencedColumnName = $joinColumn->getReferencedColumnName();
766
767 9
            if (! $joinColumn->getType()) {
768
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em));
769
            }
770
771 9
            $whereClauses[] = ($addFilters ? 't.' : '') . $quotedColumnName . ' = ?';
772 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...
773 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...
774
        }
775
776 9
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
777
            /** @var JoinColumnMetadata $joinColumn */
778 9
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
779 9
            $referencedColumnName = $joinColumn->getReferencedColumnName();
780
781 9
            if (! $joinColumn->getType()) {
782
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
783
            }
784
785 9
            $whereClauses[] = ($addFilters ? 't.' : '') . $quotedColumnName . ' = ?';
786 9
            $params[]       = $targetIdentifier[$targetClass->fieldNames[$referencedColumnName]];
787 9
            $types[]        = $joinColumn->getType();
788
        }
789
790 9
        if ($addFilters) {
791 7
            $quotedJoinTable .= ' t';
792
793 7
            [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($association);
794
795 7
            if ($filterSql) {
796 3
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
797 3
                $whereClauses[]   = $filterSql;
798
            }
799
        }
800
801 9
        return [$quotedJoinTable, $whereClauses, $params, $types];
802
    }
803
804
    /**
805
     * Expands Criteria Parameters by walking the expressions and grabbing all
806
     * parameters and types from it.
807
     *
808
     * @return mixed[]
809
     */
810 12
    private function expandCriteriaParameters(Criteria $criteria)
811
    {
812 12
        $expression = $criteria->getWhereExpression();
813
814 12
        if ($expression === null) {
815 5
            return [];
816
        }
817
818 7
        $valueVisitor = new SqlValueVisitor();
819
820 7
        $valueVisitor->dispatch($expression);
821
822 7
        [, $types] = $valueVisitor->getParamsAndTypes();
823
824 7
        return $types;
825
    }
826
827
    /**
828
     * @return string
829
     */
830 12
    private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass)
831
    {
832 12
        $orderings = $criteria->getOrderings();
833
834 12
        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...
835 3
            $orderBy = [];
836
837 3
            foreach ($orderings as $name => $direction) {
838 3
                $property   = $targetClass->getProperty($name);
839 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

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