vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php line 326

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\Entity;
  20. use Doctrine\ORM\Mapping\ClassMetadata;
  21. use Doctrine\DBAL\LockMode;
  22. use Doctrine\DBAL\Types\Type;
  23. use Doctrine\Common\Collections\Criteria;
  24. use Doctrine\ORM\Utility\PersisterHelper;
  25. use function array_combine;
  26. /**
  27.  * The joined subclass persister maps a single entity instance to several tables in the
  28.  * database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
  29.  *
  30.  * @author Roman Borschel <roman@code-factory.org>
  31.  * @author Benjamin Eberlei <kontakt@beberlei.de>
  32.  * @author Alexander <iam.asm89@gmail.com>
  33.  * @since 2.0
  34.  * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
  35.  */
  36. class JoinedSubclassPersister extends AbstractEntityInheritancePersister
  37. {
  38.     /**
  39.      * Map that maps column names to the table names that own them.
  40.      * This is mainly a temporary cache, used during a single request.
  41.      *
  42.      * @var array
  43.      */
  44.     private $owningTableMap = [];
  45.     /**
  46.      * Map of table to quoted table names.
  47.      *
  48.      * @var array
  49.      */
  50.     private $quotedTableMap = [];
  51.     /**
  52.      * {@inheritdoc}
  53.      */
  54.     protected function getDiscriminatorColumnTableName()
  55.     {
  56.         $class = ($this->class->name !== $this->class->rootEntityName)
  57.             ? $this->em->getClassMetadata($this->class->rootEntityName)
  58.             : $this->class;
  59.         return $class->getTableName();
  60.     }
  61.     /**
  62.      * This function finds the ClassMetadata instance in an inheritance hierarchy
  63.      * that is responsible for enabling versioning.
  64.      *
  65.      * @return \Doctrine\ORM\Mapping\ClassMetadata
  66.      */
  67.     private function getVersionedClassMetadata()
  68.     {
  69.         if (isset($this->class->fieldMappings[$this->class->versionField]['inherited'])) {
  70.             $definingClassName $this->class->fieldMappings[$this->class->versionField]['inherited'];
  71.             return $this->em->getClassMetadata($definingClassName);
  72.         }
  73.         return $this->class;
  74.     }
  75.     /**
  76.      * Gets the name of the table that owns the column the given field is mapped to.
  77.      *
  78.      * @param string $fieldName
  79.      *
  80.      * @return string
  81.      *
  82.      * @override
  83.      */
  84.     public function getOwningTable($fieldName)
  85.     {
  86.         if (isset($this->owningTableMap[$fieldName])) {
  87.             return $this->owningTableMap[$fieldName];
  88.         }
  89.         switch (true) {
  90.             case isset($this->class->associationMappings[$fieldName]['inherited']):
  91.                 $cm $this->em->getClassMetadata($this->class->associationMappings[$fieldName]['inherited']);
  92.                 break;
  93.             case isset($this->class->fieldMappings[$fieldName]['inherited']):
  94.                 $cm $this->em->getClassMetadata($this->class->fieldMappings[$fieldName]['inherited']);
  95.                 break;
  96.             default:
  97.                 $cm $this->class;
  98.                 break;
  99.         }
  100.         $tableName          $cm->getTableName();
  101.         $quotedTableName    $this->quoteStrategy->getTableName($cm$this->platform);
  102.         $this->owningTableMap[$fieldName] = $tableName;
  103.         $this->quotedTableMap[$tableName] = $quotedTableName;
  104.         return $tableName;
  105.     }
  106.     /**
  107.      * {@inheritdoc}
  108.      */
  109.     public function executeInserts()
  110.     {
  111.         if ( ! $this->queuedInserts) {
  112.             return [];
  113.         }
  114.         $postInsertIds  = [];
  115.         $idGenerator    $this->class->idGenerator;
  116.         $isPostInsertId $idGenerator->isPostInsertGenerator();
  117.         $rootClass      = ($this->class->name !== $this->class->rootEntityName)
  118.             ? $this->em->getClassMetadata($this->class->rootEntityName)
  119.             : $this->class;
  120.         // Prepare statement for the root table
  121.         $rootPersister $this->em->getUnitOfWork()->getEntityPersister($rootClass->name);
  122.         $rootTableName $rootClass->getTableName();
  123.         $rootTableStmt $this->conn->prepare($rootPersister->getInsertSQL());
  124.         // Prepare statements for sub tables.
  125.         $subTableStmts = [];
  126.         if ($rootClass !== $this->class) {
  127.             $subTableStmts[$this->class->getTableName()] = $this->conn->prepare($this->getInsertSQL());
  128.         }
  129.         foreach ($this->class->parentClasses as $parentClassName) {
  130.             $parentClass $this->em->getClassMetadata($parentClassName);
  131.             $parentTableName $parentClass->getTableName();
  132.             if ($parentClass !== $rootClass) {
  133.                 $parentPersister $this->em->getUnitOfWork()->getEntityPersister($parentClassName);
  134.                 $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL());
  135.             }
  136.         }
  137.         // Execute all inserts. For each entity:
  138.         // 1) Insert on root table
  139.         // 2) Insert on sub tables
  140.         foreach ($this->queuedInserts as $entity) {
  141.             $insertData $this->prepareInsertData($entity);
  142.             // Execute insert on root table
  143.             $paramIndex 1;
  144.             foreach ($insertData[$rootTableName] as $columnName => $value) {
  145.                 $rootTableStmt->bindValue($paramIndex++, $value$this->columnTypes[$columnName]);
  146.             }
  147.             $rootTableStmt->execute();
  148.             if ($isPostInsertId) {
  149.                 $generatedId $idGenerator->generate($this->em$entity);
  150.                 $id = [
  151.                     $this->class->identifier[0] => $generatedId
  152.                 ];
  153.                 $postInsertIds[] = [
  154.                     'generatedId' => $generatedId,
  155.                     'entity' => $entity,
  156.                 ];
  157.             } else {
  158.                 $id $this->em->getUnitOfWork()->getEntityIdentifier($entity);
  159.             }
  160.             if ($this->class->isVersioned) {
  161.                 $this->assignDefaultVersionValue($entity$id);
  162.             }
  163.             // Execute inserts on subtables.
  164.             // The order doesn't matter because all child tables link to the root table via FK.
  165.             foreach ($subTableStmts as $tableName => $stmt) {
  166.                 /** @var \Doctrine\DBAL\Statement $stmt */
  167.                 $paramIndex 1;
  168.                 $data       $insertData[$tableName] ?? [];
  169.                 foreach ((array) $id as $idName => $idVal) {
  170.                     $type = isset($this->columnTypes[$idName]) ? $this->columnTypes[$idName] : Type::STRING;
  171.                     $stmt->bindValue($paramIndex++, $idVal$type);
  172.                 }
  173.                 foreach ($data as $columnName => $value) {
  174.                     if (!is_array($id) || !isset($id[$columnName])) {
  175.                         $stmt->bindValue($paramIndex++, $value$this->columnTypes[$columnName]);
  176.                     }
  177.                 }
  178.                 $stmt->execute();
  179.             }
  180.         }
  181.         $rootTableStmt->closeCursor();
  182.         foreach ($subTableStmts as $stmt) {
  183.             $stmt->closeCursor();
  184.         }
  185.         $this->queuedInserts = [];
  186.         return $postInsertIds;
  187.     }
  188.     /**
  189.      * {@inheritdoc}
  190.      */
  191.     public function update($entity)
  192.     {
  193.         $updateData $this->prepareUpdateData($entity);
  194.         if ( ! $updateData) {
  195.             return;
  196.         }
  197.         if (($isVersioned $this->class->isVersioned) === false) {
  198.             return;
  199.         }
  200.         $versionedClass  $this->getVersionedClassMetadata();
  201.         $versionedTable  $versionedClass->getTableName();
  202.         foreach ($updateData as $tableName => $data) {
  203.             $tableName $this->quotedTableMap[$tableName];
  204.             $versioned $isVersioned && $versionedTable === $tableName;
  205.             $this->updateTable($entity$tableName$data$versioned);
  206.         }
  207.         // Make sure the table with the version column is updated even if no columns on that
  208.         // table were affected.
  209.         if ($isVersioned) {
  210.             if ( ! isset($updateData[$versionedTable])) {
  211.                 $tableName $this->quoteStrategy->getTableName($versionedClass$this->platform);
  212.                 $this->updateTable($entity$tableName, [], true);
  213.             }
  214.             $identifiers $this->em->getUnitOfWork()->getEntityIdentifier($entity);
  215.             $this->assignDefaultVersionValue($entity$identifiers);
  216.         }
  217.     }
  218.     /**
  219.      * {@inheritdoc}
  220.      */
  221.     public function delete($entity)
  222.     {
  223.         $identifier $this->em->getUnitOfWork()->getEntityIdentifier($entity);
  224.         $id         array_combine($this->class->getIdentifierColumnNames(), $identifier);
  225.         $this->deleteJoinTableRecords($identifier);
  226.         // If the database platform supports FKs, just
  227.         // delete the row from the root table. Cascades do the rest.
  228.         if ($this->platform->supportsForeignKeyConstraints()) {
  229.             $rootClass $this->em->getClassMetadata($this->class->rootEntityName);
  230.             $rootTable $this->quoteStrategy->getTableName($rootClass$this->platform);
  231.             $rootTypes $this->getClassIdentifiersTypes($rootClass);
  232.             return (bool) $this->conn->delete($rootTable$id$rootTypes);
  233.         }
  234.         // Delete from all tables individually, starting from this class' table up to the root table.
  235.         $rootTable $this->quoteStrategy->getTableName($this->class$this->platform);
  236.         $rootTypes $this->getClassIdentifiersTypes($this->class);
  237.         $affectedRows $this->conn->delete($rootTable$id$rootTypes);
  238.         foreach ($this->class->parentClasses as $parentClass) {
  239.             $parentMetadata $this->em->getClassMetadata($parentClass);
  240.             $parentTable    $this->quoteStrategy->getTableName($parentMetadata$this->platform);
  241.             $parentTypes    $this->getClassIdentifiersTypes($parentMetadata);
  242.             $this->conn->delete($parentTable$id$parentTypes);
  243.         }
  244.         return (bool) $affectedRows;
  245.     }
  246.     /**
  247.      * {@inheritdoc}
  248.      */
  249.     public function getSelectSQL($criteria$assoc null$lockMode null$limit null$offset null, array $orderBy null)
  250.     {
  251.         $this->switchPersisterContext($offset$limit);
  252.         $baseTableAlias $this->getSQLTableAlias($this->class->name);
  253.         $joinSql        $this->getJoinSql($baseTableAlias);
  254.         if ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) {
  255.             $joinSql .= $this->getSelectManyToManyJoinSQL($assoc);
  256.         }
  257.         $conditionSql = ($criteria instanceof Criteria)
  258.             ? $this->getSelectConditionCriteriaSQL($criteria)
  259.             : $this->getSelectConditionSQL($criteria$assoc);
  260.         // If the current class in the root entity, add the filters
  261.         if ($filterSql $this->generateFilterConditionSQL($this->em->getClassMetadata($this->class->rootEntityName), $this->getSQLTableAlias($this->class->rootEntityName))) {
  262.             $conditionSql .= $conditionSql
  263.                 ' AND ' $filterSql
  264.                 $filterSql;
  265.         }
  266.         $orderBySql '';
  267.         if ($assoc !== null && isset($assoc['orderBy'])) {
  268.             $orderBy $assoc['orderBy'];
  269.         }
  270.         if ($orderBy) {
  271.             $orderBySql $this->getOrderBySQL($orderBy$baseTableAlias);
  272.         }
  273.         $lockSql '';
  274.         switch ($lockMode) {
  275.             case LockMode::PESSIMISTIC_READ:
  276.                 $lockSql ' ' $this->platform->getReadLockSQL();
  277.                 break;
  278.             case LockMode::PESSIMISTIC_WRITE:
  279.                 $lockSql ' ' $this->platform->getWriteLockSQL();
  280.                 break;
  281.         }
  282.         $tableName  $this->quoteStrategy->getTableName($this->class$this->platform);
  283.         $from       ' FROM ' $tableName ' ' $baseTableAlias;
  284.         $where      $conditionSql != '' ' WHERE ' $conditionSql '';
  285.         $lock       $this->platform->appendLockHint($from$lockMode);
  286.         $columnList $this->getSelectColumnsSQL();
  287.         $query      'SELECT '  $columnList
  288.                     $lock
  289.                     $joinSql
  290.                     $where
  291.                     $orderBySql;
  292.         return $this->platform->modifyLimitQuery($query$limit$offset) . $lockSql;
  293.     }
  294.     /**
  295.      * {@inheritDoc}
  296.      */
  297.     public function getCountSQL($criteria = [])
  298.     {
  299.         $tableName      $this->quoteStrategy->getTableName($this->class$this->platform);
  300.         $baseTableAlias $this->getSQLTableAlias($this->class->name);
  301.         $joinSql        $this->getJoinSql($baseTableAlias);
  302.         $conditionSql = ($criteria instanceof Criteria)
  303.             ? $this->getSelectConditionCriteriaSQL($criteria)
  304.             : $this->getSelectConditionSQL($criteria);
  305.         $filterSql $this->generateFilterConditionSQL($this->em->getClassMetadata($this->class->rootEntityName), $this->getSQLTableAlias($this->class->rootEntityName));
  306.         if ('' !== $filterSql) {
  307.             $conditionSql $conditionSql
  308.                 $conditionSql ' AND ' $filterSql
  309.                 $filterSql;
  310.         }
  311.         $sql 'SELECT COUNT(*) '
  312.             'FROM ' $tableName ' ' $baseTableAlias
  313.             $joinSql
  314.             . (empty($conditionSql) ? '' ' WHERE ' $conditionSql);
  315.         return $sql;
  316.     }
  317.     /**
  318.      * {@inheritdoc}
  319.      */
  320.     protected function getLockTablesSql($lockMode)
  321.     {
  322.         $joinSql            '';
  323.         $identifierColumns  $this->class->getIdentifierColumnNames();
  324.         $baseTableAlias     $this->getSQLTableAlias($this->class->name);
  325.         // INNER JOIN parent tables
  326.         foreach ($this->class->parentClasses as $parentClassName) {
  327.             $conditions     = [];
  328.             $tableAlias     $this->getSQLTableAlias($parentClassName);
  329.             $parentClass    $this->em->getClassMetadata($parentClassName);
  330.             $joinSql       .= ' INNER JOIN ' $this->quoteStrategy->getTableName($parentClass$this->platform) . ' ' $tableAlias ' ON ';
  331.             foreach ($identifierColumns as $idColumn) {
  332.                 $conditions[] = $baseTableAlias '.' $idColumn ' = ' $tableAlias '.' $idColumn;
  333.             }
  334.             $joinSql .= implode(' AND '$conditions);
  335.         }
  336.         return parent::getLockTablesSql($lockMode) . $joinSql;
  337.     }
  338.     /**
  339.      * Ensure this method is never called. This persister overrides getSelectEntitiesSQL directly.
  340.      *
  341.      * @return string
  342.      */
  343.     protected function getSelectColumnsSQL()
  344.     {
  345.         // Create the column list fragment only once
  346.         if ($this->currentPersisterContext->selectColumnListSql !== null) {
  347.             return $this->currentPersisterContext->selectColumnListSql;
  348.         }
  349.         $columnList         = [];
  350.         $discrColumn        $this->class->discriminatorColumn['name'];
  351.         $discrColumnType    $this->class->discriminatorColumn['type'];
  352.         $baseTableAlias     $this->getSQLTableAlias($this->class->name);
  353.         $resultColumnName   $this->platform->getSQLResultCasing($discrColumn);
  354.         $this->currentPersisterContext->rsm->addEntityResult($this->class->name'r');
  355.         $this->currentPersisterContext->rsm->setDiscriminatorColumn('r'$resultColumnName);
  356.         $this->currentPersisterContext->rsm->addMetaResult('r'$resultColumnName$discrColumnfalse$discrColumnType);
  357.         // Add regular columns
  358.         foreach ($this->class->fieldMappings as $fieldName => $mapping) {
  359.             $class = isset($mapping['inherited'])
  360.                 ? $this->em->getClassMetadata($mapping['inherited'])
  361.                 : $this->class;
  362.             $columnList[] = $this->getSelectColumnSQL($fieldName$class);
  363.         }
  364.         // Add foreign key columns
  365.         foreach ($this->class->associationMappings as $mapping) {
  366.             if ( ! $mapping['isOwningSide'] || ! ($mapping['type'] & ClassMetadata::TO_ONE)) {
  367.                 continue;
  368.             }
  369.             $tableAlias = isset($mapping['inherited'])
  370.                 ? $this->getSQLTableAlias($mapping['inherited'])
  371.                 : $baseTableAlias;
  372.             $targetClass $this->em->getClassMetadata($mapping['targetEntity']);
  373.             foreach ($mapping['joinColumns'] as $joinColumn) {
  374.                 $columnList[] = $this->getSelectJoinColumnSQL(
  375.                     $tableAlias,
  376.                     $joinColumn['name'],
  377.                     $this->quoteStrategy->getJoinColumnName($joinColumn$this->class$this->platform),
  378.                     PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass$this->em)
  379.                 );
  380.             }
  381.         }
  382.         // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#processSQLResult).
  383.         $tableAlias = ($this->class->rootEntityName == $this->class->name)
  384.             ? $baseTableAlias
  385.             $this->getSQLTableAlias($this->class->rootEntityName);
  386.         $columnList[] = $tableAlias '.' $discrColumn;
  387.         // sub tables
  388.         foreach ($this->class->subClasses as $subClassName) {
  389.             $subClass   $this->em->getClassMetadata($subClassName);
  390.             $tableAlias $this->getSQLTableAlias($subClassName);
  391.             // Add subclass columns
  392.             foreach ($subClass->fieldMappings as $fieldName => $mapping) {
  393.                 if (isset($mapping['inherited'])) {
  394.                     continue;
  395.                 }
  396.                 $columnList[] = $this->getSelectColumnSQL($fieldName$subClass);
  397.             }
  398.             // Add join columns (foreign keys)
  399.             foreach ($subClass->associationMappings as $mapping) {
  400.                 if ( ! $mapping['isOwningSide']
  401.                         || ! ($mapping['type'] & ClassMetadata::TO_ONE)
  402.                         || isset($mapping['inherited'])) {
  403.                     continue;
  404.                 }
  405.                 $targetClass $this->em->getClassMetadata($mapping['targetEntity']);
  406.                 foreach ($mapping['joinColumns'] as $joinColumn) {
  407.                     $columnList[] = $this->getSelectJoinColumnSQL(
  408.                         $tableAlias,
  409.                         $joinColumn['name'],
  410.                         $this->quoteStrategy->getJoinColumnName($joinColumn$subClass$this->platform),
  411.                         PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass$this->em)
  412.                     );
  413.                 }
  414.             }
  415.         }
  416.         $this->currentPersisterContext->selectColumnListSql implode(', '$columnList);
  417.         return $this->currentPersisterContext->selectColumnListSql;
  418.     }
  419.     /**
  420.      * {@inheritdoc}
  421.      */
  422.     protected function getInsertColumnList()
  423.     {
  424.         // Identifier columns must always come first in the column list of subclasses.
  425.         $columns $this->class->parentClasses
  426.             $this->class->getIdentifierColumnNames()
  427.             : [];
  428.         foreach ($this->class->reflFields as $name => $field) {
  429.             if (isset($this->class->fieldMappings[$name]['inherited'])
  430.                     && ! isset($this->class->fieldMappings[$name]['id'])
  431.                     || isset($this->class->associationMappings[$name]['inherited'])
  432.                     || ($this->class->isVersioned && $this->class->versionField == $name)
  433.                     || isset($this->class->embeddedClasses[$name])) {
  434.                 continue;
  435.             }
  436.             if (isset($this->class->associationMappings[$name])) {
  437.                 $assoc $this->class->associationMappings[$name];
  438.                 if ($assoc['type'] & ClassMetadata::TO_ONE && $assoc['isOwningSide']) {
  439.                     foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) {
  440.                         $columns[] = $sourceCol;
  441.                     }
  442.                 }
  443.             } else if ($this->class->name != $this->class->rootEntityName ||
  444.                     ! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] != $name) {
  445.                 $columns[]                  = $this->quoteStrategy->getColumnName($name$this->class$this->platform);
  446.                 $this->columnTypes[$name]   = $this->class->fieldMappings[$name]['type'];
  447.             }
  448.         }
  449.         // Add discriminator column if it is the topmost class.
  450.         if ($this->class->name == $this->class->rootEntityName) {
  451.             $columns[] = $this->class->discriminatorColumn['name'];
  452.         }
  453.         return $columns;
  454.     }
  455.     /**
  456.      * {@inheritdoc}
  457.      */
  458.     protected function assignDefaultVersionValue($entity, array $id)
  459.     {
  460.         $value $this->fetchVersionValue($this->getVersionedClassMetadata(), $id);
  461.         $this->class->setFieldValue($entity$this->class->versionField$value);
  462.     }
  463.     /**
  464.      * @param string $baseTableAlias
  465.      *
  466.      * @return string
  467.      */
  468.     private function getJoinSql($baseTableAlias)
  469.     {
  470.         $joinSql          '';
  471.         $identifierColumn $this->class->getIdentifierColumnNames();
  472.         // INNER JOIN parent tables
  473.         foreach ($this->class->parentClasses as $parentClassName) {
  474.             $conditions   = [];
  475.             $parentClass  $this->em->getClassMetadata($parentClassName);
  476.             $tableAlias   $this->getSQLTableAlias($parentClassName);
  477.             $joinSql     .= ' INNER JOIN ' $this->quoteStrategy->getTableName($parentClass$this->platform) . ' ' $tableAlias ' ON ';
  478.             foreach ($identifierColumn as $idColumn) {
  479.                 $conditions[] = $baseTableAlias '.' $idColumn ' = ' $tableAlias '.' $idColumn;
  480.             }
  481.             $joinSql .= implode(' AND '$conditions);
  482.         }
  483.         // OUTER JOIN sub tables
  484.         foreach ($this->class->subClasses as $subClassName) {
  485.             $conditions  = [];
  486.             $subClass    $this->em->getClassMetadata($subClassName);
  487.             $tableAlias  $this->getSQLTableAlias($subClassName);
  488.             $joinSql    .= ' LEFT JOIN ' $this->quoteStrategy->getTableName($subClass$this->platform) . ' ' $tableAlias ' ON ';
  489.             foreach ($identifierColumn as $idColumn) {
  490.                 $conditions[] = $baseTableAlias '.' $idColumn ' = ' $tableAlias '.' $idColumn;
  491.             }
  492.             $joinSql .= implode(' AND '$conditions);
  493.         }
  494.         return $joinSql;
  495.     }
  496. }