Completed
Pull Request — master (#5669)
by Jeremy
08:29
created

collectJoinTableColumnParameters()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 34
c 0
b 0
f 0
ccs 19
cts 19
cp 1
rs 8.439
cc 6
eloc 19
nc 10
nop 2
crap 6
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Persisters\Collection;
21
22
use Doctrine\Common\Collections\Criteria;
23
use Doctrine\DBAL\Query\QueryBuilder;
24
use Doctrine\ORM\Mapping\ClassMetadata;
25
use Doctrine\ORM\Persisters\Collection\Expr\MappingVisitor;
26
use Doctrine\ORM\Persisters\SqlValueVisitor;
27
use Doctrine\ORM\PersistentCollection;
28
use Doctrine\ORM\Query;
29
use Doctrine\ORM\Query\Parameter;
30
use Doctrine\ORM\Query\QueryExpressionVisitor;
31
use Doctrine\ORM\Utility\PersisterHelper;
32
33
/**
34
 * Persister for many-to-many collections.
35
 *
36
 * @author  Roman Borschel <[email protected]>
37
 * @author  Guilherme Blanco <[email protected]>
38
 * @author  Alexander <[email protected]>
39
 * @since   2.0
40
 */
41
class ManyToManyPersister extends AbstractCollectionPersister
42
{
43
    /**
44
     * {@inheritdoc}
45
     */
46 17
    public function delete(PersistentCollection $collection)
47
    {
48 17
        $mapping = $collection->getMapping();
49
50 17
        if ( ! $mapping['isOwningSide']) {
51
            return; // ignore inverse side
52
        }
53
54 17
        $types = array();
55 17
        $class = $this->em->getClassMetadata($mapping['sourceEntity']);
56
57 17
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
58 17
            $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
59
        }
60
61 17
        $this->conn->executeUpdate($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types);
62 17
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 339
    public function update(PersistentCollection $collection)
68
    {
69 339
        $mapping = $collection->getMapping();
70
71 339
        if ( ! $mapping['isOwningSide']) {
72 241
            return; // ignore inverse side
73
        }
74
75 338
        list($deleteSql, $deleteTypes) = $this->getDeleteRowSQL($collection);
76 338
        list($insertSql, $insertTypes) = $this->getInsertRowSQL($collection);
77
78 338
        foreach ($collection->getDeleteDiff() as $element) {
79 12
            $this->conn->executeUpdate(
80
                $deleteSql,
81 12
                $this->getDeleteRowSQLParameters($collection, $element),
82
                $deleteTypes
83
            );
84
        }
85
86 338
        foreach ($collection->getInsertDiff() as $element) {
87 338
            $this->conn->executeUpdate(
88
                $insertSql,
89 338
                $this->getInsertRowSQLParameters($collection, $element),
90
                $insertTypes
91
            );
92
        }
93 338
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 3
    public function get(PersistentCollection $collection, $index)
99
    {
100 3
        $mapping = $collection->getMapping();
101
102 3
        if ( ! isset($mapping['indexBy'])) {
103
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
104
        }
105
106 3
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
107 3
        $mappedKey = $mapping['isOwningSide']
108 2
            ? $mapping['inversedBy']
109 3
            : $mapping['mappedBy'];
110
111 3
        return $persister->load(array($mappedKey => $collection->getOwner(), $mapping['indexBy'] => $index), null, $mapping, array(), 0, 1);
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 18
    public function count(PersistentCollection $collection)
118
    {
119 18
        $conditions     = array();
120 18
        $params         = array();
121 18
        $types          = array();
122 18
        $mapping        = $collection->getMapping();
123 18
        $id             = $this->uow->getEntityIdentifier($collection->getOwner());
124 18
        $sourceClass    = $this->em->getClassMetadata($mapping['sourceEntity']);
125 18
        $targetClass    = $this->em->getClassMetadata($mapping['targetEntity']);
126 18
        $association    = ( ! $mapping['isOwningSide'])
127 4
            ? $targetClass->associationMappings[$mapping['mappedBy']]
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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...
128 18
            : $mapping;
129
130 18
        $joinTableName  = $this->quoteStrategy->getJoinTableName($association, $sourceClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$sourceClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
131 18
        $joinColumns    = ( ! $mapping['isOwningSide'])
132 4
            ? $association['joinTable']['inverseJoinColumns']
133 18
            : $association['joinTable']['joinColumns'];
134
135 18
        foreach ($joinColumns as $joinColumn) {
136 18
            $columnName     = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$sourceClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
137 18
            $referencedName = $joinColumn['referencedColumnName'];
138 18
            $conditions[]   = 't.' . $columnName . ' = ?';
139 18
            $params[]       = $id[$sourceClass->getFieldForColumn($referencedName)];
140 18
            $types[]        = PersisterHelper::getTypeOfColumn($referencedName, $sourceClass, $this->em);
0 ignored issues
show
Compatibility introduced by
$sourceClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
141
        }
142
143 18
        list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
144
145 18
        if ($filterSql) {
146 3
            $conditions[] = $filterSql;
147
        }
148
149
        // If there is a provided criteria, make part of conditions
150
        // @todo Fix this. Current SQL returns something like:
151
        //
152
        /*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) {
153
            // A join is needed on the target entity
154
            $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
155
            $targetJoinSql   = ' JOIN ' . $targetTableName . ' te'
156
                . ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
157
158
            // And criteria conditions needs to be added
159
            $persister    = $this->uow->getEntityPersister($targetClass->name);
160
            $visitor      = new SqlExpressionVisitor($persister, $targetClass);
161
            $conditions[] = $visitor->dispatch($expression);
162
163
            $joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL;
164
        }*/
165
166
        $sql = 'SELECT COUNT(*)'
167 18
            . ' FROM ' . $joinTableName . ' t'
168 18
            . $joinTargetEntitySQL
169 18
            . ' WHERE ' . implode(' AND ', $conditions);
170
171 18
        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 171 which is incompatible with the return type declared by the interface Doctrine\ORM\Persisters\...lectionPersister::count of type integer.
Loading history...
172
    }
173
174
    /**
175
     * {@inheritDoc}
176
     */
177 8
    public function slice(PersistentCollection $collection, $offset, $length = null)
178
    {
179 8
        $mapping   = $collection->getMapping();
180 8
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
181
182 8
        return $persister->getManyToManyCollection($mapping, $collection->getOwner(), $offset, $length);
183
    }
184
    /**
185
     * {@inheritdoc}
186
     */
187 7
    public function containsKey(PersistentCollection $collection, $key)
188
    {
189 7
        $mapping = $collection->getMapping();
190
191 7
        if ( ! isset($mapping['indexBy'])) {
192
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
193
        }
194
195 7
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictionsWithKey($collection, $key, true);
196
197 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
198
199 7
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     */
205 7
    public function contains(PersistentCollection $collection, $element)
206
    {
207 7
        if ( ! $this->isValidEntityState($element)) {
208 2
            return false;
209
        }
210
211 7
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, true);
212
213 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
214
215 7
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
216
    }
217
218
    /**
219
     * {@inheritDoc}
220
     */
221 2
    public function removeElement(PersistentCollection $collection, $element)
222
    {
223 2
        if ( ! $this->isValidEntityState($element)) {
224 2
            return false;
225
        }
226
227 2
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, false);
228
229 2
        $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
230
231 2
        return (bool) $this->conn->executeUpdate($sql, $params, $types);
232
    }
233
234
    /**
235
     * {@inheritDoc}
236
     */
237 17
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
238
    {
239 17
        $mapping       = $collection->getMapping();
240 17
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
241
242 17
        $queryBuilder = $this->createQueryBuilderForAssociation($collection);
243
244 17
        if ($whereExpr = $criteria->getWhereExpression()) {
0 ignored issues
show
Unused Code introduced by
$whereExpr is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
245 12
            $mappingVisitor = new MappingVisitor(
246 12
                $this->quoteStrategy,
247
                $targetClass,
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
248 12
                $this->platform
249
            );
250
251 12
            $mappedExpr = $mappingVisitor->dispatch($criteria->getWhereExpression());
252
253 12
            $whereClauseExpressionVisitor = new QueryExpressionVisitor(['te']);
254 12
            $whereExpr = $whereClauseExpressionVisitor->dispatch($mappedExpr);
255 12
            $queryBuilder->where($whereExpr . '');
256
257
            /** @var Parameter $parameter */
258 12
            foreach($whereClauseExpressionVisitor->getParameters() as $parameter) {
259 11
                $queryBuilder->setParameter($parameter->getName(), $parameter->getValue(), $parameter->getType());
260
            }
261
        }
262
263 17
        $this->applyCriteriaOrdering($queryBuilder, $criteria, $targetClass);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
264
265 17
        $this->applyCriteriaLimit($queryBuilder, $criteria);
266
267 17
        $rsm = new Query\ResultSetMappingBuilder($this->em);
268 17
        $rsm->addRootEntityFromClassMetadata($targetClass->name, 'te');
0 ignored issues
show
Bug introduced by
Accessing name 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...
269
270 17
        $stmt = $queryBuilder->execute();
271
272
        return $this
273 17
            ->em
274 17
            ->newHydrator(Query::HYDRATE_OBJECT)
275 17
            ->hydrateAll($stmt, $rsm);
0 ignored issues
show
Bug introduced by
It seems like $stmt defined by $queryBuilder->execute() on line 270 can also be of type integer; however, Doctrine\ORM\Internal\Hy...tHydrator::hydrateAll() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
276
    }
277
278
    /**
279
     * Generates the filter SQL for a given mapping.
280
     *
281
     * This method is not used for actually grabbing the related entities
282
     * but when the extra-lazy collection methods are called on a filtered
283
     * association. This is why besides the many to many table we also
284
     * have to join in the actual entities table leading to additional
285
     * JOIN.
286
     *
287
     * @param array $mapping Array containing mapping information.
288
     *
289
     * @return string[] ordered tuple:
290
     *                   - JOIN condition to add to the SQL
291
     *                   - WHERE condition to add to the SQL
292
     */
293 32
    public function getFilterSql($mapping)
294
    {
295 32
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
296 32
        $rootClass   = $this->em->getClassMetadata($targetClass->rootEntityName);
0 ignored issues
show
Bug introduced by
Accessing rootEntityName 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...
297 32
        $filterSql   = $this->generateFilterConditionSQL($rootClass, 'te');
0 ignored issues
show
Compatibility introduced by
$rootClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
298
299 32
        if ('' === $filterSql) {
300 32
            return array('', '');
301
        }
302
303
        // A join is needed if there is filtering on the target entity
304 6
        $tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$rootClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
305 6
        $joinSql   = ' JOIN ' . $tableName . ' te'
306 6
            . ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping));
307
308 6
        return array($joinSql, $filterSql);
309
    }
310
311
    /**
312
     * Generates the filter SQL for a given entity and table alias.
313
     *
314
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
315
     * @param string        $targetTableAlias The table alias of the joined/selected table.
316
     *
317
     * @return string The SQL query part to add to a query.
318
     */
319 32
    protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
320
    {
321 32
        $filterClauses = array();
322
323 32
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
324 6
            if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
325 6
                $filterClauses[] = '(' . $filterExpr . ')';
326
            }
327
        }
328
329 32
        return $filterClauses
330 6
            ? '(' . implode(' AND ', $filterClauses) . ')'
331 32
            : '';
332
    }
