Test Setup Failed
Push — develop ( 082d66...6f26e1 )
by Guilherme
63:04
created

ManyToManyPersister::getInsertRowSQL()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 45
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 45
ccs 19
cts 19
cp 1
rs 8.439
c 0
b 0
f 0
cc 5
eloc 27
nc 9
nop 1
crap 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Persisters\Collection;
6
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\ORM\Mapping\AssociationMetadata;
9
use Doctrine\ORM\Mapping\ClassMetadata;
10
use Doctrine\ORM\Mapping\FieldMetadata;
11
use Doctrine\ORM\Mapping\JoinColumnMetadata;
12
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
13
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
14
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
15
use Doctrine\ORM\Persisters\SqlValueVisitor;
16
use Doctrine\ORM\PersistentCollection;
17
use Doctrine\ORM\Query;
18
use Doctrine\ORM\Utility\PersisterHelper;
19
20
/**
21
 * Persister for many-to-many collections.
22
 *
23
 * @author  Roman Borschel <[email protected]>
24
 * @author  Guilherme Blanco <[email protected]>
25
 * @author  Alexander <[email protected]>
26
 * @since   2.0
27
 */
28
class ManyToManyPersister extends AbstractCollectionPersister
29
{
30
    /**
31
     * {@inheritdoc}
32
     */
33
    public function delete(PersistentCollection $collection)
34
    {
35
        $association = $collection->getMapping();
36
37
        if (! $association->isOwningSide()) {
38
            return; // ignore inverse side
39
        }
40
41
        $class     = $this->em->getClassMetadata($association->getSourceEntity());
42 17
        $joinTable = $association->getJoinTable();
43
        $types     = [];
44 17
45
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
46 17
            /** @var JoinColumnMetadata $joinColumn */
47
            $referencedColumnName = $joinColumn->getReferencedColumnName();
48
49
            if (! $joinColumn->getType()) {
50 17
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em));
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
51 17
            }
52
53 17
            $types[] = $joinColumn->getType();
54 17
        }
55
56
        $sql    = $this->getDeleteSQL($collection);
57 17
        $params = $this->getDeleteSQLParameters($collection);
58 17
59
        $this->conn->executeUpdate($sql, $params, $types);
60
    }
61
62
    /**
63 330
     * {@inheritdoc}
64
     */
65 330
    public function update(PersistentCollection $collection)
66
    {
67 330
        $association = $collection->getMapping();
68 231
69
        if (! $association->isOwningSide()) {
70
            return; // ignore inverse side
71 329
        }
72 329
73
        list($deleteSql, $deleteTypes) = $this->getDeleteRowSQL($collection);
74 329
        list($insertSql, $insertTypes) = $this->getInsertRowSQL($collection);
75 12
76
        foreach ($collection->getDeleteDiff() as $element) {
77 12
            $this->conn->executeUpdate(
78
                $deleteSql,
79
                $this->getDeleteRowSQLParameters($collection, $element),
80
                $deleteTypes
81
            );
82 329
        }
83 329
84
        foreach ($collection->getInsertDiff() as $element) {
85 329
            $this->conn->executeUpdate(
86
                $insertSql,
87
                $this->getInsertRowSQLParameters($collection, $element),
88
                $insertTypes
89 329
            );
90
        }
91
    }
92
93
    /**
94 3
     * {@inheritdoc}
95
     */
96 3
    public function get(PersistentCollection $collection, $index)
97
    {
98 3
        $association = $collection->getMapping();
99
100
        if (! ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy())) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $association->getIndexedBy() of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
101
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
102 3
        }
103 3
104 2
        $persister = $this->uow->getEntityPersister($association->getTargetEntity());
105 3
        $mappedKey = $association->isOwningSide()
106
            ? $association->getInversedBy()
107 3
            : $association->getMappedBy()
108
        ;
109
110
        $criteria  = [
111
            $mappedKey                   => $collection->getOwner(),
112
            $association->getIndexedBy() => $index,
113 17
        ];
114
115 17
        return $persister->load($criteria, null, $association, [], 0, 1);
116 17
    }
117 17
118 17
    /**
119 17
     * {@inheritdoc}
120 17
     */
