Completed
Pull Request — master (#5669)
by Jeremy
63:22
created

ManyToManyPersister::expandCriteriaParameters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2
Metric Value
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 9.4285
cc 2
eloc 8
nc 2
nop 1
crap 2

1 Method

Rating   Name   Duplication   Size   Complexity  
A ManyToManyPersister::applyCriteriaLimit() 0 5 1
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\PersistentCollection;
27
use Doctrine\ORM\Query;
28
use Doctrine\ORM\Query\Parameter;
29
use Doctrine\ORM\Query\QueryExpressionVisitor;
30
use Doctrine\ORM\Utility\PersisterHelper;
31
32
/**
33
 * Persister for many-to-many collections.
34
 *
35
 * @author  Roman Borschel <[email protected]>
36
 * @author  Guilherme Blanco <[email protected]>
37
 * @author  Alexander <[email protected]>
38
 * @since   2.0
39
 */
40
class ManyToManyPersister extends AbstractCollectionPersister
41
{
42
    /**
43 17
     * {@inheritdoc}
44
     */
45 17
    public function delete(PersistentCollection $collection)
46
    {
47 17
        $mapping = $collection->getMapping();
48
49
        if ( ! $mapping['isOwningSide']) {
50
            return; // ignore inverse side
51 17
        }
52 17
53
        $types = array();
54 17
        $class = $this->em->getClassMetadata($mapping['sourceEntity']);
55 17
56
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
57
            $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...
58 17
        }
59 17
60
        $this->conn->executeUpdate($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types);
61
    }
62
63
    /**
64 329
     * {@inheritdoc}
65
     */
66 329
    public function update(PersistentCollection $collection)
67
    {
68 329
        $mapping = $collection->getMapping();
69 231
70
        if ( ! $mapping['isOwningSide']) {
71
            return; // ignore inverse side
72 328
        }
73 328
74
        list($deleteSql, $deleteTypes) = $this->getDeleteRowSQL($collection);
75 328
        list($insertSql, $insertTypes) = $this->getInsertRowSQL($collection);
76 12
77
        foreach ($collection->getDeleteDiff() as $element) {
78 12
            $this->conn->executeUpdate(
79
                $deleteSql,
80
                $this->getDeleteRowSQLParameters($collection, $element),
81
                $deleteTypes
82
            );
83 328
        }
84 328
85
        foreach ($collection->getInsertDiff() as $element) {
86 328
            $this->conn->executeUpdate(
87
                $insertSql,
88
                $this->getInsertRowSQLParameters($collection, $element),
89
                $insertTypes
90 328
            );
91
        }
92
    }
93
94
    /**
95 3
     * {@inheritdoc}
96
     */
97 3
    public function get(PersistentCollection $collection, $index)
98
    {
99 3
        $mapping = $collection->getMapping();
100
101
        if ( ! isset($mapping['indexBy'])) {
102
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
103 3
        }
104 3
105 2
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
106 3
        $mappedKey = $mapping['isOwningSide']
107
            ? $mapping['inversedBy']
108 3
            : $mapping['mappedBy'];
109
110
        return $persister->load(array($mappedKey => $collection->getOwner(), $mapping['indexBy'] => $index), null, $mapping, array(), 0, 1);
111
    }
112
113
    /**
114 18
     * {@inheritdoc}
115
     */
116 18
    public function count(PersistentCollection $collection)
117 18
    {
118 18
        $conditions     = array();
119 18
        $params         = array();
120 18
        $types          = array();
121 18
        $mapping        = $collection->getMapping();
122 18
        $id             = $this->uow->getEntityIdentifier($collection->getOwner());
123 18
        $sourceClass    = $this->em->getClassMetadata($mapping['sourceEntity']);
124 4
        $targetClass    = $this->em->getClassMetadata($mapping['targetEntity']);
125 18
        $association    = ( ! $mapping['isOwningSide'])
126
            ? $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...
127 18
            : $mapping;
128 18
129 4
        $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...
130 18
        $joinColumns    = ( ! $mapping['isOwningSide'])
131
            ? $association['joinTable']['inverseJoinColumns']
132 18
            : $association['joinTable']['joinColumns'];
133 18
134 18
        foreach ($joinColumns as $joinColumn) {
135 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...
136 18
            $referencedName = $joinColumn['referencedColumnName'];
137 18
            $conditions[]   = 't.' . $columnName . ' = ?';
138
            $params[]       = $id[$sourceClass->getFieldForColumn($referencedName)];
139
            $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...
140 18
        }
141
142 18
        list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
143 3
144
        if ($filterSql) {
145
            $conditions[] = $filterSql;
146
        }
147
148
        // If there is a provided criteria, make part of conditions
149
        // @todo Fix this. Current SQL returns something like:
150
        //
151
        /*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) {
152
            // A join is needed on the target entity
153
            $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
154
            $targetJoinSql   = ' JOIN ' . $targetTableName . ' te'
155
                . ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
156
157
            // And criteria conditions needs to be added
158
            $persister    = $this->uow->getEntityPersister($targetClass->name);
159
            $visitor      = new SqlExpressionVisitor($persister, $targetClass);
160
            $conditions[] = $visitor->dispatch($expression);
161
162
            $joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL;
163
        }*/
164 18
165 18
        $sql = 'SELECT COUNT(*)'
166 18
            . ' FROM ' . $joinTableName . ' t'
167
            . $joinTargetEntitySQL
168 18
            . ' WHERE ' . implode(' AND ', $conditions);
169
170
        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 170 which is incompatible with the return type declared by the interface Doctrine\ORM\Persisters\...lectionPersister::count of type integer.
Loading history...
171
    }
172
173
    /**
174 8
     * {@inheritDoc}
175
     */
176 8
    public function slice(PersistentCollection $collection, $offset, $length = null)
177 8
    {
178
        $mapping   = $collection->getMapping();
179 8
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
180
181
        return $persister->getManyToManyCollection($mapping, $collection->getOwner(), $offset, $length);
182
    }
183
    /**
184 7
     * {@inheritdoc}
185
     */
186 7
    public function containsKey(PersistentCollection $collection, $key)
187
    {
188 7
        $mapping = $collection->getMapping();
189
190
        if ( ! isset($mapping['indexBy'])) {
191
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
192 7
        }
193
194 7
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictionsWithKey($collection, $key, true);
195
196 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
197
198
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
199
    }
200
201
    /**
202 7
     * {@inheritDoc}
203
     */
204 7
    public function contains(PersistentCollection $collection, $element)
205 2
    {
206
        if ( ! $this->isValidEntityState($element)) {
207
            return false;
208 7
        }
209
210 7
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, true);
211
212 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
213
214
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
215
    }
