From 05e38eadbeb3de19e5b26003c2b401e00bb71df4 Mon Sep 17 00:00:00 2001 From: polo Date: Sun, 18 Oct 2026 00:08:23 +0200 Subject: =?UTF-8?q?sortie=20de=20vendor=20du=20d=C3=A9p=C3=B4t,=20oubli=20?= =?UTF-8?q?dans=20config-template.ini?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Collection/AbstractCollectionPersister.php | 50 - .../Persisters/Collection/CollectionPersister.php | 59 - .../Persisters/Collection/ManyToManyPersister.php | 770 -------- .../Persisters/Collection/OneToManyPersister.php | 264 --- .../Entity/AbstractEntityInheritancePersister.php | 66 - .../src/Persisters/Entity/BasicEntityPersister.php | 2085 -------------------- .../Persisters/Entity/CachedPersisterContext.php | 60 - .../orm/src/Persisters/Entity/EntityPersister.php | 298 --- .../Persisters/Entity/JoinedSubclassPersister.php | 601 ------ .../src/Persisters/Entity/SingleTablePersister.php | 166 -- .../Exception/CantUseInOperatorOnCompositeKeys.php | 15 - .../Persisters/Exception/InvalidOrientation.php | 15 - .../src/Persisters/Exception/UnrecognizedField.php | 24 - .../MatchingAssociationFieldRequiresObject.php | 22 - .../orm/src/Persisters/PersisterException.php | 23 - .../orm/src/Persisters/SqlExpressionVisitor.php | 79 - .../orm/src/Persisters/SqlValueVisitor.php | 88 - 17 files changed, 4685 deletions(-) delete mode 100644 vendor/doctrine/orm/src/Persisters/Collection/AbstractCollectionPersister.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Collection/CollectionPersister.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Collection/ManyToManyPersister.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Entity/AbstractEntityInheritancePersister.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Entity/CachedPersisterContext.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Entity/EntityPersister.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Entity/JoinedSubclassPersister.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Entity/SingleTablePersister.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Exception/CantUseInOperatorOnCompositeKeys.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Exception/InvalidOrientation.php delete mode 100644 vendor/doctrine/orm/src/Persisters/Exception/UnrecognizedField.php delete mode 100644 vendor/doctrine/orm/src/Persisters/MatchingAssociationFieldRequiresObject.php delete mode 100644 vendor/doctrine/orm/src/Persisters/PersisterException.php delete mode 100644 vendor/doctrine/orm/src/Persisters/SqlExpressionVisitor.php delete mode 100644 vendor/doctrine/orm/src/Persisters/SqlValueVisitor.php (limited to 'vendor/doctrine/orm/src/Persisters') diff --git a/vendor/doctrine/orm/src/Persisters/Collection/AbstractCollectionPersister.php b/vendor/doctrine/orm/src/Persisters/Collection/AbstractCollectionPersister.php deleted file mode 100644 index 26f0b9e..0000000 --- a/vendor/doctrine/orm/src/Persisters/Collection/AbstractCollectionPersister.php +++ /dev/null @@ -1,50 +0,0 @@ -uow = $em->getUnitOfWork(); - $this->conn = $em->getConnection(); - $this->platform = $this->conn->getDatabasePlatform(); - $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); - } - - /** - * Check if entity is in a valid state for operations. - */ - protected function isValidEntityState(object $entity): bool - { - $entityState = $this->uow->getEntityState($entity, UnitOfWork::STATE_NEW); - - if ($entityState === UnitOfWork::STATE_NEW) { - return false; - } - - // If Entity is scheduled for inclusion, it is not in this collection. - // We can assure that because it would have return true before on array check - return ! ($entityState === UnitOfWork::STATE_MANAGED && $this->uow->isScheduledForInsert($entity)); - } -} diff --git a/vendor/doctrine/orm/src/Persisters/Collection/CollectionPersister.php b/vendor/doctrine/orm/src/Persisters/Collection/CollectionPersister.php deleted file mode 100644 index 07c4eaf..0000000 --- a/vendor/doctrine/orm/src/Persisters/Collection/CollectionPersister.php +++ /dev/null @@ -1,59 +0,0 @@ -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 - */ - 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 - */ - 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} - */ - 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 - */ - 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} - */ - 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 - */ - 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 - */ - 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, 2: list, 3: list} - */ - 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, 2: list, 3: list} - */ - 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; - } -} diff --git a/vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php b/vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php deleted file mode 100644 index 0727b1f..0000000 --- a/vendor/doctrine/orm/src/Persisters/Collection/OneToManyPersister.php +++ /dev/null @@ -1,264 +0,0 @@ -getMapping($collection); - - if (! $mapping->orphanRemoval) { - // Handling non-orphan removal should never happen, as @OneToMany - // can only be inverse side. For owning side one to many, it is - // required to have a join table, which would classify as a ManyToManyPersister. - return; - } - - $targetClass = $this->em->getClassMetadata($mapping->targetEntity); - - $targetClass->isInheritanceTypeJoined() - ? $this->deleteJoinedEntityCollection($collection) - : $this->deleteEntityCollection($collection); - } - - public function update(PersistentCollection $collection): void - { - // This can never happen. One to many can only be inverse side. - // For owning side one to many, it is required to have a join table, - // then classifying it as a ManyToManyPersister. - return; - } - - 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); - - return $persister->load( - [ - $mapping->mappedBy => $collection->getOwner(), - $mapping->indexBy() => $index, - ], - null, - $mapping, - [], - null, - 1, - ); - } - - public function count(PersistentCollection $collection): int - { - $mapping = $this->getMapping($collection); - $persister = $this->uow->getEntityPersister($mapping->targetEntity); - - // only works with single id identifier entities. Will throw an - // exception in Entity Persisters if that is not the case for the - // 'mappedBy' field. - $criteria = new Criteria(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner())); - - return $persister->count($criteria); - } - - /** - * {@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->getOneToManyCollection($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.'); - } - - $persister = $this->uow->getEntityPersister($mapping->targetEntity); - - // only works with single id identifier entities. Will throw an - // exception in Entity Persisters if that is not the case for the - // 'mappedBy' field. - $criteria = new Criteria(); - - $criteria->andWhere(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner())); - $criteria->andWhere(Criteria::expr()->eq($mapping->indexBy(), $key)); - - return (bool) $persister->count($criteria); - } - - public function contains(PersistentCollection $collection, object $element): bool - { - if (! $this->isValidEntityState($element)) { - return false; - } - - $mapping = $this->getMapping($collection); - $persister = $this->uow->getEntityPersister($mapping->targetEntity); - - // only works with single id identifier entities. Will throw an - // exception in Entity Persisters if that is not the case for the - // 'mappedBy' field. - $criteria = new Criteria(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner())); - - return $persister->exists($element, $criteria); - } - - /** - * {@inheritDoc} - */ - public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array - { - throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.'); - } - - /** - * @throws DBALException - * @throws EntityNotFoundException - * @throws MappingException - */ - private function deleteEntityCollection(PersistentCollection $collection): int - { - $mapping = $this->getMapping($collection); - $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); - $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); - $targetClass = $this->em->getClassMetadata($mapping->targetEntity); - $columns = []; - $parameters = []; - $types = []; - - foreach ($this->em->getMetadataFactory()->getOwningSide($mapping)->joinColumns as $joinColumn) { - $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); - $parameters[] = $identifier[$sourceClass->getFieldForColumn($joinColumn->referencedColumnName)]; - $types[] = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $sourceClass, $this->em); - } - - $statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) - . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; - - if ($targetClass->isInheritanceTypeSingleTable()) { - $discriminatorColumn = $targetClass->getDiscriminatorColumn(); - $statement .= ' AND ' . $discriminatorColumn->name . ' = ?'; - $parameters[] = $targetClass->discriminatorValue; - $types[] = $discriminatorColumn->type; - } - - $numAffected = $this->conn->executeStatement($statement, $parameters, $types); - - assert(is_int($numAffected)); - - return $numAffected; - } - - /** - * Delete Class Table Inheritance entities. - * A temporary table is needed to keep IDs to be deleted in both parent and child class' tables. - * - * Thanks Steve Ebersole (Hibernate) for idea on how to tackle reliably this scenario, we owe him a beer! =) - * - * @throws DBALException - */ - private function deleteJoinedEntityCollection(PersistentCollection $collection): int - { - $mapping = $this->getMapping($collection); - $sourceClass = $this->em->getClassMetadata($mapping->sourceEntity); - $targetClass = $this->em->getClassMetadata($mapping->targetEntity); - $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); - - // 1) Build temporary table DDL - $tempTable = $this->platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); - $idColumnNames = $rootClass->getIdentifierColumnNames(); - $idColumnList = implode(', ', $idColumnNames); - $columnDefinitions = []; - - foreach ($idColumnNames as $idColumnName) { - $columnDefinitions[$idColumnName] = [ - 'name' => $idColumnName, - 'notnull' => true, - 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $this->em)), - ]; - } - - $statement = $this->platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable - . ' (' . $this->platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; - - $this->conn->executeStatement($statement); - - // 2) Build insert table records into temporary table - $query = $this->em->createQuery( - ' SELECT t0.' . implode(', t0.', $rootClass->getIdentifierFieldNames()) - . ' FROM ' . $targetClass->name . ' t0 WHERE t0.' . $mapping->mappedBy . ' = :owner', - )->setParameter('owner', $collection->getOwner()); - - $sql = $query->getSQL(); - assert(is_string($sql)); - $statement = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ') ' . $sql; - $parameters = array_values($sourceClass->getIdentifierValues($collection->getOwner())); - $numDeleted = $this->conn->executeStatement($statement, $parameters); - - // 3) Delete records on each table in the hierarchy - $classNames = [...$targetClass->parentClasses, ...[$targetClass->name], ...$targetClass->subClasses]; - - foreach (array_reverse($classNames) as $className) { - $tableName = $this->quoteStrategy->getTableName($this->em->getClassMetadata($className), $this->platform); - $statement = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ')' - . ' IN (SELECT ' . $idColumnList . ' FROM ' . $tempTable . ')'; - - $this->conn->executeStatement($statement); - } - - // 4) Drop temporary table - $statement = $this->platform->getDropTemporaryTableSQL($tempTable); - - $this->conn->executeStatement($statement); - - assert(is_int($numDeleted)); - - return $numDeleted; - } - - private function getMapping(PersistentCollection $collection): OneToManyAssociationMapping - { - $mapping = $collection->getMapping(); - - assert($mapping->isOneToMany()); - - return $mapping; - } -} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/AbstractEntityInheritancePersister.php b/vendor/doctrine/orm/src/Persisters/Entity/AbstractEntityInheritancePersister.php deleted file mode 100644 index cf8a74e..0000000 --- a/vendor/doctrine/orm/src/Persisters/Entity/AbstractEntityInheritancePersister.php +++ /dev/null @@ -1,66 +0,0 @@ -class->getDiscriminatorColumn(); - $this->columnTypes[$discColumn->name] = $discColumn->type; - $data[$this->getDiscriminatorColumnTableName()][$discColumn->name] = $this->class->discriminatorValue; - - return $data; - } - - /** - * Gets the name of the table that contains the discriminator column. - */ - abstract protected function getDiscriminatorColumnTableName(): string; - - protected function getSelectColumnSQL(string $field, ClassMetadata $class, string $alias = 'r'): string - { - $tableAlias = $alias === 'r' ? '' : $alias; - $fieldMapping = $class->fieldMappings[$field]; - $columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName); - $sql = sprintf( - '%s.%s', - $this->getSQLTableAlias($class->name, $tableAlias), - $this->quoteStrategy->getColumnName($field, $class, $this->platform), - ); - - $this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field, $class->name); - - $type = Type::getType($fieldMapping->type); - $sql = $type->convertToPHPValueSQL($sql, $this->platform); - - return $sql . ' AS ' . $columnAlias; - } - - protected function getSelectJoinColumnSQL(string $tableAlias, string $joinColumnName, string $quotedColumnName, string $type): string - { - $columnAlias = $this->getSQLColumnAlias($joinColumnName); - - $this->currentPersisterContext->rsm->addMetaResult('r', $columnAlias, $joinColumnName, false, $type); - - return $tableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias; - } -} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php b/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php deleted file mode 100644 index 377e03c..0000000 --- a/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php +++ /dev/null @@ -1,2085 +0,0 @@ - */ - private static array $comparisonMap = [ - Comparison::EQ => '= %s', - Comparison::NEQ => '!= %s', - Comparison::GT => '> %s', - Comparison::GTE => '>= %s', - Comparison::LT => '< %s', - Comparison::LTE => '<= %s', - Comparison::IN => 'IN (%s)', - Comparison::NIN => 'NOT IN (%s)', - Comparison::CONTAINS => 'LIKE %s', - Comparison::STARTS_WITH => 'LIKE %s', - Comparison::ENDS_WITH => 'LIKE %s', - ]; - - /** - * The underlying DBAL Connection of the used EntityManager. - */ - protected Connection $conn; - - /** - * The database platform. - */ - protected AbstractPlatform $platform; - - /** - * Queued inserts. - * - * @psalm-var array - */ - protected array $queuedInserts = []; - - /** - * The map of column names to DBAL mapping types of all prepared columns used - * when INSERTing or UPDATEing an entity. - * - * @see prepareInsertData($entity) - * @see prepareUpdateData($entity) - * - * @var mixed[] - */ - protected array $columnTypes = []; - - /** - * The map of quoted column names. - * - * @see prepareInsertData($entity) - * @see prepareUpdateData($entity) - * - * @var mixed[] - */ - protected array $quotedColumns = []; - - /** - * The INSERT SQL statement used for entities handled by this persister. - * This SQL is only generated once per request, if at all. - */ - private string|null $insertSql = null; - - /** - * The quote strategy. - */ - protected QuoteStrategy $quoteStrategy; - - /** - * The IdentifierFlattener used for manipulating identifiers - */ - protected readonly IdentifierFlattener $identifierFlattener; - - protected CachedPersisterContext $currentPersisterContext; - private readonly CachedPersisterContext $limitsHandlingContext; - private readonly CachedPersisterContext $noLimitsContext; - - /** - * Initializes a new BasicEntityPersister that uses the given EntityManager - * and persists instances of the class described by the given ClassMetadata descriptor. - * - * @param ClassMetadata $class Metadata object that describes the mapping of the mapped entity class. - */ - public function __construct( - protected EntityManagerInterface $em, - protected ClassMetadata $class, - ) { - $this->conn = $em->getConnection(); - $this->platform = $this->conn->getDatabasePlatform(); - $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); - $this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory()); - $this->noLimitsContext = $this->currentPersisterContext = new CachedPersisterContext( - $class, - new Query\ResultSetMapping(), - false, - ); - $this->limitsHandlingContext = new CachedPersisterContext( - $class, - new Query\ResultSetMapping(), - true, - ); - } - - public function getClassMetadata(): ClassMetadata - { - return $this->class; - } - - public function getResultSetMapping(): ResultSetMapping - { - return $this->currentPersisterContext->rsm; - } - - public function addInsert(object $entity): void - { - $this->queuedInserts[spl_object_id($entity)] = $entity; - } - - /** - * {@inheritDoc} - */ - public function getInserts(): array - { - return $this->queuedInserts; - } - - public function executeInserts(): void - { - if (! $this->queuedInserts) { - return; - } - - $uow = $this->em->getUnitOfWork(); - $idGenerator = $this->class->idGenerator; - $isPostInsertId = $idGenerator->isPostInsertGenerator(); - - $stmt = $this->conn->prepare($this->getInsertSQL()); - $tableName = $this->class->getTableName(); - - foreach ($this->queuedInserts as $key => $entity) { - $insertData = $this->prepareInsertData($entity); - - if (isset($insertData[$tableName])) { - $paramIndex = 1; - - foreach ($insertData[$tableName] as $column => $value) { - $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$column]); - } - } - - $stmt->executeStatement(); - - if ($isPostInsertId) { - $generatedId = $idGenerator->generateId($this->em, $entity); - $id = [$this->class->identifier[0] => $generatedId]; - - $uow->assignPostInsertId($entity, $generatedId); - } else { - $id = $this->class->getIdentifierValues($entity); - } - - if ($this->class->requiresFetchAfterChange) { - $this->assignDefaultVersionAndUpsertableValues($entity, $id); - } - - // Unset this queued insert, so that the prepareUpdateData() method knows right away - // (for the next entity already) that the current entity has been written to the database - // and no extra updates need to be scheduled to refer to it. - // - // In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities - // from its own list (\Doctrine\ORM\UnitOfWork::$entityInsertions) right after they - // were given to our addInsert() method. - unset($this->queuedInserts[$key]); - } - } - - /** - * Retrieves the default version value which was created - * by the preceding INSERT statement and assigns it back in to the - * entities version field if the given entity is versioned. - * Also retrieves values of columns marked as 'non insertable' and / or - * 'not updatable' and assigns them back to the entities corresponding fields. - * - * @param mixed[] $id - */ - protected function assignDefaultVersionAndUpsertableValues(object $entity, array $id): void - { - $values = $this->fetchVersionAndNotUpsertableValues($this->class, $id); - - foreach ($values as $field => $value) { - $value = Type::getType($this->class->fieldMappings[$field]->type)->convertToPHPValue($value, $this->platform); - - $this->class->setFieldValue($entity, $field, $value); - } - } - - /** - * Fetches the current version value of a versioned entity and / or the values of fields - * marked as 'not insertable' and / or 'not updatable'. - * - * @param mixed[] $id - */ - protected function fetchVersionAndNotUpsertableValues(ClassMetadata $versionedClass, array $id): mixed - { - $columnNames = []; - foreach ($this->class->fieldMappings as $key => $column) { - if (isset($column->generated) || ($this->class->isVersioned && $key === $versionedClass->versionField)) { - $columnNames[$key] = $this->quoteStrategy->getColumnName($key, $versionedClass, $this->platform); - } - } - - $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); - $identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform); - - // FIXME: Order with composite keys might not be correct - $sql = 'SELECT ' . implode(', ', $columnNames) - . ' FROM ' . $tableName - . ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?'; - - $flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id); - - $values = $this->conn->fetchNumeric( - $sql, - array_values($flatId), - $this->extractIdentifierTypes($id, $versionedClass), - ); - - if ($values === false) { - throw new LengthException('Unexpected empty result for database query.'); - } - - $values = array_combine(array_keys($columnNames), $values); - - if (! $values) { - throw new LengthException('Unexpected number of database columns.'); - } - - return $values; - } - - /** - * @param mixed[] $id - * - * @return list - * @psalm-return list - */ - final protected function extractIdentifierTypes(array $id, ClassMetadata $versionedClass): array - { - $types = []; - - foreach ($id as $field => $value) { - $types = [...$types, ...$this->getTypes($field, $value, $versionedClass)]; - } - - return $types; - } - - public function update(object $entity): void - { - $tableName = $this->class->getTableName(); - $updateData = $this->prepareUpdateData($entity); - - if (! isset($updateData[$tableName])) { - return; - } - - $data = $updateData[$tableName]; - - if (! $data) { - return; - } - - $isVersioned = $this->class->isVersioned; - $quotedTableName = $this->quoteStrategy->getTableName($this->class, $this->platform); - - $this->updateTable($entity, $quotedTableName, $data, $isVersioned); - - if ($this->class->requiresFetchAfterChange) { - $id = $this->class->getIdentifierValues($entity); - - $this->assignDefaultVersionAndUpsertableValues($entity, $id); - } - } - - /** - * Performs an UPDATE statement for an entity on a specific table. - * The UPDATE can optionally be versioned, which requires the entity to have a version field. - * - * @param object $entity The entity object being updated. - * @param string $quotedTableName The quoted name of the table to apply the UPDATE on. - * @param mixed[] $updateData The map of columns to update (column => value). - * @param bool $versioned Whether the UPDATE should be versioned. - * - * @throws UnrecognizedField - * @throws OptimisticLockException - */ - final protected function updateTable( - object $entity, - string $quotedTableName, - array $updateData, - bool $versioned = false, - ): void { - $set = []; - $types = []; - $params = []; - - foreach ($updateData as $columnName => $value) { - $placeholder = '?'; - $column = $columnName; - - switch (true) { - case isset($this->class->fieldNames[$columnName]): - $fieldName = $this->class->fieldNames[$columnName]; - $column = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); - - if (isset($this->class->fieldMappings[$fieldName])) { - $type = Type::getType($this->columnTypes[$columnName]); - $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); - } - - break; - - case isset($this->quotedColumns[$columnName]): - $column = $this->quotedColumns[$columnName]; - - break; - } - - $params[] = $value; - $set[] = $column . ' = ' . $placeholder; - $types[] = $this->columnTypes[$columnName]; - } - - $where = []; - $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); - - foreach ($this->class->identifier as $idField) { - if (! isset($this->class->associationMappings[$idField])) { - $params[] = $identifier[$idField]; - $types[] = $this->class->fieldMappings[$idField]->type; - $where[] = $this->quoteStrategy->getColumnName($idField, $this->class, $this->platform); - - continue; - } - - assert($this->class->associationMappings[$idField]->isToOneOwningSide()); - - $params[] = $identifier[$idField]; - $where[] = $this->quoteStrategy->getJoinColumnName( - $this->class->associationMappings[$idField]->joinColumns[0], - $this->class, - $this->platform, - ); - - $targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]->targetEntity); - $targetType = PersisterHelper::getTypeOfField($targetMapping->identifier[0], $targetMapping, $this->em); - - if ($targetType === []) { - throw UnrecognizedField::byFullyQualifiedName($this->class->name, $targetMapping->identifier[0]); - } - - $types[] = reset($targetType); - } - - if ($versioned) { - $versionField = $this->class->versionField; - assert($versionField !== null); - $versionFieldType = $this->class->fieldMappings[$versionField]->type; - $versionColumn = $this->quoteStrategy->getColumnName($versionField, $this->class, $this->platform); - - $where[] = $versionColumn; - $types[] = $this->class->fieldMappings[$versionField]->type; - $params[] = $this->class->reflFields[$versionField]->getValue($entity); - - switch ($versionFieldType) { - case Types::SMALLINT: - case Types::INTEGER: - case Types::BIGINT: - $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; - break; - - case Types::DATETIME_MUTABLE: - $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; - break; - } - } - - $sql = 'UPDATE ' . $quotedTableName - . ' SET ' . implode(', ', $set) - . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; - - $result = $this->conn->executeStatement($sql, $params, $types); - - if ($versioned && ! $result) { - throw OptimisticLockException::lockFailed($entity); - } - } - - /** - * @param array $identifier - * @param string[] $types - * - * @todo Add check for platform if it supports foreign keys/cascading. - */ - protected function deleteJoinTableRecords(array $identifier, array $types): void - { - foreach ($this->class->associationMappings as $mapping) { - if (! $mapping->isManyToMany() || $mapping->isOnDeleteCascade) { - continue; - } - - // @Todo this only covers scenarios with no inheritance or of the same level. Is there something - // like self-referential relationship between different levels of an inheritance hierarchy? I hope not! - $selfReferential = ($mapping->targetEntity === $mapping->sourceEntity); - $class = $this->class; - $association = $mapping; - $otherColumns = []; - $otherKeys = []; - $keys = []; - - if (! $mapping->isOwningSide()) { - $class = $this->em->getClassMetadata($mapping->targetEntity); - } - - $association = $this->em->getMetadataFactory()->getOwningSide($association); - $joinColumns = $mapping->isOwningSide() - ? $association->joinTable->joinColumns - : $association->joinTable->inverseJoinColumns; - - if ($selfReferential) { - $otherColumns = ! $mapping->isOwningSide() - ? $association->joinTable->joinColumns - : $association->joinTable->inverseJoinColumns; - } - - foreach ($joinColumns as $joinColumn) { - $keys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); - } - - foreach ($otherColumns as $joinColumn) { - $otherKeys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); - } - - $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); - - $this->conn->delete($joinTableName, array_combine($keys, $identifier), $types); - - if ($selfReferential) { - $this->conn->delete($joinTableName, array_combine($otherKeys, $identifier), $types); - } - } - } - - public function delete(object $entity): bool - { - $class = $this->class; - $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); - $tableName = $this->quoteStrategy->getTableName($class, $this->platform); - $idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform); - $id = array_combine($idColumns, $identifier); - $types = $this->getClassIdentifiersTypes($class); - - $this->deleteJoinTableRecords($identifier, $types); - - return (bool) $this->conn->delete($tableName, $id, $types); - } - - /** - * Prepares the changeset of an entity for database insertion (UPDATE). - * - * The changeset is obtained from the currently running UnitOfWork. - * - * During this preparation the array that is passed as the second parameter is filled with - * => pairs, grouped by table name. - * - * Example: - * - * array( - * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...), - * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...), - * ... - * ) - * - * - * @param object $entity The entity for which to prepare the data. - * @param bool $isInsert Whether the data to be prepared refers to an insert statement. - * - * @return mixed[][] The prepared data. - * @psalm-return array> - */ - protected function prepareUpdateData(object $entity, bool $isInsert = false): array - { - $versionField = null; - $result = []; - $uow = $this->em->getUnitOfWork(); - - $versioned = $this->class->isVersioned; - if ($versioned !== false) { - $versionField = $this->class->versionField; - } - - foreach ($uow->getEntityChangeSet($entity) as $field => $change) { - if (isset($versionField) && $versionField === $field) { - continue; - } - - if (isset($this->class->embeddedClasses[$field])) { - continue; - } - - $newVal = $change[1]; - - if (! isset($this->class->associationMappings[$field])) { - $fieldMapping = $this->class->fieldMappings[$field]; - $columnName = $fieldMapping->columnName; - - if (! $isInsert && isset($fieldMapping->notUpdatable)) { - continue; - } - - if ($isInsert && isset($fieldMapping->notInsertable)) { - continue; - } - - $this->columnTypes[$columnName] = $fieldMapping->type; - - $result[$this->getOwningTable($field)][$columnName] = $newVal; - - continue; - } - - $assoc = $this->class->associationMappings[$field]; - - // Only owning side of x-1 associations can have a FK column. - if (! $assoc->isToOneOwningSide()) { - continue; - } - - if ($newVal !== null) { - $oid = spl_object_id($newVal); - - // If the associated entity $newVal is not yet persisted and/or does not yet have - // an ID assigned, we must set $newVal = null. This will insert a null value and - // schedule an extra update on the UnitOfWork. - // - // This gives us extra time to a) possibly obtain a database-generated identifier - // value for $newVal, and b) insert $newVal into the database before the foreign - // key reference is being made. - // - // When looking at $this->queuedInserts and $uow->isScheduledForInsert, be aware - // of the implementation details that our own executeInserts() method will remove - // entities from the former as soon as the insert statement has been executed and - // a post-insert ID has been assigned (if necessary), and that the UnitOfWork has - // already removed entities from its own list at the time they were passed to our - // addInsert() method. - // - // Then, there is one extra exception we can make: An entity that references back to itself - // _and_ uses an application-provided ID (the "NONE" generator strategy) also does not - // need the extra update, although it is still in the list of insertions itself. - // This looks like a minor optimization at first, but is the capstone for being able to - // use non-NULLable, self-referencing associations in applications that provide IDs (like UUIDs). - if ( - (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) - && ! ($newVal === $entity && $this->class->isIdentifierNatural()) - ) { - $uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]); - - $newVal = null; - } - } - - $newValId = null; - - if ($newVal !== null) { - $newValId = $uow->getEntityIdentifier($newVal); - } - - $targetClass = $this->em->getClassMetadata($assoc->targetEntity); - $owningTable = $this->getOwningTable($field); - - foreach ($assoc->joinColumns as $joinColumn) { - $sourceColumn = $joinColumn->name; - $targetColumn = $joinColumn->referencedColumnName; - $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); - - $this->quotedColumns[$sourceColumn] = $quotedColumn; - $this->columnTypes[$sourceColumn] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em); - $result[$owningTable][$sourceColumn] = $newValId - ? $newValId[$targetClass->getFieldForColumn($targetColumn)] - : null; - } - } - - return $result; - } - - /** - * Prepares the data changeset of a managed entity for database insertion (initial INSERT). - * The changeset of the entity is obtained from the currently running UnitOfWork. - * - * The default insert data preparation is the same as for updates. - * - * @see prepareUpdateData - * - * @param object $entity The entity for which to prepare the data. - * - * @return mixed[][] The prepared data for the tables to update. - * @psalm-return array - */ - protected function prepareInsertData(object $entity): array - { - return $this->prepareUpdateData($entity, true); - } - - public function getOwningTable(string $fieldName): string - { - return $this->class->getTableName(); - } - - /** - * {@inheritDoc} - */ - public function load( - array $criteria, - object|null $entity = null, - AssociationMapping|null $assoc = null, - array $hints = [], - LockMode|int|null $lockMode = null, - int|null $limit = null, - array|null $orderBy = null, - ): object|null { - $this->switchPersisterContext(null, $limit); - - $sql = $this->getSelectSQL($criteria, $assoc, $lockMode, $limit, null, $orderBy); - [$params, $types] = $this->expandParameters($criteria); - $stmt = $this->conn->executeQuery($sql, $params, $types); - - if ($entity !== null) { - $hints[Query::HINT_REFRESH] = true; - $hints[Query::HINT_REFRESH_ENTITY] = $entity; - } - - $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); - $entities = $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, $hints); - - return $entities ? $entities[0] : null; - } - - /** - * {@inheritDoc} - */ - public function loadById(array $identifier, object|null $entity = null): object|null - { - return $this->load($identifier, $entity); - } - - /** - * {@inheritDoc} - */ - public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null - { - $foundEntity = $this->em->getUnitOfWork()->tryGetById($identifier, $assoc->targetEntity); - if ($foundEntity !== false) { - return $foundEntity; - } - - $targetClass = $this->em->getClassMetadata($assoc->targetEntity); - - if ($assoc->isOwningSide()) { - $isInverseSingleValued = $assoc->inversedBy !== null && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy); - - // Mark inverse side as fetched in the hints, otherwise the UoW would - // try to load it in a separate query (remember: to-one inverse sides can not be lazy). - $hints = []; - - if ($isInverseSingleValued) { - $hints['fetched']['r'][$assoc->inversedBy] = true; - } - - $targetEntity = $this->load($identifier, null, $assoc, $hints); - - // Complete bidirectional association, if necessary - if ($targetEntity !== null && $isInverseSingleValued) { - $targetClass->reflFields[$assoc->inversedBy]->setValue($targetEntity, $sourceEntity); - } - - return $targetEntity; - } - - assert(isset($assoc->mappedBy)); - $sourceClass = $this->em->getClassMetadata($assoc->sourceEntity); - $owningAssoc = $targetClass->getAssociationMapping($assoc->mappedBy); - assert($owningAssoc->isOneToOneOwningSide()); - - $computedIdentifier = []; - - // TRICKY: since the association is specular source and target are flipped - foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { - if (! isset($sourceClass->fieldNames[$sourceKeyColumn])) { - throw MappingException::joinColumnMustPointToMappedField( - $sourceClass->name, - $sourceKeyColumn, - ); - } - - $computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] = - $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - } - - $targetEntity = $this->load($computedIdentifier, null, $assoc); - - if ($targetEntity !== null) { - $targetClass->setFieldValue($targetEntity, $assoc->mappedBy, $sourceEntity); - } - - return $targetEntity; - } - - /** - * {@inheritDoc} - */ - public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void - { - $sql = $this->getSelectSQL($id, null, $lockMode); - [$params, $types] = $this->expandParameters($id); - $stmt = $this->conn->executeQuery($sql, $params, $types); - - $hydrator = $this->em->newHydrator(Query::HYDRATE_OBJECT); - $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [Query::HINT_REFRESH => true]); - } - - public function count(array|Criteria $criteria = []): int - { - $sql = $this->getCountSQL($criteria); - - [$params, $types] = $criteria instanceof Criteria - ? $this->expandCriteriaParameters($criteria) - : $this->expandParameters($criteria); - - return (int) $this->conn->executeQuery($sql, $params, $types)->fetchOne(); - } - - /** - * {@inheritDoc} - */ - public function loadCriteria(Criteria $criteria): array - { - $orderBy = array_map( - static fn (Order $order): string => $order->value, - $criteria->orderings(), - ); - $limit = $criteria->getMaxResults(); - $offset = $criteria->getFirstResult(); - $query = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); - - [$params, $types] = $this->expandCriteriaParameters($criteria); - - $stmt = $this->conn->executeQuery($query, $params, $types); - $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); - - return $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [UnitOfWork::HINT_DEFEREAGERLOAD => true]); - } - - /** - * {@inheritDoc} - */ - public function expandCriteriaParameters(Criteria $criteria): array - { - $expression = $criteria->getWhereExpression(); - $sqlParams = []; - $sqlTypes = []; - - if ($expression === null) { - return [$sqlParams, $sqlTypes]; - } - - $valueVisitor = new SqlValueVisitor(); - - $valueVisitor->dispatch($expression); - - [, $types] = $valueVisitor->getParamsAndTypes(); - - foreach ($types as $type) { - [$field, $value, $operator] = $type; - - if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) { - continue; - } - - $sqlParams = [...$sqlParams, ...$this->getValues($value)]; - $sqlTypes = [...$sqlTypes, ...$this->getTypes($field, $value, $this->class)]; - } - - return [$sqlParams, $sqlTypes]; - } - - /** - * {@inheritDoc} - */ - public function loadAll( - array $criteria = [], - array|null $orderBy = null, - int|null $limit = null, - int|null $offset = null, - ): array { - $this->switchPersisterContext($offset, $limit); - - $sql = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); - [$params, $types] = $this->expandParameters($criteria); - $stmt = $this->conn->executeQuery($sql, $params, $types); - - $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); - - return $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [UnitOfWork::HINT_DEFEREAGERLOAD => true]); - } - - /** - * {@inheritDoc} - */ - public function getManyToManyCollection( - AssociationMapping $assoc, - object $sourceEntity, - int|null $offset = null, - int|null $limit = null, - ): array { - assert($assoc->isManyToMany()); - $this->switchPersisterContext($offset, $limit); - - $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); - - return $this->loadArrayFromResult($assoc, $stmt); - } - - /** - * Loads an array of entities from a given DBAL statement. - * - * @return mixed[] - */ - private function loadArrayFromResult(AssociationMapping $assoc, Result $stmt): array - { - $rsm = $this->currentPersisterContext->rsm; - $hints = [UnitOfWork::HINT_DEFEREAGERLOAD => true]; - - if ($assoc->isIndexed()) { - $rsm = clone $this->currentPersisterContext->rsm; // this is necessary because the "default rsm" should be changed. - $rsm->addIndexBy('r', $assoc->indexBy()); - } - - return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); - } - - /** - * Hydrates a collection from a given DBAL statement. - * - * @return mixed[] - */ - private function loadCollectionFromStatement( - AssociationMapping $assoc, - Result $stmt, - PersistentCollection $coll, - ): array { - $rsm = $this->currentPersisterContext->rsm; - $hints = [ - UnitOfWork::HINT_DEFEREAGERLOAD => true, - 'collection' => $coll, - ]; - - if ($assoc->isIndexed()) { - $rsm = clone $this->currentPersisterContext->rsm; // this is necessary because the "default rsm" should be changed. - $rsm->addIndexBy('r', $assoc->indexBy()); - } - - return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); - } - - /** - * {@inheritDoc} - */ - public function loadManyToManyCollection(AssociationMapping $assoc, object $sourceEntity, PersistentCollection $collection): array - { - assert($assoc->isManyToMany()); - $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); - - return $this->loadCollectionFromStatement($assoc, $stmt, $collection); - } - - /** @throws MappingException */ - private function getManyToManyStatement( - AssociationMapping&ManyToManyAssociationMapping $assoc, - object $sourceEntity, - int|null $offset = null, - int|null $limit = null, - ): Result { - $this->switchPersisterContext($offset, $limit); - - $sourceClass = $this->em->getClassMetadata($assoc->sourceEntity); - $class = $sourceClass; - $association = $assoc; - $criteria = []; - $parameters = []; - - if (! $assoc->isOwningSide()) { - $class = $this->em->getClassMetadata($assoc->targetEntity); - } - - $association = $this->em->getMetadataFactory()->getOwningSide($assoc); - $joinColumns = $assoc->isOwningSide() - ? $association->joinTable->joinColumns - : $association->joinTable->inverseJoinColumns; - - $quotedJoinTable = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); - - foreach ($joinColumns as $joinColumn) { - $sourceKeyColumn = $joinColumn->referencedColumnName; - $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); - - switch (true) { - case $sourceClass->containsForeignIdentifier: - $field = $sourceClass->getFieldForColumn($sourceKeyColumn); - $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - - if (isset($sourceClass->associationMappings[$field])) { - $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); - $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]->targetEntity)->identifier[0]]; - } - - break; - - case isset($sourceClass->fieldNames[$sourceKeyColumn]): - $field = $sourceClass->fieldNames[$sourceKeyColumn]; - $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - - break; - - default: - throw MappingException::joinColumnMustPointToMappedField( - $sourceClass->name, - $sourceKeyColumn, - ); - } - - $criteria[$quotedJoinTable . '.' . $quotedKeyColumn] = $value; - $parameters[] = [ - 'value' => $value, - 'field' => $field, - 'class' => $sourceClass, - ]; - } - - $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); - [$params, $types] = $this->expandToManyParameters($parameters); - - return $this->conn->executeQuery($sql, $params, $types); - } - - public function getSelectSQL( - array|Criteria $criteria, - AssociationMapping|null $assoc = null, - LockMode|int|null $lockMode = null, - int|null $limit = null, - int|null $offset = null, - array|null $orderBy = null, - ): string { - $this->switchPersisterContext($offset, $limit); - - $joinSql = ''; - $orderBySql = ''; - - if ($assoc !== null && $assoc->isManyToMany()) { - $joinSql = $this->getSelectManyToManyJoinSQL($assoc); - } - - if ($assoc !== null && $assoc->isOrdered()) { - $orderBy = $assoc->orderBy(); - } - - if ($orderBy) { - $orderBySql = $this->getOrderBySQL($orderBy, $this->getSQLTableAlias($this->class->name)); - } - - $conditionSql = $criteria instanceof Criteria - ? $this->getSelectConditionCriteriaSQL($criteria) - : $this->getSelectConditionSQL($criteria, $assoc); - - $lockSql = match ($lockMode) { - LockMode::PESSIMISTIC_READ => ' ' . $this->getReadLockSQL($this->platform), - LockMode::PESSIMISTIC_WRITE => ' ' . $this->getWriteLockSQL($this->platform), - default => '', - }; - - $columnList = $this->getSelectColumnsSQL(); - $tableAlias = $this->getSQLTableAlias($this->class->name); - $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); - $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); - - if ($filterSql !== '') { - $conditionSql = $conditionSql - ? $conditionSql . ' AND ' . $filterSql - : $filterSql; - } - - $select = 'SELECT ' . $columnList; - $from = ' FROM ' . $tableName . ' ' . $tableAlias; - $join = $this->currentPersisterContext->selectJoinSql . $joinSql; - $where = ($conditionSql ? ' WHERE ' . $conditionSql : ''); - $lock = $this->platform->appendLockHint($from, $lockMode ?? LockMode::NONE); - $query = $select - . $lock - . $join - . $where - . $orderBySql; - - return $this->platform->modifyLimitQuery($query, $limit, $offset ?? 0) . $lockSql; - } - - public function getCountSQL(array|Criteria $criteria = []): string - { - $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); - $tableAlias = $this->getSQLTableAlias($this->class->name); - - $conditionSql = $criteria instanceof Criteria - ? $this->getSelectConditionCriteriaSQL($criteria) - : $this->getSelectConditionSQL($criteria); - - $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); - - if ($filterSql !== '') { - $conditionSql = $conditionSql - ? $conditionSql . ' AND ' . $filterSql - : $filterSql; - } - - return 'SELECT COUNT(*) ' - . 'FROM ' . $tableName . ' ' . $tableAlias - . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql); - } - - /** - * Gets the ORDER BY SQL snippet for ordered collections. - * - * @psalm-param array $orderBy - * - * @throws InvalidOrientation - * @throws InvalidFindByCall - * @throws UnrecognizedField - */ - final protected function getOrderBySQL(array $orderBy, string $baseTableAlias): string - { - $orderByList = []; - - foreach ($orderBy as $fieldName => $orientation) { - $orientation = strtoupper(trim($orientation)); - - if ($orientation !== 'ASC' && $orientation !== 'DESC') { - throw InvalidOrientation::fromClassNameAndField($this->class->name, $fieldName); - } - - if (isset($this->class->fieldMappings[$fieldName])) { - $tableAlias = isset($this->class->fieldMappings[$fieldName]->inherited) - ? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]->inherited) - : $baseTableAlias; - - $columnName = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); - $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; - - continue; - } - - if (isset($this->class->associationMappings[$fieldName])) { - $association = $this->class->associationMappings[$fieldName]; - if (! $association->isOwningSide()) { - throw InvalidFindByCall::fromInverseSideUsage($this->class->name, $fieldName); - } - - assert($association->isToOneOwningSide()); - - $tableAlias = isset($association->inherited) - ? $this->getSQLTableAlias($association->inherited) - : $baseTableAlias; - - foreach ($association->joinColumns as $joinColumn) { - $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); - $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; - } - - continue; - } - - throw UnrecognizedField::byFullyQualifiedName($this->class->name, $fieldName); - } - - return ' ORDER BY ' . implode(', ', $orderByList); - } - - /** - * Gets the SQL fragment with the list of columns to select when querying for - * an entity in this persister. - * - * Subclasses should override this method to alter or change the select column - * list SQL fragment. Note that in the implementation of BasicEntityPersister - * the resulting SQL fragment is generated only once and cached in {@link selectColumnListSql}. - * Subclasses may or may not do the same. - */ - protected function getSelectColumnsSQL(): string - { - if ($this->currentPersisterContext->selectColumnListSql !== null) { - return $this->currentPersisterContext->selectColumnListSql; - } - - $columnList = []; - $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r'); // r for root - - // Add regular columns to select list - foreach ($this->class->fieldNames as $field) { - $columnList[] = $this->getSelectColumnSQL($field, $this->class); - } - - $this->currentPersisterContext->selectJoinSql = ''; - $eagerAliasCounter = 0; - - foreach ($this->class->associationMappings as $assocField => $assoc) { - $assocColumnSQL = $this->getSelectColumnAssociationSQL($assocField, $assoc, $this->class); - - if ($assocColumnSQL) { - $columnList[] = $assocColumnSQL; - } - - $isAssocToOneInverseSide = $assoc->isToOne() && ! $assoc->isOwningSide(); - $isAssocFromOneEager = $assoc->isToOne() && $assoc->fetch === ClassMetadata::FETCH_EAGER; - - if (! ($isAssocFromOneEager || $isAssocToOneInverseSide)) { - continue; - } - - if ($assoc->isToMany() && $this->currentPersisterContext->handlesLimits) { - continue; - } - - $eagerEntity = $this->em->getClassMetadata($assoc->targetEntity); - - if ($eagerEntity->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { - continue; // now this is why you shouldn't use inheritance - } - - $assocAlias = 'e' . ($eagerAliasCounter++); - $this->currentPersisterContext->rsm->addJoinedEntityResult($assoc->targetEntity, $assocAlias, 'r', $assocField); - - foreach ($eagerEntity->fieldNames as $field) { - $columnList[] = $this->getSelectColumnSQL($field, $eagerEntity, $assocAlias); - } - - foreach ($eagerEntity->associationMappings as $eagerAssocField => $eagerAssoc) { - $eagerAssocColumnSQL = $this->getSelectColumnAssociationSQL( - $eagerAssocField, - $eagerAssoc, - $eagerEntity, - $assocAlias, - ); - - if ($eagerAssocColumnSQL) { - $columnList[] = $eagerAssocColumnSQL; - } - } - - $association = $assoc; - $joinCondition = []; - - if ($assoc->isIndexed()) { - assert($assoc->isToMany()); - $this->currentPersisterContext->rsm->addIndexBy($assocAlias, $assoc->indexBy()); - } - - if (! $assoc->isOwningSide()) { - $eagerEntity = $this->em->getClassMetadata($assoc->targetEntity); - $association = $eagerEntity->getAssociationMapping($assoc->mappedBy); - } - - assert($association->isToOneOwningSide()); - - $joinTableAlias = $this->getSQLTableAlias($eagerEntity->name, $assocAlias); - $joinTableName = $this->quoteStrategy->getTableName($eagerEntity, $this->platform); - - if ($assoc->isOwningSide()) { - $tableAlias = $this->getSQLTableAlias($association->targetEntity, $assocAlias); - $this->currentPersisterContext->selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($association->joinColumns); - - foreach ($association->joinColumns as $joinColumn) { - $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); - $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); - $joinCondition[] = $this->getSQLTableAlias($association->sourceEntity) - . '.' . $sourceCol . ' = ' . $tableAlias . '.' . $targetCol; - } - - // Add filter SQL - $filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias); - if ($filterSql) { - $joinCondition[] = $filterSql; - } - } else { - $this->currentPersisterContext->selectJoinSql .= ' LEFT JOIN'; - - foreach ($association->joinColumns as $joinColumn) { - $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); - $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); - - $joinCondition[] = $this->getSQLTableAlias($association->sourceEntity, $assocAlias) . '.' . $sourceCol . ' = ' - . $this->getSQLTableAlias($association->targetEntity) . '.' . $targetCol; - } - } - - $this->currentPersisterContext->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON '; - $this->currentPersisterContext->selectJoinSql .= implode(' AND ', $joinCondition); - } - - $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); - - return $this->currentPersisterContext->selectColumnListSql; - } - - /** Gets the SQL join fragment used when selecting entities from an association. */ - protected function getSelectColumnAssociationSQL( - string $field, - AssociationMapping $assoc, - ClassMetadata $class, - string $alias = 'r', - ): string { - if (! $assoc->isToOneOwningSide()) { - return ''; - } - - $columnList = []; - $targetClass = $this->em->getClassMetadata($assoc->targetEntity); - $isIdentifier = isset($assoc->id) && $assoc->id === true; - $sqlTableAlias = $this->getSQLTableAlias($class->name, ($alias === 'r' ? '' : $alias)); - - foreach ($assoc->joinColumns as $joinColumn) { - $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); - $resultColumnName = $this->getSQLColumnAlias($joinColumn->name); - $type = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em); - - $this->currentPersisterContext->rsm->addMetaResult($alias, $resultColumnName, $joinColumn->name, $isIdentifier, $type); - - $columnList[] = sprintf('%s.%s AS %s', $sqlTableAlias, $quotedColumn, $resultColumnName); - } - - return implode(', ', $columnList); - } - - /** - * Gets the SQL join fragment used when selecting entities from a - * many-to-many association. - */ - protected function getSelectManyToManyJoinSQL(AssociationMapping&ManyToManyAssociationMapping $manyToMany): string - { - $conditions = []; - $association = $manyToMany; - $sourceTableAlias = $this->getSQLTableAlias($this->class->name); - - $association = $this->em->getMetadataFactory()->getOwningSide($manyToMany); - $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); - $joinColumns = $manyToMany->isOwningSide() - ? $association->joinTable->inverseJoinColumns - : $association->joinTable->joinColumns; - - foreach ($joinColumns as $joinColumn) { - $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); - $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); - $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableName . '.' . $quotedSourceColumn; - } - - return ' INNER JOIN ' . $joinTableName . ' ON ' . implode(' AND ', $conditions); - } - - public function getInsertSQL(): string - { - if ($this->insertSql !== null) { - return $this->insertSql; - } - - $columns = $this->getInsertColumnList(); - $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); - - if (empty($columns)) { - $identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform); - $this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn); - - return $this->insertSql; - } - - $values = []; - $columns = array_unique($columns); - - foreach ($columns as $column) { - $placeholder = '?'; - - if ( - isset($this->class->fieldNames[$column]) - && isset($this->columnTypes[$this->class->fieldNames[$column]]) - && isset($this->class->fieldMappings[$this->class->fieldNames[$column]]) - ) { - $type = Type::getType($this->columnTypes[$this->class->fieldNames[$column]]); - $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); - } - - $values[] = $placeholder; - } - - $columns = implode(', ', $columns); - $values = implode(', ', $values); - - $this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values); - - return $this->insertSql; - } - - /** - * Gets the list of columns to put in the INSERT SQL statement. - * - * Subclasses should override this method to alter or change the list of - * columns placed in the INSERT statements used by the persister. - * - * @psalm-return list - */ - protected function getInsertColumnList(): array - { - $columns = []; - - foreach ($this->class->reflFields as $name => $field) { - if ($this->class->isVersioned && $this->class->versionField === $name) { - continue; - } - - if (isset($this->class->embeddedClasses[$name])) { - continue; - } - - if (isset($this->class->associationMappings[$name])) { - $assoc = $this->class->associationMappings[$name]; - - if ($assoc->isToOneOwningSide()) { - foreach ($assoc->joinColumns as $joinColumn) { - $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); - } - } - - continue; - } - - if (! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] !== $name) { - if (isset($this->class->fieldMappings[$name]->notInsertable)) { - continue; - } - - $columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform); - $this->columnTypes[$name] = $this->class->fieldMappings[$name]->type; - } - } - - return $columns; - } - - /** - * Gets the SQL snippet of a qualified column name for the given field name. - * - * @param ClassMetadata $class The class that declares this field. The table this class is - * mapped to must own the column for the given field. - */ - protected function getSelectColumnSQL(string $field, ClassMetadata $class, string $alias = 'r'): string - { - $root = $alias === 'r' ? '' : $alias; - $tableAlias = $this->getSQLTableAlias($class->name, $root); - $fieldMapping = $class->fieldMappings[$field]; - $sql = sprintf('%s.%s', $tableAlias, $this->quoteStrategy->getColumnName($field, $class, $this->platform)); - $columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName); - - $this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field); - if (! empty($fieldMapping->enumType)) { - $this->currentPersisterContext->rsm->addEnumResult($columnAlias, $fieldMapping->enumType); - } - - $type = Type::getType($fieldMapping->type); - $sql = $type->convertToPHPValueSQL($sql, $this->platform); - - return $sql . ' AS ' . $columnAlias; - } - - /** - * Gets the SQL table alias for the given class name. - * - * @todo Reconsider. Binding table aliases to class names is not such a good idea. - */ - protected function getSQLTableAlias(string $className, string $assocName = ''): string - { - if ($assocName) { - $className .= '#' . $assocName; - } - - if (isset($this->currentPersisterContext->sqlTableAliases[$className])) { - return $this->currentPersisterContext->sqlTableAliases[$className]; - } - - $tableAlias = 't' . $this->currentPersisterContext->sqlAliasCounter++; - - $this->currentPersisterContext->sqlTableAliases[$className] = $tableAlias; - - return $tableAlias; - } - - /** - * {@inheritDoc} - */ - public function lock(array $criteria, LockMode|int $lockMode): void - { - $conditionSql = $this->getSelectConditionSQL($criteria); - - $lockSql = match ($lockMode) { - LockMode::PESSIMISTIC_READ => $this->getReadLockSQL($this->platform), - LockMode::PESSIMISTIC_WRITE => $this->getWriteLockSQL($this->platform), - default => '', - }; - - $lock = $this->getLockTablesSql($lockMode); - $where = ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' '; - $sql = 'SELECT 1 ' - . $lock - . $where - . $lockSql; - - [$params, $types] = $this->expandParameters($criteria); - - $this->conn->executeQuery($sql, $params, $types); - } - - /** - * Gets the FROM and optionally JOIN conditions to lock the entity managed by this persister. - * - * @psalm-param LockMode::* $lockMode - */ - protected function getLockTablesSql(LockMode|int $lockMode): string - { - return $this->platform->appendLockHint( - 'FROM ' - . $this->quoteStrategy->getTableName($this->class, $this->platform) . ' ' - . $this->getSQLTableAlias($this->class->name), - $lockMode, - ); - } - - /** - * Gets the Select Where Condition from a Criteria object. - */ - protected function getSelectConditionCriteriaSQL(Criteria $criteria): string - { - $expression = $criteria->getWhereExpression(); - - if ($expression === null) { - return ''; - } - - $visitor = new SqlExpressionVisitor($this, $this->class); - - return $visitor->dispatch($expression); - } - - public function getSelectConditionStatementSQL( - string $field, - mixed $value, - AssociationMapping|null $assoc = null, - string|null $comparison = null, - ): string { - $selectedColumns = []; - $columns = $this->getSelectConditionStatementColumnSQL($field, $assoc); - - if (count($columns) > 1 && $comparison === Comparison::IN) { - /* - * @todo try to support multi-column IN expressions. - * Example: (col1, col2) IN (('val1A', 'val2A'), ('val1B', 'val2B')) - */ - throw CantUseInOperatorOnCompositeKeys::create(); - } - - foreach ($columns as $column) { - $placeholder = '?'; - - if (isset($this->class->fieldMappings[$field])) { - $type = Type::getType($this->class->fieldMappings[$field]->type); - $placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->platform); - } - - if ($comparison !== null) { - // special case null value handling - if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) { - $selectedColumns[] = $column . ' IS NULL'; - - continue; - } - - if ($comparison === Comparison::NEQ && $value === null) { - $selectedColumns[] = $column . ' IS NOT NULL'; - - continue; - } - - $selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder); - - continue; - } - - if (is_array($value)) { - $in = sprintf('%s IN (%s)', $column, $placeholder); - - if (array_search(null, $value, true) !== false) { - $selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column); - - continue; - } - - $selectedColumns[] = $in; - - continue; - } - - if ($value === null) { - $selectedColumns[] = sprintf('%s IS NULL', $column); - - continue; - } - - $selectedColumns[] = sprintf('%s = %s', $column, $placeholder); - } - - return implode(' AND ', $selectedColumns); - } - - /** - * Builds the left-hand-side of a where condition statement. - * - * @return string[] - * @psalm-return list - * - * @throws InvalidFindByCall - * @throws UnrecognizedField - */ - private function getSelectConditionStatementColumnSQL( - string $field, - AssociationMapping|null $assoc = null, - ): array { - if (isset($this->class->fieldMappings[$field])) { - $className = $this->class->fieldMappings[$field]->inherited ?? $this->class->name; - - return [$this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform)]; - } - - if (isset($this->class->associationMappings[$field])) { - $association = $this->class->associationMappings[$field]; - // Many-To-Many requires join table check for joinColumn - $columns = []; - $class = $this->class; - - if ($association->isManyToMany()) { - assert($assoc !== null); - if (! $association->isOwningSide()) { - $association = $assoc; - } - - assert($association->isManyToManyOwningSide()); - - $joinTableName = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); - $joinColumns = $assoc->isOwningSide() - ? $association->joinTable->joinColumns - : $association->joinTable->inverseJoinColumns; - - foreach ($joinColumns as $joinColumn) { - $columns[] = $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); - } - } else { - if (! $association->isOwningSide()) { - throw InvalidFindByCall::fromInverseSideUsage( - $this->class->name, - $field, - ); - } - - assert($association->isToOneOwningSide()); - - $className = $association->inherited ?? $this->class->name; - - foreach ($association->joinColumns as $joinColumn) { - $columns[] = $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); - } - } - - return $columns; - } - - if ($assoc !== null && ! str_contains($field, ' ') && ! str_contains($field, '(')) { - // very careless developers could potentially open up this normally hidden api for userland attacks, - // therefore checking for spaces and function calls which are not allowed. - - // found a join column condition, not really a "field" - return [$field]; - } - - throw UnrecognizedField::byFullyQualifiedName($this->class->name, $field); - } - - /** - * Gets the conditional SQL fragment used in the WHERE clause when selecting - * entities in this persister. - * - * Subclasses are supposed to override this method if they intend to change - * or alter the criteria by which entities are selected. - * - * @psalm-param array $criteria - */ - protected function getSelectConditionSQL(array $criteria, AssociationMapping|null $assoc = null): string - { - $conditions = []; - - foreach ($criteria as $field => $value) { - $conditions[] = $this->getSelectConditionStatementSQL($field, $value, $assoc); - } - - return implode(' AND ', $conditions); - } - - /** - * {@inheritDoc} - */ - public function getOneToManyCollection( - AssociationMapping $assoc, - object $sourceEntity, - int|null $offset = null, - int|null $limit = null, - ): array { - assert($assoc instanceof OneToManyAssociationMapping); - $this->switchPersisterContext($offset, $limit); - - $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); - - return $this->loadArrayFromResult($assoc, $stmt); - } - - public function loadOneToManyCollection( - AssociationMapping $assoc, - object $sourceEntity, - PersistentCollection $collection, - ): mixed { - assert($assoc instanceof OneToManyAssociationMapping); - $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); - - return $this->loadCollectionFromStatement($assoc, $stmt, $collection); - } - - /** Builds criteria and execute SQL statement to fetch the one to many entities from. */ - private function getOneToManyStatement( - OneToManyAssociationMapping $assoc, - object $sourceEntity, - int|null $offset = null, - int|null $limit = null, - ): Result { - $this->switchPersisterContext($offset, $limit); - - $criteria = []; - $parameters = []; - $owningAssoc = $this->class->associationMappings[$assoc->mappedBy]; - $sourceClass = $this->em->getClassMetadata($assoc->sourceEntity); - $tableAlias = $this->getSQLTableAlias($owningAssoc->inherited ?? $this->class->name); - assert($owningAssoc->isManyToOne()); - - foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { - if ($sourceClass->containsForeignIdentifier) { - $field = $sourceClass->getFieldForColumn($sourceKeyColumn); - $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - - if (isset($sourceClass->associationMappings[$field])) { - $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); - $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]->targetEntity)->identifier[0]]; - } - - $criteria[$tableAlias . '.' . $targetKeyColumn] = $value; - $parameters[] = [ - 'value' => $value, - 'field' => $field, - 'class' => $sourceClass, - ]; - - continue; - } - - $field = $sourceClass->fieldNames[$sourceKeyColumn]; - $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - - $criteria[$tableAlias . '.' . $targetKeyColumn] = $value; - $parameters[] = [ - 'value' => $value, - 'field' => $field, - 'class' => $sourceClass, - ]; - } - - $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); - [$params, $types] = $this->expandToManyParameters($parameters); - - return $this->conn->executeQuery($sql, $params, $types); - } - - /** - * {@inheritDoc} - */ - public function expandParameters(array $criteria): array - { - $params = []; - $types = []; - - foreach ($criteria as $field => $value) { - if ($value === null) { - continue; // skip null values. - } - - $types = [...$types, ...$this->getTypes($field, $value, $this->class)]; - $params = array_merge($params, $this->getValues($value)); - } - - return [$params, $types]; - } - - /** - * Expands the parameters from the given criteria and use the correct binding types if found, - * specialized for OneToMany or ManyToMany associations. - * - * @param mixed[][] $criteria an array of arrays containing following: - * - field to which each criterion will be bound - * - value to be bound - * - class to which the field belongs to - * - * @return mixed[][] - * @psalm-return array{0: array, 1: list} - */ - private function expandToManyParameters(array $criteria): array - { - $params = []; - $types = []; - - foreach ($criteria as $criterion) { - if ($criterion['value'] === null) { - continue; // skip null values. - } - - $types = [...$types, ...$this->getTypes($criterion['field'], $criterion['value'], $criterion['class'])]; - $params = array_merge($params, $this->getValues($criterion['value'])); - } - - return [$params, $types]; - } - - /** - * Infers field types to be used by parameter type casting. - * - * @return list - * @psalm-return list - * - * @throws QueryException - */ - private function getTypes(string $field, mixed $value, ClassMetadata $class): array - { - $types = []; - - switch (true) { - case isset($class->fieldMappings[$field]): - $types = array_merge($types, [$class->fieldMappings[$field]->type]); - break; - - case isset($class->associationMappings[$field]): - $assoc = $this->em->getMetadataFactory()->getOwningSide($class->associationMappings[$field]); - $class = $this->em->getClassMetadata($assoc->targetEntity); - - if ($assoc->isManyToManyOwningSide()) { - $columns = $assoc->relationToTargetKeyColumns; - } else { - assert($assoc->isToOneOwningSide()); - $columns = $assoc->sourceToTargetKeyColumns; - } - - foreach ($columns as $column) { - $types[] = PersisterHelper::getTypeOfColumn($column, $class, $this->em); - } - - break; - - default: - $types[] = ParameterType::STRING; - break; - } - - if (is_array($value)) { - return array_map($this->getArrayBindingType(...), $types); - } - - return $types; - } - - /** @psalm-return ArrayParameterType::* */ - private function getArrayBindingType(ParameterType|int|string $type): ArrayParameterType|int - { - if (! $type instanceof ParameterType) { - $type = Type::getType((string) $type)->getBindingType(); - } - - return match ($type) { - ParameterType::STRING => ArrayParameterType::STRING, - ParameterType::INTEGER => ArrayParameterType::INTEGER, - ParameterType::ASCII => ArrayParameterType::ASCII, - }; - } - - /** - * Retrieves the parameters that identifies a value. - * - * @return mixed[] - */ - private function getValues(mixed $value): array - { - if (is_array($value)) { - $newValue = []; - - foreach ($value as $itemValue) { - $newValue = array_merge($newValue, $this->getValues($itemValue)); - } - - return [$newValue]; - } - - return $this->getIndividualValue($value); - } - - /** - * Retrieves an individual parameter value. - * - * @psalm-return list - */ - private function getIndividualValue(mixed $value): array - { - if (! is_object($value)) { - return [$value]; - } - - if ($value instanceof BackedEnum) { - return [$value->value]; - } - - $valueClass = DefaultProxyClassNameResolver::getClass($value); - - if ($this->em->getMetadataFactory()->isTransient($valueClass)) { - return [$value]; - } - - $class = $this->em->getClassMetadata($valueClass); - - if ($class->isIdentifierComposite) { - $newValue = []; - - foreach ($class->getIdentifierValues($value) as $innerValue) { - $newValue = array_merge($newValue, $this->getValues($innerValue)); - } - - return $newValue; - } - - return [$this->em->getUnitOfWork()->getSingleIdentifierValue($value)]; - } - - public function exists(object $entity, Criteria|null $extraConditions = null): bool - { - $criteria = $this->class->getIdentifierValues($entity); - - if (! $criteria) { - return false; - } - - $alias = $this->getSQLTableAlias($this->class->name); - - $sql = 'SELECT 1 ' - . $this->getLockTablesSql(LockMode::NONE) - . ' WHERE ' . $this->getSelectConditionSQL($criteria); - - [$params, $types] = $this->expandParameters($criteria); - - if ($extraConditions !== null) { - $sql .= ' AND ' . $this->getSelectConditionCriteriaSQL($extraConditions); - [$criteriaParams, $criteriaTypes] = $this->expandCriteriaParameters($extraConditions); - - $params = [...$params, ...$criteriaParams]; - $types = [...$types, ...$criteriaTypes]; - } - - $filterSql = $this->generateFilterConditionSQL($this->class, $alias); - if ($filterSql) { - $sql .= ' AND ' . $filterSql; - } - - return (bool) $this->conn->fetchOne($sql, $params, $types); - } - - /** - * Generates the appropriate join SQL for the given join column. - * - * @param list $joinColumns The join columns definition of an association. - * - * @return string LEFT JOIN if one of the columns is nullable, INNER JOIN otherwise. - */ - protected function getJoinSQLForJoinColumns(array $joinColumns): string - { - // if one of the join columns is nullable, return left join - foreach ($joinColumns as $joinColumn) { - if (! isset($joinColumn->nullable) || $joinColumn->nullable) { - return 'LEFT JOIN'; - } - } - - return 'INNER JOIN'; - } - - public function getSQLColumnAlias(string $columnName): string - { - return $this->quoteStrategy->getColumnAlias($columnName, $this->currentPersisterContext->sqlAliasCounter++, $this->platform); - } - - /** - * 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 . ')'; - } - } - - $sql = implode(' AND ', $filterClauses); - - return $sql ? '(' . $sql . ')' : ''; // Wrap again to avoid "X or Y and FilterConditionSQL" - } - - /** - * Switches persister context according to current query offset/limits - * - * This is due to the fact that to-many associations cannot be fetch-joined when a limit is involved - */ - protected function switchPersisterContext(int|null $offset, int|null $limit): void - { - if ($offset === null && $limit === null) { - $this->currentPersisterContext = $this->noLimitsContext; - - return; - } - - $this->currentPersisterContext = $this->limitsHandlingContext; - } - - /** - * @return string[] - * @psalm-return list - */ - protected function getClassIdentifiersTypes(ClassMetadata $class): array - { - $entityManager = $this->em; - - return array_map( - static function ($fieldName) use ($class, $entityManager): string { - $types = PersisterHelper::getTypeOfField($fieldName, $class, $entityManager); - assert(isset($types[0])); - - return $types[0]; - }, - $class->identifier, - ); - } -} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/CachedPersisterContext.php b/vendor/doctrine/orm/src/Persisters/Entity/CachedPersisterContext.php deleted file mode 100644 index 03d053b..0000000 --- a/vendor/doctrine/orm/src/Persisters/Entity/CachedPersisterContext.php +++ /dev/null @@ -1,60 +0,0 @@ - - */ - public array $sqlTableAliases = []; - - public function __construct( - /** - * Metadata object that describes the mapping of the mapped entity class. - */ - public ClassMetadata $class, - /** - * ResultSetMapping that is used for all queries. Is generated lazily once per request. - */ - public ResultSetMapping $rsm, - /** - * Whether this persistent context is considering limit operations applied to the selection queries - */ - public bool $handlesLimits, - ) { - } -} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/EntityPersister.php b/vendor/doctrine/orm/src/Persisters/Entity/EntityPersister.php deleted file mode 100644 index 6b278a7..0000000 --- a/vendor/doctrine/orm/src/Persisters/Entity/EntityPersister.php +++ /dev/null @@ -1,298 +0,0 @@ -, list} - */ - public function expandParameters(array $criteria): array; - - /** - * Expands Criteria Parameters by walking the expressions and grabbing all parameters and types from it. - * - * @psalm-return array{list, list} - */ - public function expandCriteriaParameters(Criteria $criteria): array; - - /** Gets the SQL WHERE condition for matching a field with a given value. */ - public function getSelectConditionStatementSQL( - string $field, - mixed $value, - AssociationMapping|null $assoc = null, - string|null $comparison = null, - ): string; - - /** - * Adds an entity to the queued insertions. - * The entity remains queued until {@link executeInserts} is invoked. - */ - public function addInsert(object $entity): void; - - /** - * Executes all queued entity insertions. - * - * If no inserts are queued, invoking this method is a NOOP. - */ - public function executeInserts(): void; - - /** - * Updates a managed entity. The entity is updated according to its current changeset - * in the running UnitOfWork. If there is no changeset, nothing is updated. - */ - public function update(object $entity): void; - - /** - * Deletes a managed entity. - * - * The entity to delete must be managed and have a persistent identifier. - * The deletion happens instantaneously. - * - * Subclasses may override this method to customize the semantics of entity deletion. - * - * @return bool TRUE if the entity got deleted in the database, FALSE otherwise. - */ - public function delete(object $entity): bool; - - /** - * Count entities (optionally filtered by a criteria) - * - * @param mixed[]|Criteria $criteria - */ - public function count(array|Criteria $criteria = []): int; - - /** - * Gets the name of the table that owns the column the given field is mapped to. - * - * The default implementation in BasicEntityPersister always returns the name - * of the table the entity type of this persister is mapped to, since an entity - * is always persisted to a single table with a BasicEntityPersister. - */ - public function getOwningTable(string $fieldName): string; - - /** - * Loads an entity by a list of field criteria. - * - * @param mixed[] $criteria The criteria by which to load the entity. - * @param object|null $entity The entity to load the data into. If not specified, - * a new entity is created. - * @param AssociationMapping|null $assoc The association that connects the entity - * to load to another entity, if any. - * @param mixed[] $hints Hints for entity creation. - * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants - * or NULL if no specific lock mode should be used - * for loading the entity. - * @param int|null $limit Limit number of results. - * @param string[]|null $orderBy Criteria to order by. - * @psalm-param array $criteria - * @psalm-param array $hints - * @psalm-param LockMode::*|null $lockMode - * @psalm-param array|null $orderBy - * - * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. - * - * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? - */ - public function load( - array $criteria, - object|null $entity = null, - AssociationMapping|null $assoc = null, - array $hints = [], - LockMode|int|null $lockMode = null, - int|null $limit = null, - array|null $orderBy = null, - ): object|null; - - /** - * Loads an entity by identifier. - * - * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. - * @psalm-param array $identifier The entity identifier. - * - * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. - * - * @todo Check parameters - */ - public function loadById(array $identifier, object|null $entity = null): object|null; - - /** - * Loads an entity of this persister's mapped class as part of a single-valued - * association from another entity. - * - * @param AssociationMapping $assoc The association to load. - * @param object $sourceEntity The entity that owns the association (not necessarily the "owning side"). - * @psalm-param array $identifier The identifier of the entity to load. Must be provided if - * the association to load represents the owning side, otherwise - * the identifier is derived from the $sourceEntity. - * - * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. - * - * @throws MappingException - */ - public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null; - - /** - * Refreshes a managed entity. - * - * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants - * or NULL if no specific lock mode should be used - * for refreshing the managed entity. - * @psalm-param array $id The identifier of the entity as an - * associative array from column or - * field names to values. - * @psalm-param LockMode::*|null $lockMode - */ - public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void; - - /** - * Loads Entities matching the given Criteria object. - * - * @return mixed[] - */ - public function loadCriteria(Criteria $criteria): array; - - /** - * Loads a list of entities by a list of field criteria. - * - * @psalm-param array|null $orderBy - * @psalm-param array $criteria - * - * @return mixed[] - */ - public function loadAll( - array $criteria = [], - array|null $orderBy = null, - int|null $limit = null, - int|null $offset = null, - ): array; - - /** - * Gets (sliced or full) elements of the given collection. - * - * @return mixed[] - */ - public function getManyToManyCollection( - AssociationMapping $assoc, - object $sourceEntity, - int|null $offset = null, - int|null $limit = null, - ): array; - - /** - * Loads a collection of entities of a many-to-many association. - * - * @param AssociationMapping $assoc The association mapping of the association being loaded. - * @param object $sourceEntity The entity that owns the collection. - * @param PersistentCollection $collection The collection to fill. - * - * @return mixed[] - */ - public function loadManyToManyCollection( - AssociationMapping $assoc, - object $sourceEntity, - PersistentCollection $collection, - ): array; - - /** - * Loads a collection of entities in a one-to-many association. - * - * @param PersistentCollection $collection The collection to load/fill. - */ - public function loadOneToManyCollection( - AssociationMapping $assoc, - object $sourceEntity, - PersistentCollection $collection, - ): mixed; - - /** - * Locks all rows of this entity matching the given criteria with the specified pessimistic lock mode. - * - * @psalm-param array $criteria - * @psalm-param LockMode::* $lockMode - */ - public function lock(array $criteria, LockMode|int $lockMode): void; - - /** - * Returns an array with (sliced or full list) of elements in the specified collection. - * - * @return mixed[] - */ - public function getOneToManyCollection( - AssociationMapping $assoc, - object $sourceEntity, - int|null $offset = null, - int|null $limit = null, - ): array; - - /** - * Checks whether the given managed entity exists in the database. - */ - public function exists(object $entity, Criteria|null $extraConditions = null): bool; -} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/JoinedSubclassPersister.php b/vendor/doctrine/orm/src/Persisters/Entity/JoinedSubclassPersister.php deleted file mode 100644 index 76719a2..0000000 --- a/vendor/doctrine/orm/src/Persisters/Entity/JoinedSubclassPersister.php +++ /dev/null @@ -1,601 +0,0 @@ -Class Table Inheritance strategy. - * - * @see https://martinfowler.com/eaaCatalog/classTableInheritance.html - */ -class JoinedSubclassPersister extends AbstractEntityInheritancePersister -{ - use LockSqlHelper; - use SQLResultCasing; - - /** - * Map that maps column names to the table names that own them. - * This is mainly a temporary cache, used during a single request. - * - * @psalm-var array - */ - private array $owningTableMap = []; - - /** - * Map of table to quoted table names. - * - * @psalm-var array - */ - private array $quotedTableMap = []; - - protected function getDiscriminatorColumnTableName(): string - { - $class = $this->class->name !== $this->class->rootEntityName - ? $this->em->getClassMetadata($this->class->rootEntityName) - : $this->class; - - return $class->getTableName(); - } - - /** - * This function finds the ClassMetadata instance in an inheritance hierarchy - * that is responsible for enabling versioning. - */ - private function getVersionedClassMetadata(): ClassMetadata - { - if (isset($this->class->fieldMappings[$this->class->versionField]->inherited)) { - $definingClassName = $this->class->fieldMappings[$this->class->versionField]->inherited; - - return $this->em->getClassMetadata($definingClassName); - } - - return $this->class; - } - - /** - * Gets the name of the table that owns the column the given field is mapped to. - */ - public function getOwningTable(string $fieldName): string - { - if (isset($this->owningTableMap[$fieldName])) { - return $this->owningTableMap[$fieldName]; - } - - $cm = match (true) { - isset($this->class->associationMappings[$fieldName]->inherited) - => $this->em->getClassMetadata($this->class->associationMappings[$fieldName]->inherited), - isset($this->class->fieldMappings[$fieldName]->inherited) - => $this->em->getClassMetadata($this->class->fieldMappings[$fieldName]->inherited), - default => $this->class, - }; - - $tableName = $cm->getTableName(); - $quotedTableName = $this->quoteStrategy->getTableName($cm, $this->platform); - - $this->owningTableMap[$fieldName] = $tableName; - $this->quotedTableMap[$tableName] = $quotedTableName; - - return $tableName; - } - - public function executeInserts(): void - { - if (! $this->queuedInserts) { - return; - } - - $uow = $this->em->getUnitOfWork(); - $idGenerator = $this->class->idGenerator; - $isPostInsertId = $idGenerator->isPostInsertGenerator(); - $rootClass = $this->class->name !== $this->class->rootEntityName - ? $this->em->getClassMetadata($this->class->rootEntityName) - : $this->class; - - // Prepare statement for the root table - $rootPersister = $this->em->getUnitOfWork()->getEntityPersister($rootClass->name); - $rootTableName = $rootClass->getTableName(); - $rootTableStmt = $this->conn->prepare($rootPersister->getInsertSQL()); - - // Prepare statements for sub tables. - $subTableStmts = []; - - if ($rootClass !== $this->class) { - $subTableStmts[$this->class->getTableName()] = $this->conn->prepare($this->getInsertSQL()); - } - - foreach ($this->class->parentClasses as $parentClassName) { - $parentClass = $this->em->getClassMetadata($parentClassName); - $parentTableName = $parentClass->getTableName(); - - if ($parentClass !== $rootClass) { - $parentPersister = $this->em->getUnitOfWork()->getEntityPersister($parentClassName); - $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL()); - } - } - - // Execute all inserts. For each entity: - // 1) Insert on root table - // 2) Insert on sub tables - foreach ($this->queuedInserts as $entity) { - $insertData = $this->prepareInsertData($entity); - - // Execute insert on root table - $paramIndex = 1; - - foreach ($insertData[$rootTableName] as $columnName => $value) { - $rootTableStmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]); - } - - $rootTableStmt->executeStatement(); - - if ($isPostInsertId) { - $generatedId = $idGenerator->generateId($this->em, $entity); - $id = [$this->class->identifier[0] => $generatedId]; - - $uow->assignPostInsertId($entity, $generatedId); - } else { - $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity); - } - - // Execute inserts on subtables. - // The order doesn't matter because all child tables link to the root table via FK. - foreach ($subTableStmts as $tableName => $stmt) { - $paramIndex = 1; - $data = $insertData[$tableName] ?? []; - - foreach ($id as $idName => $idVal) { - $type = $this->columnTypes[$idName] ?? Types::STRING; - - $stmt->bindValue($paramIndex++, $idVal, $type); - } - - foreach ($data as $columnName => $value) { - if (! isset($id[$columnName])) { - $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]); - } - } - - $stmt->executeStatement(); - } - - if ($this->class->requiresFetchAfterChange) { - $this->assignDefaultVersionAndUpsertableValues($entity, $id); - } - } - - $this->queuedInserts = []; - } - - public function update(object $entity): void - { - $updateData = $this->prepareUpdateData($entity); - - if (! $updateData) { - return; - } - - $isVersioned = $this->class->isVersioned; - - $versionedClass = $this->getVersionedClassMetadata(); - $versionedTable = $versionedClass->getTableName(); - - foreach ($updateData as $tableName => $data) { - $tableName = $this->quotedTableMap[$tableName]; - $versioned = $isVersioned && $versionedTable === $tableName; - - $this->updateTable($entity, $tableName, $data, $versioned); - } - - if ($this->class->requiresFetchAfterChange) { - // Make sure the table with the version column is updated even if no columns on that - // table were affected. - if ($isVersioned && ! isset($updateData[$versionedTable])) { - $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); - - $this->updateTable($entity, $tableName, [], true); - } - - $identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity); - - $this->assignDefaultVersionAndUpsertableValues($entity, $identifiers); - } - } - - public function delete(object $entity): bool - { - $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); - $id = array_combine($this->class->getIdentifierColumnNames(), $identifier); - $types = $this->getClassIdentifiersTypes($this->class); - - $this->deleteJoinTableRecords($identifier, $types); - - // Delete the row from the root table. Cascades do the rest. - $rootClass = $this->em->getClassMetadata($this->class->rootEntityName); - $rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform); - $rootTypes = $this->getClassIdentifiersTypes($rootClass); - - return (bool) $this->conn->delete($rootTable, $id, $rootTypes); - } - - public function getSelectSQL( - array|Criteria $criteria, - AssociationMapping|null $assoc = null, - LockMode|int|null $lockMode = null, - int|null $limit = null, - int|null $offset = null, - array|null $orderBy = null, - ): string { - $this->switchPersisterContext($offset, $limit); - - $baseTableAlias = $this->getSQLTableAlias($this->class->name); - $joinSql = $this->getJoinSql($baseTableAlias); - - if ($assoc !== null && $assoc->isManyToMany()) { - $joinSql .= $this->getSelectManyToManyJoinSQL($assoc); - } - - $conditionSql = $criteria instanceof Criteria - ? $this->getSelectConditionCriteriaSQL($criteria) - : $this->getSelectConditionSQL($criteria, $assoc); - - $filterSql = $this->generateFilterConditionSQL( - $this->em->getClassMetadata($this->class->rootEntityName), - $this->getSQLTableAlias($this->class->rootEntityName), - ); - // If the current class in the root entity, add the filters - if ($filterSql) { - $conditionSql .= $conditionSql - ? ' AND ' . $filterSql - : $filterSql; - } - - $orderBySql = ''; - - if ($assoc !== null && $assoc->isOrdered()) { - $orderBy = $assoc->orderBy(); - } - - if ($orderBy) { - $orderBySql = $this->getOrderBySQL($orderBy, $baseTableAlias); - } - - $lockSql = ''; - - switch ($lockMode) { - case LockMode::PESSIMISTIC_READ: - $lockSql = ' ' . $this->getReadLockSQL($this->platform); - - break; - - case LockMode::PESSIMISTIC_WRITE: - $lockSql = ' ' . $this->getWriteLockSQL($this->platform); - - break; - } - - $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); - $from = ' FROM ' . $tableName . ' ' . $baseTableAlias; - $where = $conditionSql !== '' ? ' WHERE ' . $conditionSql : ''; - $lock = $this->platform->appendLockHint($from, $lockMode ?? LockMode::NONE); - $columnList = $this->getSelectColumnsSQL(); - $query = 'SELECT ' . $columnList - . $lock - . $joinSql - . $where - . $orderBySql; - - return $this->platform->modifyLimitQuery($query, $limit, $offset ?? 0) . $lockSql; - } - - public function getCountSQL(array|Criteria $criteria = []): string - { - $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); - $baseTableAlias = $this->getSQLTableAlias($this->class->name); - $joinSql = $this->getJoinSql($baseTableAlias); - - $conditionSql = $criteria instanceof Criteria - ? $this->getSelectConditionCriteriaSQL($criteria) - : $this->getSelectConditionSQL($criteria); - - $filterSql = $this->generateFilterConditionSQL($this->em->getClassMetadata($this->class->rootEntityName), $this->getSQLTableAlias($this->class->rootEntityName)); - - if ($filterSql !== '') { - $conditionSql = $conditionSql - ? $conditionSql . ' AND ' . $filterSql - : $filterSql; - } - - return 'SELECT COUNT(*) ' - . 'FROM ' . $tableName . ' ' . $baseTableAlias - . $joinSql - . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql); - } - - protected function getLockTablesSql(LockMode|int $lockMode): string - { - $joinSql = ''; - $identifierColumns = $this->class->getIdentifierColumnNames(); - $baseTableAlias = $this->getSQLTableAlias($this->class->name); - - // INNER JOIN parent tables - foreach ($this->class->parentClasses as $parentClassName) { - $conditions = []; - $tableAlias = $this->getSQLTableAlias($parentClassName); - $parentClass = $this->em->getClassMetadata($parentClassName); - $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; - - foreach ($identifierColumns as $idColumn) { - $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; - } - - $joinSql .= implode(' AND ', $conditions); - } - - return parent::getLockTablesSql($lockMode) . $joinSql; - } - - /** - * Ensure this method is never called. This persister overrides getSelectEntitiesSQL directly. - */ - protected function getSelectColumnsSQL(): string - { - // Create the column list fragment only once - if ($this->currentPersisterContext->selectColumnListSql !== null) { - return $this->currentPersisterContext->selectColumnListSql; - } - - $columnList = []; - $discrColumn = $this->class->getDiscriminatorColumn(); - $discrColumnName = $discrColumn->name; - $discrColumnType = $discrColumn->type; - $baseTableAlias = $this->getSQLTableAlias($this->class->name); - $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumnName); - - $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r'); - $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName); - $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType); - - // Add regular columns - foreach ($this->class->fieldMappings as $fieldName => $mapping) { - $class = isset($mapping->inherited) - ? $this->em->getClassMetadata($mapping->inherited) - : $this->class; - - $columnList[] = $this->getSelectColumnSQL($fieldName, $class); - } - - // Add foreign key columns - foreach ($this->class->associationMappings as $mapping) { - if (! $mapping->isToOneOwningSide()) { - continue; - } - - $tableAlias = isset($mapping->inherited) - ? $this->getSQLTableAlias($mapping->inherited) - : $baseTableAlias; - - $targetClass = $this->em->getClassMetadata($mapping->targetEntity); - - foreach ($mapping->joinColumns as $joinColumn) { - $columnList[] = $this->getSelectJoinColumnSQL( - $tableAlias, - $joinColumn->name, - $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform), - PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em), - ); - } - } - - // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#processSQLResult). - $tableAlias = $this->class->rootEntityName === $this->class->name - ? $baseTableAlias - : $this->getSQLTableAlias($this->class->rootEntityName); - - $columnList[] = $tableAlias . '.' . $discrColumnName; - - // sub tables - foreach ($this->class->subClasses as $subClassName) { - $subClass = $this->em->getClassMetadata($subClassName); - $tableAlias = $this->getSQLTableAlias($subClassName); - - // Add subclass columns - foreach ($subClass->fieldMappings as $fieldName => $mapping) { - if (isset($mapping->inherited)) { - continue; - } - - $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass); - } - - // Add join columns (foreign keys) - foreach ($subClass->associationMappings as $mapping) { - if (! $mapping->isToOneOwningSide() || isset($mapping->inherited)) { - continue; - } - - $targetClass = $this->em->getClassMetadata($mapping->targetEntity); - - foreach ($mapping->joinColumns as $joinColumn) { - $columnList[] = $this->getSelectJoinColumnSQL( - $tableAlias, - $joinColumn->name, - $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform), - PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em), - ); - } - } - } - - $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); - - return $this->currentPersisterContext->selectColumnListSql; - } - - /** - * {@inheritDoc} - */ - protected function getInsertColumnList(): array - { - // Identifier columns must always come first in the column list of subclasses. - $columns = $this->class->parentClasses - ? $this->class->getIdentifierColumnNames() - : []; - - foreach ($this->class->reflFields as $name => $field) { - if ( - isset($this->class->fieldMappings[$name]->inherited) - && ! isset($this->class->fieldMappings[$name]->id) - || isset($this->class->associationMappings[$name]->inherited) - || ($this->class->isVersioned && $this->class->versionField === $name) - || isset($this->class->embeddedClasses[$name]) - || isset($this->class->fieldMappings[$name]->notInsertable) - ) { - continue; - } - - if (isset($this->class->associationMappings[$name])) { - $assoc = $this->class->associationMappings[$name]; - if ($assoc->isToOneOwningSide()) { - foreach ($assoc->targetToSourceKeyColumns as $sourceCol) { - $columns[] = $sourceCol; - } - } - } elseif ( - $this->class->name !== $this->class->rootEntityName || - ! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] !== $name - ) { - $columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform); - $this->columnTypes[$name] = $this->class->fieldMappings[$name]->type; - } - } - - // Add discriminator column if it is the topmost class. - if ($this->class->name === $this->class->rootEntityName) { - $columns[] = $this->class->getDiscriminatorColumn()->name; - } - - return $columns; - } - - /** - * {@inheritDoc} - */ - protected function assignDefaultVersionAndUpsertableValues(object $entity, array $id): void - { - $values = $this->fetchVersionAndNotUpsertableValues($this->getVersionedClassMetadata(), $id); - - foreach ($values as $field => $value) { - $value = Type::getType($this->class->fieldMappings[$field]->type)->convertToPHPValue($value, $this->platform); - - $this->class->setFieldValue($entity, $field, $value); - } - } - - /** - * {@inheritDoc} - */ - protected function fetchVersionAndNotUpsertableValues(ClassMetadata $versionedClass, array $id): mixed - { - $columnNames = []; - foreach ($this->class->fieldMappings as $key => $column) { - $class = null; - if ($this->class->isVersioned && $key === $versionedClass->versionField) { - $class = $versionedClass; - } elseif (isset($column->generated)) { - $class = isset($column->inherited) - ? $this->em->getClassMetadata($column->inherited) - : $this->class; - } else { - continue; - } - - $columnNames[$key] = $this->getSelectColumnSQL($key, $class); - } - - $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); - $baseTableAlias = $this->getSQLTableAlias($this->class->name); - $joinSql = $this->getJoinSql($baseTableAlias); - $identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform); - foreach ($identifier as $i => $idValue) { - $identifier[$i] = $baseTableAlias . '.' . $idValue; - } - - $sql = 'SELECT ' . implode(', ', $columnNames) - . ' FROM ' . $tableName . ' ' . $baseTableAlias - . $joinSql - . ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?'; - - $flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id); - $values = $this->conn->fetchNumeric( - $sql, - array_values($flatId), - $this->extractIdentifierTypes($id, $versionedClass), - ); - - if ($values === false) { - throw new LengthException('Unexpected empty result for database query.'); - } - - $values = array_combine(array_keys($columnNames), $values); - - if (! $values) { - throw new LengthException('Unexpected number of database columns.'); - } - - return $values; - } - - private function getJoinSql(string $baseTableAlias): string - { - $joinSql = ''; - $identifierColumn = $this->class->getIdentifierColumnNames(); - - // INNER JOIN parent tables - foreach ($this->class->parentClasses as $parentClassName) { - $conditions = []; - $parentClass = $this->em->getClassMetadata($parentClassName); - $tableAlias = $this->getSQLTableAlias($parentClassName); - $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; - - foreach ($identifierColumn as $idColumn) { - $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; - } - - $joinSql .= implode(' AND ', $conditions); - } - - // OUTER JOIN sub tables - foreach ($this->class->subClasses as $subClassName) { - $conditions = []; - $subClass = $this->em->getClassMetadata($subClassName); - $tableAlias = $this->getSQLTableAlias($subClassName); - $joinSql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON '; - - foreach ($identifierColumn as $idColumn) { - $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; - } - - $joinSql .= implode(' AND ', $conditions); - } - - return $joinSql; - } -} diff --git a/vendor/doctrine/orm/src/Persisters/Entity/SingleTablePersister.php b/vendor/doctrine/orm/src/Persisters/Entity/SingleTablePersister.php deleted file mode 100644 index 4a4d999..0000000 --- a/vendor/doctrine/orm/src/Persisters/Entity/SingleTablePersister.php +++ /dev/null @@ -1,166 +0,0 @@ -class->getTableName(); - } - - protected function getSelectColumnsSQL(): string - { - $columnList = []; - if ($this->currentPersisterContext->selectColumnListSql !== null) { - return $this->currentPersisterContext->selectColumnListSql; - } - - $columnList[] = parent::getSelectColumnsSQL(); - - $rootClass = $this->em->getClassMetadata($this->class->rootEntityName); - $tableAlias = $this->getSQLTableAlias($rootClass->name); - - // Append discriminator column - $discrColumn = $this->class->getDiscriminatorColumn(); - $discrColumnName = $discrColumn->name; - $discrColumnType = $discrColumn->type; - - $columnList[] = $tableAlias . '.' . $discrColumnName; - - $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumnName); - - $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName); - $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType); - - // Append subclass columns - foreach ($this->class->subClasses as $subClassName) { - $subClass = $this->em->getClassMetadata($subClassName); - - // Regular columns - foreach ($subClass->fieldMappings as $fieldName => $mapping) { - if (isset($mapping->inherited)) { - continue; - } - - $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass); - } - - // Foreign key columns - foreach ($subClass->associationMappings as $assoc) { - if (! $assoc->isToOneOwningSide() || isset($assoc->inherited)) { - continue; - } - - $targetClass = $this->em->getClassMetadata($assoc->targetEntity); - - foreach ($assoc->joinColumns as $joinColumn) { - $columnList[] = $this->getSelectJoinColumnSQL( - $tableAlias, - $joinColumn->name, - $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform), - PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em), - ); - } - } - } - - $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); - - return $this->currentPersisterContext->selectColumnListSql; - } - - /** - * {@inheritDoc} - */ - protected function getInsertColumnList(): array - { - $columns = parent::getInsertColumnList(); - - // Add discriminator column to the INSERT SQL - $columns[] = $this->class->getDiscriminatorColumn()->name; - - return $columns; - } - - protected function getSQLTableAlias(string $className, string $assocName = ''): string - { - return parent::getSQLTableAlias($this->class->rootEntityName, $assocName); - } - - /** - * {@inheritDoc} - */ - protected function getSelectConditionSQL(array $criteria, AssociationMapping|null $assoc = null): string - { - $conditionSql = parent::getSelectConditionSQL($criteria, $assoc); - - if ($conditionSql) { - $conditionSql .= ' AND '; - } - - return $conditionSql . $this->getSelectConditionDiscriminatorValueSQL(); - } - - protected function getSelectConditionCriteriaSQL(Criteria $criteria): string - { - $conditionSql = parent::getSelectConditionCriteriaSQL($criteria); - - if ($conditionSql) { - $conditionSql .= ' AND '; - } - - return $conditionSql . $this->getSelectConditionDiscriminatorValueSQL(); - } - - protected function getSelectConditionDiscriminatorValueSQL(): string - { - $values = array_map($this->conn->quote(...), array_map( - strval(...), - array_flip(array_intersect($this->class->discriminatorMap, $this->class->subClasses)), - )); - - if ($this->class->discriminatorValue !== null) { // discriminators can be 0 - array_unshift($values, $this->conn->quote((string) $this->class->discriminatorValue)); - } - - $discColumnName = $this->class->getDiscriminatorColumn()->name; - - $values = implode(', ', $values); - $tableAlias = $this->getSQLTableAlias($this->class->name); - - return $tableAlias . '.' . $discColumnName . ' IN (' . $values . ')'; - } - - protected function generateFilterConditionSQL(ClassMetadata $targetEntity, string $targetTableAlias): string - { - // Ensure that the filters are applied to the root entity of the inheritance tree - $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName); - // we don't care about the $targetTableAlias, in a STI there is only one table. - - return parent::generateFilterConditionSQL($targetEntity, $targetTableAlias); - } -} diff --git a/vendor/doctrine/orm/src/Persisters/Exception/CantUseInOperatorOnCompositeKeys.php b/vendor/doctrine/orm/src/Persisters/Exception/CantUseInOperatorOnCompositeKeys.php deleted file mode 100644 index 5c91312..0000000 --- a/vendor/doctrine/orm/src/Persisters/Exception/CantUseInOperatorOnCompositeKeys.php +++ /dev/null @@ -1,15 +0,0 @@ -getField(); - $value = $comparison->getValue()->getValue(); // shortcut for walkValue() - - if ( - isset($this->classMetadata->associationMappings[$field]) && - $value !== null && - ! is_object($value) && - ! in_array($comparison->getOperator(), [Comparison::IN, Comparison::NIN], true) - ) { - throw MatchingAssociationFieldRequiresObject::fromClassAndAssociation( - $this->classMetadata->name, - $field, - ); - } - - return $this->persister->getSelectConditionStatementSQL($field, $value, null, $comparison->getOperator()); - } - - /** - * Converts a composite expression into the target query language output. - * - * @throws RuntimeException - */ - public function walkCompositeExpression(CompositeExpression $expr): string - { - $expressionList = []; - - foreach ($expr->getExpressionList() as $child) { - $expressionList[] = $this->dispatch($child); - } - - return match ($expr->getType()) { - CompositeExpression::TYPE_AND => '(' . implode(' AND ', $expressionList) . ')', - CompositeExpression::TYPE_OR => '(' . implode(' OR ', $expressionList) . ')', - CompositeExpression::TYPE_NOT => 'NOT (' . $expressionList[0] . ')', - default => throw new RuntimeException('Unknown composite ' . $expr->getType()), - }; - } - - /** - * Converts a value expression into the target query language part. - */ - public function walkValue(Value $value): string - { - return '?'; - } -} diff --git a/vendor/doctrine/orm/src/Persisters/SqlValueVisitor.php b/vendor/doctrine/orm/src/Persisters/SqlValueVisitor.php deleted file mode 100644 index 7f987ad..0000000 --- a/vendor/doctrine/orm/src/Persisters/SqlValueVisitor.php +++ /dev/null @@ -1,88 +0,0 @@ -getValueFromComparison($comparison); - - $this->values[] = $value; - $this->types[] = [$comparison->getField(), $value, $comparison->getOperator()]; - - return null; - } - - /** - * Converts a composite expression into the target query language output. - * - * {@inheritDoc} - */ - public function walkCompositeExpression(CompositeExpression $expr) - { - foreach ($expr->getExpressionList() as $child) { - $this->dispatch($child); - } - - return null; - } - - /** - * Converts a value expression into the target query language part. - * - * {@inheritDoc} - */ - public function walkValue(Value $value) - { - return null; - } - - /** - * Returns the Parameters and Types necessary for matching the last visited expression. - * - * @return mixed[][] - * @psalm-return array{0: array, 1: array>} - */ - public function getParamsAndTypes(): array - { - return [$this->values, $this->types]; - } - - /** - * Returns the value from a Comparison. In case of a CONTAINS comparison, - * the value is wrapped in %-signs, because it will be used in a LIKE clause. - */ - protected function getValueFromComparison(Comparison $comparison): mixed - { - $value = $comparison->getValue()->getValue(); - - return match ($comparison->getOperator()) { - Comparison::CONTAINS => '%' . $value . '%', - Comparison::STARTS_WITH => $value . '%', - Comparison::ENDS_WITH => '%' . $value, - default => $value, - }; - } -} -- cgit v1.2.3