121 17
    public function count(PersistentCollection $collection)
122 17
    {
123 4
        $conditions        = [];
124 17
        $params            = [];
125
        $types             = [];
126 17
        $association       = $collection->getMapping();
127 17
        $identifier        = $this->uow->getEntityIdentifier($collection->getOwner());
128 4
        $sourceClass       = $this->em->getClassMetadata($association->getSourceEntity());
129 17
        $targetClass       = $this->em->getClassMetadata($association->getTargetEntity());
130
        $owningAssociation = ! $association->isOwningSide()
131 17
            ? $targetClass->getProperty($association->getMappedBy())
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
132 17
            : $association
133 17
        ;
134
135 17
        $joinTable      = $owningAssociation->getJoinTable();
136 17
        $joinTableName  = $joinTable->getQuotedQualifiedName($this->platform);
137 17
        $joinColumns    = $association->isOwningSide()
138
            ? $joinTable->getJoinColumns()
139
            : $joinTable->getInverseJoinColumns()
140 17
        ;
141
142 17 View Code Duplication
        foreach ($joinColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143 3
            /** @var JoinColumnMetadata $joinColumn */
144
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
145
            $referencedColumnName = $joinColumn->getReferencedColumnName();
146
147
            if (! $joinColumn->getType()) {
148
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em));
0 ignored issues
show
Documentation introduced by
$sourceClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
149
            }
150
151
            $conditions[]   = sprintf('t.%s = ?', $quotedColumnName);
152
            $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
153
            $types[]        = $joinColumn->getType();
154
        }
155
156
        list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($association);
0 ignored issues
show
Compatibility introduced by
$association of type object<Doctrine\ORM\Mapp...anyAssociationMetadata> is not a sub-type of object<Doctrine\ORM\Mapp...anyAssociationMetadata>. It seems like you assume a child class of the class Doctrine\ORM\Mapping\ToManyAssociationMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
157
158
        if ($filterSql) {
159
            $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 17
        //
165 17
        /*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
166 17
            // A join is needed on the target entity
167
            $targetTableName = $targetClass->table->getQuotedQualifiedName($this->platform);
168 17
            $targetJoinSql   = ' JOIN ' . $targetTableName . ' te'
169
                . ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
170
171
            // And criteria conditions needs to be added
172
            $persister    = $this->uow->getEntityPersister($targetClass->getClassName());
173
            $visitor      = new SqlExpressionVisitor($persister, $targetClass);
174 8
            $conditions[] = $visitor->dispatch($expression);
175
176 8
            $joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL;
177 8
        }*/
178
179 8
        $sql = 'SELECT COUNT(*)'
180
            . ' FROM ' . $joinTableName . ' t'
181
            . $joinTargetEntitySQL
182
            . ' WHERE ' . implode(' AND ', $conditions);
183
184 7
        return $this->conn->fetchColumn($sql, $params, 0, $types);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->conn->fetchColumn...l, $params, 0, $types); of type string|boolean adds the type boolean to the return on line 184 which is incompatible with the return type declared by the interface Doctrine\ORM\Persisters\...lectionPersister::count of type integer.
Loading history...
185
    }
186 7
187
    /**
188 7
     * {@inheritDoc}
189
     */
190 View Code Duplication
    public function slice(PersistentCollection $collection, $offset, $length = null)
191
    {
192 7
        $association = $collection->getMapping();
193
        $persister   = $this->uow->getEntityPersister($association->getTargetEntity());
194 7
195
        return $persister->getManyToManyCollection($association, $collection->getOwner(), $offset, $length);
0 ignored issues
show
Compatibility introduced by
$association of type object<Doctrine\ORM\Mapp...anyAssociationMetadata> is not a sub-type of object<Doctrine\ORM\Mapp...anyAssociationMetadata>. It seems like you assume a child class of the class Doctrine\ORM\Mapping\ToManyAssociationMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
196 7
    }
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function containsKey(PersistentCollection $collection, $key)
201
    {
202 7
        $association = $collection->getMapping();
203
204 7
        if (! ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy())) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $association->getIndexedBy() of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
205 2
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
206
        }
