mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19912de927 | ||
|
|
737cca5b78 | ||
|
|
9e999ea1ff | ||
|
|
6755bb0c7b | ||
|
|
c2a49327a7 | ||
|
|
9bd7242376 | ||
|
|
fff085b63f | ||
|
|
5ad5b11ae1 | ||
|
|
c12fd2cb94 | ||
|
|
44d5d4a779 | ||
|
|
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 }}
|
||||
|
||||
@@ -1368,8 +1368,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::
|
||||
|
||||
@@ -1409,7 +1408,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
|
||||
@@ -1418,7 +1417,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;
|
||||
|
||||
@@ -1442,7 +1441,6 @@ Is essentially the same as following:
|
||||
joinColumn:
|
||||
name: shipment_id
|
||||
referencedColumnName: id
|
||||
nullable: false
|
||||
|
||||
If you accept these defaults, you can reduce the mapping code to a
|
||||
minimum.
|
||||
|
||||
@@ -3144,6 +3144,15 @@ parameters:
|
||||
count: 1
|
||||
path: src/Persisters/SqlValueVisitor.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Class Doctrine\\ORM\\Proxy\\Autoloader extends deprecated class Doctrine\\Common\\Proxy\\Autoloader\:
|
||||
The Autoloader class is deprecated since doctrine/common 3\.5\.$#
|
||||
'''
|
||||
identifier: class.extendsDeprecatedClass
|
||||
count: 1
|
||||
path: src/Proxy/Autoloader.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Proxy\\DefaultProxyClassNameResolver\:\:resolveClassName\(\) should return class\-string\<T of object\> but returns class\-string\<Doctrine\\Persistence\\Proxy\<T of object\>\>\|class\-string\<T of object\>\.$#'
|
||||
identifier: return.type
|
||||
@@ -3186,6 +3195,42 @@ parameters:
|
||||
count: 1
|
||||
path: src/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Call to method __construct\(\) of deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
|
||||
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
|
||||
'''
|
||||
identifier: staticMethod.deprecatedClass
|
||||
count: 1
|
||||
path: src/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Call to method generateProxyClasses\(\) of deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
|
||||
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
|
||||
'''
|
||||
identifier: staticMethod.deprecatedClass
|
||||
count: 1
|
||||
path: src/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Call to method getProxy\(\) of deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
|
||||
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
|
||||
'''
|
||||
identifier: staticMethod.deprecatedClass
|
||||
count: 1
|
||||
path: src/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Class Doctrine\\ORM\\Proxy\\ProxyFactory extends deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
|
||||
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
|
||||
'''
|
||||
identifier: class.extendsDeprecatedClass
|
||||
count: 1
|
||||
path: src/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: '#^Comparison operation "\<" between 0\|1\|2\|3\|4 and 0 is always false\.$#'
|
||||
identifier: smaller.alwaysFalse
|
||||
@@ -3198,6 +3243,15 @@ parameters:
|
||||
count: 1
|
||||
path: src/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Instantiation of deprecated class Doctrine\\Common\\Proxy\\ProxyGenerator\:
|
||||
The ProxyGenerator class is deprecated since doctrine/common 3\.5\.$#
|
||||
'''
|
||||
identifier: new.deprecated
|
||||
count: 1
|
||||
path: src/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createCloner\(\) has Doctrine\\ORM\\EntityNotFoundException in PHPDoc @throws tag but it''s not thrown\.$#'
|
||||
identifier: throws.unusedType
|
||||
@@ -3336,6 +3390,15 @@ parameters:
|
||||
count: 1
|
||||
path: src/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Return type of method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:getProxy\(\) has typehint with deprecated interface Doctrine\\Common\\Proxy\\Proxy\:
|
||||
The Proxy interface is deprecated since doctrine/common 3\.5\.$#
|
||||
'''
|
||||
identifier: return.deprecatedInterface
|
||||
count: 1
|
||||
path: src/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Cache\\\\QueryCacheProfile'' and ''getResultCache'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
|
||||
@@ -43,7 +43,7 @@ class CollectionCacheKey extends CacheKey
|
||||
* @param string $association The field name that represents the association.
|
||||
* @param array<string, mixed> $ownerIdentifier The identifier of the owning entity.
|
||||
*/
|
||||
public function __construct($entityClass, $association, array $ownerIdentifier)
|
||||
public function __construct($entityClass, $association, array $ownerIdentifier, string $filterHash = '')
|
||||
{
|
||||
ksort($ownerIdentifier);
|
||||
|
||||
@@ -51,6 +51,8 @@ class CollectionCacheKey extends CacheKey
|
||||
$this->entityClass = (string) $entityClass;
|
||||
$this->association = (string) $association;
|
||||
|
||||
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association);
|
||||
$filterHash = $filterHash === '' ? '' : '_' . $filterHash;
|
||||
|
||||
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association . $filterHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,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;
|
||||
@@ -55,6 +56,9 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
/** @var string */
|
||||
protected $regionName;
|
||||
|
||||
/** @var FilterCollection */
|
||||
protected $filters;
|
||||
|
||||
/** @var CollectionHydrator */
|
||||
protected $hydrator;
|
||||
|
||||
@@ -76,6 +80,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
$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();
|
||||
@@ -189,7 +194,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
public function count(PersistentCollection $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());
|
||||
$entry = $this->region->get($key);
|
||||
|
||||
if ($entry !== null) {
|
||||
@@ -241,7 +246,8 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
$key = new CollectionCacheKey(
|
||||
$this->sourceEntity->rootEntityName,
|
||||
$this->association['fieldName'],
|
||||
$this->uow->getEntityIdentifier($collection->getOwner())
|
||||
$this->uow->getEntityIdentifier($collection->getOwner()),
|
||||
$this->filters->getHash()
|
||||
);
|
||||
|
||||
$this->region->evict($key);
|
||||
|
||||
@@ -45,7 +45,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
|
||||
public function delete(PersistentCollection $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());
|
||||
|
||||
$this->persister->delete($collection);
|
||||
|
||||
@@ -65,7 +65,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 || isset($this->association['orderBy'])) {
|
||||
|
||||
@@ -68,7 +68,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
public function delete(PersistentCollection $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);
|
||||
|
||||
$this->persister->delete($collection);
|
||||
@@ -98,7 +98,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) {
|
||||
|
||||
@@ -22,9 +22,11 @@ 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\UnitOfWork;
|
||||
|
||||
use function array_merge;
|
||||
use function func_get_args;
|
||||
use function serialize;
|
||||
use function sha1;
|
||||
|
||||
@@ -62,6 +64,9 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/** @var Cache */
|
||||
protected $cache;
|
||||
|
||||
/** @var FilterCollection */
|
||||
protected $filters;
|
||||
|
||||
/** @var CacheLogger|null */
|
||||
protected $cacheLogger;
|
||||
|
||||
@@ -91,6 +96,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$this->region = $region;
|
||||
$this->persister = $persister;
|
||||
$this->cache = $em->getCache();
|
||||
$this->filters = $em->getFilters();
|
||||
$this->regionName = $region->getName();
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->metadataFactory = $em->getMetadataFactory();
|
||||
@@ -261,7 +267,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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -524,7 +530,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) {
|
||||
@@ -559,7 +565,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) {
|
||||
@@ -611,12 +617,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*
|
||||
* @return CollectionCacheKey
|
||||
*/
|
||||
protected function buildCollectionCacheKey(array $association, $ownerId)
|
||||
protected function buildCollectionCacheKey(array $association, $ownerId/*, string $filterHash */)
|
||||
{
|
||||
$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
|
||||
$ownerId,
|
||||
$filterHash
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use function array_keys;
|
||||
use function array_search;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function key;
|
||||
use function reset;
|
||||
use function sprintf;
|
||||
@@ -143,14 +144,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
|
||||
);
|
||||
|
||||
@@ -32,8 +32,13 @@ trait ReflectionBasedDriver
|
||||
|| $metadata->isInheritedEmbeddedClass($property->name);
|
||||
}
|
||||
|
||||
/** @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
|
||||
|
||||
@@ -1560,7 +1560,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'])) {
|
||||
|
||||
@@ -69,6 +69,13 @@ class ResultSetMapping
|
||||
*/
|
||||
public $fieldMappings = [];
|
||||
|
||||
/**
|
||||
* Map field names for each class to alias
|
||||
*
|
||||
* @var array<class-string, array<string, array<string, string>>>
|
||||
*/
|
||||
public $columnAliasMappings = [];
|
||||
|
||||
/**
|
||||
* Maps column names in the result set to the alias/field name to use in the mapped result.
|
||||
*
|
||||
@@ -335,7 +342,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;
|
||||
@@ -344,6 +354,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.
|
||||
*
|
||||
|
||||
@@ -32,6 +32,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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,3 +188,38 @@ class GH10450MappedSuperclassChildProtected extends GH10450BaseMappedSuperclassP
|
||||
*/
|
||||
protected $field;
|
||||
}
|
||||
|
||||
abstract class GH10450AbstractEntity
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorMap({ "cat": "GH10450Cat" })
|
||||
* @ORM\DiscriminatorColumn(name="type")
|
||||
*/
|
||||
abstract class GH10450Animal extends GH10450AbstractEntity
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="text", name="base")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH10450Cat extends GH10450Animal
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $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,46 @@
|
||||
<?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")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity="Category", fetch="EAGER", indexBy="type")
|
||||
*
|
||||
* @var Collection<int, Category>
|
||||
*/
|
||||
public $categories;
|
||||
|
||||
/** @param Category[] $categories */
|
||||
public function __construct(string $name, array $categories)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->categories = new ArrayCollection($categories);
|
||||
}
|
||||
}
|
||||
@@ -7,12 +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 +158,28 @@ class SimpleObjectHydratorTest extends HydrationTestCase
|
||||
$result = $hydrator->hydrateAll($stmt, $rsm);
|
||||
self::assertEquals($result[0], $expectedEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8.1
|
||||
*/
|
||||
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::createFromArray($resultSet);
|
||||
$hydrator = new SimpleObjectHydrator($this->entityManager);
|
||||
$hydrator->hydrateAll($stmt, $rsm);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user