Failed Conditions
Push — master ( 2ade86...13f838 )
by Jonathan
18s
created

ORM/Persisters/Collection/ManyToManyPersister.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 17
    public function delete(PersistentCollection $collection)
43
    {
44 17
        $mapping = $collection->getMapping();
45
46 17
        if ( ! $mapping['isOwningSide']) {
47
            return; // ignore inverse side
48
        }
49
50 17
        $types = [];
51 17
        $class = $this->em->getClassMetadata($mapping['sourceEntity']);
52
53 17
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
54 17
            $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
55
        }
56
57 17
        $this->conn->executeUpdate($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types);
58 17
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 329
    public function update(PersistentCollection $collection)
64
    {
65 329
        $mapping = $collection->getMapping();
66
67 329
        if ( ! $mapping['isOwningSide']) {
68 231
            return; // ignore inverse side
69
        }
70
71 328
        list($deleteSql, $deleteTypes) = $this->getDeleteRowSQL($collection);
72 328
        list($insertSql, $insertTypes) = $this->getInsertRowSQL($collection);
73
74 328
        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 328
        foreach ($collection->getInsertDiff() as $element) {
83 328
            $this->conn->executeUpdate(
84 328
                $insertSql,
85 328
                $this->getInsertRowSQLParameters($collection, $element),
86 328
                $insertTypes
87
            );
88
        }
89 328
    }
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']]
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);
168
    }
169
170
    /**
171
     * {@inheritDoc}
172
     */
173 8 View Code Duplication
    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 View Code Duplication
    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 View Code Duplication
    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 7
    public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
234
    {
235 7
        $mapping       = $collection->getMapping();
236 7
        $owner         = $collection->getOwner();
237 7
        $ownerMetadata = $this->em->getClassMetadata(get_class($owner));
238 7
        $id            = $this->uow->getEntityIdentifier($owner);
239 7
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
240 7
        $onConditions  = $this->getOnConditionSQL($mapping);
241 7
        $whereClauses  = $params = [];
242
243 7
        if ( ! $mapping['isOwningSide']) {
244 1
            $associationSourceClass = $targetClass;
245 1
            $mapping = $targetClass->associationMappings[$mapping['mappedBy']];
246 1
            $sourceRelationMode = 'relationToTargetKeyColumns';
247
        } else {
248 6
            $associationSourceClass = $ownerMetadata;
249 6
            $sourceRelationMode = 'relationToSourceKeyColumns';
250
        }
251
252 7
        foreach ($mapping[$sourceRelationMode] as $key => $value) {
253 7
            $whereClauses[] = sprintf('t.%s = ?', $key);
254 7
            $params[] = $ownerMetadata->containsForeignIdentifier
255
                ? $id[$ownerMetadata->getFieldForColumn($value)]
256 7
                : $id[$ownerMetadata->fieldNames[$value]];
257
        }
258
259 7
        $parameters = $this->expandCriteriaParameters($criteria);
260
261 7
        foreach ($parameters as $parameter) {
262 2
            list($name, $value) = $parameter;
263 2
            $field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform);
264 2
            $whereClauses[]     = sprintf('te.%s = ?', $field);
265 2
            $params[]           = $value;
266
        }
267
268 7
        $tableName    = $this->quoteStrategy->getTableName($targetClass, $this->platform);
269 7
        $joinTable    = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform);
270
271 7
        $rsm = new Query\ResultSetMappingBuilder($this->em);
272 7
        $rsm->addRootEntityFromClassMetadata($targetClass->name, 'te');
273
274 7
        $sql = 'SELECT ' . $rsm->generateSelectClause()
275 7
            . ' FROM ' . $tableName . ' te'
276 7
            . ' JOIN ' . $joinTable  . ' t ON'
277 7
            . implode(' AND ', $onConditions)
278 7
            . ' WHERE ' . implode(' AND ', $whereClauses);
279
280 7
        $sql .= $this->getOrderingSql($criteria, $targetClass);
281
282 7
        $sql .= $this->getLimitSql($criteria);
283
284 7
        $stmt = $this->conn->executeQuery($sql, $params);
285
286
        return $this
287 7
            ->em
288 7
            ->newHydrator(Query::HYDRATE_OBJECT)
289 7
            ->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 32
    public function getFilterSql($mapping)
