Failed Conditions
Push — 2.8.x ( 0ee171...60c486 )
by Benjamin
22:42 queued 16:29
created

ManyToManyPersister::getInsertRowSQL()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 17
nc 4
nop 1
dl 0
loc 24
rs 9.7
c 0
b 0
f 0
ccs 17
cts 17
cp 1
crap 3
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 19
    public function delete(PersistentCollection $collection)
43
    {
44 19
        $mapping = $collection->getMapping();
45
46 19
        if ( ! $mapping['isOwningSide']) {
47
            return; // ignore inverse side
48
        }
49
50 19
        $types = [];
51 19
        $class = $this->em->getClassMetadata($mapping['sourceEntity']);
52
53 19
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
54 19
            $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
55
        }
56
57 19
        $this->conn->executeUpdate($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types);
58 19
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 326
    public function update(PersistentCollection $collection)
64
    {
65 326
        $mapping = $collection->getMapping();
66
67 326
        if ( ! $mapping['isOwningSide']) {
68 225
            return; // ignore inverse side
69
        }
70
71 325
        list($deleteSql, $deleteTypes) = $this->getDeleteRowSQL($collection);
72 325
        list($insertSql, $insertTypes) = $this->getInsertRowSQL($collection);
73
74 325
        foreach ($collection->getDeleteDiff() as $element) {
75 12
            $this->conn->executeUpdate(
76 12
                $deleteSql,
77 12
                $this->getDeleteRowSQLParameters($collection, $element),
78 12
                $deleteTypes
79
            );
80
        }
81
82 325
        foreach ($collection->getInsertDiff() as $element) {
83 325
            $this->conn->executeUpdate(
84 325
                $insertSql,
85 325
                $this->getInsertRowSQLParameters($collection, $element),
86 325
                $insertTypes
87
            );
88
        }
89 325
    }
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 18
    public function count(PersistentCollection $collection)
114
    {
115 18
        $conditions     = [];
116 18
        $params         = [];
117 18
        $types          = [];
118 18
        $mapping        = $collection->getMapping();
119 18
        $id             = $this->uow->getEntityIdentifier($collection->getOwner());
120 18
        $sourceClass    = $this->em->getClassMetadata($mapping['sourceEntity']);
121 18
        $targetClass    = $this->em->getClassMetadata($mapping['targetEntity']);
122 18
        $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?
Loading history...
124 18
            : $mapping;
125
126 18
        $joinTableName  = $this->quoteStrategy->getJoinTableName($association, $sourceClass, $this->platform);
127 18
        $joinColumns    = ( ! $mapping['isOwningSide'])
128 4
            ? $association['joinTable']['inverseJoinColumns']
129 18
            : $association['joinTable']['joinColumns'];
130
131 18
        foreach ($joinColumns as $joinColumn) {
132 18
            $columnName     = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->platform);
133 18
            $referencedName = $joinColumn['referencedColumnName'];
134 18
            $conditions[]   = 't.' . $columnName . ' = ?';
135 18
            $params[]       = $id[$sourceClass->getFieldForColumn($referencedName)];
136 18
            $types[]        = PersisterHelper::getTypeOfColumn($referencedName, $sourceClass, $this->em);
137
        }
138
139 18
        list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
140
141 18
        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 18
            . ' FROM ' . $joinTableName . ' t'
164 18
            . $joinTargetEntitySQL
165 18
            . ' WHERE ' . implode(' AND ', $conditions);