207
208 7
        list($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 2 View Code Duplication
    public function contains(PersistentCollection $collection, $element)
219
    {
220 2
        if ( ! $this->isValidEntityState($element)) {
221 2
            return false;
222
        }
223
224 2
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, true);
225
226 2
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
227
228 2
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234 7 View Code Duplication
    public function removeElement(PersistentCollection $collection, $element)
235
    {
236 7
        if ( ! $this->isValidEntityState($element)) {
237 7
            return false;
238 7
        }
239 7
240 7
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, false);
241 7
242 7
        $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
243
244 7
        return (bool) $this->conn->executeUpdate($sql, $params, $types);
245 1
    }
246 1
247 1
    /**
248
     * {@inheritDoc}
249 6
     */
250 6
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
251
    {
252
        $association   = $collection->getMapping();
253 7
        $owner         = $collection->getOwner();
254 7
        $ownerMetadata = $this->em->getClassMetadata(get_class($owner));
255 7
        $identifier    = $this->uow->getEntityIdentifier($owner);
256
        $targetClass   = $this->em->getClassMetadata($association->getTargetEntity());
257
        $onConditions  = $this->getOnConditionSQL($association);
0 ignored issues
show
Compatibility introduced by
$association of type object<Doctrine\ORM\Mapp...anyAssociationMetadata> is not a sub-type of object<Doctrine\ORM\Mapp...anyAssociationMetadata>. It seems like you assume a child class of the class Doctrine\ORM\Mapping\ToManyAssociationMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
258 7
        $whereClauses  = $params = $types = [];
259
260 7
        if (! $association->isOwningSide()) {
261 2
            $association = $targetClass->getProperty($association->getMappedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
262
            $joinColumns = $association->getJoinTable()->getInverseJoinColumns();
263 2
        } else {
264 2
            $joinColumns = $association->getJoinTable()->getJoinColumns();
265
        }
266 2
267 2 View Code Duplication
        foreach ($joinColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
268
            /** @var JoinColumnMetadata $joinColumn */
269
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
270 7
            $referencedColumnName = $joinColumn->getReferencedColumnName();
271 7
272
            if (! $joinColumn->getType()) {
273 7
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $ownerMetadata, $this->em));
0 ignored issues
show
Documentation introduced by
$ownerMetadata is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
274 7
            }
275
276 7
            $whereClauses[] = sprintf('t.%s = ?', $quotedColumnName);
277 7
            $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
278 7
            $types[] = $joinColumn->getType();
279 7
        }
280 7
281
        $parameters = $this->expandCriteriaParameters($criteria);
282 7
283
        foreach ($parameters as $parameter) {
284 7
            list($name, $value) = $parameter;
285
286 7
            $property   = $targetClass->getProperty($name);
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
287
            $columnName = $this->platform->quoteIdentifier($property->getColumnName());
288
289 7
            $whereClauses[] = sprintf('te.%s = ?', $columnName);
290 7
            $params[]       = $value;
291 7
            $types[]        = $property->getType();
292
        }
293
294
        $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
295
        $joinTableName    = $association->getJoinTable()->getQuotedQualifiedName($this->platform);
296
        $resultSetMapping = new Query\ResultSetMappingBuilder($this->em);
297
298
        $resultSetMapping->addRootEntityFromClassMetadata($targetClass->getClassName(), 'te');
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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
        $sql = 'SELECT ' . $resultSetMapping->generateSelectClause()
301
            . ' FROM ' . $tableName . ' te'
302
            . ' JOIN ' . $joinTableName . ' t ON'
303
            . implode(' AND ', $onConditions)
304
            . ' WHERE ' . implode(' AND ', $whereClauses);
305
306
        $sql .= $this->getOrderingSql($criteria, $targetClass);
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
307
        $sql .= $this->getLimitSql($criteria);
308
309 31
        $stmt = $this->conn->executeQuery($sql, $params, $types);
310
311 31
        return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $resultSetMapping);
312 31
    }
313 31
314
    /**
315 31
     * Generates the filter SQL for a given mapping.
316 31
     *
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 6
     * have to join in the actual entities table leading to additional
321 6
     * JOIN.
322 6
     *
323
     * @param ManyToManyAssociationMetadata $association
324 6
     *
325
     * @return string[] ordered tuple:
326
     *                   - JOIN condition to add to the SQL
327
     *                   - WHERE condition to add to the SQL
328
     */
