Completed
Pull Request — master (#6440)
by Ondřej
21:27
created

ManyToManyPersister::removeElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
c 0
b 0
f 0
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 2
crap 2
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\ORM\Mapping\ClassMetadata;
24
use Doctrine\ORM\Persisters\SqlValueVisitor;
25
use Doctrine\ORM\PersistentCollection;
26
use Doctrine\ORM\Query;
27
use Doctrine\ORM\Utility\PersisterHelper;
28
29
/**
30
 * Persister for many-to-many collections.
31
 *
32
 * @author  Roman Borschel <[email protected]>
33
 * @author  Guilherme Blanco <[email protected]>
34
 * @author  Alexander <[email protected]>
35
 * @since   2.0
36
 */
37
class ManyToManyPersister extends AbstractCollectionPersister
38
{
39
    /**
40
     * {@inheritdoc}
41
     */
42 14
    public function delete(PersistentCollection $collection)
43
    {
44 14
        $mapping = $collection->getMapping();
45
46 14
        if ( ! $mapping['isOwningSide']) {
47
            return; // ignore inverse side
48
        }
49
50 14
        $types = [];
51 14
        $class = $this->em->getClassMetadata($mapping['sourceEntity']);
52
53 14
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
54 14
            $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...
55
        }
56
57 14
        $this->conn->executeUpdate($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types);
58 14
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 320
    public function update(PersistentCollection $collection)
64
    {
65 320
        $mapping = $collection->getMapping();
66
67 320
        if ( ! $mapping['isOwningSide']) {
68 230
            return; // ignore inverse side
69
        }
70
71 319
        list($deleteSql, $deleteTypes) = $this->getDeleteRowSQL($collection);
72 319
        list($insertSql, $insertTypes) = $this->getInsertRowSQL($collection);
73
74 319
        foreach ($collection->getDeleteDiff() as $element) {
75 11
            $this->conn->executeUpdate(
76
                $deleteSql,
77 11
                $this->getDeleteRowSQLParameters($collection, $element),
78 11
                $deleteTypes
79
            );
80
        }
81
82 319
        foreach ($collection->getInsertDiff() as $element) {
83 319
            $this->conn->executeUpdate(
84
                $insertSql,
85 319
                $this->getInsertRowSQLParameters($collection, $element),
86 319
                $insertTypes
87
            );
88
        }
89 318
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 3
    public function get(PersistentCollection $collection, $index)
95
    {
96 3
        $mapping = $collection->getMapping();
97
98 3
        if ( ! isset($mapping['indexBy'])) {
99
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
100
        }
101
102 3
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
103 3
        $mappedKey = $mapping['isOwningSide']
104 2
            ? $mapping['inversedBy']
105 3
            : $mapping['mappedBy'];
106
107 3
        return $persister->load([$mappedKey => $collection->getOwner(), $mapping['indexBy'] => $index], null, $mapping, [], 0, 1);
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 16
    public function count(PersistentCollection $collection)
114
    {
115 16
        $conditions     = [];
116 16
        $params         = [];
117 16
        $types          = [];
118 16
        $mapping        = $collection->getMapping();
119 16
        $id             = $this->uow->getEntityIdentifier($collection->getOwner());
120 16
        $sourceClass    = $this->em->getClassMetadata($mapping['sourceEntity']);
121 16
        $targetClass    = $this->em->getClassMetadata($mapping['targetEntity']);
122 16
        $association    = ( ! $mapping['isOwningSide'])
123 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...
124 16
            : $mapping;
125
126 16
        $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...
127 16
        $joinColumns    = ( ! $mapping['isOwningSide'])
128 4
            ? $association['joinTable']['inverseJoinColumns']
129 16
            : $association['joinTable']['joinColumns'];
130
131 16
        foreach ($joinColumns as $joinColumn) {
132 16
            $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...
133 16
            $referencedName = $joinColumn['referencedColumnName'];
134 16
            $conditions[]   = 't.' . $columnName . ' = ?';
135 16
            $params[]       = $id[$sourceClass->getFieldForColumn($referencedName)];
136 16
            $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...
137
        }
138
139 16
        list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
140
141 16
        if ($filterSql) {
142 3
            $conditions[] = $filterSql;
143
        }
144
145
        // If there is a provided criteria, make part of conditions
146
        // @todo Fix this. Current SQL returns something like:
147
        //
148
        /*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) {
149
            // A join is needed on the target entity
150
            $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
151
            $targetJoinSql   = ' JOIN ' . $targetTableName . ' te'
152
                . ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
153
154
            // And criteria conditions needs to be added
155
            $persister    = $this->uow->getEntityPersister($targetClass->name);
156
            $visitor      = new SqlExpressionVisitor($persister, $targetClass);
157
            $conditions[] = $visitor->dispatch($expression);
158
159
            $joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL;
160
        }*/
161
162
        $sql = 'SELECT COUNT(*)'
163 16
            . ' FROM ' . $joinTableName . ' t'
164 16
            . $joinTargetEntitySQL
165 16
            . ' WHERE ' . implode(' AND ', $conditions);
166
167 16
        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 167 which is incompatible with the return type declared by the interface Doctrine\ORM\Persisters\...lectionPersister::count of type integer.
Loading history...
168
    }
169
170
    /**
171
     * {@inheritDoc}
172
     */
173 8
    public function slice(PersistentCollection $collection, $offset, $length = null)
174
    {
175 8
        $mapping   = $collection->getMapping();
176 8
        $persister = $this->uow->getEntityPersister($mapping['targetEntity']);
177
178 8
        return $persister->getManyToManyCollection($mapping, $collection->getOwner(), $offset, $length);
179
    }
180
    /**
181
     * {@inheritdoc}
182
     */
183 7
    public function containsKey(PersistentCollection $collection, $key)
184
    {
185 7
        $mapping = $collection->getMapping();
186
187 7
        if ( ! isset($mapping['indexBy'])) {
188
            throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
189
        }
190
191 7
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictionsWithKey($collection, $key, true);
192
193 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
194
195 7
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
196
    }
197
198
    /**
199
     * {@inheritDoc}
200
     */
201 7
    public function contains(PersistentCollection $collection, $element)
202
    {
203 7
        if ( ! $this->isValidEntityState($element)) {
204 2
            return false;
205
        }
206
207 7
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, true);
208
209 7
        $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
210
211 7
        return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     */
217 2
    public function removeElement(PersistentCollection $collection, $element)
218
    {
219 2
        if ( ! $this->isValidEntityState($element)) {
220 2
            return false;
221
        }
222
223 2
        list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, false);
224
225 2
        $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
226
227 2
        return (bool) $this->conn->executeUpdate($sql, $params, $types);
228
    }
229
230
    /**
231
     * {@inheritDoc}
232
     */
233 6
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
234
    {
235 6
        $mapping       = $collection->getMapping();
236 6
        $owner         = $collection->getOwner();
237 6
        $ownerMetadata = $this->em->getClassMetadata(get_class($owner));
238 6
        $id            = $this->uow->getEntityIdentifier($owner);
239 6
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
240 6
        $onConditions  = $this->getOnConditionSQL($mapping);
241 6
        $whereClauses  = $params = [];
242
243 6
        if ( ! $mapping['isOwningSide']) {
244 1
            $associationSourceClass = $targetClass;
245 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...
246 1
            $sourceRelationMode = 'relationToTargetKeyColumns';
247
        } else {
248 5
            $associationSourceClass = $ownerMetadata;
249 5
            $sourceRelationMode = 'relationToSourceKeyColumns';
250
        }
251
252 6
        foreach ($mapping[$sourceRelationMode] as $key => $value) {
253 6
            $whereClauses[] = sprintf('t.%s = ?', $key);
254 6
            $params[] = $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...
255
                ? $id[$ownerMetadata->getFieldForColumn($value)]
256 6
                : $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...
257
        }
258
259 6
        $parameters = $this->expandCriteriaParameters($criteria);
260
261 6
        foreach ($parameters as $parameter) {
262 2
            list($name, $value) = $parameter;
263 2
            $field = $this->quoteStrategy->getColumnName($name, $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...
264 2
            $whereClauses[]     = sprintf('te.%s = ?', $field);
265 2
            $params[]           = $value;
266
        }
267
268 6
        $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...
269 6
        $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...
270
271 6
        $rsm = new Query\ResultSetMappingBuilder($this->em);
272 6
        $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...
273
274 6
        $sql = 'SELECT ' . $rsm->generateSelectClause()
275 6
            . ' FROM ' . $tableName . ' te'
276 6
            . ' JOIN ' . $joinTable  . ' t ON'
277 6
            . implode(' AND ', $onConditions)
278 6
            . ' WHERE ' . implode(' AND ', $whereClauses);
279
280 6
        $sql .= $this->getOrderingSql($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...
281
282 6
        $sql .= $this->getLimitSql($criteria);
283
284 6
        $stmt = $this->conn->executeQuery($sql, $params);
285
286
        return $this
287 6
            ->em
288 6
            ->newHydrator(Query::HYDRATE_OBJECT)
289 6
            ->hydrateAll($stmt, $rsm);
290
    }
291
292
    /**
293
     * Generates the filter SQL for a given mapping.
294
     *
295
     * This method is not used for actually grabbing the related entities
296
     * but when the extra-lazy collection methods are called on a filtered
297
     * association. This is why besides the many to many table we also
298
     * have to join in the actual entities table leading to additional
299
     * JOIN.
300
     *
301
     * @param array $mapping Array containing mapping information.
302
     *
303
     * @return string[] ordered tuple:
304
     *                   - JOIN condition to add to the SQL
305
     *                   - WHERE condition to add to the SQL
306
     */
307 30
    public function getFilterSql($mapping)
308
    {
309 30
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
310 30
        $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...
311 30
        $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...
312
313 30
        if ('' === $filterSql) {
314 30
            return ['', ''];
315
        }
316
317
        // A join is needed if there is filtering on the target entity
318 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...
319 6
        $joinSql   = ' JOIN ' . $tableName . ' te'
320 6
            . ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping));
321
322 6
        return [$joinSql, $filterSql];
323
    }
324
325
    /**
326
     * Generates the filter SQL for a given entity and table alias.
327
     *
328
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
329
     * @param string        $targetTableAlias The table alias of the joined/selected table.
330
     *
331
     * @return string The SQL query part to add to a query.
332
     */
333 30
    protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
334
    {
335 30
        $filterClauses = [];
336
337 30
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
338 6
            if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
339 6
                $filterClauses[] = '(' . $filterExpr . ')';
340
            }
341
        }
342
343 30
        return $filterClauses
344 6
            ? '(' . implode(' AND ', $filterClauses) . ')'
345 30
            : '';
346
    }
347
348
    /**
349
     * Generate ON condition
350
     *
351
     * @param  array $mapping
352
     *
353
     * @return array
354
     */
355 12
    protected function getOnConditionSQL($mapping)
356
    {
357 12
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
358 12
        $association = ( ! $mapping['isOwningSide'])
359 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...
360 12
            : $mapping;
361
362 12
        $joinColumns = $mapping['isOwningSide']
363 9
            ? $association['joinTable']['inverseJoinColumns']
364 12
            : $association['joinTable']['joinColumns'];
365
366 12
        $conditions = [];
367
368 12
        foreach ($joinColumns as $joinColumn) {
369 12
            $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...
370 12
            $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...
371
372 12
            $conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName;
373
        }
374
375 12
        return $conditions;
376
    }
377
378
    /**
379
     * {@inheritdoc}
380
     *
381
     * @override
382
     */
383 14
    protected function getDeleteSQL(PersistentCollection $collection)
384
    {
385 14
        $columns    = [];
386 14
        $mapping    = $collection->getMapping();
387 14
        $class      = $this->em->getClassMetadata(get_class($collection->getOwner()));
388 14
        $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...
389
390 14
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
391 14
            $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...
392
        }
393
394 14
        return 'DELETE FROM ' . $joinTable
395 14
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
396
    }