216
217
    /**
218 2
     * {@inheritDoc}
219
     */
220 2
    public function removeElement(PersistentCollection $collection, $element)
221 2
    {
222
        if ( ! $this->isValidEntityState($element)) {
223
            return false;
224 2
        }
225
226 2
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, false);
227
228 2
        $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
1 ignored issue
show
Security introduced by
'DELETE FROM ' . $quoted...' AND ', $whereClauses) is used as a query on line 230. If $quotedJoinTable can contain user-input, it is usually preferable to use a parameter placeholder like :paramName and pass the dynamic input as second argument array('param' => $quotedJoinTable).

Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead:

function findUser(Doctrine\DBAL\Connection $con, $email) {
    // Unsafe
    $con->executeQuery("SELECT * FROM users WHERE email = '".$email."'");

    // Safe
    $con->executeQuery(
        "SELECT * FROM users WHERE email = :email",
        array('email' => $email)
    );
}
Loading history...
229
230
        return (bool) $this->conn->executeUpdate($sql, $params, $types);
231
    }
232
233
    /**
234 7
     * {@inheritDoc}
235
     */
236 7
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
237 7
    {
238 7
        $mapping       = $collection->getMapping();
239 7
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
240 7
241 7
        $queryBuilder = $this->createQueryBuilderForAssociation($collection);
242 7
243
        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...
244 7
            $mappingVisitor = new MappingVisitor(
245 1
                $this->quoteStrategy,
246 1
                $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...
247 1
                $this->platform
248
            );
249 6
250 6
            $mappedExpr = $mappingVisitor->dispatch($criteria->getWhereExpression());
251
252
            $whereClauseExpressionVisitor = new QueryExpressionVisitor(['te']);
253 7
            $whereExpr = $whereClauseExpressionVisitor->dispatch($mappedExpr);
254 7
            $queryBuilder->where($whereExpr . '');
255 7
256
            /** @var Parameter $parameter */
257 7
            foreach($whereClauseExpressionVisitor->getParameters() as $parameter) {
258
                $queryBuilder->setParameter($parameter->getName(), $parameter->getValue(), $parameter->getType());
259
            }
260 7
        }