333
334
    /**
335
     * Generate ON condition
336
     *
337
     * @param  array $mapping
338
     *
339
     * @return array
340
     */
341 23
    protected function getOnConditionSQL($mapping)
342
    {
343 23
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
344 23
        $association = ( ! $mapping['isOwningSide'])
345 3
            ? $targetClass->associationMappings[$mapping['mappedBy']]
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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...
346 23
            : $mapping;
347
348 23
        $joinColumns = $mapping['isOwningSide']
349 20
            ? $association['joinTable']['inverseJoinColumns']
350 23
            : $association['joinTable']['joinColumns'];
351
352 23
        $conditions = array();
353
354 23
        foreach ($joinColumns as $joinColumn) {
355 23
            $joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
356 23
            $refColumnName  = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
357
358 23
            $conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName;
359
        }
360
361 23
        return $conditions;
362
    }
363
364
    /**
365
     * {@inheritdoc}
366
     *
367
     * @override
368
     */
369 17
    protected function getDeleteSQL(PersistentCollection $collection)
370
    {
371 17
        $columns    = array();
372 17
        $mapping    = $collection->getMapping();
373 17
        $class      = $this->em->getClassMetadata(get_class($collection->getOwner()));
374 17
        $joinTable  = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
375
376 17
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
377 17
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
378
        }