397
398
    /**
399
     * {@inheritdoc}
400
     *
401
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql.
402
     * @override
403
     */
404 14
    protected function getDeleteSQLParameters(PersistentCollection $collection)
405
    {
406 14
        $mapping    = $collection->getMapping();
407 14
        $identifier = $this->uow->getEntityIdentifier($collection->getOwner());
408
409
        // Optimization for single column identifier
410 14
        if (count($mapping['relationToSourceKeyColumns']) === 1) {
411 13
            return [reset($identifier)];
412
        }
413
414
        // Composite identifier
415 1
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
416 1
        $params      = [];
417
418 1
        foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) {
419 1
            $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...
420
                ? $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...
421 1
                : $identifier[$sourceClass->getFieldForColumn($columnName)];
422
        }
423
424 1
        return $params;
425
    }
426
427
    /**
428
     * Gets the SQL statement used for deleting a row from the collection.
429
     *
430
     * @param \Doctrine\ORM\PersistentCollection $collection
431
     *
432
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
433
     *                             of types for bound parameters
434
     */
435 319
    protected function getDeleteRowSQL(PersistentCollection $collection)
436
    {
437 319
        $mapping     = $collection->getMapping();
438 319
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
439 319
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
440 319
        $columns     = [];
441 319
        $types       = [];
442
443 319
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
444 319
            $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...
445 319
            $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...
446
        }