166
167 18
        return $this->conn->fetchColumn($sql, $params, 0, $types);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->fetc...ql, $params, 0, $types) also could return the type false which is incompatible with the return type mandated by Doctrine\ORM\Persisters\...ctionPersister::count() of 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 12
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
218
    {
219 12
        $mapping       = $collection->getMapping();
220 12
        $owner         = $collection->getOwner();
221 12
        $ownerMetadata = $this->em->getClassMetadata(get_class($owner));
222 12
        $id            = $this->uow->getEntityIdentifier($owner);
223 12
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
224 12
        $onConditions  = $this->getOnConditionSQL($mapping);
225 12
        $whereClauses  = $params = [];
226
227 12
        if ( ! $mapping['isOwningSide']) {
228 1
            $associationSourceClass = $targetClass;
229 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?
Loading history...
230 1
            $sourceRelationMode = 'relationToTargetKeyColumns';
231
        } else {
232 11
            $associationSourceClass = $ownerMetadata;
233 11
            $sourceRelationMode = 'relationToSourceKeyColumns';
234
        }
235
236 12
        foreach ($mapping[$sourceRelationMode] as $key => $value) {
237 12
            $whereClauses[] = sprintf('t.%s = ?', $key);
238 12
            $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?
Loading history...
239
                ? $id[$ownerMetadata->getFieldForColumn($value)]
240 12
                : $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?
Loading history...
241
        }
242
243 12
        $parameters = $this->expandCriteriaParameters($criteria);
244
245 12
        foreach ($parameters as $parameter) {
246 7
            [$name, $value, $operator] = $parameter;
247
248 7
            $field          = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform);
249 7
            $whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
250 7
            $params[]       = $value;
251
        }
252
253 12
        $tableName    = $this->quoteStrategy->getTableName($targetClass, $this->platform);
254 12
        $joinTable    = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform);
255
256 12
        $rsm = new Query\ResultSetMappingBuilder($this->em);
257 12
        $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?
Loading history...
258
259 12
        $sql = 'SELECT ' . $rsm->generateSelectClause()
260 12
            . ' FROM ' . $tableName . ' te'
261 12
            . ' JOIN ' . $joinTable  . ' t ON'
262 12
            . implode(' AND ', $onConditions)
263 12
            . ' WHERE ' . implode(' AND ', $whereClauses);
264
265 12
        $sql .= $this->getOrderingSql($criteria, $targetClass);
266
267 12
        $sql .= $this->getLimitSql($criteria);
268
269 12
        $stmt = $this->conn->executeQuery($sql, $params);
270
271
        return $this
272 12
            ->em
273 12
            ->newHydrator(Query::HYDRATE_OBJECT)
274 12
            ->hydrateAll($stmt, $rsm);
275
    }
276
277
    /**
278
     * Generates the filter SQL for a given mapping.
279
     *
280
     * This method is not used for actually grabbing the related entities
281
     * 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
     * have to join in the actual entities table leading to additional
284
     * JOIN.
285
     *
286
     * @param array $mapping Array containing mapping information.
287
     *
288
     * @return string[] ordered tuple:
289
     *                   - JOIN condition to add to the SQL
290
     *                   - WHERE condition to add to the SQL
291
     */
292 32
    public function getFilterSql($mapping)
293
    {
294 32
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
295 32
        $rootClass   = $this->em->getClassMetadata($targetClass->rootEntityName);
0 ignored issues
show
Bug introduced by
Accessing rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
296 32
        $filterSql   = $this->generateFilterConditionSQL($rootClass, 'te');
297
298 32
        if ('' === $filterSql) {
299 32
            return ['', ''];
300
        }
301
302
        // A join is needed if there is filtering on the target entity
303 6
        $tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform);
304 6
        $joinSql   = ' JOIN ' . $tableName . ' te'
305 6
            . ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping));
306
307 6
        return [$joinSql, $filterSql];
308
    }
309
310
    /**
311
     * Generates the filter SQL for a given entity and table alias.
312
     *
313
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
314
     * @param string        $targetTableAlias The table alias of the joined/selected table.
315
     *
316
     * @return string The SQL query part to add to a query.
317
     */
318 32
    protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
319
    {
320 32
        $filterClauses = [];
321
322 32
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
323 6
            if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
324 6
                $filterClauses[] = '(' . $filterExpr . ')';
325
            }
326
        }
327
328 32
        return $filterClauses
329 6
            ? '(' . implode(' AND ', $filterClauses) . ')'
330 32
            : '';
331
    }
332
333
    /**
334
     * Generate ON condition
335
     *
336
     * @param  array $mapping
337
     *
338
     * @return array
339
     */
340 18
    protected function getOnConditionSQL($mapping)
