Find this useful? Enter your email to receive occasional updates for securing PHP code.
Signing you up...
Thank you for signing up!
PHP Decode
<?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Collection; use BadMet..
Decoded Output download
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Persisters\Collection;
use BadMethodCallException;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\InverseSideMapping;
use Doctrine\ORM\Mapping\ManyToManyAssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\SqlValueVisitor;
use Doctrine\ORM\Query;
use Doctrine\ORM\Utility\PersisterHelper;
use function array_fill;
use function array_pop;
use function assert;
use function count;
use function implode;
use function in_array;
use function reset;
use function sprintf;
/**
* Persister for many-to-many collections.
*/
class ManyToManyPersister extends AbstractCollectionPersister
{
public function delete(PersistentCollection $collection): void
{
$mapping = $this->getMapping($collection);
if (! $mapping->isOwningSide()) {
return; // ignore inverse side
}
assert($mapping->isManyToManyOwningSide());
$types = [];
$class = $this->em->getClassMetadata($mapping->sourceEntity);
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $class, $this->em);
}
$this->conn->executeStatement($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types);
}
public function update(PersistentCollection $collection): void
{
$mapping = $this->getMapping($collection);
if (! $mapping->isOwningSide()) {
return; // ignore inverse side
}
[$deleteSql, $deleteTypes] = $this->getDeleteRowSQL($collection);
[$insertSql, $insertTypes] = $this->getInsertRowSQL($collection);
foreach ($collection->getDeleteDiff() as $element) {
$this->conn->executeStatement(
$deleteSql,
$this->getDeleteRowSQLParameters($collection, $element),
$deleteTypes,
);
}
foreach ($collection->getInsertDiff() as $element) {
$this->conn->executeStatement(
$insertSql,
$this->getInsertRowSQLParameters($collection, $element),
$insertTypes,
);
}
}
public function get(PersistentCollection $collection, mixed $index): object|null
{
$mapping = $this->getMapping($collection);
if (! $mapping->isIndexed()) {
throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
}
$persister = $this->uow->getEntityPersister($mapping->targetEntity);
$mappedKey = $mapping->isOwningSide()
? $mapping->inversedBy
: $mapping->mappedBy;
assert($mappedKey !== null);
return $persister->load(
[$mappedKey => $collection->getOwner(), $mapping->indexBy() => $index],
null,
$mapping,
[],
LockMode::NONE,
1,
);
}
public function count(PersistentCollection $collection): int
{
$conditions = [];
$params = [];
$types = [];
$mapping = $this->getMapping($collection);
$id = $this->uow->getEntityIdentifier($collection->getOwner());
$sourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$association = $this->em->getMetadataFactory()->getOwningSide($mapping);
$joinTableName = $this->quoteStrategy->getJoinTableName($association, $sourceClass, $this->platform);
$joinColumns = ! $mapping->isOwningSide()
? $association->joinTable->inverseJoinColumns
: $association->joinTable->joinColumns;
foreach ($joinColumns as $joinColumn) {
$columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->platform);
$referencedName = $joinColumn->referencedColumnName;
$conditions[] = 't.' . $columnName . ' = ?';
$params[] = $id[$sourceClass->getFieldForColumn($referencedName)];
$types[] = PersisterHelper::getTypeOfColumn($referencedName, $sourceClass, $this->em);
}
[$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($mapping);
if ($filterSql) {
$conditions[] = $filterSql;
}
// If there is a provided criteria, make part of conditions
// @todo Fix this. Current SQL returns something like:
/*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) {
// A join is needed on the target entity
$targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
$targetJoinSql = ' JOIN ' . $targetTableName . ' te'
. ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
// And criteria conditions needs to be added
$persister = $this->uow->getEntityPersister($targetClass->name);
$visitor = new SqlExpressionVisitor($persister, $targetClass);
$conditions[] = $visitor->dispatch($expression);
$joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL;
}*/
$sql = 'SELECT COUNT(*)'
. ' FROM ' . $joinTableName . ' t'
. $joinTargetEntitySQL
. ' WHERE ' . implode(' AND ', $conditions);
return (int) $this->conn->fetchOne($sql, $params, $types);
}
/**
* {@inheritDoc}
*/
public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array
{
$mapping = $this->getMapping($collection);
$persister = $this->uow->getEntityPersister($mapping->targetEntity);
return $persister->getManyToManyCollection($mapping, $collection->getOwner(), $offset, $length);
}
public function containsKey(PersistentCollection $collection, mixed $key): bool
{
$mapping = $this->getMapping($collection);
if (! $mapping->isIndexed()) {
throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
}
[$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictionsWithKey(
$collection,
(string) $key,
true,
);
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return (bool) $this->conn->fetchOne($sql, $params, $types);
}
public function contains(PersistentCollection $collection, object $element): bool
{
if (! $this->isValidEntityState($element)) {
return false;
}
[$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictions(
$collection,
$element,
true,
);
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return (bool) $this->conn->fetchOne($sql, $params, $types);
}
/**
* {@inheritDoc}
*/
public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array
{
$mapping = $this->getMapping($collection);
$owner = $collection->getOwner();
$ownerMetadata = $this->em->getClassMetadata($owner::class);
$id = $this->uow->getEntityIdentifier($owner);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
$onConditions = $this->getOnConditionSQL($mapping);
$whereClauses = $params = [];
$paramTypes = [];
if (! $mapping->isOwningSide()) {
assert($mapping instanceof InverseSideMapping);
$associationSourceClass = $targetClass;
$sourceRelationMode = 'relationToTargetKeyColumns';
} else {
$associationSourceClass = $ownerMetadata;
$sourceRelationMode = 'relationToSourceKeyColumns';
}
$mapping = $this->em->getMetadataFactory()->getOwningSide($mapping);
foreach ($mapping->$sourceRelationMode as $key => $value) {
$whereClauses[] = sprintf('t.%s = ?', $key);
$params[] = $ownerMetadata->containsForeignIdentifier
? $id[$ownerMetadata->getFieldForColumn($value)]
: $id[$ownerMetadata->fieldNames[$value]];
$paramTypes[] = PersisterHelper::getTypeOfColumn($value, $ownerMetadata, $this->em);
}
$parameters = $this->expandCriteriaParameters($criteria);
foreach ($parameters as $parameter) {
[$name, $value, $operator] = $parameter;
$field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform);
if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) {
$whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT');
} else {
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
$params[] = $value;
$paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
}
}
$tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
$joinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform);
$rsm = new Query\ResultSetMappingBuilder($this->em);
$rsm->addRootEntityFromClassMetadata($targetClass->name, 'te');
$sql = 'SELECT ' . $rsm->generateSelectClause()
. ' FROM ' . $tableName . ' te'
. ' JOIN ' . $joinTable . ' t ON'
. implode(' AND ', $onConditions)
. ' WHERE ' . implode(' AND ', $whereClauses);
$sql .= $this->getOrderingSql($criteria, $targetClass);
$sql .= $this->getLimitSql($criteria);
$stmt = $this->conn->executeQuery($sql, $params, $paramTypes);
return $this
->em
->newHydrator(Query::HYDRATE_OBJECT)
->hydrateAll($stmt, $rsm);
}
/**
* Generates the filter SQL for a given mapping.
*
* This method is not used for actually grabbing the related entities
* but when the extra-lazy collection methods are called on a filtered
* association. This is why besides the many to many table we also
* have to join in the actual entities table leading to additional
* JOIN.
*
* @param AssociationMapping $mapping Array containing mapping information.
*
* @return string[] ordered tuple:
* - JOIN condition to add to the SQL
* - WHERE condition to add to the SQL
* @psalm-return array{0: string, 1: string}
*/
public function getFilterSql(AssociationMapping $mapping): array
{
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
$rootClass = $this->em->getClassMetadata($targetClass->rootEntityName);
$filterSql = $this->generateFilterConditionSQL($rootClass, 'te');
if ($filterSql === '') {
return ['', ''];
}
// A join is needed if there is filtering on the target entity
$tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform);
$joinSql = ' JOIN ' . $tableName . ' te'
. ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping));
return [$joinSql, $filterSql];
}
/**
* Generates the filter SQL for a given entity and table alias.
*
* @param ClassMetadata $targetEntity Metadata of the target entity.
* @param string $targetTableAlias The table alias of the joined/selected table.
*
* @return string The SQL query part to add to a query.
*/
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, string $targetTableAlias): string
{
$filterClauses = [];
foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
$filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
if ($filterExpr) {
$filterClauses[] = '(' . $filterExpr . ')';
}
}
return $filterClauses
? '(' . implode(' AND ', $filterClauses) . ')'
: '';
}
/**
* Generate ON condition
*
* @return string[]
* @psalm-return list<string>
*/
protected function getOnConditionSQL(AssociationMapping $mapping): array
{
$association = $this->em->getMetadataFactory()->getOwningSide($mapping);
$joinColumns = $mapping->isOwningSide()
? $association->joinTable->inverseJoinColumns
: $association->joinTable->joinColumns;
$conditions = [];
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
foreach ($joinColumns as $joinColumn) {
$joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
$refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
$conditions[] = ' t.' . $joinColumnName . ' = te.' . $refColumnName;
}
return $conditions;
}
protected function getDeleteSQL(PersistentCollection $collection): string
{
$columns = [];
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$class = $this->em->getClassMetadata($collection->getOwner()::class);
$joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform);
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
}
return 'DELETE FROM ' . $joinTable
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
}
/**
* Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql.
*
* @return list<mixed>
*/
protected function getDeleteSQLParameters(PersistentCollection $collection): array
{
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$identifier = $this->uow->getEntityIdentifier($collection->getOwner());
// Optimization for single column identifier
if (count($mapping->relationToSourceKeyColumns) === 1) {
return [reset($identifier)];
}
// Composite identifier
$sourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$params = [];
foreach ($mapping->relationToSourceKeyColumns as $columnName => $refColumnName) {
$params[] = isset($sourceClass->fieldNames[$refColumnName])
? $identifier[$sourceClass->fieldNames[$refColumnName]]
: $identifier[$sourceClass->getFieldForColumn($refColumnName)];
}
return $params;
}
/**
* Gets the SQL statement used for deleting a row from the collection.
*
* @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
* of types for bound parameters
* @psalm-return array{0: string, 1: list<string>}
*/
protected function getDeleteRowSQL(PersistentCollection $collection): array
{
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$class = $this->em->getClassMetadata($mapping->sourceEntity);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
$columns = [];
$types = [];
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $class, $this->em);
}
foreach ($mapping->joinTable->inverseJoinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em);
}
return [
'DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?',
$types,
];
}
/**
* Gets the SQL parameters for the corresponding SQL statement to delete the given
* element from the given collection.
*
* Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql.
*
* @return mixed[]
* @psalm-return list<mixed>
*/
protected function getDeleteRowSQLParameters(PersistentCollection $collection, object $element): array
{
return $this->collectJoinTableColumnParameters($collection, $element);
}
/**
* Gets the SQL statement used for inserting a row in the collection.
*
* @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
* of types for bound parameters
* @psalm-return array{0: string, 1: list<string>}
*/
protected function getInsertRowSQL(PersistentCollection $collection): array
{
$columns = [];
$types = [];
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$class = $this->em->getClassMetadata($mapping->sourceEntity);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $class, $this->em);
}
foreach ($mapping->joinTable->inverseJoinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em);
}
return [
'INSERT INTO ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
. ' (' . implode(', ', $columns) . ')'
. ' VALUES'
. ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')',
$types,
];
}
/**
* Gets the SQL parameters for the corresponding SQL statement to insert the given
* element of the given collection into the database.
*
* Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql.
*
* @return mixed[]
* @psalm-return list<mixed>
*/
protected function getInsertRowSQLParameters(PersistentCollection $collection, object $element): array
{
return $this->collectJoinTableColumnParameters($collection, $element);
}
/**
* Collects the parameters for inserting/deleting on the join table in the order
* of the join table columns as specified in ManyToManyMapping#joinTableColumns.
*
* @return mixed[]
* @psalm-return list<mixed>
*/
private function collectJoinTableColumnParameters(
PersistentCollection $collection,
object $element,
): array {
$params = [];
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$isComposite = count($mapping->joinTableColumns) > 2;
$identifier1 = $this->uow->getEntityIdentifier($collection->getOwner());
$identifier2 = $this->uow->getEntityIdentifier($element);
$class1 = $class2 = null;
if ($isComposite) {
$class1 = $this->em->getClassMetadata($collection->getOwner()::class);
$class2 = $collection->getTypeClass();
}
foreach ($mapping->joinTableColumns as $joinTableColumn) {
$isRelationToSource = isset($mapping->relationToSourceKeyColumns[$joinTableColumn]);
if (! $isComposite) {
$params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2);
continue;
}
if ($isRelationToSource) {
$params[] = $identifier1[$class1->getFieldForColumn($mapping->relationToSourceKeyColumns[$joinTableColumn])];
continue;
}
$params[] = $identifier2[$class2->getFieldForColumn($mapping->relationToTargetKeyColumns[$joinTableColumn])];
}
return $params;
}
/**
* @param bool $addFilters Whether the filter SQL should be included or not.
*
* @return mixed[] ordered vector:
* - quoted join table name
* - where clauses to be added for filtering
* - parameters to be bound for filtering
* - types of the parameters to be bound for filtering
* @psalm-return array{0: string, 1: list<string>, 2: list<mixed>, 3: list<string>}
*/
private function getJoinTableRestrictionsWithKey(
PersistentCollection $collection,
string $key,
bool $addFilters,
): array {
$filterMapping = $this->getMapping($collection);
$mapping = $filterMapping;
$indexBy = $mapping->indexBy();
$id = $this->uow->getEntityIdentifier($collection->getOwner());
$sourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
if (! $mapping->isOwningSide()) {
assert($mapping instanceof InverseSideMapping);
$associationSourceClass = $this->em->getClassMetadata($mapping->targetEntity);
$mapping = $associationSourceClass->associationMappings[$mapping->mappedBy];
assert($mapping->isManyToManyOwningSide());
$joinColumns = $mapping->joinTable->joinColumns;
$sourceRelationMode = 'relationToTargetKeyColumns';
$targetRelationMode = 'relationToSourceKeyColumns';
} else {
assert($mapping->isManyToManyOwningSide());
$associationSourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$joinColumns = $mapping->joinTable->inverseJoinColumns;
$sourceRelationMode = 'relationToSourceKeyColumns';
$targetRelationMode = 'relationToTargetKeyColumns';
}
$quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform) . ' t';
$whereClauses = [];
$params = [];
$types = [];
$joinNeeded = ! in_array($indexBy, $targetClass->identifier, true);
if ($joinNeeded) { // extra join needed if indexBy is not a @id
$joinConditions = [];
foreach ($joinColumns as $joinTableColumn) {
$joinConditions[] = 't.' . $joinTableColumn->name . ' = tr.' . $joinTableColumn->referencedColumnName;
}
$tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
$quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
$columnName = $targetClass->getColumnName($indexBy);
$whereClauses[] = 'tr.' . $columnName . ' = ?';
$params[] = $key;
$types[] = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em);
}
foreach ($mapping->joinTableColumns as $joinTableColumn) {
if (isset($mapping->{$sourceRelationMode}[$joinTableColumn])) {
$column = $mapping->{$sourceRelationMode}[$joinTableColumn];
$whereClauses[] = 't.' . $joinTableColumn . ' = ?';
$params[] = $sourceClass->containsForeignIdentifier
? $id[$sourceClass->getFieldForColumn($column)]
: $id[$sourceClass->fieldNames[$column]];
$types[] = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em);
} elseif (! $joinNeeded) {
$column = $mapping->{$targetRelationMode}[$joinTableColumn];
$whereClauses[] = 't.' . $joinTableColumn . ' = ?';
$params[] = $key;
$types[] = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em);
}
}
if ($addFilters) {
[$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping);
if ($filterSql) {
$quotedJoinTable .= ' ' . $joinTargetEntitySQL;
$whereClauses[] = $filterSql;
}
}
return [$quotedJoinTable, $whereClauses, $params, $types];
}
/**
* @param bool $addFilters Whether the filter SQL should be included or not.
*
* @return mixed[] ordered vector:
* - quoted join table name
* - where clauses to be added for filtering
* - parameters to be bound for filtering
* - types of the parameters to be bound for filtering
* @psalm-return array{0: string, 1: list<string>, 2: list<mixed>, 3: list<string>}
*/
private function getJoinTableRestrictions(
PersistentCollection $collection,
object $element,
bool $addFilters,
): array {
$filterMapping = $this->getMapping($collection);
$mapping = $filterMapping;
if (! $mapping->isOwningSide()) {
$sourceClass = $this->em->getClassMetadata($mapping->targetEntity);
$targetClass = $this->em->getClassMetadata($mapping->sourceEntity);
$sourceId = $this->uow->getEntityIdentifier($element);
$targetId = $this->uow->getEntityIdentifier($collection->getOwner());
} else {
$sourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
$sourceId = $this->uow->getEntityIdentifier($collection->getOwner());
$targetId = $this->uow->getEntityIdentifier($element);
}
$mapping = $this->em->getMetadataFactory()->getOwningSide($mapping);
$quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform);
$whereClauses = [];
$params = [];
$types = [];
foreach ($mapping->joinTableColumns as $joinTableColumn) {
$whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?';
if (isset($mapping->relationToTargetKeyColumns[$joinTableColumn])) {
$targetColumn = $mapping->relationToTargetKeyColumns[$joinTableColumn];
$params[] = $targetId[$targetClass->getFieldForColumn($targetColumn)];
$types[] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em);
continue;
}
// relationToSourceKeyColumns
$targetColumn = $mapping->relationToSourceKeyColumns[$joinTableColumn];
$params[] = $sourceId[$sourceClass->getFieldForColumn($targetColumn)];
$types[] = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em);
}
if ($addFilters) {
$quotedJoinTable .= ' t';
[$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping);
if ($filterSql) {
$quotedJoinTable .= ' ' . $joinTargetEntitySQL;
$whereClauses[] = $filterSql;
}
}
return [$quotedJoinTable, $whereClauses, $params, $types];
}
/**
* Expands Criteria Parameters by walking the expressions and grabbing all
* parameters and types from it.
*
* @return mixed[][]
*/
private function expandCriteriaParameters(Criteria $criteria): array
{
$expression = $criteria->getWhereExpression();
if ($expression === null) {
return [];
}
$valueVisitor = new SqlValueVisitor();
$valueVisitor->dispatch($expression);
[, $types] = $valueVisitor->getParamsAndTypes();
return $types;
}
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string
{
$orderings = $criteria->orderings();
if ($orderings) {
$orderBy = [];
foreach ($orderings as $name => $direction) {
$field = $this->quoteStrategy->getColumnName(
$name,
$targetClass,
$this->platform,
);
$orderBy[] = $field . ' ' . $direction->value;
}
return ' ORDER BY ' . implode(', ', $orderBy);
}
return '';
}
/** @throws DBALException */
private function getLimitSql(Criteria $criteria): string
{
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
return $this->platform->modifyLimitQuery('', $limit, $offset ?? 0);
}
private function getMapping(PersistentCollection $collection): AssociationMapping&ManyToManyAssociationMapping
{
$mapping = $collection->getMapping();
assert($mapping instanceof ManyToManyAssociationMapping);
return $mapping;
}
}
?>
Did this file decode correctly?
Original Code
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Persisters\Collection;
use BadMethodCallException;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\InverseSideMapping;
use Doctrine\ORM\Mapping\ManyToManyAssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\SqlValueVisitor;
use Doctrine\ORM\Query;
use Doctrine\ORM\Utility\PersisterHelper;
use function array_fill;
use function array_pop;
use function assert;
use function count;
use function implode;
use function in_array;
use function reset;
use function sprintf;
/**
* Persister for many-to-many collections.
*/
class ManyToManyPersister extends AbstractCollectionPersister
{
public function delete(PersistentCollection $collection): void
{
$mapping = $this->getMapping($collection);
if (! $mapping->isOwningSide()) {
return; // ignore inverse side
}
assert($mapping->isManyToManyOwningSide());
$types = [];
$class = $this->em->getClassMetadata($mapping->sourceEntity);
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $class, $this->em);
}
$this->conn->executeStatement($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types);
}
public function update(PersistentCollection $collection): void
{
$mapping = $this->getMapping($collection);
if (! $mapping->isOwningSide()) {
return; // ignore inverse side
}
[$deleteSql, $deleteTypes] = $this->getDeleteRowSQL($collection);
[$insertSql, $insertTypes] = $this->getInsertRowSQL($collection);
foreach ($collection->getDeleteDiff() as $element) {
$this->conn->executeStatement(
$deleteSql,
$this->getDeleteRowSQLParameters($collection, $element),
$deleteTypes,
);
}
foreach ($collection->getInsertDiff() as $element) {
$this->conn->executeStatement(
$insertSql,
$this->getInsertRowSQLParameters($collection, $element),
$insertTypes,
);
}
}
public function get(PersistentCollection $collection, mixed $index): object|null
{
$mapping = $this->getMapping($collection);
if (! $mapping->isIndexed()) {
throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
}
$persister = $this->uow->getEntityPersister($mapping->targetEntity);
$mappedKey = $mapping->isOwningSide()
? $mapping->inversedBy
: $mapping->mappedBy;
assert($mappedKey !== null);
return $persister->load(
[$mappedKey => $collection->getOwner(), $mapping->indexBy() => $index],
null,
$mapping,
[],
LockMode::NONE,
1,
);
}
public function count(PersistentCollection $collection): int
{
$conditions = [];
$params = [];
$types = [];
$mapping = $this->getMapping($collection);
$id = $this->uow->getEntityIdentifier($collection->getOwner());
$sourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$association = $this->em->getMetadataFactory()->getOwningSide($mapping);
$joinTableName = $this->quoteStrategy->getJoinTableName($association, $sourceClass, $this->platform);
$joinColumns = ! $mapping->isOwningSide()
? $association->joinTable->inverseJoinColumns
: $association->joinTable->joinColumns;
foreach ($joinColumns as $joinColumn) {
$columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->platform);
$referencedName = $joinColumn->referencedColumnName;
$conditions[] = 't.' . $columnName . ' = ?';
$params[] = $id[$sourceClass->getFieldForColumn($referencedName)];
$types[] = PersisterHelper::getTypeOfColumn($referencedName, $sourceClass, $this->em);
}
[$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($mapping);
if ($filterSql) {
$conditions[] = $filterSql;
}
// If there is a provided criteria, make part of conditions
// @todo Fix this. Current SQL returns something like:
/*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) {
// A join is needed on the target entity
$targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
$targetJoinSql = ' JOIN ' . $targetTableName . ' te'
. ' ON' . implode(' AND ', $this->getOnConditionSQL($association));
// And criteria conditions needs to be added
$persister = $this->uow->getEntityPersister($targetClass->name);
$visitor = new SqlExpressionVisitor($persister, $targetClass);
$conditions[] = $visitor->dispatch($expression);
$joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL;
}*/
$sql = 'SELECT COUNT(*)'
. ' FROM ' . $joinTableName . ' t'
. $joinTargetEntitySQL
. ' WHERE ' . implode(' AND ', $conditions);
return (int) $this->conn->fetchOne($sql, $params, $types);
}
/**
* {@inheritDoc}
*/
public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array
{
$mapping = $this->getMapping($collection);
$persister = $this->uow->getEntityPersister($mapping->targetEntity);
return $persister->getManyToManyCollection($mapping, $collection->getOwner(), $offset, $length);
}
public function containsKey(PersistentCollection $collection, mixed $key): bool
{
$mapping = $this->getMapping($collection);
if (! $mapping->isIndexed()) {
throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
}
[$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictionsWithKey(
$collection,
(string) $key,
true,
);
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return (bool) $this->conn->fetchOne($sql, $params, $types);
}
public function contains(PersistentCollection $collection, object $element): bool
{
if (! $this->isValidEntityState($element)) {
return false;
}
[$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictions(
$collection,
$element,
true,
);
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return (bool) $this->conn->fetchOne($sql, $params, $types);
}
/**
* {@inheritDoc}
*/
public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array
{
$mapping = $this->getMapping($collection);
$owner = $collection->getOwner();
$ownerMetadata = $this->em->getClassMetadata($owner::class);
$id = $this->uow->getEntityIdentifier($owner);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
$onConditions = $this->getOnConditionSQL($mapping);
$whereClauses = $params = [];
$paramTypes = [];
if (! $mapping->isOwningSide()) {
assert($mapping instanceof InverseSideMapping);
$associationSourceClass = $targetClass;
$sourceRelationMode = 'relationToTargetKeyColumns';
} else {
$associationSourceClass = $ownerMetadata;
$sourceRelationMode = 'relationToSourceKeyColumns';
}
$mapping = $this->em->getMetadataFactory()->getOwningSide($mapping);
foreach ($mapping->$sourceRelationMode as $key => $value) {
$whereClauses[] = sprintf('t.%s = ?', $key);
$params[] = $ownerMetadata->containsForeignIdentifier
? $id[$ownerMetadata->getFieldForColumn($value)]
: $id[$ownerMetadata->fieldNames[$value]];
$paramTypes[] = PersisterHelper::getTypeOfColumn($value, $ownerMetadata, $this->em);
}
$parameters = $this->expandCriteriaParameters($criteria);
foreach ($parameters as $parameter) {
[$name, $value, $operator] = $parameter;
$field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform);
if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) {
$whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT');
} else {
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
$params[] = $value;
$paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
}
}
$tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
$joinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform);
$rsm = new Query\ResultSetMappingBuilder($this->em);
$rsm->addRootEntityFromClassMetadata($targetClass->name, 'te');
$sql = 'SELECT ' . $rsm->generateSelectClause()
. ' FROM ' . $tableName . ' te'
. ' JOIN ' . $joinTable . ' t ON'
. implode(' AND ', $onConditions)
. ' WHERE ' . implode(' AND ', $whereClauses);
$sql .= $this->getOrderingSql($criteria, $targetClass);
$sql .= $this->getLimitSql($criteria);
$stmt = $this->conn->executeQuery($sql, $params, $paramTypes);
return $this
->em
->newHydrator(Query::HYDRATE_OBJECT)
->hydrateAll($stmt, $rsm);
}
/**
* Generates the filter SQL for a given mapping.
*
* This method is not used for actually grabbing the related entities
* but when the extra-lazy collection methods are called on a filtered
* association. This is why besides the many to many table we also
* have to join in the actual entities table leading to additional
* JOIN.
*
* @param AssociationMapping $mapping Array containing mapping information.
*
* @return string[] ordered tuple:
* - JOIN condition to add to the SQL
* - WHERE condition to add to the SQL
* @psalm-return array{0: string, 1: string}
*/
public function getFilterSql(AssociationMapping $mapping): array
{
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
$rootClass = $this->em->getClassMetadata($targetClass->rootEntityName);
$filterSql = $this->generateFilterConditionSQL($rootClass, 'te');
if ($filterSql === '') {
return ['', ''];
}
// A join is needed if there is filtering on the target entity
$tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform);
$joinSql = ' JOIN ' . $tableName . ' te'
. ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping));
return [$joinSql, $filterSql];
}
/**
* Generates the filter SQL for a given entity and table alias.
*
* @param ClassMetadata $targetEntity Metadata of the target entity.
* @param string $targetTableAlias The table alias of the joined/selected table.
*
* @return string The SQL query part to add to a query.
*/
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, string $targetTableAlias): string
{
$filterClauses = [];
foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
$filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
if ($filterExpr) {
$filterClauses[] = '(' . $filterExpr . ')';
}
}
return $filterClauses
? '(' . implode(' AND ', $filterClauses) . ')'
: '';
}
/**
* Generate ON condition
*
* @return string[]
* @psalm-return list<string>
*/
protected function getOnConditionSQL(AssociationMapping $mapping): array
{
$association = $this->em->getMetadataFactory()->getOwningSide($mapping);
$joinColumns = $mapping->isOwningSide()
? $association->joinTable->inverseJoinColumns
: $association->joinTable->joinColumns;
$conditions = [];
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
foreach ($joinColumns as $joinColumn) {
$joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
$refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
$conditions[] = ' t.' . $joinColumnName . ' = te.' . $refColumnName;
}
return $conditions;
}
protected function getDeleteSQL(PersistentCollection $collection): string
{
$columns = [];
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$class = $this->em->getClassMetadata($collection->getOwner()::class);
$joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform);
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
}
return 'DELETE FROM ' . $joinTable
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
}
/**
* Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql.
*
* @return list<mixed>
*/
protected function getDeleteSQLParameters(PersistentCollection $collection): array
{
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$identifier = $this->uow->getEntityIdentifier($collection->getOwner());
// Optimization for single column identifier
if (count($mapping->relationToSourceKeyColumns) === 1) {
return [reset($identifier)];
}
// Composite identifier
$sourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$params = [];
foreach ($mapping->relationToSourceKeyColumns as $columnName => $refColumnName) {
$params[] = isset($sourceClass->fieldNames[$refColumnName])
? $identifier[$sourceClass->fieldNames[$refColumnName]]
: $identifier[$sourceClass->getFieldForColumn($refColumnName)];
}
return $params;
}
/**
* Gets the SQL statement used for deleting a row from the collection.
*
* @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
* of types for bound parameters
* @psalm-return array{0: string, 1: list<string>}
*/
protected function getDeleteRowSQL(PersistentCollection $collection): array
{
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$class = $this->em->getClassMetadata($mapping->sourceEntity);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
$columns = [];
$types = [];
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $class, $this->em);
}
foreach ($mapping->joinTable->inverseJoinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em);
}
return [
'DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?',
$types,
];
}
/**
* Gets the SQL parameters for the corresponding SQL statement to delete the given
* element from the given collection.
*
* Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql.
*
* @return mixed[]
* @psalm-return list<mixed>
*/
protected function getDeleteRowSQLParameters(PersistentCollection $collection, object $element): array
{
return $this->collectJoinTableColumnParameters($collection, $element);
}
/**
* Gets the SQL statement used for inserting a row in the collection.
*
* @return string[]|string[][] ordered tuple containing the SQL to be executed and an array
* of types for bound parameters
* @psalm-return array{0: string, 1: list<string>}
*/
protected function getInsertRowSQL(PersistentCollection $collection): array
{
$columns = [];
$types = [];
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$class = $this->em->getClassMetadata($mapping->sourceEntity);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $class, $this->em);
}
foreach ($mapping->joinTable->inverseJoinColumns as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
$types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em);
}
return [
'INSERT INTO ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform)
. ' (' . implode(', ', $columns) . ')'
. ' VALUES'
. ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')',
$types,
];
}
/**
* Gets the SQL parameters for the corresponding SQL statement to insert the given
* element of the given collection into the database.
*
* Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql.
*
* @return mixed[]
* @psalm-return list<mixed>
*/
protected function getInsertRowSQLParameters(PersistentCollection $collection, object $element): array
{
return $this->collectJoinTableColumnParameters($collection, $element);
}
/**
* Collects the parameters for inserting/deleting on the join table in the order
* of the join table columns as specified in ManyToManyMapping#joinTableColumns.
*
* @return mixed[]
* @psalm-return list<mixed>
*/
private function collectJoinTableColumnParameters(
PersistentCollection $collection,
object $element,
): array {
$params = [];
$mapping = $this->getMapping($collection);
assert($mapping->isManyToManyOwningSide());
$isComposite = count($mapping->joinTableColumns) > 2;
$identifier1 = $this->uow->getEntityIdentifier($collection->getOwner());
$identifier2 = $this->uow->getEntityIdentifier($element);
$class1 = $class2 = null;
if ($isComposite) {
$class1 = $this->em->getClassMetadata($collection->getOwner()::class);
$class2 = $collection->getTypeClass();
}
foreach ($mapping->joinTableColumns as $joinTableColumn) {
$isRelationToSource = isset($mapping->relationToSourceKeyColumns[$joinTableColumn]);
if (! $isComposite) {
$params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2);
continue;
}
if ($isRelationToSource) {
$params[] = $identifier1[$class1->getFieldForColumn($mapping->relationToSourceKeyColumns[$joinTableColumn])];
continue;
}
$params[] = $identifier2[$class2->getFieldForColumn($mapping->relationToTargetKeyColumns[$joinTableColumn])];
}
return $params;
}
/**
* @param bool $addFilters Whether the filter SQL should be included or not.
*
* @return mixed[] ordered vector:
* - quoted join table name
* - where clauses to be added for filtering
* - parameters to be bound for filtering
* - types of the parameters to be bound for filtering
* @psalm-return array{0: string, 1: list<string>, 2: list<mixed>, 3: list<string>}
*/
private function getJoinTableRestrictionsWithKey(
PersistentCollection $collection,
string $key,
bool $addFilters,
): array {
$filterMapping = $this->getMapping($collection);
$mapping = $filterMapping;
$indexBy = $mapping->indexBy();
$id = $this->uow->getEntityIdentifier($collection->getOwner());
$sourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
if (! $mapping->isOwningSide()) {
assert($mapping instanceof InverseSideMapping);
$associationSourceClass = $this->em->getClassMetadata($mapping->targetEntity);
$mapping = $associationSourceClass->associationMappings[$mapping->mappedBy];
assert($mapping->isManyToManyOwningSide());
$joinColumns = $mapping->joinTable->joinColumns;
$sourceRelationMode = 'relationToTargetKeyColumns';
$targetRelationMode = 'relationToSourceKeyColumns';
} else {
assert($mapping->isManyToManyOwningSide());
$associationSourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$joinColumns = $mapping->joinTable->inverseJoinColumns;
$sourceRelationMode = 'relationToSourceKeyColumns';
$targetRelationMode = 'relationToTargetKeyColumns';
}
$quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform) . ' t';
$whereClauses = [];
$params = [];
$types = [];
$joinNeeded = ! in_array($indexBy, $targetClass->identifier, true);
if ($joinNeeded) { // extra join needed if indexBy is not a @id
$joinConditions = [];
foreach ($joinColumns as $joinTableColumn) {
$joinConditions[] = 't.' . $joinTableColumn->name . ' = tr.' . $joinTableColumn->referencedColumnName;
}
$tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
$quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
$columnName = $targetClass->getColumnName($indexBy);
$whereClauses[] = 'tr.' . $columnName . ' = ?';
$params[] = $key;
$types[] = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em);
}
foreach ($mapping->joinTableColumns as $joinTableColumn) {
if (isset($mapping->{$sourceRelationMode}[$joinTableColumn])) {
$column = $mapping->{$sourceRelationMode}[$joinTableColumn];
$whereClauses[] = 't.' . $joinTableColumn . ' = ?';
$params[] = $sourceClass->containsForeignIdentifier
? $id[$sourceClass->getFieldForColumn($column)]
: $id[$sourceClass->fieldNames[$column]];
$types[] = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em);
} elseif (! $joinNeeded) {
$column = $mapping->{$targetRelationMode}[$joinTableColumn];
$whereClauses[] = 't.' . $joinTableColumn . ' = ?';
$params[] = $key;
$types[] = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em);
}
}
if ($addFilters) {
[$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping);
if ($filterSql) {
$quotedJoinTable .= ' ' . $joinTargetEntitySQL;
$whereClauses[] = $filterSql;
}
}
return [$quotedJoinTable, $whereClauses, $params, $types];
}
/**
* @param bool $addFilters Whether the filter SQL should be included or not.
*
* @return mixed[] ordered vector:
* - quoted join table name
* - where clauses to be added for filtering
* - parameters to be bound for filtering
* - types of the parameters to be bound for filtering
* @psalm-return array{0: string, 1: list<string>, 2: list<mixed>, 3: list<string>}
*/
private function getJoinTableRestrictions(
PersistentCollection $collection,
object $element,
bool $addFilters,
): array {
$filterMapping = $this->getMapping($collection);
$mapping = $filterMapping;
if (! $mapping->isOwningSide()) {
$sourceClass = $this->em->getClassMetadata($mapping->targetEntity);
$targetClass = $this->em->getClassMetadata($mapping->sourceEntity);
$sourceId = $this->uow->getEntityIdentifier($element);
$targetId = $this->uow->getEntityIdentifier($collection->getOwner());
} else {
$sourceClass = $this->em->getClassMetadata($mapping->sourceEntity);
$targetClass = $this->em->getClassMetadata($mapping->targetEntity);
$sourceId = $this->uow->getEntityIdentifier($collection->getOwner());
$targetId = $this->uow->getEntityIdentifier($element);
}
$mapping = $this->em->getMetadataFactory()->getOwningSide($mapping);
$quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform);
$whereClauses = [];
$params = [];
$types = [];
foreach ($mapping->joinTableColumns as $joinTableColumn) {
$whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?';
if (isset($mapping->relationToTargetKeyColumns[$joinTableColumn])) {
$targetColumn = $mapping->relationToTargetKeyColumns[$joinTableColumn];
$params[] = $targetId[$targetClass->getFieldForColumn($targetColumn)];
$types[] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em);
continue;
}
// relationToSourceKeyColumns
$targetColumn = $mapping->relationToSourceKeyColumns[$joinTableColumn];
$params[] = $sourceId[$sourceClass->getFieldForColumn($targetColumn)];
$types[] = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em);
}
if ($addFilters) {
$quotedJoinTable .= ' t';
[$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping);
if ($filterSql) {
$quotedJoinTable .= ' ' . $joinTargetEntitySQL;
$whereClauses[] = $filterSql;
}
}
return [$quotedJoinTable, $whereClauses, $params, $types];
}
/**
* Expands Criteria Parameters by walking the expressions and grabbing all
* parameters and types from it.
*
* @return mixed[][]
*/
private function expandCriteriaParameters(Criteria $criteria): array
{
$expression = $criteria->getWhereExpression();
if ($expression === null) {
return [];
}
$valueVisitor = new SqlValueVisitor();
$valueVisitor->dispatch($expression);
[, $types] = $valueVisitor->getParamsAndTypes();
return $types;
}
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string
{
$orderings = $criteria->orderings();
if ($orderings) {
$orderBy = [];
foreach ($orderings as $name => $direction) {
$field = $this->quoteStrategy->getColumnName(
$name,
$targetClass,
$this->platform,
);
$orderBy[] = $field . ' ' . $direction->value;
}
return ' ORDER BY ' . implode(', ', $orderBy);
}
return '';
}
/** @throws DBALException */
private function getLimitSql(Criteria $criteria): string
{
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
return $this->platform->modifyLimitQuery('', $limit, $offset ?? 0);
}
private function getMapping(PersistentCollection $collection): AssociationMapping&ManyToManyAssociationMapping
{
$mapping = $collection->getMapping();
assert($mapping instanceof ManyToManyAssociationMapping);
return $mapping;
}
}
Function Calls
None |
Stats
MD5 | 265c06289bdafb07bcfac6cc7588f064 |
Eval Count | 0 |
Decode Time | 105 ms |