447
448 319
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
449 319
            $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...
450 319
            $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...
451
        }
452
453
        return [
454 319
            '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...
455 319
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?',
456 319
            $types,
457
        ];
458
    }
459
460
    /**
461
     * Gets the SQL parameters for the corresponding SQL statement to delete the given
462
     * element from the given collection.
463
     *
464
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql.
465
     *
466
     * @param \Doctrine\ORM\PersistentCollection $collection
467
     * @param mixed                              $element
468
     *
469
     * @return array
470
     */
471 11
    protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element)
472
    {
473 11
        return $this->collectJoinTableColumnParameters($collection, $element);
474
    }
475
476
    /**
477
     * Gets the SQL statement used for inserting a row in the collection.
478
     *
479
     * @param \Doctrine\ORM\PersistentCollection $collection
480
     *
481
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
482
     *                             of types for bound parameters
483
     */
484 319
    protected function getInsertRowSQL(PersistentCollection $collection)
485
    {
486 319
        $columns     = [];
487 319
        $types       = [];
488 319
        $mapping     = $collection->getMapping();
489 319
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
490 319
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
491
492 319
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
493 319
            $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...
494 319
            $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...
495
        }
496
497 319
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
498 319
            $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...
499 319
            $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...
500
        }
501
502
        return [
503 319
            '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...
504 319
            . ' (' . implode(', ', $columns) . ')'
505 319
            . ' VALUES'
506 319
            . ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')',
507 319
            $types,
508
        ];