379
380 17
        return 'DELETE FROM ' . $joinTable
381 17
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
382
    }
383
384
    /**
385
     * {@inheritdoc}
386
     *
387
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql.
388
     * @override
389
     */
390 17
    protected function getDeleteSQLParameters(PersistentCollection $collection)
391
    {
392 17
        $mapping    = $collection->getMapping();
393 17
        $identifier = $this->uow->getEntityIdentifier($collection->getOwner());
394
395
        // Optimization for single column identifier
396 17
        if (count($mapping['relationToSourceKeyColumns']) === 1) {
397 15
            return array(reset($identifier));
398
        }
399
400
        // Composite identifier
401 2
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
402 2
        $params      = array();
403
404 2
        foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) {
405 2
            $params[] = isset($sourceClass->fieldNames[$refColumnName])
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...
406 1
                ? $identifier[$sourceClass->fieldNames[$refColumnName]]
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...
407 2
                : $identifier[$sourceClass->getFieldForColumn($columnName)];
408
        }
409
410 2
        return $params;
411
    }
412
413
    /**
414
     * Gets the SQL statement used for deleting a row from the collection.
415
     *
416
     * @param \Doctrine\ORM\PersistentCollection $collection
417
     *
418
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
419
     *                             of types for bound parameters
420
     */
