mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3e96745cc | ||
|
|
21221f73cc | ||
|
|
ab5e9e393b | ||
|
|
52a6a21387 | ||
|
|
4fc8629414 | ||
|
|
feb27f00c1 | ||
|
|
719d007a81 | ||
|
|
76c4539ffa | ||
|
|
0f8d193512 | ||
|
|
cc314d0fb7 |
14
.github/workflows/continuous-integration.yml
vendored
14
.github/workflows/continuous-integration.yml
vendored
@@ -97,9 +97,9 @@ jobs:
|
||||
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.proxy }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
@@ -170,9 +170,9 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
|
||||
path: "coverage.xml"
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
@@ -317,7 +317,7 @@ jobs:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage files"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
@@ -372,7 +372,7 @@ jobs:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Download coverage files"
|
||||
uses: "actions/download-artifact@v3"
|
||||
uses: "actions/download-artifact@v4"
|
||||
with:
|
||||
path: "reports"
|
||||
|
||||
|
||||
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
|
||||
- name: "Remove existing composer file"
|
||||
run: "rm composer.json"
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@3.0.0"
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@4.0.0"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
|
||||
4
.github/workflows/static-analysis.yml
vendored
4
.github/workflows/static-analysis.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
|
||||
- name: "Require specific DBAL version"
|
||||
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
|
||||
- name: "Require specific persistence version"
|
||||
run: "composer require doctrine/persistence ^3.1 --no-update"
|
||||
|
||||
@@ -42,14 +42,14 @@
|
||||
"doctrine/annotations": "^1.13 || ^2",
|
||||
"doctrine/coding-standard": "^9.0.2 || ^12.0",
|
||||
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.10.35",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.10.59",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"squizlabs/php_codesniffer": "3.7.2",
|
||||
"symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0",
|
||||
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
|
||||
"vimeo/psalm": "4.30.0 || 5.16.0"
|
||||
"vimeo/psalm": "4.30.0 || 5.22.2"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >= 3.0"
|
||||
|
||||
@@ -279,4 +279,9 @@
|
||||
<!-- https://github.com/doctrine/orm/issues/8537 -->
|
||||
<exclude-pattern>src/QueryBuilder.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.PHP.UselessParentheses">
|
||||
<!-- We need those parentheses to make enum access seem like valid syntax on PHP 7 -->
|
||||
<exclude-pattern>src/Mapping/Driver/XmlDriver.php</exclude-pattern>
|
||||
</rule>
|
||||
</ruleset>
|
||||
|
||||
@@ -170,11 +170,6 @@ parameters:
|
||||
count: 2
|
||||
path: src/Mapping/ClassMetadataFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\:\\:fullyQualifiedClassName\\(\\) should return class\\-string\\|null but returns string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Mapping/ClassMetadataInfo.php
|
||||
|
||||
-
|
||||
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\NamingStrategy\\:\\:joinColumnName\\(\\) invoked with 2 parameters, 1 required\\.$#"
|
||||
count: 2
|
||||
@@ -236,7 +231,7 @@ parameters:
|
||||
path: src/Mapping/Driver/XmlDriver.php
|
||||
|
||||
-
|
||||
message: "#^Offset 'version' on \\*NEVER\\* in isset\\(\\) always exists and is always null\\.$#"
|
||||
message: "#^Offset 'version' on \\*NEVER\\* in isset\\(\\) always exists and is not nullable\\.$#"
|
||||
count: 1
|
||||
path: src/Mapping/Driver/XmlDriver.php
|
||||
|
||||
@@ -331,22 +326,7 @@ parameters:
|
||||
path: src/Query/AST/Functions/DateSubFunction.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$simpleArithmeticExpr of method Doctrine\\\\ORM\\\\Query\\\\SqlWalker\\:\\:walkSimpleArithmeticExpression\\(\\) expects Doctrine\\\\ORM\\\\Query\\\\AST\\\\SimpleArithmeticExpression, Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node given\\.$#"
|
||||
count: 1
|
||||
path: src/Query/AST/Functions/LengthFunction.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$simpleArithmeticExpr of method Doctrine\\\\ORM\\\\Query\\\\SqlWalker\\:\\:walkSimpleArithmeticExpression\\(\\) expects Doctrine\\\\ORM\\\\Query\\\\AST\\\\SimpleArithmeticExpression, Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node given\\.$#"
|
||||
count: 1
|
||||
path: src/Query/AST/Functions/LowerFunction.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$simpleArithmeticExpr of method Doctrine\\\\ORM\\\\Query\\\\SqlWalker\\:\\:walkSimpleArithmeticExpression\\(\\) expects Doctrine\\\\ORM\\\\Query\\\\AST\\\\SimpleArithmeticExpression, Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node given\\.$#"
|
||||
count: 1
|
||||
path: src/Query/AST/Functions/UpperFunction.php
|
||||
|
||||
-
|
||||
message: "#^Method Doctrine\\\\ORM\\\\Query\\\\AST\\\\IndexBy\\:\\:dispatch\\(\\) should return string but returns void\\.$#"
|
||||
message: "#^Method Doctrine\\\\ORM\\\\Query\\\\AST\\\\IndexBy\\:\\:dispatch\\(\\) should return string but returns null\\.$#"
|
||||
count: 1
|
||||
path: src/Query/AST/IndexBy.php
|
||||
|
||||
@@ -395,11 +375,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Query/Expr/Select.php
|
||||
|
||||
-
|
||||
message: "#^Comparison operation \"\\<\" between null and 102 is always true\\.$#"
|
||||
count: 1
|
||||
path: src/Query/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Method Doctrine\\\\ORM\\\\Query\\\\Parser\\:\\:ArithmeticFactor\\(\\) should return Doctrine\\\\ORM\\\\Query\\\\AST\\\\ArithmeticFactor but returns Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node\\|string\\.$#"
|
||||
count: 1
|
||||
@@ -415,14 +390,9 @@ parameters:
|
||||
count: 1
|
||||
path: src/Query/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Result of && is always true\\.$#"
|
||||
count: 1
|
||||
path: src/Query/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Unreachable statement \\- code above always terminates\\.$#"
|
||||
count: 4
|
||||
count: 3
|
||||
path: src/Query/Parser.php
|
||||
|
||||
-
|
||||
|
||||
@@ -31,6 +31,31 @@ parameters:
|
||||
message: '/^Instanceof between Doctrine\\DBAL\\Platforms\\AbstractPlatform and Doctrine\\DBAL\\Platforms\\MySQLPlatform will always evaluate to false\.$/'
|
||||
path: src/Utility/LockSqlHelper.php
|
||||
|
||||
# Forward compatibility with Collections 3
|
||||
-
|
||||
message: '#^Parameter \$order of anonymous function has invalid type Doctrine\\Common\\Collections\\Order\.$#'
|
||||
path: src/Internal/CriteriaOrderings.php
|
||||
|
||||
-
|
||||
message: '#^Anonymous function has invalid return type Doctrine\\Common\\Collections\\Order\.$#'
|
||||
path: src/Internal/CriteriaOrderings.php
|
||||
|
||||
-
|
||||
message: '#^Access to property \$value on an unknown class Doctrine\\Common\\Collections\\Order\.$#'
|
||||
path: src/Internal/CriteriaOrderings.php
|
||||
|
||||
-
|
||||
message: '#^Call to static method from\(\) on an unknown class Doctrine\\Common\\Collections\\Order\.$#'
|
||||
path: src/Internal/CriteriaOrderings.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\Common\\Collections\\Criteria\:\:orderings\(\)\.$#'
|
||||
path: src/Internal/CriteriaOrderings.php
|
||||
|
||||
-
|
||||
message: '#^Method .+\:\:mapToOrderEnumIfAvailable\(\) has invalid return type Doctrine\\Common\\Collections\\Order\.$#'
|
||||
path: src/Internal/CriteriaOrderings.php
|
||||
|
||||
# False positive
|
||||
-
|
||||
message: '/^Call to an undefined method Doctrine\\Common\\Cache\\Cache::deleteAll\(\)\.$/'
|
||||
|
||||
1634
psalm-baseline.xml
1634
psalm-baseline.xml
File diff suppressed because it is too large
Load Diff
@@ -243,6 +243,12 @@
|
||||
<file name="src/Mapping/ClassMetadataFactory.php"/>
|
||||
</errorLevel>
|
||||
</ReferenceConstraintViolation>
|
||||
<RiskyTruthyFalsyComparison>
|
||||
<!-- TODO: Enable this new rule on higher branches. -->
|
||||
<errorLevel type="suppress">
|
||||
<directory name="src" />
|
||||
</errorLevel>
|
||||
</RiskyTruthyFalsyComparison>
|
||||
<TooManyArguments>
|
||||
<errorLevel type="suppress">
|
||||
<!-- Symfony cache supports passing a key prefix to the clear method. -->
|
||||
|
||||
@@ -16,6 +16,7 @@ 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\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
@@ -30,6 +31,8 @@ use function sha1;
|
||||
|
||||
abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
/** @var UnitOfWork */
|
||||
protected $uow;
|
||||
|
||||
@@ -475,7 +478,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria)
|
||||
{
|
||||
$orderBy = $criteria->getOrderings();
|
||||
$orderBy = self::getCriteriaOrderings($criteria);
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->persister->getSelectSQL($criteria);
|
||||
|
||||
54
src/Internal/CriteriaOrderings.php
Normal file
54
src/Internal/CriteriaOrderings.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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 class_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 function (Order $order): string {
|
||||
return $order->value;
|
||||
},
|
||||
$criteria->orderings()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $orderings
|
||||
*
|
||||
* @return array<string, string>|array<string, Order>
|
||||
*/
|
||||
private static function mapToOrderEnumIfAvailable(array $orderings): array
|
||||
{
|
||||
if (! class_exists(Order::class)) {
|
||||
return $orderings;
|
||||
}
|
||||
|
||||
return array_map(
|
||||
static function (string $order): Order {
|
||||
return Order::from(strtoupper($order));
|
||||
},
|
||||
$orderings
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3707,7 +3707,6 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* @param string|null $className
|
||||
*
|
||||
* @return string|null null if the input value is null
|
||||
* @psalm-return class-string|null
|
||||
*/
|
||||
public function fullyQualifiedClassName($className)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use BackedEnum;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
@@ -16,6 +17,7 @@ use ReflectionProperty;
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function enum_exists;
|
||||
use function is_a;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
@@ -54,18 +56,18 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
) {
|
||||
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {
|
||||
$mapping['enumType'] = $type->getName();
|
||||
|
||||
$reflection = new ReflectionEnum($type->getName());
|
||||
if (! $reflection->isBacked()) {
|
||||
throw MappingException::backedEnumTypeRequired(
|
||||
$field->class,
|
||||
$mapping['fieldName'],
|
||||
$mapping['enumType']
|
||||
$type->getName()
|
||||
);
|
||||
}
|
||||
|
||||
$type = $reflection->getBackingType();
|
||||
assert(is_a($type->getName(), BackedEnum::class, true));
|
||||
$mapping['enumType'] = $type->getName();
|
||||
$type = $reflection->getBackingType();
|
||||
|
||||
assert($type instanceof ReflectionNamedType);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
@@ -16,6 +17,7 @@ use LogicException;
|
||||
use SimpleXMLElement;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
use function constant;
|
||||
use function count;
|
||||
use function defined;
|
||||
@@ -481,9 +483,10 @@ class XmlDriver extends FileDriver
|
||||
if (isset($oneToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
|
||||
/** @psalm-suppress DeprecatedConstant */
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
: Criteria::ASC;
|
||||
: (class_exists(Order::class) ? (Order::Ascending)->value : Criteria::ASC);
|
||||
}
|
||||
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
@@ -609,9 +612,10 @@ class XmlDriver extends FileDriver
|
||||
if (isset($manyToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
|
||||
/** @psalm-suppress DeprecatedConstant */
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
: Criteria::ASC;
|
||||
: (class_exists(Order::class) ? (Order::Ascending)->value : Criteria::ASC);
|
||||
}
|
||||
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
|
||||
@@ -9,6 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use ReturnTypeWillChange;
|
||||
use RuntimeException;
|
||||
@@ -41,6 +42,8 @@ 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.
|
||||
@@ -671,7 +674,9 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
|
||||
$criteria = clone $criteria;
|
||||
$criteria->where($expression);
|
||||
$criteria->orderBy($criteria->getOrderings() ?: $association['orderBy'] ?? []);
|
||||
$criteria->orderBy(self::mapToOrderEnumIfAvailable(
|
||||
self::getCriteriaOrderings($criteria) ?: $association['orderBy'] ?? []
|
||||
));
|
||||
|
||||
$persister = $this->getUnitOfWork()->getEntityPersister($association['targetEntity']);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ use BadMethodCallException;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Expr\Comparison;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Persisters\SqlValueVisitor;
|
||||
@@ -30,6 +31,8 @@ use function sprintf;
|
||||
*/
|
||||
class ManyToManyPersister extends AbstractCollectionPersister
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -745,7 +748,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
|
||||
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string
|
||||
{
|
||||
$orderings = $criteria->getOrderings();
|
||||
$orderings = self::getCriteriaOrderings($criteria);
|
||||
if ($orderings) {
|
||||
$orderBy = [];
|
||||
foreach ($orderings as $name => $direction) {
|
||||
|
||||
@@ -15,6 +15,7 @@ use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Mapping\QuoteStrategy;
|
||||
@@ -93,6 +94,7 @@ use function trim;
|
||||
*/
|
||||
class BasicEntityPersister implements EntityPersister
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
use LockSqlHelper;
|
||||
|
||||
/** @var array<string,string> */
|
||||
@@ -884,7 +886,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria)
|
||||
{
|
||||
$orderBy = $criteria->getOrderings();
|
||||
$orderBy = self::getCriteriaOrderings($criteria);
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
|
||||
|
||||
@@ -2588,7 +2588,7 @@ class SqlWalker implements TreeWalker
|
||||
/**
|
||||
* Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
* @param AST\SimpleArithmeticExpression $simpleArithmeticExpr
|
||||
* @param AST\Node|string $simpleArithmeticExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\ORM;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Query\QueryExpressionVisitor;
|
||||
@@ -39,6 +40,8 @@ use function substr;
|
||||
*/
|
||||
class QueryBuilder
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
/** @deprecated */
|
||||
public const SELECT = 0;
|
||||
|
||||
@@ -1375,22 +1378,20 @@ class QueryBuilder
|
||||
}
|
||||
}
|
||||
|
||||
if ($criteria->getOrderings()) {
|
||||
foreach ($criteria->getOrderings() as $sort => $order) {
|
||||
$hasValidAlias = false;
|
||||
foreach ($allAliases as $alias) {
|
||||
if (str_starts_with($sort . '.', $alias . '.')) {
|
||||
$hasValidAlias = true;
|
||||
break;
|
||||
}
|
||||
foreach (self::getCriteriaOrderings($criteria) as $sort => $order) {
|
||||
$hasValidAlias = false;
|
||||
foreach ($allAliases as $alias) {
|
||||
if (str_starts_with($sort . '.', $alias . '.')) {
|
||||
$hasValidAlias = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $hasValidAlias) {
|
||||
$sort = $allAliases[0] . '.' . $sort;
|
||||
}
|
||||
|
||||
$this->addOrderBy($sort, $order);
|
||||
}
|
||||
|
||||
if (! $hasValidAlias) {
|
||||
$sort = $allAliases[0] . '.' . $sort;
|
||||
}
|
||||
|
||||
$this->addOrderBy($sort, $order);
|
||||
}
|
||||
|
||||
// Overwrite limits only if they was set in criteria
|
||||
|
||||
@@ -365,7 +365,7 @@ class SchemaTool
|
||||
|
||||
if (isset($class->table['uniqueConstraints'])) {
|
||||
foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) {
|
||||
$uniqIndex = new Index($indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []);
|
||||
$uniqIndex = new Index('tmp__' . $indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []);
|
||||
|
||||
foreach ($table->getIndexes() as $tableIndexName => $tableIndex) {
|
||||
$method = method_exists($tableIndex, 'isFulfilledBy') ? 'isFulfilledBy' : 'isFullfilledBy';
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\Models\CMS\CmsGroup;
|
||||
@@ -14,6 +15,7 @@ use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
@@ -436,7 +438,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$user = $this->_em->find(get_class($user), $user->id);
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->orderBy(['name' => Criteria::ASC]);
|
||||
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
|
||||
|
||||
self::assertEquals(
|
||||
['A', 'B', 'C', 'Developers_0'],
|
||||
@@ -478,7 +480,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$user = $this->_em->find(get_class($user), $user->id);
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->orderBy(['name' => Criteria::ASC]);
|
||||
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
|
||||
|
||||
self::assertEquals(
|
||||
['A', 'B', 'C'],
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
@@ -16,6 +18,7 @@ use Doctrine\ORM\Mapping\OrderBy;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
|
||||
/** @group GH7767 */
|
||||
class GH7767Test extends OrmFunctionalTestCase
|
||||
@@ -53,7 +56,9 @@ class GH7767Test extends OrmFunctionalTestCase
|
||||
$parent = $this->_em->find(GH7767ParentEntity::class, 1);
|
||||
assert($parent instanceof GH7767ParentEntity);
|
||||
|
||||
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['position' => 'DESC']));
|
||||
$children = $parent->getChildren()->matching(
|
||||
Criteria::create()->orderBy(['position' => class_exists(Order::class) ? Order::Descending : 'DESC'])
|
||||
);
|
||||
|
||||
self::assertEquals(300, $children[0]->position);
|
||||
self::assertEquals(200, $children[1]->position);
|
||||
@@ -73,7 +78,7 @@ class GH7767ParentEntity
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @psalm-var Collection<int, GH7767ChildEntity>
|
||||
* @psalm-var Collection<int, GH7767ChildEntity>&Selectable<int, GH7767ChildEntity>
|
||||
* @OneToMany(targetEntity=GH7767ChildEntity::class, mappedBy="parent", fetch="EXTRA_LAZY", cascade={"persist"})
|
||||
* @OrderBy({"position" = "ASC"})
|
||||
*/
|
||||
@@ -84,7 +89,7 @@ class GH7767ParentEntity
|
||||
$this->children[] = new GH7767ChildEntity($this, $position);
|
||||
}
|
||||
|
||||
/** @psalm-return Collection<int, GH7767ChildEntity> */
|
||||
/** @psalm-return Collection<int, GH7767ChildEntity>&Selectable<int, GH7767ChildEntity> */
|
||||
public function getChildren(): Collection
|
||||
{
|
||||
return $this->children;
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
@@ -16,6 +18,7 @@ use Doctrine\ORM\Mapping\OrderBy;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
|
||||
/** @group GH7836 */
|
||||
class GH7836Test extends OrmFunctionalTestCase
|
||||
@@ -56,7 +59,13 @@ class GH7836Test extends OrmFunctionalTestCase
|
||||
$parent = $this->_em->find(GH7836ParentEntity::class, 1);
|
||||
assert($parent instanceof GH7836ParentEntity);
|
||||
|
||||
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['position' => 'DESC', 'name' => 'ASC']));
|
||||
$children = $parent->getChildren()->matching(
|
||||
Criteria::create()->orderBy(
|
||||
class_exists(Order::class)
|
||||
? ['position' => Order::Descending, 'name' => Order::Ascending]
|
||||
: ['position' => 'DESC', 'name' => 'ASC']
|
||||
)
|
||||
);
|
||||
|
||||
self::assertSame(200, $children[0]->position);
|
||||
self::assertSame('baz', $children[0]->name);
|
||||
@@ -71,7 +80,13 @@ class GH7836Test extends OrmFunctionalTestCase
|
||||
$parent = $this->_em->find(GH7836ParentEntity::class, 1);
|
||||
assert($parent instanceof GH7836ParentEntity);
|
||||
|
||||
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['name' => 'ASC', 'position' => 'ASC']));
|
||||
$children = $parent->getChildren()->matching(
|
||||
Criteria::create()->orderBy(
|
||||
class_exists(Order::class)
|
||||
? ['name' => Order::Ascending, 'position' => Order::Ascending]
|
||||
: ['name' => 'ASC', 'position' => 'ASC']
|
||||
)
|
||||
);
|
||||
|
||||
self::assertSame(100, $children[0]->position);
|
||||
self::assertSame('bar', $children[0]->name);
|
||||
@@ -94,7 +109,7 @@ class GH7836ParentEntity
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var Collection<int, GH7836ChildEntity>
|
||||
* @var Collection<int, GH7836ChildEntity>&Selectable<int, GH7836ChildEntity>
|
||||
* @OneToMany(targetEntity=GH7836ChildEntity::class, mappedBy="parent", fetch="EXTRA_LAZY", cascade={"persist"})
|
||||
* @OrderBy({"position" = "ASC", "name" = "ASC"})
|
||||
*/
|
||||
@@ -105,7 +120,7 @@ class GH7836ParentEntity
|
||||
$this->children[] = new GH7836ChildEntity($this, $position, $name);
|
||||
}
|
||||
|
||||
/** @psalm-return Collection<int, GH7836ChildEntity> */
|
||||
/** @psalm-return Collection<int, GH7836ChildEntity>&Selectable<int, GH7836ChildEntity> */
|
||||
public function getChildren(): Collection
|
||||
{
|
||||
return $this->children;
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query;
|
||||
@@ -22,6 +23,7 @@ use Doctrine\Tests\OrmTestCase;
|
||||
use InvalidArgumentException;
|
||||
|
||||
use function array_filter;
|
||||
use function class_exists;
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
@@ -576,7 +578,7 @@ class QueryBuilderTest extends OrmTestCase
|
||||
->from(CmsUser::class, 'u');
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria->orderBy(['field' => Criteria::DESC]);
|
||||
$criteria->orderBy(['field' => class_exists(Order::class) ? Order::Descending : Criteria::DESC]);
|
||||
|
||||
$qb->addCriteria($criteria);
|
||||
|
||||
@@ -593,7 +595,7 @@ class QueryBuilderTest extends OrmTestCase
|
||||
->join('u.article', 'a');
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria->orderBy(['a.field' => Criteria::DESC]);
|
||||
$criteria->orderBy(['a.field' => class_exists(Order::class) ? Order::Descending : Criteria::DESC]);
|
||||
|
||||
$qb->addCriteria($criteria);
|
||||
|
||||
|
||||
@@ -374,6 +374,27 @@ class SchemaToolTest extends OrmTestCase
|
||||
self::assertTrue($schema->hasTable('first_entity'), 'Table first_entity should exist.');
|
||||
self::assertFalse($schema->hasTable('second_entity'), 'Table second_entity should not exist.');
|
||||
}
|
||||
|
||||
/** @group GH-11314 */
|
||||
public function testLoadUniqueConstraintWithoutName(): void
|
||||
{
|
||||
$em = $this->getTestEntityManager();
|
||||
$entity = $em->getClassMetadata(GH11314Entity::class);
|
||||
|
||||
$schemaTool = new SchemaTool($em);
|
||||
$schema = $schemaTool->getSchemaFromMetadata([$entity]);
|
||||
|
||||
self::assertTrue($schema->hasTable('GH11314Entity'));
|
||||
|
||||
$tableEntity = $schema->getTable('GH11314Entity');
|
||||
|
||||
self::assertTrue($tableEntity->hasIndex('uniq_2d81a3ed5bf54558875f7fd5'));
|
||||
|
||||
$tableIndex = $tableEntity->getIndex('uniq_2d81a3ed5bf54558875f7fd5');
|
||||
|
||||
self::assertTrue($tableIndex->isUnique());
|
||||
self::assertSame(['field', 'anotherField'], $tableIndex->getColumns());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -559,6 +580,32 @@ class IndexByFieldEntity
|
||||
public $fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(uniqueConstraints={@UniqueConstraint(columns={"field", "anotherField"})})
|
||||
*/
|
||||
class GH11314Entity
|
||||
{
|
||||
/**
|
||||
* @Column(type="integer")
|
||||
* @Id
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Column(name="field", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $field;
|
||||
|
||||
/**
|
||||
* @Column(name="anotherField", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $anotherField;
|
||||
}
|
||||
|
||||
class IncorrectIndexByFieldEntity
|
||||
{
|
||||
/** @var int */
|
||||
|
||||
Reference in New Issue
Block a user