509
    }
510
511
    /**
512
     * Gets the SQL parameters for the corresponding SQL statement to insert the given
513
     * element of the given collection into the database.
514
     *
515
     * Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql.
516
     *
517
     * @param \Doctrine\ORM\PersistentCollection $collection
518
     * @param mixed                              $element
519
     *
520
     * @return array
521
     */
522 319
    protected function getInsertRowSQLParameters(PersistentCollection $collection, $element)
523
    {
524 319
        return $this->collectJoinTableColumnParameters($collection, $element);
525
    }
526
527
    /**
528
     * Collects the parameters for inserting/deleting on the join table in the order
529
     * of the join table columns as specified in ManyToManyMapping#joinTableColumns.
530
     *
531
     * @param \Doctrine\ORM\PersistentCollection $collection
532
     * @param object                             $element
533
     *
534
     * @return array
535
     */
536 319
    private function collectJoinTableColumnParameters(PersistentCollection $collection, $element)
537
    {
538 319
        $params      = [];
539 319
        $mapping     = $collection->getMapping();
540 319
        $isComposite = count($mapping['joinTableColumns']) > 2;
541
542 319
        $identifier1 = $this->uow->getEntityIdentifier($collection->getOwner());
543 319
        $identifier2 = $this->uow->getEntityIdentifier($element);
544
545 319
        if ($isComposite) {
546 16
            $class1 = $this->em->getClassMetadata(get_class($collection->getOwner()));
547 16
            $class2 = $collection->getTypeClass();
548
        }
549
550 319
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
551 319
            $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]);