341
    {
342 18
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
343 18
        $association = ( ! $mapping['isOwningSide'])
344 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?
Loading history...
345 18
            : $mapping;
346
347 18
        $joinColumns = $mapping['isOwningSide']
348 15
            ? $association['joinTable']['inverseJoinColumns']
349 18
            : $association['joinTable']['joinColumns'];
350
351 18
        $conditions = [];
352
353 18
        foreach ($joinColumns as $joinColumn) {
354 18
            $joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
355 18
            $refColumnName  = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
356
357 18
            $conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName;
358
        }
359
360 18
        return $conditions;
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     *
366
     * @override
367
     */
368 19
    protected function getDeleteSQL(PersistentCollection $collection)
369
    {
370 19
        $columns    = [];
371 19
        $mapping    = $collection->getMapping();
372 19
        $class      = $this->em->getClassMetadata(get_class($collection->getOwner()));
373 19
        $joinTable  = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform);
374
375 19
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
376 19
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
377
        }
378
379 19
        return 'DELETE FROM ' . $joinTable
380 19
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
381
    }
382
383
    /**
384
     * {@inheritdoc}
385
     *
386
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql.
387
     * @override
388
     */
389 19
    protected function getDeleteSQLParameters(PersistentCollection $collection)
390
    {
391 19
        $mapping    = $collection->getMapping();
392 19
        $identifier = $this->uow->getEntityIdentifier($collection->getOwner());
393
394
        // Optimization for single column identifier
395 19
        if (count($mapping['relationToSourceKeyColumns']) === 1) {
396 16
            return [reset($identifier)];
397
        }
398
399
        // Composite identifier
400 3
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
401 3
        $params      = [];
402
403 3
        foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) {
404 3
            $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?
Loading history...
405 2
                ? $identifier[$sourceClass->fieldNames[$refColumnName]]
406 3
                : $identifier[$sourceClass->getFieldForColumn($refColumnName)];
407
        }
408
409 3
        return $params;
410
    }
411
412
    /**
413
     * Gets the SQL statement used for deleting a row from the collection.
414
     *
415
     * @param \Doctrine\ORM\PersistentCollection $collection
416
     *
417
     * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
418
     *                             of types for bound parameters
419
     */
420 325
    protected function getDeleteRowSQL(PersistentCollection $collection)
421
    {
422 325
        $mapping     = $collection->getMapping();
423 325
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
424 325
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
425 325
        $columns     = [];
426 325
        $types       = [];
427
428 325
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
429 325
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
430 325
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
431
        }
432
433 325
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
434 325
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
435 325
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
436
        }
437
438
        return [
439 325
            'DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
440 325
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?',
441 325
            $types,
442
        ];
443
    }
444
445
    /**
446
     * Gets the SQL parameters for the corresponding SQL statement to delete the given
447
     * element from the given collection.
448
     *
449
     * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql.
450
     *
451
     * @param \Doctrine\ORM\PersistentCollection $collection
452
     * @param mixed                              $element
453
     *
454
     * @return array
455
     */
456 12
    protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element)
457
    {
458 12
        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 325
    protected function getInsertRowSQL(PersistentCollection $collection)
470
    {
471 325
        $columns     = [];
472 325
        $types       = [];
473 325
        $mapping     = $collection->getMapping();
474 325
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
475 325
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
476
477 325
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
478 325
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
479 325
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
480
        }
481
482 325
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
483 325
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
484 325
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
485
        }
486
487
        return [
488 325
            'INSERT INTO ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
489 325
            . ' (' . implode(', ', $columns) . ')'
490 325
            . ' VALUES'
491 325
            . ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')',
492 325
            $types,
493
        ];
494
    }
495
496
    /**
497
     * Gets the SQL parameters for the corresponding SQL statement to insert the given
498
     * element of the given collection into the database.
499
     *
500
     * 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
     *
505
     * @return array
506
     */
507 325
    protected function getInsertRowSQLParameters(PersistentCollection $collection, $element)
508
    {
509 325
        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 325
    private function collectJoinTableColumnParameters(PersistentCollection $collection, $element)
522
    {
523 325
        $params      = [];
524 325
        $mapping     = $collection->getMapping();
525 325
        $isComposite = count($mapping['joinTableColumns']) > 2;
526
527 325
        $identifier1 = $this->uow->getEntityIdentifier($collection->getOwner());
528 325
        $identifier2 = $this->uow->getEntityIdentifier($element);
529
530 325
        $class1 = $class2 = null;
531 325
        if ($isComposite) {
532 22
            $class1 = $this->em->getClassMetadata(get_class($collection->getOwner()));
533 22
            $class2 = $collection->getTypeClass();
534
        }
535
536 325
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
537 325
            $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]);