421 338
    protected function getDeleteRowSQL(PersistentCollection $collection)
422
    {
423 338
        $mapping     = $collection->getMapping();
424 338
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
425 338
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
426 338
        $columns     = array();
427 338
        $types       = array();
428
429 338
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
430 338
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
431 338
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
432
        }
433
434 338
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
435 338
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
436 338
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
437
        }
438
439
        return array(
440 338
            'DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
441 338
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?',
442 338
            $types,
443
        );
444
    }
445
446
    /**
447
     * Gets the SQL parameters for the corresponding SQL statement to delete the given
448
     * element from the given collection.
449
     *
450
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql.
451
     *
452
     * @param \Doctrine\ORM\PersistentCollection $collection
453
     * @param mixed                              $element
454
     *
455
     * @return array
456
     */
457 12
    protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element)
458
    {
459 12
        return $this->collectJoinTableColumnParameters($collection, $element);
460
    }
461
462
    /**
463
     * Gets the SQL statement used for inserting a row in the collection.
464
     *
465
     * @param \Doctrine\ORM\PersistentCollection $collection
466
     *
467
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
468
     *                             of types for bound parameters
469
     */
470 338
    protected function getInsertRowSQL(PersistentCollection $collection)
471
    {
472 338
        $columns     = array();
473 338
        $types       = array();
474 338
        $mapping     = $collection->getMapping();
475 338
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
476 338
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
477
478 338
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
479 338
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
480 338
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
481
        }
482
483 338
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
484 338
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
485 338
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
486
        }
487
488
        return array(
489 338
            'INSERT INTO ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
490 338
            . ' (' . implode(', ', $columns) . ')'
491 338
            . ' VALUES'
492 338
            . ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')',
493 338
            $types,
494
        );