308
    {
309 32
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
310 32
        $rootClass   = $this->em->getClassMetadata($targetClass->rootEntityName);
311 32
        $filterSql   = $this->generateFilterConditionSQL($rootClass, 'te');
312
313 32
        if ('' === $filterSql) {
314 32
            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);
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 32 View Code Duplication
    protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
334
    {
335 32
        $filterClauses = [];
336
337 32
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
338 6
            if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
339 6
                $filterClauses[] = '(' . $filterExpr . ')';
340
            }
341
        }
342
343 32
        return $filterClauses
344 6
            ? '(' . implode(' AND ', $filterClauses) . ')'
345 32
            : '';
346
    }
347
348
    /**
349
     * Generate ON condition
350
     *
351
     * @param  array $mapping
352
     *
353
     * @return array
354
     */
355 13
    protected function getOnConditionSQL($mapping)
356
    {
357 13
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
358 13
        $association = ( ! $mapping['isOwningSide'])
359 3
            ? $targetClass->associationMappings[$mapping['mappedBy']]
360 13
            : $mapping;
361
362 13
        $joinColumns = $mapping['isOwningSide']
363 10
            ? $association['joinTable']['inverseJoinColumns']
364 13
            : $association['joinTable']['joinColumns'];
365
366 13
        $conditions = [];
367
368 13 View Code Duplication
        foreach ($joinColumns as $joinColumn) {
369 13
            $joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
370 13
            $refColumnName  = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
371
372 13
            $conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName;
373
        }
374
375 13
        return $conditions;
376
    }
377
378
    /**
379
     * {@inheritdoc}
380
     *
381
     * @override
382
     */
383 17
    protected function getDeleteSQL(PersistentCollection $collection)
384
    {
385 17
        $columns    = [];
386 17
        $mapping    = $collection->getMapping();
387 17
        $class      = $this->em->getClassMetadata(get_class($collection->getOwner()));
388 17
        $joinTable  = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform);
389
390 17
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
391 17
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
392
        }
393
394 17
        return 'DELETE FROM ' . $joinTable
395 17
            . ' 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 17
    protected function getDeleteSQLParameters(PersistentCollection $collection)
405
    {
406 17
        $mapping    = $collection->getMapping();
407 17
        $identifier = $this->uow->getEntityIdentifier($collection->getOwner());
408
409
        // Optimization for single column identifier
410 17
        if (count($mapping['relationToSourceKeyColumns']) === 1) {
411 15
            return [reset($identifier)];
412
        }
413
414
        // Composite identifier
415 2
        $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
416 2
        $params      = [];
417
418 2
        foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) {
419 2
            $params[] = isset($sourceClass->fieldNames[$refColumnName])
420 1
                ? $identifier[$sourceClass->fieldNames[$refColumnName]]
421 2
                : $identifier[$sourceClass->getFieldForColumn($columnName)];
422
        }
423
424 2
        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 328
    protected function getDeleteRowSQL(PersistentCollection $collection)
436
    {
437 328
        $mapping     = $collection->getMapping();
438 328
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
439 328
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
440 328
        $columns     = [];
441 328
        $types       = [];
442
443 328
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
444 328
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
445 328
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
446
        }
447
448 328
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
449 328
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
450 328
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
451
        }
452
453
        return [
454 328
            'DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
455 328
            . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?',
456 328
            $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 12
    protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element)
472
    {
473 12
        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 328
    protected function getInsertRowSQL(PersistentCollection $collection)
485
    {
486 328
        $columns     = [];
487 328
        $types       = [];
488 328
        $mapping     = $collection->getMapping();
489 328
        $class       = $this->em->getClassMetadata($mapping['sourceEntity']);
490 328
        $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
491
492 328
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
493 328
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
494 328
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
495
        }
496
497 328
        foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
498 328
            $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
499 328
            $types[]   = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
500
        }
501
502
        return [
503 328
            'INSERT INTO ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
504 328
            . ' (' . implode(', ', $columns) . ')'
505 328
            . ' VALUES'
506 328
            . ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')',
507 328
            $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 328
    protected function getInsertRowSQLParameters(PersistentCollection $collection, $element)
523
    {
524 328
        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 328
    private function collectJoinTableColumnParameters(PersistentCollection $collection, $element)
537
    {
538 328
        $params      = [];
539 328
        $mapping     = $collection->getMapping();
540 328
        $isComposite = count($mapping['joinTableColumns']) > 2;
541
542 328
        $identifier1 = $this->uow->getEntityIdentifier($collection->getOwner());
543 328
        $identifier2 = $this->uow->getEntityIdentifier($element);
544
545 328
        $class1 = $class2 = null;
546 328
        if ($isComposite) {
547 21
            $class1 = $this->em->getClassMetadata(get_class($collection->getOwner()));
548 21
            $class2 = $collection->getTypeClass();
549
        }
550
551 328
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
552 328
            $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]);
553
554 328
            if ( ! $isComposite) {
555 307
                $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2);
556
557 307
                continue;
558
            }
