vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php line 285

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