495
    }
496
497
    /**
498
     * Gets the SQL parameters for the corresponding SQL statement to insert the given
499
     * element of the given collection into the database.
500
     *
501
     * Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql.
502
     *
503
     * @param \Doctrine\ORM\PersistentCollection $collection
504
     * @param mixed                              $element
505
     *
506
     * @return array
507
     */
508 338
    protected function getInsertRowSQLParameters(PersistentCollection $collection, $element)
509
    {
510 338
        return $this->collectJoinTableColumnParameters($collection, $element);
511
    }
512
513
    /**
514
     * Collects the parameters for inserting/deleting on the join table in the order
515
     * of the join table columns as specified in ManyToManyMapping#joinTableColumns.
516
     *
517
     * @param \Doctrine\ORM\PersistentCollection $collection
518
     * @param object                             $element
519
     *
520
     * @return array
521
     */
522 338
    private function collectJoinTableColumnParameters(PersistentCollection $collection, $element)
523
    {
524 338
        $params      = array();
525 338
        $mapping     = $collection->getMapping();
526 338
        $isComposite = count($mapping['joinTableColumns']) > 2;
527
528 338
        $identifier1 = $this->uow->getEntityIdentifier($collection->getOwner());
529 338
        $identifier2 = $this->uow->getEntityIdentifier($element);
530
531 338
        if ($isComposite) {
532 21
            $class1 = $this->em->getClassMetadata(get_class($collection->getOwner()));
533 21
            $class2 = $collection->getTypeClass();
534
        }
535
536 338
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
537 338
            $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]);
538
539 338
            if ( ! $isComposite) {
540 317
                $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2);
541
542 317
                continue;
543
            }
544
545 21
            if ($isRelationToSource) {
546 21
                $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
0 ignored issues
show
Bug introduced by
The variable $class1 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
547
548 21
                continue;
549
            }
550
551 21
            $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
0 ignored issues
show
Bug introduced by
The variable $class2 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
552
        }
553
554 338
        return $params;
555
    }
556
557
    /**
558
     * @param \Doctrine\ORM\PersistentCollection $collection
559
     * @param string                             $key
560
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
561
     *
562
     * @return array ordered vector:
563
     *                - quoted join table name
564
     *                - where clauses to be added for filtering
565
     *                - parameters to be bound for filtering
566
     *                - types of the parameters to be bound for filtering
567
     */
568 7
    private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters)
569
    {
570 7
        $filterMapping = $collection->getMapping();
571 7
        $mapping       = $filterMapping;
572 7
        $indexBy       = $mapping['indexBy'];
573 7
        $id            = $this->uow->getEntityIdentifier($collection->getOwner());
574 7
        $sourceClass   = $this->em->getClassMetadata($mapping['sourceEntity']);
575 7
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
576
577 7
        if (! $mapping['isOwningSide']) {
578 3
            $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
579 3
            $mapping                = $associationSourceClass->associationMappings[$mapping['mappedBy']];
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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...
580 3
            $joinColumns            = $mapping['joinTable']['joinColumns'];
581 3
            $sourceRelationMode     = 'relationToTargetKeyColumns';
582 3
            $targetRelationMode     = 'relationToSourceKeyColumns';
583
        } else {
584 4
            $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
585 4
            $joinColumns            = $mapping['joinTable']['inverseJoinColumns'];
586 4
            $sourceRelationMode     = 'relationToSourceKeyColumns';
587 4
            $targetRelationMode     = 'relationToTargetKeyColumns';
588
        }
589
590 7
        $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform). ' t';
0 ignored issues
show
Compatibility introduced by
$associationSourceClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
591 7
        $whereClauses    = array();
592 7
        $params          = array();
593 7
        $types           = array();
594
595 7
        $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...