559
560 21
            if ($isRelationToSource) {
561 21
                $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
562
563 21
                continue;
564
            }
565
566 21
            $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
567
        }
568
569 328
        return $params;
570
    }
571
572
    /**
573
     * @param \Doctrine\ORM\PersistentCollection $collection
574
     * @param string                             $key
575
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
576
     *
577
     * @return array ordered vector:
578
     *                - quoted join table name
579
     *                - where clauses to be added for filtering
580
     *                - parameters to be bound for filtering
581
     *                - types of the parameters to be bound for filtering
582
     */
583 7
    private function getJoinTableRestrictionsWithKey(PersistentCollection $collection, $key, $addFilters)
584
    {
585 7
        $filterMapping = $collection->getMapping();
586 7
        $mapping       = $filterMapping;
587 7
        $indexBy       = $mapping['indexBy'];
588 7
        $id            = $this->uow->getEntityIdentifier($collection->getOwner());
589 7
        $sourceClass   = $this->em->getClassMetadata($mapping['sourceEntity']);
590 7
        $targetClass   = $this->em->getClassMetadata($mapping['targetEntity']);
591
592 7
        if (! $mapping['isOwningSide']) {
593 3
            $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
594 3
            $mapping                = $associationSourceClass->associationMappings[$mapping['mappedBy']];
595 3
            $joinColumns            = $mapping['joinTable']['joinColumns'];
596 3
            $sourceRelationMode     = 'relationToTargetKeyColumns';
597 3
            $targetRelationMode     = 'relationToSourceKeyColumns';
598
        } else {
599 4
            $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
600 4
            $joinColumns            = $mapping['joinTable']['inverseJoinColumns'];
601 4
            $sourceRelationMode     = 'relationToSourceKeyColumns';
602 4
            $targetRelationMode     = 'relationToTargetKeyColumns';
603
        }
604
605 7
        $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform). ' t';
606 7
        $whereClauses    = [];
607 7
        $params          = [];
608 7
        $types           = [];
609
610 7
        $joinNeeded = ! in_array($indexBy, $targetClass->identifier);
611
612 7
        if ($joinNeeded) { // extra join needed if indexBy is not a @id
613 3
            $joinConditions = [];
614
615 3
            foreach ($joinColumns as $joinTableColumn) {
616 3
                $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName'];
617
            }
618
619 3
            $tableName        = $this->quoteStrategy->getTableName($targetClass, $this->platform);
620 3
            $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
621 3
            $columnName       = $targetClass->getColumnName($indexBy);
622
623 3
            $whereClauses[] = 'tr.' . $columnName . ' = ?';
624 3
            $params[]       = $key;
625 3
            $types[]        = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em);
626
        }
627
628 7
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
629 7
            if (isset($mapping[$sourceRelationMode][$joinTableColumn])) {
630 7
                $column         = $mapping[$sourceRelationMode][$joinTableColumn];
631 7
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
632 7
                $params[]       = $sourceClass->containsForeignIdentifier
633
                    ? $id[$sourceClass->getFieldForColumn($column)]
634 7
                    : $id[$sourceClass->fieldNames[$column]];
635 7
                $types[]        = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em);
636 7
            } elseif ( ! $joinNeeded) {
637 4
                $column = $mapping[$targetRelationMode][$joinTableColumn];
638
639 4
                $whereClauses[] = 't.' . $joinTableColumn . ' = ?';
640 4
                $params[]       = $key;
641 7
                $types[]        = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em);
642
            }
643
        }
644
645 7 View Code Duplication
        if ($addFilters) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
646 7
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
647
648 7
            if ($filterSql) {
649
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
650
                $whereClauses[] = $filterSql;
651
            }
652
        }
653
654 7
        return [$quotedJoinTable, $whereClauses, $params, $types];
655
    }
