mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9557c588b | ||
|
|
19912de927 | ||
|
|
737cca5b78 | ||
|
|
aff82af7de | ||
|
|
9e999ea1ff | ||
|
|
6755bb0c7b | ||
|
|
aa141bf001 | ||
|
|
cf39e00553 | ||
|
|
27b47841be | ||
|
|
c2a49327a7 | ||
|
|
9bd7242376 | ||
|
|
fff085b63f | ||
|
|
5ad5b11ae1 | ||
|
|
c12fd2cb94 | ||
|
|
44d5d4a779 | ||
|
|
5a599233c9 | ||
|
|
596da353c2 | ||
|
|
68c87740aa | ||
|
|
55dc02c39f | ||
|
|
4feaa470af |
2
.github/workflows/coding-standards.yml
vendored
2
.github/workflows/coding-standards.yml
vendored
@@ -24,4 +24,4 @@ on:
|
||||
|
||||
jobs:
|
||||
coding-standards:
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.1.0"
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.2.1"
|
||||
|
||||
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@@ -17,4 +17,4 @@ on:
|
||||
jobs:
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
uses: "doctrine/.github/.github/workflows/documentation.yml@7.1.0"
|
||||
uses: "doctrine/.github/.github/workflows/documentation.yml@7.2.1"
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.1.0"
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.2.1"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
|
||||
30
UPGRADE.md
30
UPGRADE.md
@@ -124,6 +124,36 @@ WARNING: This was relaxed in ORM 3.2 when partial was re-allowed for array-hydra
|
||||
`Doctrine\ORM\Query::HINT_FORCE_PARTIAL_LOAD` are removed.
|
||||
- `Doctrine\ORM\EntityManager*::getPartialReference()` is removed.
|
||||
|
||||
## BC BREAK: Enforce ArrayCollection Type on `\Doctrine\ORM\QueryBuilder::setParameters(ArrayCollection $parameters)`
|
||||
|
||||
The argument $parameters can no longer be a key=>value array. Only ArrayCollection types are allowed.
|
||||
|
||||
### Before
|
||||
|
||||
```php
|
||||
$qb = $em->createQueryBuilder()
|
||||
->select('u')
|
||||
->from('User', 'u')
|
||||
->where('u.id = :user_id1 OR u.id = :user_id2')
|
||||
->setParameter(array(
|
||||
'user_id1' => 1,
|
||||
'user_id2' => 2
|
||||
));
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
```php
|
||||
$qb = $em->createQueryBuilder()
|
||||
->select('u')
|
||||
->from('User', 'u')
|
||||
->where('u.id = :user_id1 OR u.id = :user_id2')
|
||||
->setParameter(new ArrayCollection(array(
|
||||
new Parameter('user_id1', 1),
|
||||
new Parameter('user_id2', 2)
|
||||
)));
|
||||
```
|
||||
|
||||
## BC BREAK: `Doctrine\ORM\Persister\Entity\EntityPersister::executeInserts()` return type changed to `void`
|
||||
|
||||
Implementors should adapt to the new signature, and should call
|
||||
|
||||
@@ -903,8 +903,7 @@ defaults to "id", just as in one-to-one or many-to-one mappings.
|
||||
|
||||
Additionally, when using typed properties with Doctrine 2.9 or newer
|
||||
you can skip ``targetEntity`` in ``ManyToOne`` and ``OneToOne``
|
||||
associations as they will be set based on type. Also ``nullable``
|
||||
attribute on ``JoinColumn`` will be inherited from PHP type. So that:
|
||||
associations as they will be set based on type. So that:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -931,7 +930,7 @@ Is essentially the same as following:
|
||||
<?php
|
||||
/** One Product has One Shipment. */
|
||||
#[OneToOne(targetEntity: Shipment::class)]
|
||||
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
|
||||
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: annotation
|
||||
@@ -940,7 +939,7 @@ Is essentially the same as following:
|
||||
/**
|
||||
* One Product has One Shipment.
|
||||
* @OneToOne(targetEntity="Shipment")
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id", nullable=false)
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id")
|
||||
*/
|
||||
private Shipment $shipment;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ is common to multiple entity classes.
|
||||
Mapped superclasses, just as regular, non-mapped classes, can
|
||||
appear in the middle of an otherwise mapped inheritance hierarchy
|
||||
(through Single Table Inheritance or Class Table Inheritance). They
|
||||
are not query-able, and need not have an ``#[Id]`` property.
|
||||
are not query-able, and do not require an ``#[Id]`` property.
|
||||
|
||||
No database table will be created for a mapped superclass itself,
|
||||
only for entity classes inheriting from it. That implies that a
|
||||
|
||||
@@ -166,7 +166,7 @@ your code. See the following code:
|
||||
|
||||
Traversing the object graph for parts that are lazy-loaded will
|
||||
easily trigger lots of SQL queries and will perform badly if used
|
||||
to heavily. Make sure to use DQL to fetch-join all the parts of the
|
||||
too heavily. Make sure to use DQL to fetch-join all the parts of the
|
||||
object-graph that you need as efficiently as possible.
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
colors="true"
|
||||
displayDetailsOnTestsThatTriggerDeprecations="true"
|
||||
displayDetailsOnTestsThatTriggerNotices="true"
|
||||
displayDetailsOnTestsThatTriggerWarnings="true"
|
||||
failOnNotice="true"
|
||||
@@ -67,5 +68,12 @@
|
||||
<var name="privileged_db_port" value="3306"/>
|
||||
-->
|
||||
<env name="COLUMNS" value="120"/>
|
||||
<env name="DOCTRINE_DEPRECATIONS" value="trigger"/>
|
||||
</php>
|
||||
|
||||
<source ignoreSuppressionOfDeprecations="true">
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
|
||||
@@ -29,11 +29,14 @@ class CollectionCacheKey extends CacheKey
|
||||
public readonly string $entityClass,
|
||||
public readonly string $association,
|
||||
array $ownerIdentifier,
|
||||
string $filterHash = '',
|
||||
) {
|
||||
ksort($ownerIdentifier);
|
||||
|
||||
$this->ownerIdentifier = $ownerIdentifier;
|
||||
|
||||
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association);
|
||||
$filterHash = $filterHash === '' ? '' : '_' . $filterHash;
|
||||
|
||||
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association . $filterHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
|
||||
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
|
||||
use Doctrine\ORM\Query\FilterCollection;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
|
||||
use function array_values;
|
||||
@@ -35,6 +36,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
protected array $queuedCache = [];
|
||||
|
||||
protected string $regionName;
|
||||
protected FilterCollection $filters;
|
||||
protected CollectionHydrator $hydrator;
|
||||
protected CacheLogger|null $cacheLogger;
|
||||
|
||||
@@ -48,6 +50,10 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
|
||||
$cacheFactory = $cacheConfig->getCacheFactory();
|
||||
|
||||
$this->region = $region;
|
||||
$this->persister = $persister;
|
||||
$this->association = $association;
|
||||
$this->filters = $em->getFilters();
|
||||
$this->regionName = $region->getName();
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->metadataFactory = $em->getMetadataFactory();
|
||||
@@ -135,7 +141,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
public function count(PersistentCollection $collection): int
|
||||
{
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
|
||||
$entry = $this->region->get($key);
|
||||
|
||||
if ($entry !== null) {
|
||||
|
||||
@@ -36,7 +36,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
|
||||
public function delete(PersistentCollection $collection): void
|
||||
{
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
|
||||
|
||||
$this->persister->delete($collection);
|
||||
|
||||
@@ -53,7 +53,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
|
||||
}
|
||||
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
|
||||
|
||||
// Invalidate non initialized collections OR ordered collection
|
||||
if ($isDirty && ! $isInitialized || $this->association->isOrdered()) {
|
||||
|
||||
@@ -61,7 +61,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
public function delete(PersistentCollection $collection): void
|
||||
{
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
|
||||
$lock = $this->region->lock($key);
|
||||
|
||||
$this->persister->delete($collection);
|
||||
@@ -88,7 +88,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
$this->persister->update($collection);
|
||||
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
|
||||
$lock = $this->region->lock($key);
|
||||
|
||||
if ($lock === null) {
|
||||
|
||||
@@ -24,10 +24,12 @@ use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
|
||||
use Doctrine\ORM\Query\FilterCollection;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
|
||||
use function array_merge;
|
||||
use function func_get_args;
|
||||
use function serialize;
|
||||
use function sha1;
|
||||
|
||||
@@ -43,6 +45,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
protected TimestampCacheKey $timestampKey;
|
||||
protected EntityHydrator $hydrator;
|
||||
protected Cache $cache;
|
||||
protected FilterCollection $filters;
|
||||
protected CacheLogger|null $cacheLogger = null;
|
||||
protected string $regionName;
|
||||
|
||||
@@ -64,6 +67,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$cacheFactory = $cacheConfig->getCacheFactory();
|
||||
|
||||
$this->cache = $em->getCache();
|
||||
$this->filters = $em->getFilters();
|
||||
$this->regionName = $region->getName();
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->metadataFactory = $em->getMetadataFactory();
|
||||
@@ -215,7 +219,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
? $this->persister->expandCriteriaParameters($criteria)
|
||||
: $this->persister->expandParameters($criteria);
|
||||
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $this->filters->getHash());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -472,7 +476,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
}
|
||||
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
|
||||
$key = $this->buildCollectionCacheKey($assoc, $ownerId, $this->filters->getHash());
|
||||
$list = $persister->loadCollectionCache($collection, $key);
|
||||
|
||||
if ($list !== null) {
|
||||
@@ -503,7 +507,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
}
|
||||
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
|
||||
$key = $this->buildCollectionCacheKey($assoc, $ownerId, $this->filters->getHash());
|
||||
$list = $persister->loadCollectionCache($collection, $key);
|
||||
|
||||
if ($list !== null) {
|
||||
@@ -546,12 +550,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $ownerId */
|
||||
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId): CollectionCacheKey
|
||||
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId, /* string $filterHash */): CollectionCacheKey
|
||||
{
|
||||
$filterHash = (string) (func_get_args()[2] ?? ''); // todo: move to argument in next major release
|
||||
|
||||
return new CollectionCacheKey(
|
||||
$this->metadataFactory->getMetadataFor($association->sourceEntity)->rootEntityName,
|
||||
$association->fieldName,
|
||||
$ownerId,
|
||||
$filterHash,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use function array_search;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function key;
|
||||
use function reset;
|
||||
use function sprintf;
|
||||
@@ -138,14 +139,21 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
}
|
||||
|
||||
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
|
||||
$originalValue = $value;
|
||||
$originalValue = $currentValue = $value;
|
||||
try {
|
||||
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
|
||||
if (! is_array($originalValue)) {
|
||||
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
|
||||
} else {
|
||||
$value = [];
|
||||
foreach ($originalValue as $i => $currentValue) {
|
||||
$value[$i] = $this->buildEnum($currentValue, $cacheKeyInfo['enumType']);
|
||||
}
|
||||
}
|
||||
} catch (ValueError $e) {
|
||||
throw MappingException::invalidEnumValue(
|
||||
$entityName,
|
||||
$cacheKeyInfo['fieldName'],
|
||||
(string) $originalValue,
|
||||
(string) $currentValue,
|
||||
$cacheKeyInfo['enumType'],
|
||||
$e,
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
public function getColumnName(string $fieldName, ClassMetadata $class, AbstractPlatform $platform): string
|
||||
{
|
||||
return isset($class->fieldMappings[$fieldName]->quoted)
|
||||
? $platform->quoteIdentifier($class->fieldMappings[$fieldName]->columnName)
|
||||
? $platform->quoteSingleIdentifier($class->fieldMappings[$fieldName]->columnName)
|
||||
: $class->fieldMappings[$fieldName]->columnName;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
}
|
||||
|
||||
return isset($class->table['quoted'])
|
||||
? $platform->quoteIdentifier($tableName)
|
||||
? $platform->quoteSingleIdentifier($tableName)
|
||||
: $tableName;
|
||||
}
|
||||
|
||||
@@ -52,14 +52,14 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string
|
||||
{
|
||||
return isset($definition['quoted'])
|
||||
? $platform->quoteIdentifier($definition['sequenceName'])
|
||||
? $platform->quoteSingleIdentifier($definition['sequenceName'])
|
||||
: $definition['sequenceName'];
|
||||
}
|
||||
|
||||
public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string
|
||||
{
|
||||
return isset($joinColumn->quoted)
|
||||
? $platform->quoteIdentifier($joinColumn->name)
|
||||
? $platform->quoteSingleIdentifier($joinColumn->name)
|
||||
: $joinColumn->name;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
AbstractPlatform $platform,
|
||||
): string {
|
||||
return isset($joinColumn->quoted)
|
||||
? $platform->quoteIdentifier($joinColumn->referencedColumnName)
|
||||
? $platform->quoteSingleIdentifier($joinColumn->referencedColumnName)
|
||||
: $joinColumn->referencedColumnName;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
$tableName = $association->joinTable->name;
|
||||
|
||||
if (isset($association->joinTable->quoted)) {
|
||||
$tableName = $platform->quoteIdentifier($tableName);
|
||||
$tableName = $platform->quoteSingleIdentifier($tableName);
|
||||
}
|
||||
|
||||
return $schema . $tableName;
|
||||
@@ -113,7 +113,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
$joinColumns = $assoc->joinColumns;
|
||||
$assocQuotedColumnNames = array_map(
|
||||
static fn (JoinColumnMapping $joinColumn) => isset($joinColumn->quoted)
|
||||
? $platform->quoteIdentifier($joinColumn->name)
|
||||
? $platform->quoteSingleIdentifier($joinColumn->name)
|
||||
: $joinColumn->name,
|
||||
$joinColumns,
|
||||
);
|
||||
|
||||
@@ -22,8 +22,13 @@ trait ReflectionBasedDriver
|
||||
*/
|
||||
private function isRepeatedPropertyDeclaration(ReflectionProperty $property, ClassMetadata $metadata): bool
|
||||
{
|
||||
/** @var class-string $declaringClass */
|
||||
$declaringClass = $property->class;
|
||||
|
||||
if ($this->isTransient($declaringClass)) {
|
||||
return isset($metadata->fieldMappings[$property->name]);
|
||||
}
|
||||
|
||||
if (
|
||||
isset($metadata->fieldMappings[$property->name]->declared)
|
||||
&& $metadata->fieldMappings[$property->name]->declared === $declaringClass
|
||||
|
||||
@@ -1500,7 +1500,15 @@ class BasicEntityPersister implements EntityPersister
|
||||
$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);
|
||||
|
||||
$columnAlias = null;
|
||||
if ($this->currentPersisterContext->rsm->hasColumnAliasByField($alias, $field)) {
|
||||
$columnAlias = $this->currentPersisterContext->rsm->getColumnAliasByField($alias, $field);
|
||||
}
|
||||
|
||||
if ($columnAlias === null) {
|
||||
$columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);
|
||||
}
|
||||
|
||||
$this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field);
|
||||
if (! empty($fieldMapping->enumType)) {
|
||||
|
||||
@@ -66,6 +66,13 @@ class ResultSetMapping
|
||||
*/
|
||||
public array $fieldMappings = [];
|
||||
|
||||
/**
|
||||
* Map field names for each class to alias
|
||||
*
|
||||
* @var array<class-string, array<string, array<string, string>>>
|
||||
*/
|
||||
public array $columnAliasMappings = [];
|
||||
|
||||
/**
|
||||
* Maps column names in the result set to the alias/field name to use in the mapped result.
|
||||
*
|
||||
@@ -328,7 +335,10 @@ class ResultSetMapping
|
||||
// column name => alias of owner
|
||||
$this->columnOwnerMap[$columnName] = $alias;
|
||||
// field name => class name of declaring class
|
||||
$this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
|
||||
$declaringClass = $declaringClass ?: $this->aliasMap[$alias];
|
||||
$this->declaringClasses[$columnName] = $declaringClass;
|
||||
|
||||
$this->columnAliasMappings[$declaringClass][$alias][$fieldName] = $columnName;
|
||||
|
||||
if (! $this->isMixed && $this->scalarMappings) {
|
||||
$this->isMixed = true;
|
||||
@@ -337,6 +347,20 @@ class ResultSetMapping
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasColumnAliasByField(string $alias, string $fieldName): bool
|
||||
{
|
||||
$declaringClass = $this->aliasMap[$alias];
|
||||
|
||||
return isset($this->columnAliasMappings[$declaringClass][$alias][$fieldName]);
|
||||
}
|
||||
|
||||
public function getColumnAliasByField(string $alias, string $fieldName): string
|
||||
{
|
||||
$declaringClass = $this->aliasMap[$alias];
|
||||
|
||||
return $this->columnAliasMappings[$declaringClass][$alias][$fieldName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a joined entity result.
|
||||
*
|
||||
|
||||
@@ -12,6 +12,7 @@ use Doctrine\ORM\Query\AST\Literal;
|
||||
use Doctrine\ORM\Query\AST\PathExpression;
|
||||
use Doctrine\ORM\Query\AST\SelectStatement;
|
||||
use Doctrine\ORM\Query\AST\WhereClause;
|
||||
use Doctrine\ORM\Query\SqlOutputWalker;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
use Doctrine\ORM\Query\TreeWalkerAdapter;
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
@@ -643,7 +644,7 @@ class PaginationTest extends OrmFunctionalTestCase
|
||||
self::assertCount(2, $getCountQuery->invoke($paginator)->getParameters());
|
||||
self::assertCount(9, $paginator);
|
||||
|
||||
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SqlWalker::class);
|
||||
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SqlOutputWalker::class);
|
||||
|
||||
$paginator = new Paginator($query);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Generator;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use ReflectionMethod;
|
||||
use Symfony\Component\VarExporter\Instantiator;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
@@ -34,6 +35,7 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
|
||||
|
||||
/** @param Closure(ParserResult): ParserResult $toSerializedAndBack */
|
||||
#[DataProvider('provideToSerializedAndBack')]
|
||||
#[WithoutErrorHandler]
|
||||
public function testSerializeParserResultForQueryWithSqlWalker(Closure $toSerializedAndBack): void
|
||||
{
|
||||
$query = $this->_em
|
||||
|
||||
@@ -42,14 +42,14 @@ class DDC832Test extends OrmFunctionalTestCase
|
||||
$platform = $this->_em->getConnection()->getDatabasePlatform();
|
||||
|
||||
$sm = $this->createSchemaManager();
|
||||
$sm->dropTable($platform->quoteIdentifier('TREE_INDEX'));
|
||||
$sm->dropTable($platform->quoteIdentifier('INDEX'));
|
||||
$sm->dropTable($platform->quoteIdentifier('LIKE'));
|
||||
$sm->dropTable($platform->quoteSingleIdentifier('TREE_INDEX'));
|
||||
$sm->dropTable($platform->quoteSingleIdentifier('INDEX'));
|
||||
$sm->dropTable($platform->quoteSingleIdentifier('LIKE'));
|
||||
|
||||
// DBAL 3
|
||||
if ($platform instanceof PostgreSQLPlatform && method_exists($platform, 'getIdentitySequenceName')) {
|
||||
$sm->dropSequence($platform->quoteIdentifier('INDEX_id_seq'));
|
||||
$sm->dropSequence($platform->quoteIdentifier('LIKE_id_seq'));
|
||||
$sm->dropSequence($platform->quoteSingleIdentifier('INDEX_id_seq'));
|
||||
$sm->dropSequence($platform->quoteSingleIdentifier('LIKE_id_seq'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,15 @@ class GH10450Test extends OrmTestCase
|
||||
yield 'Entity class that redeclares a protected field inherited from a base entity' => [GH10450EntityChildProtected::class];
|
||||
yield 'Entity class that redeclares a protected field inherited from a mapped superclass' => [GH10450MappedSuperclassChildProtected::class];
|
||||
}
|
||||
|
||||
public function testFieldsOfTransientClassesAreNotConsideredDuplicate(): void
|
||||
{
|
||||
$em = $this->getTestEntityManager();
|
||||
|
||||
$metadata = $em->getClassMetadata(GH10450Cat::class);
|
||||
|
||||
self::assertArrayHasKey('id', $metadata->fieldMappings);
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
@@ -113,3 +122,26 @@ class GH10450MappedSuperclassChildProtected extends GH10450BaseMappedSuperclassP
|
||||
#[ORM\Column(type: 'text', name: 'child')]
|
||||
protected string $field;
|
||||
}
|
||||
|
||||
abstract class GH10450AbstractEntity
|
||||
{
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
protected int $id;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorMap(['cat' => GH10450Cat::class])]
|
||||
#[ORM\DiscriminatorColumn(name: 'type')]
|
||||
abstract class GH10450Animal extends GH10450AbstractEntity
|
||||
{
|
||||
#[ORM\Column(type: 'text', name: 'base')]
|
||||
private string $field;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH10450Cat extends GH10450Animal
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilterAndIndexedRelation;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'Category_Master')]
|
||||
class Category
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
public int $id;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
public string $name;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
public string $type;
|
||||
|
||||
public function __construct(string $name, string $type)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilterAndIndexedRelation;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\Filter\SQLFilter;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
class CategoryTypeSQLFilter extends SQLFilter
|
||||
{
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
|
||||
{
|
||||
if ($targetEntity->getName() === Category::class) {
|
||||
return sprintf('%s.%s = %s', $targetTableAlias, $targetEntity->fieldMappings['type']->fieldName, $this->getParameter('type'));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilterAndIndexedRelation;
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
final class ChangeFiltersTest extends OrmFunctionalTestCase
|
||||
{
|
||||
private const COMPANY_A = 'A';
|
||||
private const CAT_BAR = 'bar';
|
||||
private const CAT_FOO = 'foo';
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
Company::class,
|
||||
Category::class,
|
||||
]);
|
||||
}
|
||||
|
||||
private function prepareData(): void
|
||||
{
|
||||
$cat1 = new Category('cat1', self::CAT_FOO);
|
||||
$cat2 = new Category('cat2', self::CAT_BAR);
|
||||
$companyA = new Company(self::COMPANY_A, [$cat1, $cat2]);
|
||||
|
||||
$this->_em->persist($cat1);
|
||||
$this->_em->persist($cat2);
|
||||
$this->_em->persist($companyA);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
public function testIndexAliasUpdatedWithUpdatedFilter(): void
|
||||
{
|
||||
$this->prepareData();
|
||||
|
||||
$company = $this->_em->getRepository(Company::class)->findOneBy([]);
|
||||
|
||||
self::assertCount(2, $company->categories);
|
||||
self::assertEquals([self::CAT_FOO, self::CAT_BAR], $company->categories->map(static function (Category $c): string {
|
||||
return $c->type;
|
||||
})->getValues());
|
||||
|
||||
$this->_em->clear();
|
||||
$this->_em->getConfiguration()->addFilter(CategoryTypeSQLFilter::class, CategoryTypeSQLFilter::class);
|
||||
$this->_em->getFilters()->enable(CategoryTypeSQLFilter::class)->setParameter('type', self::CAT_FOO);
|
||||
|
||||
$company = $this->_em->getRepository(Company::class)->findOneBy([]);
|
||||
|
||||
self::assertCount(1, $company->categories);
|
||||
self::assertEquals([self::CAT_FOO], $company->categories->map(static function (Category $c): string {
|
||||
return $c->type;
|
||||
})->getValues());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilterAndIndexedRelation;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'Company_Master')]
|
||||
class Company
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
public int $id;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
public string $name;
|
||||
|
||||
/** @var Collection<int, Category> */
|
||||
#[ORM\ManyToMany(targetEntity: Category::class, fetch: 'EAGER', indexBy: 'type')]
|
||||
public Collection $categories;
|
||||
|
||||
/** @param Category[] $categories */
|
||||
public function __construct(string $name, array $categories)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->categories = new ArrayCollection($categories);
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,15 @@ namespace Doctrine\Tests\ORM\Hydration;
|
||||
use Doctrine\DBAL\Types\Type as DBALType;
|
||||
use Doctrine\ORM\Internal\Hydration\HydrationException;
|
||||
use Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Tests\DbalTypes\GH8565EmployeePayloadType;
|
||||
use Doctrine\Tests\DbalTypes\GH8565ManagerPayloadType;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\Company\CompanyPerson;
|
||||
use Doctrine\Tests\Models\Enums\Scale;
|
||||
use Doctrine\Tests\Models\Enums\Unit;
|
||||
use Doctrine\Tests\Models\GH8565\GH8565Employee;
|
||||
use Doctrine\Tests\Models\GH8565\GH8565Manager;
|
||||
use Doctrine\Tests\Models\GH8565\GH8565Person;
|
||||
@@ -155,4 +159,25 @@ class SimpleObjectHydratorTest extends HydrationTestCase
|
||||
$result = $hydrator->hydrateAll($stmt, $rsm);
|
||||
self::assertEquals($result[0], $expectedEntity);
|
||||
}
|
||||
|
||||
public function testNotListedValueInEnumArray(): void
|
||||
{
|
||||
$this->expectException(MappingException::class);
|
||||
$this->expectExceptionMessage('Case "unknown_case" is not listed in enum "Doctrine\Tests\Models\Enums\Unit"');
|
||||
$rsm = new ResultSetMapping();
|
||||
$rsm->addEntityResult(Scale::class, 's');
|
||||
$rsm->addFieldResult('s', 's__id', 'id');
|
||||
$rsm->addFieldResult('s', 's__supported_units', 'supportedUnits');
|
||||
$rsm->addEnumResult('s__supported_units', Unit::class);
|
||||
$resultSet = [
|
||||
[
|
||||
's__id' => 1,
|
||||
's__supported_units' => 'g,m,unknown_case',
|
||||
],
|
||||
];
|
||||
|
||||
$stmt = ArrayResultFactory::createWrapperResultFromArray($resultSet);
|
||||
$hydrator = new SimpleObjectHydrator($this->entityManager);
|
||||
$hydrator->hydrateAll($stmt, $rsm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ use Doctrine\Tests\OrmTestCase;
|
||||
use DoctrineGlobalArticle;
|
||||
use LogicException;
|
||||
use PHPUnit\Framework\Attributes\Group as TestGroup;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use ReflectionClass;
|
||||
use stdClass;
|
||||
|
||||
@@ -1074,6 +1075,7 @@ class ClassMetadataTest extends OrmTestCase
|
||||
$metadata->addLifecycleCallback('foo', 'bar');
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testGettingAnFQCNForNullIsDeprecated(): void
|
||||
{
|
||||
$metadata = new ClassMetadata(self::class);
|
||||
@@ -1112,6 +1114,7 @@ class ClassMetadataTest extends OrmTestCase
|
||||
);
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testDiscriminatorMapWithSameClassMultipleTimesDeprecated(): void
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issues/3519');
|
||||
|
||||
@@ -6,12 +6,14 @@ namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class TableMappingTest extends TestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testDeprecationOnIndexesPropertyIsTriggered(): void
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11357');
|
||||
@@ -19,6 +21,7 @@ final class TableMappingTest extends TestCase
|
||||
new Table(indexes: []);
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testDeprecationOnUniqueConstraintsPropertyIsTriggered(): void
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11357');
|
||||
|
||||
@@ -614,7 +614,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
||||
}
|
||||
|
||||
if (isset($this->_usedModelSets['directorytree'])) {
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteIdentifier('file'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteSingleIdentifier('file'));
|
||||
// MySQL doesn't know deferred deletions therefore only executing the second query gives errors.
|
||||
$conn->executeStatement('DELETE FROM Directory WHERE parentDirectory_id IS NOT NULL');
|
||||
$conn->executeStatement('DELETE FROM Directory');
|
||||
@@ -707,17 +707,17 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
||||
$conn->executeStatement(
|
||||
sprintf(
|
||||
'UPDATE %s SET %s = NULL',
|
||||
$platform->quoteIdentifier('quote-address'),
|
||||
$platform->quoteIdentifier('user-id'),
|
||||
$platform->quoteSingleIdentifier('quote-address'),
|
||||
$platform->quoteSingleIdentifier('user-id'),
|
||||
),
|
||||
);
|
||||
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteIdentifier('quote-users-groups'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteIdentifier('quote-group'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteIdentifier('quote-phone'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteIdentifier('quote-user'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteIdentifier('quote-address'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteIdentifier('quote-city'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteSingleIdentifier('quote-users-groups'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteSingleIdentifier('quote-group'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteSingleIdentifier('quote-phone'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteSingleIdentifier('quote-user'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteSingleIdentifier('quote-address'));
|
||||
$conn->executeStatement('DELETE FROM ' . $platform->quoteSingleIdentifier('quote-city'));
|
||||
}
|
||||
|
||||
if (isset($this->_usedModelSets['vct_onetoone'])) {
|
||||
|
||||
Reference in New Issue
Block a user