329
    public function getFilterSql(ManyToManyAssociationMetadata $association)
330
    {
331
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
332
        $rootClass   = $this->em->getClassMetadata($targetClass->getRootClassName());
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
333
        $filterSql   = $this->generateFilterConditionSQL($rootClass, 'te');
0 ignored issues
show
Documentation introduced by
$rootClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
334
335 31
        if ('' === $filterSql) {
336
            return ['', ''];
337 31
        }
338
339 31
        // A join is needed if there is filtering on the target entity
340 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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
341 6
        $joinSql   = ' JOIN ' . $tableName . ' te'
342
            . ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
343
344
        return [$joinSql, $filterSql];
345 31
    }
346 6
347 31
    /**
348
     * Generates the filter SQL for a given entity and table alias.
349
     *
350
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
351
     * @param string        $targetTableAlias The table alias of the joined/selected table.
352
     *
353
     * @return string The SQL query part to add to a query.
354
     */
355
    protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
356
    {
357 13
        $filterClauses = [];
358
359 13 View Code Duplication
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
360 13
            if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
361 3
                $filterClauses[] = '(' . $filterExpr . ')';
362 13
            }
363
        }
364 13
365 10
        if (! $filterClauses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filterClauses of type array 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...
366 13
            return '';
367
        }
368 13
369
        $filterSql = implode(' AND ', $filterClauses);
370 13
371 13
        return count($filterClauses) > 1
372 13
            ? '(' . $filterSql . ')'
373
            : $filterSql
374 13
        ;
375
    }
376
377 13
    /**
378
     * Generate ON condition
379
     *
380
     * @param ManyToManyAssociationMetadata $association
381
     *
382
     * @return array
383
     */
384
    protected function getOnConditionSQL(ManyToManyAssociationMetadata $association)
385 17
    {
386
        $targetClass       = $this->em->getClassMetadata($association->getTargetEntity());
387 17
        $owningAssociation = ! $association->isOwningSide()
388 17
            ? $targetClass->getProperty($association->getMappedBy())
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
389 17
            : $association;
390 17
391
        $joinTable   = $owningAssociation->getJoinTable();
392 17
        $joinColumns = $association->isOwningSide()
393 17
            ? $joinTable->getInverseJoinColumns()
394
            : $joinTable->getJoinColumns()
395
        ;
396 17
397 17
        $conditions = [];
398
399 View Code Duplication
        foreach ($joinColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
400
            $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
401
            $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
402
403
            $conditions[] = ' t.' . $quotedColumnName . ' = ' . 'te.' . $quotedReferencedColumnName;
404
        }
405
406 17
        return $conditions;
407
    }
408 17
409 17
    /**
410
     * {@inheritdoc}
411
     *
412 17
     * @override
413 15
     */
414
    protected function getDeleteSQL(PersistentCollection $collection)
415
    {
416
        $association   = $collection->getMapping();
417 2
        $joinTable     = $association->getJoinTable();
418 2
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
419
        $columns       = [];
420 2
421 2
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
422
            $columns[] = $this->platform->quoteIdentifier($joinColumn->getColumnName());
423
        }
424 2
425
        return 'DELETE FROM ' . $joinTableName . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
426
    }
427
428
    /**
429
     * {@inheritdoc}
430
     *
431
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql.
432
     * @override
433
     */
434
    protected function getDeleteSQLParameters(PersistentCollection $collection)
435 329
    {
436
        $association = $collection->getMapping();
437 329
        $identifier  = $this->uow->getEntityIdentifier($collection->getOwner());
438 329
        $joinTable   = $association->getJoinTable();
439 329
        $joinColumns = $joinTable->getJoinColumns();
440 329
441 329
        // Optimization for single column identifier
442
        if (count($joinColumns) === 1) {
443 329
            return [reset($identifier)];
444 329
        }
445 329
446
        // Composite identifier
447
        $sourceClass = $this->em->getClassMetadata($association->getSourceEntity());
448 329
        $params      = [];
449 329
450 329
        foreach ($joinColumns as $joinColumn) {
451
            $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
452
        }
453
454 329
        return $params;
455 329
    }