596
597 7
        if ($joinNeeded) { // extra join needed if indexBy is not a @id
598 3
            $joinConditions = array();
599
600 3
            foreach ($joinColumns as $joinTableColumn) {
601 3
                $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName'];
602
            }
603
604 3
            $tableName        = $this->quoteStrategy->getTableName($targetClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
605 3
            $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
606 3
            $columnName       = $targetClass->getColumnName($indexBy);
607
608 3
            $whereClauses[] = 'tr.' . $columnName . ' = ?';
609 3
            $params[]       = $key;
610 3
            $types[]        = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
611
        }
612
613 7
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
614 7
            if (isset($mapping[$sourceRelationMode][$joinTableColumn])) {
615 7
                $column         = $mapping[$sourceRelationMode][$joinTableColumn];
616 7
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
617 7
                $params[]       = $sourceClass->containsForeignIdentifier
0 ignored issues
show
Bug introduced by
Accessing containsForeignIdentifier 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...
618
                    ? $id[$sourceClass->getFieldForColumn($column)]
619 7
                    : $id[$sourceClass->fieldNames[$column]];
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...
620 7
                $types[]        = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em);
0 ignored issues
show
Compatibility introduced by
$sourceClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
621 7
            } elseif ( ! $joinNeeded) {
622 4
                $column = $mapping[$targetRelationMode][$joinTableColumn];
623
624 4
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
625 4
                $params[]       = $key;
626 7
                $types[]        = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
627
            }
628
        }
629
630 7
        if ($addFilters) {
631 7
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
632
633 7
            if ($filterSql) {
634
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
635
                $whereClauses[] = $filterSql;
636
            }
637
        }
638
639 7
        return array($quotedJoinTable, $whereClauses, $params, $types);
640
    }
641
642
    /**
643
     * @param \Doctrine\ORM\PersistentCollection $collection
644
     * @param object                             $element
645
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
646
     *
647
     * @return array ordered vector:
648
     *                - quoted join table name
649
     *                - where clauses to be added for filtering
650
     *                - parameters to be bound for filtering
651
     *                - types of the parameters to be bound for filtering
652
     */
653 9
    private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters)
654
    {
655 9
        $filterMapping  = $collection->getMapping();
656 9
        $mapping        = $filterMapping;
657
658 9
        if ( ! $mapping['isOwningSide']) {
659 4
            $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
660 4
            $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']);
661 4
            $sourceId = $this->uow->getEntityIdentifier($element);
662 4
            $targetId = $this->uow->getEntityIdentifier($collection->getOwner());
663
664 4
            $mapping = $sourceClass->associationMappings[$mapping['mappedBy']];
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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
        } else {
666 5
            $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
667 5
            $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
668 5
            $sourceId = $this->uow->getEntityIdentifier($collection->getOwner());
669 5
            $targetId = $this->uow->getEntityIdentifier($element);
670
        }
671
672 9
        $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$sourceClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
673 9
        $whereClauses    = array();
674 9
        $params          = array();
675 9
        $types           = array();
676
677 9
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
678 9
            $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?';
679
680 9
            if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
681 9
                $targetColumn = $mapping['relationToTargetKeyColumns'][$joinTableColumn];
682 9
                $params[]     = $targetId[$targetClass->getFieldForColumn($targetColumn)];
683 9
                $types[]      = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
684
685 9
                continue;
686
            }
687
688
            // relationToSourceKeyColumns
689 9
            $targetColumn = $mapping['relationToSourceKeyColumns'][$joinTableColumn];
690 9
            $params[]     = $sourceId[$sourceClass->getFieldForColumn($targetColumn)];
691 9
            $types[]      = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em);
0 ignored issues
show
Compatibility introduced by
$sourceClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
692
        }
693
694 9
        if ($addFilters) {
695 7
            $quotedJoinTable .= ' t';
696
697 7
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
698
699 7
            if ($filterSql) {
700 3
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
701 3
                $whereClauses[] = $filterSql;
702
            }
703
        }
704
705 9
        return array($quotedJoinTable, $whereClauses, $params, $types);
706
    }
707
708
    /**
709
     * Expands Criteria Parameters by walking the expressions and grabbing all
710
     * parameters and types from it.
711
     *
712
     * @param \Doctrine\Common\Collections\Criteria $criteria
713
     *
714
     * @return array
715
     */