261
262 7
        $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...
263 2
264 2
        $this->applyCriteriaLimit($queryBuilder, $criteria);
265 2
266 2
        $rsm = new Query\ResultSetMappingBuilder($this->em);
267
        $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...
268
269 7
        $stmt = $queryBuilder->execute();
270 7
271
        return $this
272 7
            ->em
273 7
            ->newHydrator(Query::HYDRATE_OBJECT)
274
            ->hydrateAll($stmt, $rsm);
0 ignored issues
show
Bug introduced by
It seems like $stmt defined by $queryBuilder->execute() on line 269 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...
275 7
    }
276 7
277 7
    /**
278 7
     * Generates the filter SQL for a given mapping.
279 7
     *
280
     * This method is not used for actually grabbing the related entities
281 7
     * but when the extra-lazy collection methods are called on a filtered
282
     * association. This is why besides the many to many table we also
283 7
     * have to join in the actual entities table leading to additional
284
     * JOIN.
285 7
     *
286
     * @param array $mapping Array containing mapping information.
287
     *
288 7
     * @return string[] ordered tuple:
289 7
     *                   - JOIN condition to add to the SQL
290 7
     *                   - WHERE condition to add to the SQL
291
     */
292
    public function getFilterSql($mapping)
293
    {
294
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
295
        $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...
296
        $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...
297
298
        if ('' === $filterSql) {
299
            return array('', '');
300
        }
301
302
        // A join is needed if there is filtering on the target entity
303
        $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...
304
        $joinSql   = ' JOIN ' . $tableName . ' te'
305
            . ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping));
306
307
        return array($joinSql, $filterSql);
308 32
    }
309
310 32
    /**
311 32
     * Generates the filter SQL for a given entity and table alias.
312 32
     *
313
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
314 32
     * @param string        $targetTableAlias The table alias of the joined/selected table.
315 32
     *
316
     * @return string The SQL query part to add to a query.
317
     */
318
    protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
319 6
    {
320 6
        $filterClauses = array();
321 6
322
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
323 6
            if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
324
                $filterClauses[] = '(' . $filterExpr . ')';
325
            }
326
        }
327
328
        return $filterClauses
329
            ? '(' . implode(' AND ', $filterClauses) . ')'
330
            : '';
331
    }
332
333
    /**
334 32
     * Generate ON condition
335
     *
336 32
     * @param  array $mapping
337
     *
338 32
     * @return array
339 6
     */
340 6
    protected function getOnConditionSQL($mapping)
341
    {
342
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
343
        $association = ( ! $mapping['isOwningSide'])
344 32
            ? $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...
345 6
            : $mapping;
346 32
347
        $joinColumns = $mapping['isOwningSide']
348
            ? $association['joinTable']['inverseJoinColumns']
349
            : $association['joinTable']['joinColumns'];
350
351
        $conditions = array();
352
353
        foreach ($joinColumns as $joinColumn) {
354
            $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...
355
            $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...
356 13
357
            $conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName;
358 13
        }
359 13
360 3
        return $conditions;
361 13
    }
362
363 13
    /**
364 10
     * {@inheritdoc}
365 13
     *
366
     * @override
367 13
     */
368
    protected function getDeleteSQL(PersistentCollection $collection)
369 13
    {
370 13
        $columns    = array();
371 13
        $mapping    = $collection->getMapping();
372
        $class      = $this->em->getClassMetadata(get_class($collection->getOwner()));
373 13
        $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...
374
375
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
376 13
            $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...
377
        }
378
379
        return 'DELETE FROM ' . $joinTable
380
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
381
    }
382
383
    /**
384 17
     * {@inheritdoc}
385
     *
386 17
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql.
387 17
     * @override
388 17
     */