456 329
457
    /**
458
     * Gets the SQL statement used for deleting a row from the collection.
459
     *
460
     * @param \Doctrine\ORM\PersistentCollection $collection
461
     *
462
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
463
     *                             of types for bound parameters
464
     */
465
    protected function getDeleteRowSQL(PersistentCollection $collection)
466
    {
467
        $association = $collection->getMapping();
468
        $class       = $this->em->getClassMetadata($association->getSourceEntity());
469
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
470
        $columns     = [];
471 12
        $types       = [];
472
473 12
        $joinTable     = $association->getJoinTable();
474
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
475
476
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
477
            /** @var JoinColumnMetadata $joinColumn */
478
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
479
            $referencedColumnName = $joinColumn->getReferencedColumnName();
480
481
            if (! $joinColumn->getType()) {
482
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em));
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
483
            }
484 329
485
            $columns[] = $quotedColumnName;
486 329
            $types[]   = $joinColumn->getType();
487 329
        }
488 329
489 329
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
490 329
            /** @var JoinColumnMetadata $joinColumn */
491
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
492 329
            $referencedColumnName = $joinColumn->getReferencedColumnName();
493 329
494 329
            if (! $joinColumn->getType()) {
495
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
496
            }
497 329
498 329
            $columns[] = $quotedColumnName;
499 329
            $types[]   = $joinColumn->getType();
500
        }
501
502
        return [
503 329
            sprintf('DELETE FROM %s WHERE %s = ?', $joinTableName, implode(' = ? AND ', $columns)),
504 329
            $types,
505 329
        ];
506 329
    }
507 329
508
    /**
509
     * Gets the SQL parameters for the corresponding SQL statement to delete the given
510
     * element from the given collection.
511
     *
512
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql.
513
     *
514
     * @param \Doctrine\ORM\PersistentCollection $collection
515
     * @param mixed                              $element
516
     *
517
     * @return array
518
     */
519
    protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element)
520
    {
521
        return $this->collectJoinTableColumnParameters($collection, $element);
522 329
    }
523
524 329
    /**
525
     * Gets the SQL statement used for inserting a row in the collection.
526
     *
527
     * @param \Doctrine\ORM\PersistentCollection $collection
528
     *
529
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
530
     *                             of types for bound parameters
531
     */
532
    protected function getInsertRowSQL(PersistentCollection $collection)
533
    {
534
        $association = $collection->getMapping();
535
        $class       = $this->em->getClassMetadata($association->getSourceEntity());
536 329
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
537
        $columns     = [];
538 329
        $types       = [];
539 329
540 329
        $joinTable     = $association->getJoinTable();
541
        $joinTableName = $joinTable->getQuotedQualifiedName($this->platform);
542 329
543 329
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
544
            /** @var JoinColumnMetadata $joinColumn */
545 329
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
546 22
            $referencedColumnName = $joinColumn->getReferencedColumnName();
547 22
548
            if (! $joinColumn->getType()) {
549
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $class, $this->em));
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
550 329
            }
551 329
552
            $columns[] = $quotedColumnName;
553 329
            $types[]   = $joinColumn->getType();
554 307
        }
555
556 307
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
557
            /** @var JoinColumnMetadata $joinColumn */
558
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
559 22
            $referencedColumnName = $joinColumn->getReferencedColumnName();
560 22
561
            if (! $joinColumn->getType()) {
562 22
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
563
            }
564
565 22
            $columns[] = $quotedColumnName;
566
            $types[]   = $joinColumn->getType();
567
        }
568 329
569
        $columnNamesAsString  = implode(', ', $columns);
570
        $columnValuesAsString = implode(', ', array_fill(0, count($columns), '?'));
571
572
        return [
573
            sprintf('INSERT INTO %s (%s) VALUES (%s)', $joinTableName, $columnNamesAsString, $columnValuesAsString),
574
            $types,
575
        ];
576
    }