716
    private function expandCriteriaParameters(Criteria $criteria)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
717
    {
718
        $expression = $criteria->getWhereExpression();
719
720
        if ($expression === null) {
721
            return array();
722
        }
723
724
        $valueVisitor = new SqlValueVisitor();
725
726
        $valueVisitor->dispatch($expression);
727
728
        list(, $types) = $valueVisitor->getParamsAndTypes();
729
730
        return $types;
731
    }
732
733
    /**
734
     * @param QueryBuilder $queryBuilder
735
     * @param Criteria $criteria
736
     * @param ClassMetadata $targetClass
737
     */
738 17
    private function applyCriteriaOrdering(QueryBuilder $queryBuilder, Criteria $criteria, ClassMetadata $targetClass)
739
    {
740 17
        $orderings = $criteria->getOrderings();
741 17
        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...
742 2
            foreach ($orderings as $name => $direction) {
743 2
                $field = $this->quoteStrategy->getColumnName(
744
                    $name,
745
                    $targetClass,
746 2
                    $this->platform
747
                );
748
749 2
                $queryBuilder->addOrderBy($field, $direction);
750
            }
751
        }
752 17
    }
753
754
    /**
755
     * @param QueryBuilder $queryBuilder
756
     * @param Criteria $criteria
757
     */
758 17
    private function applyCriteriaLimit(QueryBuilder $queryBuilder, Criteria $criteria)
759
    {
760 17
        $queryBuilder->setFirstResult($criteria->getFirstResult());
761 17
        $queryBuilder->setMaxResults($criteria->getMaxResults());
762 17
    }
763
764
    /**
765
     * @param PersistentCollection $collection
766
     * @return \Doctrine\DBAL\Query\QueryBuilder
767
     */
768 17
    private function createQueryBuilderForAssociation(PersistentCollection $collection)
769
    {
770 17
        $qb = $this->em->getConnection()->createQueryBuilder();
771
772 17
        $mapping       = $collection->getMapping();
773 17
        $owner         = $collection->getOwner();
774 17
        $ownerMetadata = $this->em->getClassMetadata(get_class($owner));
775 17
        $id            = $this->uow->getEntityIdentifier($owner);
776 17
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
777 17
        $onConditions  = $this->getOnConditionSQL($mapping);
778
779
780 17
        if ( ! $mapping['isOwningSide']) {
781 1
            $associationSourceClass = $targetClass;
782 1
            $mapping = $targetClass->associationMappings[$mapping['mappedBy']];
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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...
783 1
            $sourceRelationMode = 'relationToTargetKeyColumns';
784
        } else {
785 16
            $associationSourceClass = $ownerMetadata;
786 16
            $sourceRelationMode = 'relationToSourceKeyColumns';
787
        }
788
789 17
        foreach ($mapping[$sourceRelationMode] as $key => $value) {
790 17
            $paramName = sprintf(':t%s', $key);
791 17
            $qb->where(sprintf('t.%s = %s', $key, $paramName));
792
793 17
            $paramValue = $ownerMetadata->containsForeignIdentifier
0 ignored issues
show
Bug introduced by
Accessing containsForeignIdentifier 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...
794
                ? $id[$ownerMetadata->getFieldForColumn($value)]
795 17
                : $id[$ownerMetadata->fieldNames[$value]];
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...
796
797 17
            $qb->setParameter($paramName, $paramValue, $ownerMetadata->getTypeOfField($key));
798
        }
799
800 17
        $tableName    = $this->quoteStrategy->getTableName($targetClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
801 17
        $joinTable    = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform);
0 ignored issues
show
Compatibility introduced by
$associationSourceClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
802
803 17
        $qb->select('te.*');
804 17
        $qb->from($tableName, 'te');
805 17
        $qb->join('te', $joinTable, 't', join(' AND ', $onConditions));
806
807 17
        return $qb;
808
    }
809
}
810