389 17
    protected function getDeleteSQLParameters(PersistentCollection $collection)
390
    {
391 17
        $mapping    = $collection->getMapping();
392 17
        $identifier = $this->uow->getEntityIdentifier($collection->getOwner());
393
394
        // Optimization for single column identifier
395 17
        if (count($mapping['relationToSourceKeyColumns']) === 1) {
396 17
            return array(reset($identifier));
397
        }
398
399
        // Composite identifier
400
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
401
        $params      = array();
402
403
        foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) {
404
            $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...
405 17
                ? $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...
406
                : $identifier[$sourceClass->getFieldForColumn($columnName)];
407 17
        }
408 17
409
        return $params;
410
    }
411 17
412 15
    /**
413
     * Gets the SQL statement used for deleting a row from the collection.
414
     *
415
     * @param \Doctrine\ORM\PersistentCollection $collection
416 2
     *
417 2
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
418
     *                             of types for bound parameters
419 2
     */
420 2
    protected function getDeleteRowSQL(PersistentCollection $collection)
421 1
    {
422 2
        $mapping     = $collection->getMapping();
423
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
424
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
425 2
        $columns     = array();
426
        $types       = array();
427
428
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
429
            $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...
430
            $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...
431
        }
432
433
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
434
            $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...
435
            $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...
436 328
        }
437
438 328
        return array(
439 328
            '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...
440 328
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?',
441 328
            $types,
442 328
        );
443
    }
444 328
445 328
    /**
446 328
     * Gets the SQL parameters for the corresponding SQL statement to delete the given
447
     * element from the given collection.
448
     *
449 328
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql.
450 328
     *
451 328
     * @param \Doctrine\ORM\PersistentCollection $collection
452
     * @param mixed                              $element
453
     *
454
     * @return array
455 328
     */
456 328
    protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element)
457 328
    {
458
        return $this->collectJoinTableColumnParameters($collection, $element);
459
    }
460
461
    /**
462
     * Gets the SQL statement used for inserting a row in the collection.
463
     *
464
     * @param \Doctrine\ORM\PersistentCollection $collection
465
     *
466
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
467
     *                             of types for bound parameters
468
     */
469
    protected function getInsertRowSQL(PersistentCollection $collection)
470
    {
471
        $columns     = array();
472 12
        $types       = array();
473
        $mapping     = $collection->getMapping();
474 12
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
475
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
476
477
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
478
            $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...
479
            $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...
480
        }
481
482
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
483
            $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...
484
            $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...
485 328
        }
486
487 328
        return array(
488 328
            '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...
489 328
            . ' (' . implode(', ', $columns) . ')'
490 328
            . ' VALUES'
491 328
            . ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')',
492
            $types,
493 328
        );
494 328
    }
495 328
496
    /**
497
     * Gets the SQL parameters for the corresponding SQL statement to insert the given
498 328
     * element of the given collection into the database.
499 328
     *
500 328
     * Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql.
501
     *
502
     * @param \Doctrine\ORM\PersistentCollection $collection
503
     * @param mixed                              $element
504 328
     *
505 328
     * @return array
506 328
     */
507 328
    protected function getInsertRowSQLParameters(PersistentCollection $collection, $element)
508 328
    {
509
        return $this->collectJoinTableColumnParameters($collection, $element);
510
    }
511
512
    /**
513
     * Collects the parameters for inserting/deleting on the join table in the order
514
     * of the join table columns as specified in ManyToManyMapping#joinTableColumns.
515
     *
516
     * @param \Doctrine\ORM\PersistentCollection $collection
517
     * @param object                             $element
518
     *
519
     * @return array
520
     */
521
    private function collectJoinTableColumnParameters(PersistentCollection $collection, $element)
522
    {
523 328
        $params      = array();
524
        $mapping     = $collection->getMapping();
525 328
        $isComposite = count($mapping['joinTableColumns']) > 2;
526
527
        $identifier1 = $this->uow->getEntityIdentifier($collection->getOwner());
528
        $identifier2 = $this->uow->getEntityIdentifier($element);
529
530
        if ($isComposite) {
531
            $class1 = $this->em->getClassMetadata(get_class($collection->getOwner()));
532
            $class2 = $collection->getTypeClass();
533
        }
534
535
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
536
            $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]);
