Failed Conditions
Push — 2.7 ( c036c0...266f0d )
by Jonathan
57:23 queued 50:07
created

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

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 18
    public function delete(PersistentCollection $collection)
43
    {
44 18
        $mapping = $collection->getMapping();
45
46 18
        if ( ! $mapping['isOwningSide']) {
47
            return; // ignore inverse side
48
        }
49
50 18
        $types = [];
51 18
        $class = $this->em->getClassMetadata($mapping['sourceEntity']);
52
53 18
        foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
54 18
            $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em);
55
        }
56
57 18
        $this->conn->executeUpdate($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types);
58 18
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 335
    public function update(PersistentCollection $collection)
64
    {
65 335
        $mapping = $collection->getMapping();
66
67 335
        if ( ! $mapping['isOwningSide']) {
68 237
            return; // ignore inverse side
69
        }
70
71 334
        list($deleteSql, $deleteTypes) = $this->getDeleteRowSQL($collection);
72 334
        list($insertSql, $insertTypes) = $this->getInsertRowSQL($collection);
73
74 334
        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 334
        foreach ($collection->getInsertDiff() as $element) {
83 334
            $this->conn->executeUpdate(
84 334
                $insertSql,
85 334
                $this->getInsertRowSQLParameters($collection, $element),
86 334
                $insertTypes
87
            );
88
        }
89 334
    }
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);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->fetc...ql, $params, 0, $types) returns the type boolean|string which is incompatible with the return type mandated by Doctrine\ORM\Persisters\...ctionPersister::count() of integer.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

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