mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
716fc97b70 | ||
|
|
a809a71aa6 | ||
|
|
4617a5e310 | ||
|
|
e77c5a3a5e | ||
|
|
bd4449c462 | ||
|
|
12e0cefba1 | ||
|
|
507c73c073 | ||
|
|
ba0ea8953b | ||
|
|
e62571c8f4 | ||
|
|
53763d432b | ||
|
|
154920a0b3 | ||
|
|
98f9de2af6 | ||
|
|
cb497826be | ||
|
|
ba0d3842a9 | ||
|
|
29e1935c65 | ||
|
|
33e02b2796 | ||
|
|
26f7588479 | ||
|
|
83c81f6c41 | ||
|
|
791667a9e4 | ||
|
|
c02ddd692f | ||
|
|
151a3fba9d | ||
|
|
1e056842fe | ||
|
|
ebb0c67ecc | ||
|
|
abd9186d00 | ||
|
|
08d3f72755 | ||
|
|
ee5b2ce5b0 | ||
|
|
d54c9678d0 | ||
|
|
859e6af972 | ||
|
|
8c3c9f115d | ||
|
|
779781173a | ||
|
|
2df4d75565 | ||
|
|
dc21ab63ac | ||
|
|
c9c493b2fe | ||
|
|
e4c27092cd | ||
|
|
adadf1fb90 | ||
|
|
380b5b62ef | ||
|
|
a0e7a59572 | ||
|
|
fb6c0c1d8b | ||
|
|
fcf1116e33 | ||
|
|
78dc63df27 | ||
|
|
c0dfba2ef3 | ||
|
|
b1f553eba3 | ||
|
|
0c4aac5a35 | ||
|
|
e0081b59be | ||
|
|
4bd574daee | ||
|
|
b59189ab48 | ||
|
|
6290747bf9 | ||
|
|
b6f4220493 | ||
|
|
afbf293c94 | ||
|
|
b7860c782b | ||
|
|
7baef1e120 | ||
|
|
9a24ce5fad | ||
|
|
9fcb8f1305 | ||
|
|
5049b615c5 | ||
|
|
1051817d92 | ||
|
|
517d038e5b | ||
|
|
3db79ebbf3 | ||
|
|
a2faeb9a26 | ||
|
|
3764ebf7a3 | ||
|
|
a7d5adb3ce | ||
|
|
6f507c322a | ||
|
|
54013671a7 | ||
|
|
f5dea25b6c |
@@ -11,29 +11,41 @@
|
||||
"slug": "latest",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.2",
|
||||
"branchName": "3.2.x",
|
||||
"slug": "3.2",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.1",
|
||||
"branchName": "3.1.x",
|
||||
"slug": "3.1",
|
||||
"upcoming": true
|
||||
"current": true
|
||||
},
|
||||
{
|
||||
"name": "3.0",
|
||||
"branchName": "3.0.x",
|
||||
"slug": "3.0",
|
||||
"current": true
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.20",
|
||||
"branchName": "2.20.x",
|
||||
"slug": "2.20",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.19",
|
||||
"branchName": "2.19.x",
|
||||
"slug": "2.19",
|
||||
"upcoming": true
|
||||
"maintained": true
|
||||
},
|
||||
{
|
||||
"name": "2.18",
|
||||
"branchName": "2.18.x",
|
||||
"slug": "2.18",
|
||||
"maintained": true
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.17",
|
||||
|
||||
24
README.md
24
README.md
@@ -1,7 +1,7 @@
|
||||
| [4.0.x][4.0] | [3.1.x][3.1] | [3.0.x][3.0] | [2.19.x][2.19] | [2.18.x][2.18] |
|
||||
|:------------------------------------------------------:|:------------------------------------------------------:|:-------------------------------------------------------:|:--------------------------------------------------------:|:---------------------------------------------------------:|
|
||||
| [![Build status][4.0 image]][4.0] | [![Build status][3.1 image]][3.1] | [![Build status][3.0 image]][3.0] | [![Build status][2.19 image]][2.19] | [![Build status][2.18 image]][2.18] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.1 coverage image]][3.1 coverage] | [![Coverage Status][3.0 coverage image]][3.0 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] | [![Coverage Status][2.18 coverage image]][2.18 coverage] |
|
||||
| [4.0.x][4.0] | [3.2.x][3.2] | [3.1.x][3.1] | [2.20.x][2.20] | [2.19.x][2.19] |
|
||||
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
|
||||
| [![Build status][4.0 image]][4.0] | [![Build status][3.2 image]][3.2] | [![Build status][3.1 image]][3.1] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][3.1 coverage image]][3.1 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] |
|
||||
|
||||
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
|
||||
|
||||
@@ -22,19 +22,19 @@ without requiring unnecessary code duplication.
|
||||
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
|
||||
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
|
||||
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
|
||||
[3.2 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.2.x
|
||||
[3.2]: https://github.com/doctrine/orm/tree/3.2.x
|
||||
[3.2 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.2.x/graph/badge.svg
|
||||
[3.2 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.2.x
|
||||
[3.1 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.1.x
|
||||
[3.1]: https://github.com/doctrine/orm/tree/3.1.x
|
||||
[3.1 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.1.x/graph/badge.svg
|
||||
[3.1 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.1.x
|
||||
[3.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.0.x
|
||||
[3.0]: https://github.com/doctrine/orm/tree/3.0.x
|
||||
[3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg
|
||||
[3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x
|
||||
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
|
||||
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
|
||||
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
|
||||
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
|
||||
[2.19 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.19.x
|
||||
[2.19]: https://github.com/doctrine/orm/tree/2.19.x
|
||||
[2.19 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.19.x/graph/badge.svg
|
||||
[2.19 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.19.x
|
||||
[2.18 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.18.x
|
||||
[2.18]: https://github.com/doctrine/orm/tree/2.18.x
|
||||
[2.18 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.18.x/graph/badge.svg
|
||||
[2.18 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.18.x
|
||||
|
||||
40
UPGRADE.md
40
UPGRADE.md
@@ -1,3 +1,26 @@
|
||||
# Upgrade to 3.1
|
||||
|
||||
## Deprecate `Doctrine\ORM\Mapping\ReflectionEnumProperty`
|
||||
|
||||
This class is deprecated and will be removed in 4.0.
|
||||
Instead, use `Doctrine\Persistence\Reflection\EnumReflectionProperty` from
|
||||
`doctrine/persistence`.
|
||||
|
||||
## Deprecate passing null to `ClassMetadata::fullyQualifiedClassName()`
|
||||
|
||||
Passing `null` to `Doctrine\ORM\ClassMetadata::fullyQualifiedClassName()` is
|
||||
deprecated and will no longer be possible in 4.0.
|
||||
|
||||
## Deprecate array access
|
||||
|
||||
Using array access on instances of the following classes is deprecated:
|
||||
|
||||
- `Doctrine\ORM\Mapping\DiscriminatorColumnMapping`
|
||||
- `Doctrine\ORM\Mapping\EmbedClassMapping`
|
||||
- `Doctrine\ORM\Mapping\FieldMapping`
|
||||
- `Doctrine\ORM\Mapping\JoinColumnMapping`
|
||||
- `Doctrine\ORM\Mapping\JoinTableMapping`
|
||||
|
||||
# Upgrade to 3.0
|
||||
|
||||
## BC BREAK: Calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association now throws an exception
|
||||
@@ -674,6 +697,23 @@ following classes and methods:
|
||||
|
||||
Use `toIterable()` instead.
|
||||
|
||||
# Upgrade to 2.19
|
||||
|
||||
## Deprecate calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association
|
||||
|
||||
Calling
|
||||
`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with
|
||||
the owning side of an association returns `null`, which is undocumented, and
|
||||
wrong according to the phpdoc of the parent method.
|
||||
|
||||
If you do not know whether you are on the owning or inverse side of an association,
|
||||
you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()`
|
||||
to find out.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Query\Lexer::T_*` constants
|
||||
|
||||
Use `Doctrine\ORM\Query\TokenType::T_*` instead.
|
||||
|
||||
# Upgrade to 2.17
|
||||
|
||||
## Deprecate annotations classes for named queries
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
"php": "^8.1",
|
||||
"composer-runtime-api": "^2",
|
||||
"ext-ctype": "*",
|
||||
"doctrine/collections": "^2.1",
|
||||
"doctrine/collections": "^2.2",
|
||||
"doctrine/dbal": "^3.8.2 || ^4",
|
||||
"doctrine/deprecations": "^0.5.3 || ^1",
|
||||
"doctrine/event-manager": "^1.2 || ^2",
|
||||
"doctrine/inflector": "^1.4 || ^2.0",
|
||||
"doctrine/instantiator": "^1.3 || ^2",
|
||||
"doctrine/lexer": "^3",
|
||||
"doctrine/persistence": "^3.1.1",
|
||||
"doctrine/persistence": "^3.3.1",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"symfony/console": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/var-exporter": "~6.2.13 || ^6.3.2 || ^7.0"
|
||||
|
||||
@@ -290,11 +290,6 @@
|
||||
<code><![CDATA[$repositoryClassName]]></code>
|
||||
</ArgumentTypeCoercion>
|
||||
</file>
|
||||
<file src="src/Mapping/Builder/EntityListenerBuilder.php">
|
||||
<PossiblyNullArgument>
|
||||
<code><![CDATA[$class]]></code>
|
||||
</PossiblyNullArgument>
|
||||
</file>
|
||||
<file src="src/Mapping/ClassMetadata.php">
|
||||
<DeprecatedProperty>
|
||||
<code><![CDATA[$this->columnNames]]></code>
|
||||
@@ -322,8 +317,6 @@
|
||||
<code><![CDATA[$entity]]></code>
|
||||
</ParamNameMismatch>
|
||||
<PossiblyNullArgument>
|
||||
<code><![CDATA[$class]]></code>
|
||||
<code><![CDATA[$className]]></code>
|
||||
<code><![CDATA[$mapping['targetEntity']]]></code>
|
||||
<code><![CDATA[$mapping['targetEntity']]]></code>
|
||||
<code><![CDATA[$parentReflFields[$embeddedClass->declaredField]]]></code>
|
||||
@@ -1034,10 +1027,6 @@
|
||||
</file>
|
||||
<file src="src/QueryBuilder.php">
|
||||
<ArgumentTypeCoercion>
|
||||
<code><![CDATA[$having]]></code>
|
||||
<code><![CDATA[$having]]></code>
|
||||
<code><![CDATA[$where]]></code>
|
||||
<code><![CDATA[$where]]></code>
|
||||
<code><![CDATA[[$rootAlias => $join]]]></code>
|
||||
<code><![CDATA[[$rootAlias => $join]]]></code>
|
||||
</ArgumentTypeCoercion>
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Cache\Persister\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
@@ -17,7 +18,6 @@ use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\TimestampCacheKey;
|
||||
use Doctrine\ORM\Cache\TimestampRegion;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
@@ -34,8 +34,6 @@ use function sha1;
|
||||
|
||||
abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
protected UnitOfWork $uow;
|
||||
protected ClassMetadataFactory $metadataFactory;
|
||||
|
||||
@@ -204,8 +202,8 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* Generates a string of currently query
|
||||
*
|
||||
* @param string[]|Criteria $criteria
|
||||
* @param string[]|null $orderBy
|
||||
* @param string[]|Criteria $criteria
|
||||
* @param array<string, Order>|null $orderBy
|
||||
*/
|
||||
protected function getHash(
|
||||
string $query,
|
||||
@@ -429,7 +427,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria): array
|
||||
{
|
||||
$orderBy = self::getCriteriaOrderings($criteria);
|
||||
$orderBy = $criteria->orderings();
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->persister->getSelectSQL($criteria);
|
||||
|
||||
@@ -67,7 +67,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
$time = $this->getLockTime($filename);
|
||||
$content = $this->getLockContent($filename);
|
||||
|
||||
if (! $content || ! $time) {
|
||||
if ($content === false || $time === false) {
|
||||
@unlink($filename);
|
||||
|
||||
return false;
|
||||
@@ -156,12 +156,10 @@ class FileLockRegion implements ConcurrentRegion
|
||||
{
|
||||
// The check below is necessary because on some platforms glob returns false
|
||||
// when nothing matched (even though no errors occurred)
|
||||
$filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION));
|
||||
$filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION)) ?: [];
|
||||
|
||||
if ($filenames) {
|
||||
foreach ($filenames as $filename) {
|
||||
@unlink($filename);
|
||||
}
|
||||
foreach ($filenames as $filename) {
|
||||
@unlink($filename);
|
||||
}
|
||||
|
||||
return $this->region->evictAll();
|
||||
@@ -176,7 +174,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
$lock = Lock::createLockRead();
|
||||
$filename = $this->getLockFileName($key);
|
||||
|
||||
if (! @file_put_contents($filename, $lock->value, LOCK_EX)) {
|
||||
if (@file_put_contents($filename, $lock->value, LOCK_EX) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
|
||||
use function array_map;
|
||||
use function enum_exists;
|
||||
use function method_exists;
|
||||
use function strtoupper;
|
||||
|
||||
trait CriteriaOrderings
|
||||
{
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*
|
||||
* @psalm-suppress DeprecatedMethod We need to call the deprecated API if the new one does not exist yet.
|
||||
*/
|
||||
private static function getCriteriaOrderings(Criteria $criteria): array
|
||||
{
|
||||
if (! method_exists(Criteria::class, 'orderings')) {
|
||||
return $criteria->getOrderings();
|
||||
}
|
||||
|
||||
return array_map(
|
||||
static fn (Order $order): string => $order->value,
|
||||
$criteria->orderings(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $orderings
|
||||
*
|
||||
* @return array<string, string>|array<string, Order>
|
||||
*/
|
||||
private static function mapToOrderEnumIfAvailable(array $orderings): array
|
||||
{
|
||||
if (! enum_exists(Order::class)) {
|
||||
return $orderings;
|
||||
}
|
||||
|
||||
return array_map(
|
||||
static fn (string $order): Order => Order::from(strtoupper($order)),
|
||||
$orderings,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
}
|
||||
|
||||
// handle fetch-joined owning side bi-directional one-to-one associations
|
||||
if ($assoc->inversedBy) {
|
||||
if ($assoc->inversedBy !== null) {
|
||||
$class = $this->getClassMetadata($className);
|
||||
$inverseAssoc = $class->associationMappings[$assoc->inversedBy];
|
||||
|
||||
@@ -439,7 +439,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
if ($relation->isOwningSide()) {
|
||||
// TODO: Just check hints['fetched'] here?
|
||||
// If there is an inverse mapping on the target class its bidirectional
|
||||
if ($relation->inversedBy) {
|
||||
if ($relation->inversedBy !== null) {
|
||||
$inverseAssoc = $targetClass->associationMappings[$relation->inversedBy];
|
||||
if ($inverseAssoc->isToOne()) {
|
||||
$targetClass->reflFields[$inverseAssoc->fieldName]->setValue($element, $parentObject);
|
||||
|
||||
55
src/Internal/NoUnknownNamedArguments.php
Normal file
55
src/Internal/NoUnknownNamedArguments.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal;
|
||||
|
||||
use BadMethodCallException;
|
||||
|
||||
use function array_filter;
|
||||
use function array_is_list;
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function debug_backtrace;
|
||||
use function implode;
|
||||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
|
||||
/**
|
||||
* Checks if a variadic parameter contains unexpected named arguments.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait NoUnknownNamedArguments
|
||||
{
|
||||
/**
|
||||
* @param TItem[] $parameter
|
||||
*
|
||||
* @template TItem
|
||||
* @psalm-assert list<TItem> $parameter
|
||||
*/
|
||||
private static function validateVariadicParameter(array $parameter): void
|
||||
{
|
||||
if (array_is_list($parameter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
[, $trace] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
assert(isset($trace['class']));
|
||||
|
||||
$additionalArguments = array_values(array_filter(
|
||||
array_keys($parameter),
|
||||
is_string(...),
|
||||
));
|
||||
|
||||
throw new BadMethodCallException(sprintf(
|
||||
'Invalid call to %s::%s(), unknown named arguments: %s',
|
||||
$trace['class'],
|
||||
$trace['function'],
|
||||
implode(', ', $additionalArguments),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use InvalidArgumentException;
|
||||
|
||||
use function property_exists;
|
||||
@@ -14,12 +15,26 @@ trait ArrayAccessImplementation
|
||||
/** @param string $offset */
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11211',
|
||||
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
|
||||
static::class,
|
||||
);
|
||||
|
||||
return isset($this->$offset);
|
||||
}
|
||||
|
||||
/** @param string $offset */
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11211',
|
||||
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
|
||||
static::class,
|
||||
);
|
||||
|
||||
if (! property_exists($this, $offset)) {
|
||||
throw new InvalidArgumentException('Undefined property: ' . $offset);
|
||||
}
|
||||
@@ -30,12 +45,26 @@ trait ArrayAccessImplementation
|
||||
/** @param string $offset */
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11211',
|
||||
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
|
||||
static::class,
|
||||
);
|
||||
|
||||
$this->$offset = $value;
|
||||
}
|
||||
|
||||
/** @param string $offset */
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11211',
|
||||
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
|
||||
static::class,
|
||||
);
|
||||
|
||||
$this->$offset = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ class ClassMetadataBuilder
|
||||
): ClassMetadataBuilder {
|
||||
$builder = $this->createManyToOne($name, $targetEntity);
|
||||
|
||||
if ($inversedBy) {
|
||||
if ($inversedBy !== null) {
|
||||
$builder->inversedBy($inversedBy);
|
||||
}
|
||||
|
||||
@@ -348,7 +348,7 @@ class ClassMetadataBuilder
|
||||
): ClassMetadataBuilder {
|
||||
$builder = $this->createOneToOne($name, $targetEntity);
|
||||
|
||||
if ($inversedBy) {
|
||||
if ($inversedBy !== null) {
|
||||
$builder->inversedBy($inversedBy);
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ class ClassMetadataBuilder
|
||||
): ClassMetadataBuilder {
|
||||
$builder = $this->createManyToMany($name, $targetEntity);
|
||||
|
||||
if ($inversedBy) {
|
||||
if ($inversedBy !== null) {
|
||||
$builder->inversedBy($inversedBy);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,18 +4,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
|
||||
use ReflectionProperty;
|
||||
|
||||
final class ChainTypedFieldMapper implements TypedFieldMapper
|
||||
{
|
||||
/**
|
||||
* @readonly
|
||||
* @var TypedFieldMapper[] $typedFieldMappers
|
||||
*/
|
||||
use NoUnknownNamedArguments;
|
||||
|
||||
/** @var list<TypedFieldMapper> $typedFieldMappers */
|
||||
private readonly array $typedFieldMappers;
|
||||
|
||||
public function __construct(TypedFieldMapper ...$typedFieldMappers)
|
||||
{
|
||||
self::validateVariadicParameter($typedFieldMappers);
|
||||
|
||||
$this->typedFieldMappers = $typedFieldMappers;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Mapping;
|
||||
use BackedEnum;
|
||||
use BadMethodCallException;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\Instantiator\Instantiator;
|
||||
use Doctrine\Instantiator\InstantiatorInterface;
|
||||
use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation;
|
||||
@@ -14,6 +15,7 @@ use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Id\AbstractIdGenerator;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\ReflectionService;
|
||||
use Doctrine\Persistence\Reflection\EnumReflectionProperty;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use ReflectionClass;
|
||||
@@ -821,7 +823,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
assert($childProperty !== null);
|
||||
|
||||
if (isset($mapping->enumType)) {
|
||||
$childProperty = new ReflectionEnumProperty(
|
||||
$childProperty = new EnumReflectionProperty(
|
||||
$childProperty,
|
||||
$mapping->enumType,
|
||||
);
|
||||
@@ -840,7 +842,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
: $this->getAccessibleProperty($reflService, $this->name, $field);
|
||||
|
||||
if (isset($mapping->enumType) && $this->reflFields[$field] !== null) {
|
||||
$this->reflFields[$field] = new ReflectionEnumProperty(
|
||||
$this->reflFields[$field] = new EnumReflectionProperty(
|
||||
$this->reflFields[$field],
|
||||
$mapping->enumType,
|
||||
);
|
||||
@@ -2004,6 +2006,12 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
*/
|
||||
public function setCustomRepositoryClass(string|null $repositoryClassName): void
|
||||
{
|
||||
if ($repositoryClassName === null) {
|
||||
$this->customRepositoryClassName = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
|
||||
}
|
||||
|
||||
@@ -2476,11 +2484,25 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
return $assoc->mappedBy;
|
||||
}
|
||||
|
||||
/** @return string|null null if the input value is null */
|
||||
/**
|
||||
* @param C $className
|
||||
*
|
||||
* @return string|null null if and only if the input value is null
|
||||
* @psalm-return (C is class-string ? class-string : (C is string ? string : null))
|
||||
*
|
||||
* @template C of string|null
|
||||
*/
|
||||
public function fullyQualifiedClassName(string|null $className): string|null
|
||||
{
|
||||
if (empty($className)) {
|
||||
return $className;
|
||||
if ($className === null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11294',
|
||||
'Passing null to %s is deprecated and will not be supported in Doctrine ORM 4.0',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! str_contains($className, '\\') && $this->namespace) {
|
||||
@@ -2523,12 +2545,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
throw MappingException::missingEmbeddedClass($mapping['fieldName']);
|
||||
}
|
||||
|
||||
$fqcn = $this->fullyQualifiedClassName($mapping['class']);
|
||||
|
||||
assert($fqcn !== null);
|
||||
|
||||
$this->embeddedClasses[$mapping['fieldName']] = EmbeddedClassMapping::fromMappingArray([
|
||||
'class' => $fqcn,
|
||||
'class' => $this->fullyQualifiedClassName($mapping['class']),
|
||||
'columnPrefix' => $mapping['columnPrefix'] ?? null,
|
||||
'declaredField' => $mapping['declaredField'] ?? null,
|
||||
'originalField' => $mapping['originalField'] ?? null,
|
||||
|
||||
@@ -30,7 +30,7 @@ final class ReflectionEmbeddedProperty extends ReflectionProperty
|
||||
private readonly ReflectionProperty $childProperty,
|
||||
private readonly string $embeddedClass,
|
||||
) {
|
||||
parent::__construct($childProperty->class, $childProperty->name);
|
||||
parent::__construct($childProperty->getDeclaringClass()->name, $childProperty->getName());
|
||||
}
|
||||
|
||||
public function getValue(object|null $object = null): mixed
|
||||
|
||||
@@ -11,6 +11,7 @@ use ValueError;
|
||||
use function array_map;
|
||||
use function is_array;
|
||||
|
||||
/** @deprecated use Doctrine\Persistence\Reflection\EnumReflectionProperty instead */
|
||||
final class ReflectionEnumProperty extends ReflectionProperty
|
||||
{
|
||||
/** @param class-string<BackedEnum> $enumType */
|
||||
|
||||
@@ -8,8 +8,8 @@ use Doctrine\Common\Collections\AbstractLazyCollection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ToManyAssociationMapping;
|
||||
@@ -24,6 +24,7 @@ use function array_walk;
|
||||
use function assert;
|
||||
use function is_object;
|
||||
use function spl_object_id;
|
||||
use function strtoupper;
|
||||
|
||||
/**
|
||||
* A PersistentCollection represents a collection of elements that have persistent state.
|
||||
@@ -41,8 +42,6 @@ use function spl_object_id;
|
||||
*/
|
||||
final class PersistentCollection extends AbstractLazyCollection implements Selectable
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
/**
|
||||
* A snapshot of the collection at the moment it was fetched from the database.
|
||||
* This is used to create a diff of the collection at commit time.
|
||||
@@ -588,9 +587,12 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
|
||||
$criteria = clone $criteria;
|
||||
$criteria->where($expression);
|
||||
$criteria->orderBy(self::mapToOrderEnumIfAvailable(
|
||||
self::getCriteriaOrderings($criteria) ?: $association->orderBy(),
|
||||
));
|
||||
$criteria->orderBy(
|
||||
$criteria->orderings() ?: array_map(
|
||||
static fn (string $order): Order => Order::from(strtoupper($order)),
|
||||
$association->orderBy(),
|
||||
),
|
||||
);
|
||||
|
||||
$persister = $this->getUnitOfWork()->getEntityPersister($association->targetEntity);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Expr\Comparison;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\InverseSideMapping;
|
||||
@@ -33,8 +32,6 @@ use function sprintf;
|
||||
*/
|
||||
class ManyToManyPersister extends AbstractCollectionPersister
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
public function delete(PersistentCollection $collection): void
|
||||
{
|
||||
$mapping = $this->getMapping($collection);
|
||||
@@ -735,7 +732,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
|
||||
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string
|
||||
{
|
||||
$orderings = self::getCriteriaOrderings($criteria);
|
||||
$orderings = $criteria->orderings();
|
||||
if ($orderings) {
|
||||
$orderBy = [];
|
||||
foreach ($orderings as $name => $direction) {
|
||||
@@ -744,7 +741,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
$targetClass,
|
||||
$this->platform,
|
||||
);
|
||||
$orderBy[] = $field . ' ' . $direction;
|
||||
$orderBy[] = $field . ' ' . $direction->value;
|
||||
}
|
||||
|
||||
return ' ORDER BY ' . implode(', ', $orderBy);
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Persisters\Entity;
|
||||
use BackedEnum;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Expr\Comparison;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
@@ -16,7 +17,6 @@ use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\JoinColumnMapping;
|
||||
@@ -98,7 +98,6 @@ use function trim;
|
||||
*/
|
||||
class BasicEntityPersister implements EntityPersister
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
use LockSqlHelper;
|
||||
|
||||
/** @var array<string,string> */
|
||||
@@ -766,7 +765,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
$targetClass = $this->em->getClassMetadata($assoc->targetEntity);
|
||||
|
||||
if ($assoc->isOwningSide()) {
|
||||
$isInverseSingleValued = $assoc->inversedBy && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy);
|
||||
$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).
|
||||
@@ -844,7 +843,10 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria): array
|
||||
{
|
||||
$orderBy = self::getCriteriaOrderings($criteria);
|
||||
$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);
|
||||
@@ -1153,7 +1155,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
|
||||
if (isset($this->class->fieldMappings[$fieldName])) {
|
||||
$tableAlias = isset($this->class->fieldMappings[$fieldName]->inherited)
|
||||
? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]['inherited'])
|
||||
? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]->inherited)
|
||||
: $baseTableAlias;
|
||||
|
||||
$columnName = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform);
|
||||
|
||||
@@ -466,7 +466,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|| isset($this->class->associationMappings[$name]->inherited)
|
||||
|| ($this->class->isVersioned && $this->class->versionField === $name)
|
||||
|| isset($this->class->embeddedClasses[$name])
|
||||
|| isset($this->class->fieldMappings[$name]['notInsertable'])
|
||||
|| isset($this->class->fieldMappings[$name]->notInsertable)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -519,9 +519,9 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
$class = null;
|
||||
if ($this->class->isVersioned && $key === $versionedClass->versionField) {
|
||||
$class = $versionedClass;
|
||||
} elseif (isset($column['generated'])) {
|
||||
$class = isset($column['inherited'])
|
||||
? $this->em->getClassMetadata($column['inherited'])
|
||||
} elseif (isset($column->generated)) {
|
||||
$class = isset($column->inherited)
|
||||
? $this->em->getClassMetadata($column->inherited)
|
||||
: $this->class;
|
||||
} else {
|
||||
continue;
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
|
||||
use Traversable;
|
||||
|
||||
use function implode;
|
||||
@@ -23,6 +24,8 @@ use function str_replace;
|
||||
*/
|
||||
class Expr
|
||||
{
|
||||
use NoUnknownNamedArguments;
|
||||
|
||||
/**
|
||||
* Creates a conjunction of the given boolean expressions.
|
||||
*
|
||||
@@ -38,6 +41,8 @@ class Expr
|
||||
*/
|
||||
public function andX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Andx
|
||||
{
|
||||
self::validateVariadicParameter($x);
|
||||
|
||||
return new Expr\Andx($x);
|
||||
}
|
||||
|
||||
@@ -56,6 +61,8 @@ class Expr
|
||||
*/
|
||||
public function orX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Orx
|
||||
{
|
||||
self::validateVariadicParameter($x);
|
||||
|
||||
return new Expr\Orx($x);
|
||||
}
|
||||
|
||||
@@ -225,6 +232,8 @@ class Expr
|
||||
*/
|
||||
public function countDistinct(mixed ...$x): string
|
||||
{
|
||||
self::validateVariadicParameter($x);
|
||||
|
||||
return 'COUNT(DISTINCT ' . implode(', ', $x) . ')';
|
||||
}
|
||||
|
||||
@@ -470,6 +479,8 @@ class Expr
|
||||
*/
|
||||
public function concat(mixed ...$x): Expr\Func
|
||||
{
|
||||
self::validateVariadicParameter($x);
|
||||
|
||||
return new Expr\Func('CONCAT', $x);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ class ResultSetMappingBuilder extends ResultSetMapping implements Stringable
|
||||
|
||||
$this->addFieldResult($alias, $columnAlias, $propertyName);
|
||||
|
||||
$enumType = $classMetadata->getFieldMapping($propertyName)['enumType'] ?? null;
|
||||
$enumType = $classMetadata->getFieldMapping($propertyName)->enumType ?? null;
|
||||
if (! empty($enumType)) {
|
||||
$this->addEnumResult($columnAlias, $enumType);
|
||||
}
|
||||
|
||||
@@ -798,7 +798,7 @@ class SqlWalker
|
||||
$class = $this->getMetadataForDqlAlias($alias);
|
||||
|
||||
if (isset($class->associationMappings[$fieldName]->inherited)) {
|
||||
$class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
|
||||
$class = $this->em->getClassMetadata($class->associationMappings[$fieldName]->inherited);
|
||||
}
|
||||
|
||||
$association = $class->associationMappings[$fieldName];
|
||||
|
||||
@@ -8,7 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
|
||||
use Doctrine\ORM\Internal\QueryType;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
@@ -41,7 +41,7 @@ use function substr;
|
||||
*/
|
||||
class QueryBuilder implements Stringable
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
use NoUnknownNamedArguments;
|
||||
|
||||
/**
|
||||
* The array of DQL parts collected.
|
||||
@@ -616,6 +616,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function select(mixed ...$select): static
|
||||
{
|
||||
self::validateVariadicParameter($select);
|
||||
|
||||
$this->type = QueryType::Select;
|
||||
|
||||
if ($select === []) {
|
||||
@@ -662,6 +664,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function addSelect(mixed ...$select): static
|
||||
{
|
||||
self::validateVariadicParameter($select);
|
||||
|
||||
$this->type = QueryType::Select;
|
||||
|
||||
if ($select === []) {
|
||||
@@ -956,6 +960,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function where(mixed ...$predicates): static
|
||||
{
|
||||
self::validateVariadicParameter($predicates);
|
||||
|
||||
if (! (count($predicates) === 1 && $predicates[0] instanceof Expr\Composite)) {
|
||||
$predicates = new Expr\Andx($predicates);
|
||||
}
|
||||
@@ -981,6 +987,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function andWhere(mixed ...$where): static
|
||||
{
|
||||
self::validateVariadicParameter($where);
|
||||
|
||||
$dql = $this->getDQLPart('where');
|
||||
|
||||
if ($dql instanceof Expr\Andx) {
|
||||
@@ -1011,6 +1019,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function orWhere(mixed ...$where): static
|
||||
{
|
||||
self::validateVariadicParameter($where);
|
||||
|
||||
$dql = $this->getDQLPart('where');
|
||||
|
||||
if ($dql instanceof Expr\Orx) {
|
||||
@@ -1038,6 +1048,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function groupBy(string ...$groupBy): static
|
||||
{
|
||||
self::validateVariadicParameter($groupBy);
|
||||
|
||||
return $this->add('groupBy', new Expr\GroupBy($groupBy));
|
||||
}
|
||||
|
||||
@@ -1056,6 +1068,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function addGroupBy(string ...$groupBy): static
|
||||
{
|
||||
self::validateVariadicParameter($groupBy);
|
||||
|
||||
return $this->add('groupBy', new Expr\GroupBy($groupBy), true);
|
||||
}
|
||||
|
||||
@@ -1067,6 +1081,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function having(mixed ...$having): static
|
||||
{
|
||||
self::validateVariadicParameter($having);
|
||||
|
||||
if (! (count($having) === 1 && ($having[0] instanceof Expr\Andx || $having[0] instanceof Expr\Orx))) {
|
||||
$having = new Expr\Andx($having);
|
||||
}
|
||||
@@ -1082,6 +1098,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function andHaving(mixed ...$having): static
|
||||
{
|
||||
self::validateVariadicParameter($having);
|
||||
|
||||
$dql = $this->getDQLPart('having');
|
||||
|
||||
if ($dql instanceof Expr\Andx) {
|
||||
@@ -1102,6 +1120,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function orHaving(mixed ...$having): static
|
||||
{
|
||||
self::validateVariadicParameter($having);
|
||||
|
||||
$dql = $this->getDQLPart('having');
|
||||
|
||||
if ($dql instanceof Expr\Orx) {
|
||||
@@ -1167,7 +1187,7 @@ class QueryBuilder implements Stringable
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::getCriteriaOrderings($criteria) as $sort => $order) {
|
||||
foreach ($criteria->orderings() as $sort => $order) {
|
||||
$hasValidAlias = false;
|
||||
foreach ($allAliases as $alias) {
|
||||
if (str_starts_with($sort . '.', $alias . '.')) {
|
||||
@@ -1180,7 +1200,7 @@ class QueryBuilder implements Stringable
|
||||
$sort = $allAliases[0] . '.' . $sort;
|
||||
}
|
||||
|
||||
$this->addOrderBy($sort, $order);
|
||||
$this->addOrderBy($sort, $order->value);
|
||||
}
|
||||
|
||||
// Overwrite limits only if they was set in criteria
|
||||
|
||||
@@ -5,8 +5,10 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Tools;
|
||||
|
||||
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
|
||||
use function assert;
|
||||
use function ltrim;
|
||||
|
||||
/**
|
||||
@@ -14,16 +16,22 @@ use function ltrim;
|
||||
*/
|
||||
class AttachEntityListenersListener
|
||||
{
|
||||
/** @var mixed[][] */
|
||||
/**
|
||||
* @var array<class-string, list<array{
|
||||
* event: Events::*|null,
|
||||
* class: class-string,
|
||||
* method: string|null,
|
||||
* }>>
|
||||
*/
|
||||
private array $entityListeners = [];
|
||||
|
||||
/**
|
||||
* Adds an entity listener for a specific entity.
|
||||
*
|
||||
* @param string $entityClass The entity to attach the listener.
|
||||
* @param string $listenerClass The listener class.
|
||||
* @param string|null $eventName The entity lifecycle event.
|
||||
* @param string|null $listenerCallback The listener callback method or NULL to use $eventName.
|
||||
* @param class-string $entityClass The entity to attach the listener.
|
||||
* @param class-string $listenerClass The listener class.
|
||||
* @param Events::*|null $eventName The entity lifecycle event.
|
||||
* @param non-falsy-string|null $listenerCallback The listener callback method or NULL to use $eventName.
|
||||
*/
|
||||
public function addEntityListener(
|
||||
string $entityClass,
|
||||
@@ -34,7 +42,7 @@ class AttachEntityListenersListener
|
||||
$this->entityListeners[ltrim($entityClass, '\\')][] = [
|
||||
'event' => $eventName,
|
||||
'class' => $listenerClass,
|
||||
'method' => $listenerCallback ?: $eventName,
|
||||
'method' => $listenerCallback ?? $eventName,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -53,6 +61,7 @@ class AttachEntityListenersListener
|
||||
if ($listener['event'] === null) {
|
||||
EntityListenerBuilder::bindEntityListener($metadata, $listener['class']);
|
||||
} else {
|
||||
assert($listener['method'] !== null);
|
||||
$metadata->addEntityListener($listener['event'], $listener['class'], $listener['method']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ class SchemaValidator
|
||||
}
|
||||
}
|
||||
|
||||
if ($assoc->isOwningSide() && $assoc->inversedBy) {
|
||||
if ($assoc->isOwningSide() && $assoc->inversedBy !== null) {
|
||||
if ($targetMetadata->hasField($assoc->inversedBy)) {
|
||||
$ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the inverse side ' .
|
||||
'field ' . $assoc->targetEntity . '#' . $assoc->inversedBy . ' which is not defined as association.';
|
||||
@@ -343,7 +343,7 @@ class SchemaValidator
|
||||
return null;
|
||||
}
|
||||
|
||||
$metadataFieldType = $this->findBuiltInType(Type::getType($fieldMapping['type']));
|
||||
$metadataFieldType = $this->findBuiltInType(Type::getType($fieldMapping->type));
|
||||
|
||||
//If the metadata field type is not a mapped built-in type, we cannot check it
|
||||
if ($metadataFieldType === null) {
|
||||
@@ -371,7 +371,7 @@ class SchemaValidator
|
||||
);
|
||||
}
|
||||
|
||||
if (! isset($fieldMapping['enumType']) || $propertyType === $fieldMapping['enumType']) {
|
||||
if (! isset($fieldMapping->enumType) || $propertyType === $fieldMapping->enumType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -380,17 +380,17 @@ class SchemaValidator
|
||||
$class->name,
|
||||
$fieldName,
|
||||
$propertyType,
|
||||
$fieldMapping['enumType'],
|
||||
$fieldMapping->enumType,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isset($fieldMapping['enumType'])
|
||||
&& $propertyType !== $fieldMapping['enumType']
|
||||
isset($fieldMapping->enumType)
|
||||
&& $propertyType !== $fieldMapping->enumType
|
||||
&& interface_exists($propertyType)
|
||||
&& is_a($fieldMapping['enumType'], $propertyType, true)
|
||||
&& is_a($fieldMapping->enumType, $propertyType, true)
|
||||
) {
|
||||
$backingType = (string) (new ReflectionEnum($fieldMapping['enumType']))->getBackingType();
|
||||
$backingType = (string) (new ReflectionEnum($fieldMapping->enumType))->getBackingType();
|
||||
|
||||
if ($metadataFieldType === $backingType) {
|
||||
return null;
|
||||
@@ -400,14 +400,14 @@ class SchemaValidator
|
||||
"The field '%s#%s' has the metadata enumType '%s' with a backing type of '%s' that differs from the metadata field type '%s'.",
|
||||
$class->name,
|
||||
$fieldName,
|
||||
$fieldMapping['enumType'],
|
||||
$fieldMapping->enumType,
|
||||
$backingType,
|
||||
$metadataFieldType,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
$fieldMapping['type'] === 'json'
|
||||
$fieldMapping->type === 'json'
|
||||
&& in_array($propertyType, ['string', 'int', 'float', 'bool', 'true', 'false', 'null'], true)
|
||||
) {
|
||||
return null;
|
||||
@@ -419,7 +419,7 @@ class SchemaValidator
|
||||
$fieldName,
|
||||
$propertyType,
|
||||
$metadataFieldType,
|
||||
$fieldMapping['type'],
|
||||
$fieldMapping->type,
|
||||
);
|
||||
},
|
||||
$class->fieldMappings,
|
||||
|
||||
@@ -997,7 +997,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
foreach ($actualData as $propName => $actualValue) {
|
||||
$orgValue = $originalData[$propName] ?? null;
|
||||
|
||||
if (isset($class->fieldMappings[$propName]['enumType'])) {
|
||||
if (isset($class->fieldMappings[$propName]->enumType)) {
|
||||
if (is_array($orgValue)) {
|
||||
foreach ($orgValue as $id => $val) {
|
||||
if ($val instanceof BackedEnum) {
|
||||
@@ -1267,16 +1267,16 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
$joinColumns = reset($assoc->joinColumns);
|
||||
if (! isset($joinColumns['onDelete'])) {
|
||||
if (! isset($joinColumns->onDelete)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$onDeleteOption = strtolower($joinColumns['onDelete']);
|
||||
$onDeleteOption = strtolower($joinColumns->onDelete);
|
||||
if ($onDeleteOption !== 'cascade') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetEntity = $class->getFieldValue($entity, $assoc['fieldName']);
|
||||
$targetEntity = $class->getFieldValue($entity, $assoc->fieldName);
|
||||
|
||||
// If the association does not refer to another entity or that entity
|
||||
// is not to be deleted, there is no ordering problem and we can
|
||||
@@ -1557,18 +1557,15 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
final public static function getIdHashByIdentifier(array $identifier): string
|
||||
{
|
||||
foreach ($identifier as $k => $value) {
|
||||
if ($value instanceof BackedEnum) {
|
||||
$identifier[$k] = $value->value;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(
|
||||
' ',
|
||||
array_map(
|
||||
static function ($value) {
|
||||
if ($value instanceof BackedEnum) {
|
||||
return $value->value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
$identifier,
|
||||
),
|
||||
$identifier,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2550,7 +2547,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$this->originalEntityData[$oid][$field] = $newValue;
|
||||
$class->reflFields[$field]->setValue($entity, $newValue);
|
||||
|
||||
if ($assoc->inversedBy && $assoc->isOneToOne() && $newValue !== null) {
|
||||
if ($assoc->inversedBy !== null && $assoc->isOneToOne() && $newValue !== null) {
|
||||
$inverseAssoc = $targetClass->associationMappings[$assoc->inversedBy];
|
||||
$targetClass->reflFields[$inverseAssoc->fieldName]->setValue($newValue, $entity);
|
||||
}
|
||||
@@ -2713,7 +2710,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
private function scheduleCollectionForBatchLoading(PersistentCollection $collection, ClassMetadata $sourceClass): void
|
||||
{
|
||||
$mapping = $collection->getMapping();
|
||||
$name = $mapping['sourceEntity'] . '#' . $mapping['fieldName'];
|
||||
$name = $mapping->sourceEntity . '#' . $mapping->fieldName;
|
||||
|
||||
if (! isset($this->eagerLoadingCollections[$name])) {
|
||||
$this->eagerLoadingCollections[$name] = [
|
||||
|
||||
@@ -25,8 +25,8 @@ class GH11135Test extends OrmFunctionalTestCase
|
||||
$cm1 = $this->_em->getClassMetadata(GH11135EntityWithOverride::class);
|
||||
$cm2 = $this->_em->getClassMetadata(GH11135EntityWithoutOverride::class);
|
||||
|
||||
self::assertSame($cm1->getFieldMapping('id')['declared'], $cm2->getFieldMapping('id')['declared']);
|
||||
self::assertSame($cm1->getAssociationMapping('ref')['declared'], $cm2->getAssociationMapping('ref')['declared']);
|
||||
self::assertSame($cm1->getFieldMapping('id')->declared, $cm2->getFieldMapping('id')->declared);
|
||||
self::assertSame($cm1->getAssociationMapping('ref')->declared, $cm2->getAssociationMapping('ref')->declared);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use ArrayObject;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping\ChainTypedFieldMapper;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -67,6 +68,8 @@ require_once __DIR__ . '/../../Models/Global/GlobalNamespaceModel.php';
|
||||
|
||||
class ClassMetadataTest extends OrmTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
public function testClassMetadataInstanceSerialization(): void
|
||||
{
|
||||
$cm = new ClassMetadata(CmsUser::class);
|
||||
@@ -860,8 +863,7 @@ class ClassMetadataTest extends OrmTestCase
|
||||
|
||||
$mapping = $cm->getFieldMapping('id');
|
||||
|
||||
self::assertArrayHasKey('declared', $mapping);
|
||||
self::assertSame(AbstractContentItem::class, $mapping['declared']);
|
||||
self::assertSame(AbstractContentItem::class, $mapping->declared);
|
||||
}
|
||||
|
||||
public function testAssociationOverrideKeepsDeclaringClass(): void
|
||||
@@ -872,8 +874,7 @@ class ClassMetadataTest extends OrmTestCase
|
||||
|
||||
$mapping = $cm->getAssociationMapping('parentDirectory');
|
||||
|
||||
self::assertArrayHasKey('declared', $mapping);
|
||||
self::assertSame(Directory::class, $mapping['declared']);
|
||||
self::assertSame(Directory::class, $mapping->declared);
|
||||
}
|
||||
|
||||
#[TestGroup('DDC-1955')]
|
||||
@@ -1056,6 +1057,15 @@ class ClassMetadataTest extends OrmTestCase
|
||||
$metadata->addLifecycleCallback('foo', 'bar');
|
||||
}
|
||||
|
||||
public function testGettingAnFQCNForNullIsDeprecated(): void
|
||||
{
|
||||
$metadata = new ClassMetadata(self::class);
|
||||
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11294');
|
||||
|
||||
$metadata->fullyQualifiedClassName(null);
|
||||
}
|
||||
|
||||
public function testItThrowsOnInvalidCallToGetAssociationMappedByTargetField(): void
|
||||
{
|
||||
$metadata = new ClassMetadata(self::class);
|
||||
|
||||
@@ -297,8 +297,8 @@ class XmlMappingDriverTest extends MappingDriverTestCase
|
||||
/** @var array{type: string} $name */
|
||||
$name = $class->getFieldMapping('name');
|
||||
|
||||
self::assertEquals(ProjectId::class, $id['type']);
|
||||
self::assertEquals(ProjectName::class, $name['type']);
|
||||
self::assertEquals(ProjectId::class, $id->type);
|
||||
self::assertEquals(ProjectName::class, $name->type);
|
||||
}
|
||||
|
||||
public function testDisablingXmlValidationIsPossible(): void
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
@@ -286,6 +287,18 @@ class QueryBuilderTest extends OrmTestCase
|
||||
$this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = :uid');
|
||||
}
|
||||
|
||||
public function testWhereWithUnexpectedNamedArguments(): void
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
->select('u')
|
||||
->from(CmsUser::class, 'u');
|
||||
|
||||
$this->expectException(BadMethodCallException::class);
|
||||
$this->expectExceptionMessage('Invalid call to Doctrine\ORM\QueryBuilder::where(), unknown named arguments: foo, bar');
|
||||
|
||||
$qb->where(foo: 'u.id = :uid', bar: 'u.name = :name');
|
||||
}
|
||||
|
||||
public function testComplexAndWhere(): void
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
|
||||
Reference in New Issue
Block a user