656
657
    /**
658
     * @param \Doctrine\ORM\PersistentCollection $collection
659
     * @param object                             $element
660
     * @param boolean                            $addFilters Whether the filter SQL should be included or not.
661
     *
662
     * @return array ordered vector:
663
     *                - quoted join table name
664
     *                - where clauses to be added for filtering
665
     *                - parameters to be bound for filtering
666
     *                - types of the parameters to be bound for filtering
667
     */
668 9
    private function getJoinTableRestrictions(PersistentCollection $collection, $element, $addFilters)
669
    {
670 9
        $filterMapping  = $collection->getMapping();
671 9
        $mapping        = $filterMapping;
672
673 9
        if ( ! $mapping['isOwningSide']) {
674 4
            $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
675 4
            $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']);
676 4
            $sourceId = $this->uow->getEntityIdentifier($element);
677 4
            $targetId = $this->uow->getEntityIdentifier($collection->getOwner());
678
679 4
            $mapping = $sourceClass->associationMappings[$mapping['mappedBy']];
680
        } else {
681 5
            $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
682 5
            $targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
683 5
            $sourceId = $this->uow->getEntityIdentifier($collection->getOwner());
684 5
            $targetId = $this->uow->getEntityIdentifier($element);
685
        }
686
687 9
        $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform);
688 9
        $whereClauses    = [];
689 9
        $params          = [];
690 9
        $types           = [];
691
692 9
        foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
693 9
            $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?';
694
695 9
            if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
696 9
                $targetColumn = $mapping['relationToTargetKeyColumns'][$joinTableColumn];
697 9
                $params[]     = $targetId[$targetClass->getFieldForColumn($targetColumn)];
698 9
                $types[]      = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em);
699
700 9
                continue;
701
            }
702
703
            // relationToSourceKeyColumns
704 9
            $targetColumn = $mapping['relationToSourceKeyColumns'][$joinTableColumn];
705 9
            $params[]     = $sourceId[$sourceClass->getFieldForColumn($targetColumn)];
706 9
            $types[]      = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em);
707
        }
708
709 9 View Code Duplication
        if ($addFilters) {
710 7
            $quotedJoinTable .= ' t';
711
712 7
            list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
713
714 7
            if ($filterSql) {
715 3
                $quotedJoinTable .= ' ' . $joinTargetEntitySQL;
716 3
                $whereClauses[] = $filterSql;
717
            }
718
        }
719
720 9
        return [$quotedJoinTable, $whereClauses, $params, $types];
721
    }
722
723
    /**
724
     * Expands Criteria Parameters by walking the expressions and grabbing all
725
     * parameters and types from it.
726
     *
727
     * @param \Doctrine\Common\Collections\Criteria $criteria
728
     *
729
     * @return array
730
     */
731 7
    private function expandCriteriaParameters(Criteria $criteria)
732
    {
733 7
        $expression = $criteria->getWhereExpression();
734
735 7
        if ($expression === null) {
736 5
            return [];
737
        }
738
739 2
        $valueVisitor = new SqlValueVisitor();
740
741 2
        $valueVisitor->dispatch($expression);
742
743 2
        list(, $types) = $valueVisitor->getParamsAndTypes();
744
745 2
        return $types;
746
    }
747
748
    /**
749
     * @param Criteria $criteria
750
     * @param ClassMetadata $targetClass
751
     * @return string
752
     */
753 7
    private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass)
754
    {
755 7
        $orderings = $criteria->getOrderings();
756 7
        if ($orderings) {
757 2
            $orderBy = [];
758 2
            foreach ($orderings as $name => $direction) {
759 2
                $field = $this->quoteStrategy->getColumnName(
760 2
                    $name,
761 2
                    $targetClass,
762 2
                    $this->platform
763
                );
764 2
                $orderBy[] = $field . ' ' . $direction;
765
            }
766
767 2
            return ' ORDER BY ' . implode(', ', $orderBy);
768
        }
769 5
        return '';
770
    }
771
772
    /**
773
     * @param Criteria $criteria
774
     * @return string
775
     * @throws \Doctrine\DBAL\DBALException
776
     */
777 7
    private function getLimitSql(Criteria $criteria)
778
    {
779 7
        $limit  = $criteria->getMaxResults();
780 7
        $offset = $criteria->getFirstResult();
781 7 View Code Duplication
        if ($limit !== null || $offset !== null) {
782 3
            return $this->platform->modifyLimitQuery('', $limit, $offset);
783
        }
784 4
        return '';
785
    }
786
}
787