577
578
    /**
579
     * Gets the SQL parameters for the corresponding SQL statement to insert the given
580
     * element of the given collection into the database.
581
     *
582 7
     * Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql.
583
     *
584 7
     * @param \Doctrine\ORM\PersistentCollection $collection
585 7
     * @param mixed                              $element
586 7
     *
587 7
     * @return array
588 7
     */
589 7
    protected function getInsertRowSQLParameters(PersistentCollection $collection, $element)
590
    {
591 7
        return $this->collectJoinTableColumnParameters($collection, $element);
592 3
    }
593 3
594 3
    /**
595 3
     * Collects the parameters for inserting/deleting on the join table in the order
596 3
     * of the join table columns.
597
     *
598 4
     * @param \Doctrine\ORM\PersistentCollection $collection
599 4
     * @param object                             $element
600 4
     *
601 4
     * @return array
602
     */
603
    private function collectJoinTableColumnParameters(PersistentCollection $collection, $element)
604 7
    {
605 7
        $params           = [];
606 7
        $association      = $collection->getMapping();
607 7
        $owningClass      = $this->em->getClassMetadata(get_class($collection->getOwner()));
608
        $targetClass      = $collection->getTypeClass();
609 7
        $owningIdentifier = $this->uow->getEntityIdentifier($collection->getOwner());
610
        $targetIdentifier = $this->uow->getEntityIdentifier($element);
611 7
        $joinTable        = $association->getJoinTable();
612 3
613
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
614 3
            $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
615 3
616
            $params[] = $owningIdentifier[$fieldName];
617
        }
618 3
619 3
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
620 3
            $fieldName = $targetClass->fieldNames[$joinColumn->getReferencedColumnName()];
621
622 3
            $params[] = $targetIdentifier[$fieldName];
623 3
        }
624 3
625
        return $params;
626
    }
627 7
628 7
    /**
629 7
     * @param \Doctrine\ORM\PersistentCollection $collection
630 7
     * @param string                             $key
631 7
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
632 7
     *
633 7
     * @return array ordered vector:
634 4
     *                - quoted join table name
635
     *                - where clauses to be added for filtering
636 4
     *                - parameters to be bound for filtering
637 4
     *                - types of the parameters to be bound for filtering
638 7
     */
639
    private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters)
640
    {
641
        $association       = $collection->getMapping();
642 7
        $owningAssociation = $association;
643 7
        $indexBy           = $owningAssociation->getIndexedBy();
644
        $identifier        = $this->uow->getEntityIdentifier($collection->getOwner());
645 7
        $sourceClass       = $this->em->getClassMetadata($owningAssociation->getSourceEntity());
646
        $targetClass       = $this->em->getClassMetadata($owningAssociation->getTargetEntity());
647
648
        if (! $owningAssociation->isOwningSide()) {
649
            $owningAssociation  = $targetClass->getProperty($owningAssociation->getMappedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
650
            $joinTable          = $owningAssociation->getJoinTable();
651 7
            $joinColumns        = $joinTable->getJoinColumns();
652
            $inverseJoinColumns = $joinTable->getInverseJoinColumns();
653
        } else {
654
            $joinTable          = $owningAssociation->getJoinTable();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\ORM\Mapping\ToManyAssociationMetadata as the method getJoinTable() does only exist in the following sub-classes of Doctrine\ORM\Mapping\ToManyAssociationMetadata: Doctrine\ORM\Mapping\ManyToManyAssociationMetadata. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
655
            $joinColumns        = $joinTable->getInverseJoinColumns();
656
            $inverseJoinColumns = $joinTable->getJoinColumns();
657
        }
658
659
        $joinTableName   = $joinTable->getQuotedQualifiedName($this->platform);
660
        $quotedJoinTable = $joinTableName . ' t';
661
        $whereClauses    = [];
662
        $params          = [];
663
        $types           = [];
664
        $joinNeeded      = ! in_array($indexBy, $targetClass->identifier);
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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
665 9
666
        if ($joinNeeded) { // extra join needed if indexBy is not a @id
667 9
            $joinConditions = [];
668 9
669 View Code Duplication
            foreach ($joinColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
670 9
                /** @var JoinColumnMetadata $joinColumn */
671 4
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
672 4
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
673 4
674 4
                $joinConditions[] = ' t.' . $quotedColumnName . ' = ' . 'tr.' . $quotedReferencedColumnName;
675
            }
676 4
677
            $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
678 5
            $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
679 5
            $indexByProperty  = $targetClass->getProperty($indexBy);
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
680 5
681 5
            switch (true) {
682 View Code Duplication
                case ($indexByProperty instanceof FieldMetadata):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
683
                    $quotedColumnName = $this->platform->quoteIdentifier($indexByProperty->getColumnName());
684 9
685 9
                    $whereClauses[] = sprintf('tr.%s = ?', $quotedColumnName);
686 9
                    $params[]       = $key;
687 9
                    $types[]        = $indexByProperty->getType();
688
                    break;
689 9
690 9
                case ($indexByProperty instanceof ToOneAssociationMetadata && $indexByProperty->isOwningSide()):
691
                    // Cannot be supported because PHP does not accept objects as keys. =(
692 9
                    break;
693 9
            }
694 9
        }
695 9
696 View Code Duplication
        foreach ($inverseJoinColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
697 9
            /** @var JoinColumnMetadata $joinColumn */
698
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
699
            $referencedColumnName = $joinColumn->getReferencedColumnName();
700
701 9
            if (! $joinColumn->getType()) {
702 9
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em));
0 ignored issues
show
Documentation introduced by
$sourceClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
703 9
            }
704
705
            $whereClauses[] = sprintf('t.%s = ?', $quotedColumnName);
706 9
            $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
707 7
            $types[]        = $joinColumn->getType();
708
        }