538
539 325
            if ( ! $isComposite) {
540 303
                $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2);
541
542 303
                continue;
543
            }
544
545 22
            if ($isRelationToSource) {
546 22
                $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
547
548 22
                continue;
549
            }
550
551 22
            $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
552
        }
553
554 325
        return $params;
555
    }
556
557
    /**
558
     * @param \Doctrine\ORM\PersistentCollection $collection
559
     * @param string                             $key
560
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
561
     *
562
     * @return array ordered vector:
563
     *                - quoted join table name
564
     *                - where clauses to be added for filtering
565
     *                - parameters to be bound for filtering
566
     *                - types of the parameters to be bound for filtering
567
     */
568 7
    private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters)
569
    {
570 7
        $filterMapping = $collection->getMapping();
571 7
        $mapping       = $filterMapping;
572 7
        $indexBy       = $mapping['indexBy'];
573 7
        $id            = $this->uow->getEntityIdentifier($collection->getOwner());
574 7
        $sourceClass   = $this->em->getClassMetadata($mapping['sourceEntity']);
575 7
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
576
577 7
        if (! $mapping['isOwningSide']) {
578 3
            $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
579 3
            $mapping                = $associationSourceClass->associationMappings[$mapping['mappedBy']];
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
580 3
            $joinColumns            = $mapping['joinTable']['joinColumns'];
581 3
            $sourceRelationMode     = 'relationToTargetKeyColumns';
582 3
            $targetRelationMode     = 'relationToSourceKeyColumns';
583
        } else {
584 4
            $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
585 4
            $joinColumns            = $mapping['joinTable']['inverseJoinColumns'];
586 4
            $sourceRelationMode     = 'relationToSourceKeyColumns';
587 4
            $targetRelationMode     = 'relationToTargetKeyColumns';
588
        }
589
590 7
        $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform). ' t';
591 7
        $whereClauses    = [];
592 7
        $params          = [];
593 7
        $types           = [];
594
595 7
        $joinNeeded = ! in_array($indexBy, $targetClass->identifier);
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
596
597 7
        if ($joinNeeded) { // extra join needed if indexBy is not a @id
598 3
            $joinConditions = [];
599
600 3
            foreach ($joinColumns as $joinTableColumn) {
601 3
                $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName'];
602
            }
603
604 3
            $tableName        = $this->quoteStrategy->getTableName($targetClass, $this->platform);
605 3
            $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
606 3
            $columnName       = $targetClass->getColumnName($indexBy);
607
608 3
            $whereClauses[] = 'tr.' . $columnName . ' = ?';
609 3
            $params[]       = $key;
610 3
            $types[]        = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em);
611
        }
612
613 7
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
614 7
            if (isset($mapping[$sourceRelationMode][$joinTableColumn])) {
615 7
                $column         = $mapping[$sourceRelationMode][$joinTableColumn];
616 7
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
617 7
                $params[]       = $sourceClass->containsForeignIdentifier
0 ignored issues
show
Bug introduced by
Accessing containsForeignIdentifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
618
                    ? $id[$sourceClass->getFieldForColumn($column)]
619 7
                    : $id[$sourceClass->fieldNames[$column]];
0 ignored issues
show
Bug introduced by
Accessing fieldNames on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
620 7
                $types[]        = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em);
621 7
            } elseif ( ! $joinNeeded) {
622 4
                $column = $mapping[$targetRelationMode][$joinTableColumn];
623
624 4
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
625 4
                $params[]       = $key;
626 7
                $types[]        = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em);
627
            }
628
        }
629
630 7
        if ($addFilters) {
631 7
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
632
633 7
            if ($filterSql) {
634
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
635
                $whereClauses[] = $filterSql;
636
            }
637
        }
638
639 7
        return [$quotedJoinTable, $whereClauses, $params, $types];
640
    }