552
553 319
            if ( ! $isComposite) {
554 303
                $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2);
555
556 303
                continue;
557
            }
558
559 16
            if ($isRelationToSource) {
560 16
                $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...
561
562 16
                continue;
563
            }
564
565 16
            $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...
566
        }
567
568 319
        return $params;
569
    }
570
571
    /**
572
     * @param \Doctrine\ORM\PersistentCollection $collection
573
     * @param string                             $key
574
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
575
     *
576
     * @return array ordered vector:
577
     *                - quoted join table name
578
     *                - where clauses to be added for filtering
579
     *                - parameters to be bound for filtering
580
     *                - types of the parameters to be bound for filtering
581
     */
582 7
    private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters)
583
    {
584 7
        $filterMapping = $collection->getMapping();
585 7
        $mapping       = $filterMapping;
586 7
        $indexBy       = $mapping['indexBy'];
587 7
        $id            = $this->uow->getEntityIdentifier($collection->getOwner());
588 7
        $sourceClass   = $this->em->getClassMetadata($mapping['sourceEntity']);
589 7
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
590
591 7
        if (! $mapping['isOwningSide']) {
592 3
            $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
593 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...
594 3
            $joinColumns            = $mapping['joinTable']['joinColumns'];
595 3
            $sourceRelationMode     = 'relationToTargetKeyColumns';
596 3
            $targetRelationMode     = 'relationToSourceKeyColumns';
597
        } else {
598 4
            $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
599 4
            $joinColumns            = $mapping['joinTable']['inverseJoinColumns'];
600 4
            $sourceRelationMode     = 'relationToSourceKeyColumns';
601 4
            $targetRelationMode     = 'relationToTargetKeyColumns';
602
        }
603
604 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...
605 7
        $whereClauses    = [];
606 7
        $params          = [];
607 7
        $types           = [];
608
609 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...
610
611 7
        if ($joinNeeded) { // extra join needed if indexBy is not a @id
612 3
            $joinConditions = [];
613
614 3
            foreach ($joinColumns as $joinTableColumn) {
615 3
                $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName'];
616
            }
617
618 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...
619 3
            $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
620 3
            $columnName       = $targetClass->getColumnName($indexBy);
621
622 3
            $whereClauses[] = 'tr.' . $columnName . ' = ?';
623 3
            $params[]       = $key;
624 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...
625
        }
626
627 7
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
628 7
            if (isset($mapping[$sourceRelationMode][$joinTableColumn])) {
629 7
                $column         = $mapping[$sourceRelationMode][$joinTableColumn];
630 7
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
631 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...
632
                    ? $id[$sourceClass->getFieldForColumn($column)]
633 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...
634 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...
635 7
            } elseif ( ! $joinNeeded) {
636 4
                $column = $mapping[$targetRelationMode][$joinTableColumn];
637
638 4
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
639 4
                $params[]       = $key;
640 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...
641
            }
642
        }
643
644 7
        if ($addFilters) {
645 7
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
646
647 7
            if ($filterSql) {
648
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
649
                $whereClauses[] = $filterSql;
650
            }
651
        }
652
653 7
        return [$quotedJoinTable, $whereClauses, $params, $types];
654
    }