537 328
538
            if ( ! $isComposite) {
539 328
                $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2);
540 328
541 328
                continue;
542
            }
543 328
544 328
            if ($isRelationToSource) {
545
                $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...
546 328
547 21
                continue;
548 21
            }
549
550
            $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...
551 328
        }
552 328
553
        return $params;
554 328
    }
555 307
556
    /**
557 307
     * @param \Doctrine\ORM\PersistentCollection $collection
558
     * @param string                             $key
559
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
560 21
     *
561 21
     * @return array ordered vector:
562
     *                - quoted join table name
563 21
     *                - where clauses to be added for filtering
564
     *                - parameters to be bound for filtering
565
     *                - types of the parameters to be bound for filtering
566 21
     */
567
    private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters)
568
    {
569 328
        $filterMapping = $collection->getMapping();
570
        $mapping       = $filterMapping;
571
        $indexBy       = $mapping['indexBy'];
572
        $id            = $this->uow->getEntityIdentifier($collection->getOwner());
573
        $sourceClass   = $this->em->getClassMetadata($mapping['sourceEntity']);
574
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
575
576
        if (! $mapping['isOwningSide']) {
577
            $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
578
            $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...
579
            $joinColumns            = $mapping['joinTable']['joinColumns'];
580
            $sourceRelationMode     = 'relationToTargetKeyColumns';
581
            $targetRelationMode     = 'relationToSourceKeyColumns';
582
        } else {
583 7
            $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
584
            $joinColumns            = $mapping['joinTable']['inverseJoinColumns'];
585 7
            $sourceRelationMode     = 'relationToSourceKeyColumns';
586 7
            $targetRelationMode     = 'relationToTargetKeyColumns';
587 7
        }
588 7
589 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...
590 7
        $whereClauses    = array();
591
        $params          = array();
592 7
        $types           = array();
593 3
594 3
        $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...
595 3
596 3
        if ($joinNeeded) { // extra join needed if indexBy is not a @id
597 3
            $joinConditions = array();
598
599 4
            foreach ($joinColumns as $joinTableColumn) {
600 4
                $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName'];
601 4
            }
602 4
603
            $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...
604
            $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
605 7
            $columnName       = $targetClass->getColumnName($indexBy);
606 7
607 7
            $whereClauses[] = 'tr.' . $columnName . ' = ?';
608 7
            $params[]       = $key;
609
            $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...
610 7
        }
611
612 7
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
613 3
            if (isset($mapping[$sourceRelationMode][$joinTableColumn])) {
614
                $column         = $mapping[$sourceRelationMode][$joinTableColumn];
615 3
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
616 3
                $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...
617
                    ? $id[$sourceClass->getFieldForColumn($column)]
618
                    : $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...
619 3
                $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...
620 3
            } elseif ( ! $joinNeeded) {
621 3
                $column = $mapping[$targetRelationMode][$joinTableColumn];
622
623 3
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
624 3
                $params[]       = $key;
625 3
                $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...
626
            }
627
        }
628 7
629 7
        if ($addFilters) {
630 7
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
631 7
632 7
            if ($filterSql) {
633
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
634 7
                $whereClauses[] = $filterSql;
635 7
            }
636 7
        }
637 4
638
        return array($quotedJoinTable, $whereClauses, $params, $types);
639 4
    }
640 4
641 7
    /**
642
     * @param \Doctrine\ORM\PersistentCollection $collection
643
     * @param object                             $element
644
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
645 7
     *
646 7
     * @return array ordered vector:
647
     *                - quoted join table name
648 7
     *                - where clauses to be added for filtering
649
     *                - parameters to be bound for filtering
650
     *                - types of the parameters to be bound for filtering
651
     */
652
    private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters)