641
642
    /**
643
     * @param \Doctrine\ORM\PersistentCollection $collection
644
     * @param object                             $element
645
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
646
     *
647
     * @return array ordered vector:
648
     *                - quoted join table name
649
     *                - where clauses to be added for filtering
650
     *                - parameters to be bound for filtering
651
     *                - types of the parameters to be bound for filtering
652
     */
653 7
    private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters)
654
    {
655 7
        $filterMapping  = $collection->getMapping();
656 7
        $mapping        = $filterMapping;
657
658 7
        if ( ! $mapping['isOwningSide']) {
659 3
            $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
660 3
            $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']);
661 3
            $sourceId = $this->uow->getEntityIdentifier($element);
662 3
            $targetId = $this->uow->getEntityIdentifier($collection->getOwner());
663
664 3
            $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?
Loading history...
665
        } else {
666 4
            $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
667 4
            $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
668 4
            $sourceId = $this->uow->getEntityIdentifier($collection->getOwner());
669 4
            $targetId = $this->uow->getEntityIdentifier($element);
670
        }
671
672 7
        $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform);
673 7
        $whereClauses    = [];
674 7
        $params          = [];
675 7
        $types           = [];
676
677 7
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
678 7
            $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?';
679
680 7
            if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
681 7
                $targetColumn = $mapping['relationToTargetKeyColumns'][$joinTableColumn];
682 7
                $params[]     = $targetId[$targetClass->getFieldForColumn($targetColumn)];
683 7
                $types[]      = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em);
684
685 7
                continue;
686
            }
687
688
            // relationToSourceKeyColumns
689 7
            $targetColumn = $mapping['relationToSourceKeyColumns'][$joinTableColumn];
690 7
            $params[]     = $sourceId[$sourceClass->getFieldForColumn($targetColumn)];
691 7
            $types[]      = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em);
692
        }
693
694 7
        if ($addFilters) {
695 7
            $quotedJoinTable .= ' t';
696
697 7
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
698
699 7
            if ($filterSql) {
700 3
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
701 3
                $whereClauses[] = $filterSql;
702
            }
703
        }
704
705 7
        return [$quotedJoinTable, $whereClauses, $params, $types];
706
    }
707
708
    /**
709
     * Expands Criteria Parameters by walking the expressions and grabbing all
710
     * parameters and types from it.
711
     *
712
     * @param \Doctrine\Common\Collections\Criteria $criteria
713
     *
714
     * @return array
715
     */
716 12
    private function expandCriteriaParameters(Criteria $criteria)
717
    {
718 12
        $expression = $criteria->getWhereExpression();
719
720 12
        if ($expression === null) {
721 5
            return [];
722
        }
723
724 7
        $valueVisitor = new SqlValueVisitor();
725
726 7
        $valueVisitor->dispatch($expression);
727
728 7
        list(, $types) = $valueVisitor->getParamsAndTypes();
729
730 7
        return $types;
731
    }
732
733
    /**
734
     * @param Criteria $criteria
735
     * @param ClassMetadata $targetClass
736
     * @return string
737
     */
738 12
    private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass)
739
    {
740 12
        $orderings = $criteria->getOrderings();
741 12
        if ($orderings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $orderings of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
742 2
            $orderBy = [];
743 2
            foreach ($orderings as $name => $direction) {
744 2
                $field = $this->quoteStrategy->getColumnName(
745 2
                    $name,
746 2
                    $targetClass,
747 2
                    $this->platform
748
                );
749 2
                $orderBy[] = $field . ' ' . $direction;
750
            }
751
752 2
            return ' ORDER BY ' . implode(', ', $orderBy);
753
        }
754 10
        return '';
755
    }
756
757
    /**
758
     * @param Criteria $criteria
759
     * @return string
760
     * @throws \Doctrine\DBAL\DBALException
761
     */
762 12
    private function getLimitSql(Criteria $criteria)
763
    {
764 12
        $limit  = $criteria->getMaxResults();
765 12
        $offset = $criteria->getFirstResult();
766 12
        if ($limit !== null || $offset !== null) {
767 3
            return $this->platform->modifyLimitQuery('', $limit, $offset);
768
        }
769 9
        return '';
770
    }
771
}
772