709 7
710
        if (! $joinNeeded) {
711 7 View Code Duplication
            foreach ($joinColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
712 3
                /** @var JoinColumnMetadata $joinColumn */
713 3
                $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
714
                $referencedColumnName = $joinColumn->getReferencedColumnName();
715
716
                if (! $joinColumn->getType()) {
717 9
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
718
                }
719
720
                $whereClauses[] = sprintf('t.%s = ?', $quotedColumnName);
721
                $params[]       = $key;
722
                $types[]        = $joinColumn->getType();
723
            }
724
        }
725
726 View Code Duplication
        if ($addFilters) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
727
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($association);
0 ignored issues
show
Compatibility introduced by
$association of type object<Doctrine\ORM\Mapp...anyAssociationMetadata> is not a sub-type of object<Doctrine\ORM\Mapp...anyAssociationMetadata>. It seems like you assume a child class of the class Doctrine\ORM\Mapping\ToManyAssociationMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
728 7
729
            if ($filterSql) {
730 7
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
731
                $whereClauses[] = $filterSql;
732 7
            }
733 5
        }
734
735
        return [$quotedJoinTable, $whereClauses, $params, $types];
736 2
    }
737
738 2
    /**
739
     * @param \Doctrine\ORM\PersistentCollection $collection
740 2
     * @param object                             $element
741
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
742 2
     *
743
     * @return array ordered vector:
744
     *                - quoted join table name
745
     *                - where clauses to be added for filtering
746
     *                - parameters to be bound for filtering
747
     *                - types of the parameters to be bound for filtering
748
     */
749
    private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters)
750 7
    {
751
        $association       = $collection->getMapping();
752 7
        $owningAssociation = $association;
753
754 7
        if (! $association->isOwningSide()) {
755 2
            $sourceClass      = $this->em->getClassMetadata($association->getTargetEntity());
756
            $targetClass      = $this->em->getClassMetadata($association->getSourceEntity());
757 2
            $sourceIdentifier = $this->uow->getEntityIdentifier($element);
758 2
            $targetIdentifier = $this->uow->getEntityIdentifier($collection->getOwner());
759 2
760
            $owningAssociation = $sourceClass->getProperty($association->getMappedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
761 2
        } else {
762
            $sourceClass      = $this->em->getClassMetadata($association->getSourceEntity());
763
            $targetClass      = $this->em->getClassMetadata($association->getTargetEntity());
764 2
            $sourceIdentifier = $this->uow->getEntityIdentifier($collection->getOwner());
765
            $targetIdentifier = $this->uow->getEntityIdentifier($element);
766 5
        }
767
768
        $joinTable       = $owningAssociation->getJoinTable();
769
        $joinTableName   = $joinTable->getQuotedQualifiedName($this->platform);
770
        $quotedJoinTable = $joinTableName;
771
        $whereClauses    = [];
772
        $params          = [];
773
        $types           = [];
774 7
775 View Code Duplication
        foreach ($joinTable->getJoinColumns() as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
776 7
            /** @var JoinColumnMetadata $joinColumn */
777 7
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
778 7
            $referencedColumnName = $joinColumn->getReferencedColumnName();
779 3
780
            if (! $joinColumn->getType()) {
781 4
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $sourceClass, $this->em));
0 ignored issues
show
Documentation introduced by
$sourceClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
782
            }
783
784
            $whereClauses[] = ($addFilters ? 't.' : '') . $quotedColumnName . ' = ?';
785
            $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
786
            $types[]        = $joinColumn->getType();
787
        }