653
    {
654 7
        $filterMapping  = $collection->getMapping();
655
        $mapping        = $filterMapping;
656
657
        if ( ! $mapping['isOwningSide']) {
658
            $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
659
            $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']);
660
            $sourceId = $this->uow->getEntityIdentifier($element);
661
            $targetId = $this->uow->getEntityIdentifier($collection->getOwner());
662
663
            $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...
664
        } else {
665
            $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
666
            $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
667
            $sourceId = $this->uow->getEntityIdentifier($collection->getOwner());
668 9
            $targetId = $this->uow->getEntityIdentifier($element);
669
        }
670 9
671 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...
672
        $whereClauses    = array();
673 9
        $params          = array();
674 4
        $types           = array();
675 4
676 4
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
677 4
            $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?';
678
679 4
            if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
680
                $targetColumn = $mapping['relationToTargetKeyColumns'][$joinTableColumn];
681 5
                $params[]     = $targetId[$targetClass->getFieldForColumn($targetColumn)];
682 5
                $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...
683 5
684 5
                continue;
685
            }
686
687 9
            // relationToSourceKeyColumns
688 9
            $targetColumn = $mapping['relationToSourceKeyColumns'][$joinTableColumn];
689 9
            $params[]     = $sourceId[$sourceClass->getFieldForColumn($targetColumn)];
690 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...
691
        }
692 9
693 9
        if ($addFilters) {
694
            $quotedJoinTable .= ' t';
695 9
696 9
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
697 9
698 9
            if ($filterSql) {
699
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
700 9
                $whereClauses[] = $filterSql;
701
            }
702
        }
703
704 9
        return array($quotedJoinTable, $whereClauses, $params, $types);
705 9
    }
706 9
707
    /**
708
     * @param QueryBuilder $queryBuilder
709 9
     * @param Criteria $criteria
710 7
     * @param ClassMetadata $targetClass
711
     */
712 7
    private function applyCriteriaOrdering(QueryBuilder $queryBuilder, Criteria $criteria, ClassMetadata $targetClass)
713
    {
714 7
        $orderings = $criteria->getOrderings();
715 3
        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...
716 3
            foreach ($orderings as $name => $direction) {
717
                $field = $this->quoteStrategy->getColumnName(
718
                    $name,
719
                    $targetClass,
720 9
                    $this->platform
721
                );
722
723
                $queryBuilder->addOrderBy($field, $direction);
724
            }
725
        }
726
    }
727
728
    /**
729
     * @param QueryBuilder $queryBuilder
730
     * @param Criteria $criteria
731 7
     */
732
    private function applyCriteriaLimit(QueryBuilder $queryBuilder, Criteria $criteria)
733 7
    {
734
        $queryBuilder->setFirstResult($criteria->getFirstResult());
735 7
        $queryBuilder->setMaxResults($criteria->getMaxResults());
736 5
    }
737
738
    /**
739 2
     * @param PersistentCollection $collection
740
     * @return \Doctrine\DBAL\Query\QueryBuilder
741 2
     */
742
    private function createQueryBuilderForAssociation(PersistentCollection $collection)
743 2
    {
744
        $qb = $this->em->getConnection()->createQueryBuilder();
745 2
746
        $mapping       = $collection->getMapping();
747
        $owner         = $collection->getOwner();
748
        $ownerMetadata = $this->em->getClassMetadata(get_class($owner));
749
        $id            = $this->uow->getEntityIdentifier($owner);
750
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
751
        $onConditions  = $this->getOnConditionSQL($mapping);
752
753 7
754
        if ( ! $mapping['isOwningSide']) {
755 7
            $associationSourceClass = $targetClass;
756 7
            $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...
757 2
            $sourceRelationMode = 'relationToTargetKeyColumns';
758 2
        } else {
759 2
            $associationSourceClass = $ownerMetadata;
760
            $sourceRelationMode = 'relationToSourceKeyColumns';
761
        }
762 2
763
        foreach ($mapping[$sourceRelationMode] as $key => $value) {
764 2
            $paramName = sprintf(':t%s', $key);
765
            $qb->where(sprintf('t.%s = %s', $key, $paramName));
766
767 2
            $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...
768
                ? $id[$ownerMetadata->getFieldForColumn($value)]
769 5
                : $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...
770
771
            $qb->setParameter($paramName, $paramValue, $ownerMetadata->getTypeOfField($key));
772
        }
773
774
        $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...
775
        $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...
776
777 7
        $qb->select('te.*');
778
        $qb->from($tableName, 'te');
779 7
        $qb->join('te', $joinTable, 't', join(' AND ', $onConditions));
780 7
781 7
        return $qb;
782 3
    }
783
}
784