655
656
    /**
657
     * @param \Doctrine\ORM\PersistentCollection $collection
658
     * @param object                             $element
659
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
660
     *
661
     * @return array ordered vector:
662
     *                - quoted join table name
663
     *                - where clauses to be added for filtering
664
     *                - parameters to be bound for filtering
665
     *                - types of the parameters to be bound for filtering
666
     */
667 9
    private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters)
668
    {
669 9
        $filterMapping  = $collection->getMapping();
670 9
        $mapping        = $filterMapping;
671
672 9
        if ( ! $mapping['isOwningSide']) {
673 4
            $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
674 4
            $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']);
675 4
            $sourceId = $this->uow->getEntityIdentifier($element);
676 4
            $targetId = $this->uow->getEntityIdentifier($collection->getOwner());
677
678 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...
679
        } else {
680 5
            $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
681 5
            $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
682 5
            $sourceId = $this->uow->getEntityIdentifier($collection->getOwner());
683 5
            $targetId = $this->uow->getEntityIdentifier($element);
684
        }
685
686 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...
687 9
        $whereClauses    = [];
688 9
        $params          = [];
689 9
        $types           = [];
690
691 9
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
692 9
            $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?';
693
694 9
            if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
695 9
                $targetColumn = $mapping['relationToTargetKeyColumns'][$joinTableColumn];
696 9
                $params[]     = $targetId[$targetClass->getFieldForColumn($targetColumn)];
697 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...
698
699 9
                continue;
700
            }
701
702
            // relationToSourceKeyColumns
703 9
            $targetColumn = $mapping['relationToSourceKeyColumns'][$joinTableColumn];
704 9
            $params[]     = $sourceId[$sourceClass->getFieldForColumn($targetColumn)];
705 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...
706
        }
707
708 9
        if ($addFilters) {
709 7
            $quotedJoinTable .= ' t';
710
711 7
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
712
713 7
            if ($filterSql) {
714 3
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
715 3
                $whereClauses[] = $filterSql;
716
            }
717
        }
718
719 9
        return [$quotedJoinTable, $whereClauses, $params, $types];
720
    }
721
722
    /**
723
     * Expands Criteria Parameters by walking the expressions and grabbing all
724
     * parameters and types from it.
725
     *
726
     * @param \Doctrine\Common\Collections\Criteria $criteria
727
     *
728
     * @return array
729
     */
730 6
    private function expandCriteriaParameters(Criteria $criteria)
731
    {
732 6
        $expression = $criteria->getWhereExpression();
733
734 6
        if ($expression === null) {
735 4
            return [];
736
        }
737
738 2
        $valueVisitor = new SqlValueVisitor();
739
740 2
        $valueVisitor->dispatch($expression);
741
742 2
        list(, $types) = $valueVisitor->getParamsAndTypes();
743
744 2
        return $types;
745
    }
746
747
    /**
748
     * @param Criteria $criteria
749
     * @param ClassMetadata $targetClass
750
     * @return string
751
     */
752 6
    private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass)
753
    {
754 6
        $orderings = $criteria->getOrderings();
755 6
        if ($orderings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $orderings of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
756 1
            $orderBy = [];
757 1
            foreach ($orderings as $name => $direction) {
758 1
                $field = $this->quoteStrategy->getColumnName(
759
                    $name,
760
                    $targetClass,
761 1
                    $this->platform
762
                );
763 1
                $orderBy[] = $field . ' ' . $direction;
764
            }
765
766 1
            return ' ORDER BY ' . implode(', ', $orderBy);
767
        }
768 5
        return '';
769
    }
770
771
    /**
772
     * @param Criteria $criteria
773
     * @return string
774
     * @throws \Doctrine\DBAL\DBALException
775
     */
776 6
    private function getLimitSql(Criteria $criteria)
777
    {
778 6
        $limit  = $criteria->getMaxResults();
779 6
        $offset = $criteria->getFirstResult();
780 6
        if ($limit !== null || $offset !== null) {
781 3
            return $this->platform->modifyLimitQuery('', $limit, $offset);
782
        }
783 3
        return '';
784
    }
785
}
786