788
789 View Code Duplication
        foreach ($joinTable->getInverseJoinColumns() as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
790
            /** @var JoinColumnMetadata $joinColumn */
791
            $quotedColumnName     = $this->platform->quoteIdentifier($joinColumn->getColumnName());
792
            $referencedColumnName = $joinColumn->getReferencedColumnName();
793
794
            if (! $joinColumn->getType()) {
795
                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
796
            }
797
798
            $whereClauses[] = ($addFilters ? 't.' : '') . $quotedColumnName . ' = ?';
799
            $params[]       = $targetIdentifier[$targetClass->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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
800
            $types[]        = $joinColumn->getType();
801
        }
802
803 View Code Duplication
        if ($addFilters) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
804
            $quotedJoinTable .= ' t';
805
806
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($association);
0 ignored issues
show
Compatibility introduced by
$association of type object<Doctrine\ORM\Mapp...anyAssociationMetadata> is not a sub-type of object<Doctrine\ORM\Mapp...anyAssociationMetadata>. It seems like you assume a child class of the class Doctrine\ORM\Mapping\ToManyAssociationMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
807
808
            if ($filterSql) {
809
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
810
                $whereClauses[] = $filterSql;
811
            }
812
        }
813
814
        return [$quotedJoinTable, $whereClauses, $params, $types];
815
    }
816
817
    /**
818
     * Expands Criteria Parameters by walking the expressions and grabbing all
819
     * parameters and types from it.
820
     *
821
     * @param \Doctrine\Common\Collections\Criteria $criteria
822
     *
823
     * @return array
824
     */
825
    private function expandCriteriaParameters(Criteria $criteria)
826
    {
827
        $expression = $criteria->getWhereExpression();
828
829
        if ($expression === null) {
830
            return [];
831
        }
832
833
        $valueVisitor = new SqlValueVisitor();
834
835
        $valueVisitor->dispatch($expression);
836
837
        list(, $types) = $valueVisitor->getParamsAndTypes();
838
839
        return $types;
840
    }
841
842
    /**
843
     * @param Criteria $criteria
844
     * @param ClassMetadata $targetClass
845
     * @return string
846
     */
847
    private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass)
848
    {
849
        $orderings = $criteria->getOrderings();
850
851
        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...
852
            $orderBy = [];
853
854
            foreach ($orderings as $name => $direction) {
855
                $property   = $targetClass->getProperty($name);
856
                $columnName = $this->platform->quoteIdentifier($property->getColumnName());
857
858
                $orderBy[] = $columnName . ' ' . $direction;
859
            }
860
861
            return ' ORDER BY ' . implode(', ', $orderBy);
862
        }
863
        return '';
864
    }
865
866
    /**
867
     * @param Criteria $criteria
868
     * @return string
869
     * @throws \Doctrine\DBAL\DBALException
870
     */
871
    private function getLimitSql(Criteria $criteria)
872
    {
873
        $limit  = $criteria->getMaxResults();
874
        $offset = $criteria->getFirstResult();
875 View Code Duplication
        if ($limit !== null || $offset !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
876
            return $this->platform->modifyLimitQuery('', $limit, $offset);
877
        }
878
        return '';
879
    }
880
}
881