Compare commits

...

36 Commits
3.5.4 ... 3.5.8

Author SHA1 Message Date
Alexander M. Turek
78dd074266 Remove obsolete VarExporter feature detection (#12309) 2025-11-30 00:11:02 +01:00
Alexander M. Turek
ff22a00fcf Allow Symfony 8 (#12308) 2025-11-30 00:10:09 +01:00
Alexander M. Turek
02e8ff9663 Explicitly set a cache in testDisablingXmlValidationIsPossible (#12307) 2025-11-29 23:16:54 +01:00
Alexander M. Turek
2e75a7f1c1 Merge branch '2.20.x' into 3.5.x
* 2.20.x:
  Support Symfony Console 8 (#12300)
  Bump doctrine/.github/.github/workflows/composer-lint.yml (#12288)
  Bump doctrine/.github/.github/workflows/documentation.yml (#12289)
  Bump doctrine/.github/.github/workflows/coding-standards.yml (#12290)
  Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml (#12291)
  Bump actions/checkout from 5 to 6 (#12292)
2025-11-29 22:07:13 +01:00
Alexander M. Turek
152b0e3d65 Removes Guides from our dependencies (#12303) 2025-11-29 21:55:58 +01:00
Alexander M. Turek
9d11fdd3da Fix PHPStan and test errors after DBAL 4.4 and Symfony 7.4 releases (#12301)
* Fix PHPStan errors after DBAL 4.4 and Symfony 7.4 releases

* Fix PHPStan and test errors after DBAL 4.4 and Symfony 7.4 releases
2025-11-29 18:04:54 +01:00
Alexander M. Turek
87f1ba74e0 Support Symfony Console 8 (#12300) 2025-11-29 15:03:56 +01:00
Grégoire Paris
ee70178314 Merge pull request #12287 from greg0ire/more-order-by
Add ORDER BY clause to more test cases
2025-11-24 09:40:48 +01:00
dependabot[bot]
ab148d3d9d Bump doctrine/.github/.github/workflows/composer-lint.yml (#12288)
Bumps [doctrine/.github/.github/workflows/composer-lint.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/composer-lint.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:25:08 +01:00
dependabot[bot]
3924c38fab Bump doctrine/.github/.github/workflows/documentation.yml (#12289)
Bumps [doctrine/.github/.github/workflows/documentation.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/documentation.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:24:42 +01:00
dependabot[bot]
9814078a2c Bump doctrine/.github/.github/workflows/coding-standards.yml (#12290)
Bumps [doctrine/.github/.github/workflows/coding-standards.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/coding-standards.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:24:14 +01:00
dependabot[bot]
6de5684fd9 Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml (#12291)
Bumps [doctrine/.github/.github/workflows/release-on-milestone-closed.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/release-on-milestone-closed.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:23:46 +01:00
dependabot[bot]
c142503a52 Bump actions/checkout from 5 to 6 (#12292) 2025-11-24 07:27:27 +01:00
Grégoire Paris
01c178b297 Add ORDER BY clause to more test cases
In https://github.com/doctrine/orm/pull/12222, I thought wrongly thought
the issue I was fixing only affected one test.
2025-11-23 19:18:20 +01:00
Grégoire Paris
ffa50a777f Merge pull request #12286 from greg0ire/3.5.x
Merge 2.20.x up into 3.5.x
2025-11-21 20:24:51 +01:00
Grégoire Paris
649048f745 Merge remote-tracking branch 'origin/2.20.x' into 3.5.x 2025-11-21 19:46:45 +01:00
Grégoire Paris
15537bc218 Merge pull request #12285 from HypeMC/fix-is-foreign-key-composite
Fix check for composite foreign key
2025-11-21 19:29:05 +01:00
HypeMC
bc95c7c08d Fix check for composite foreign key 2025-11-21 07:27:53 +01:00
Grégoire Paris
3df11d518c Merge pull request #12283 from doctrine/2.20.x
Merge 2.20.x up into 3.5.x
2025-11-20 21:31:19 +01:00
Grégoire Paris
c1becd54e6 Merge pull request #12281 from greg0ire/document-default-expressions
Fix documentation about default values
2025-11-20 17:58:48 +01:00
Grégoire Paris
e4d7df29c2 Fix documentation about default values
Saying it is not possible to get Doctrine to use the `DEFAULT` SQL
keyword is wrong.
2025-11-19 23:17:53 +01:00
Grégoire Paris
608705427e Merge pull request #12277 from greg0ire/3.5.x
Merge 2.20.x up into 3.5.x
2025-11-19 07:13:23 +01:00
Grégoire Paris
9f19310f27 Merge remote-tracking branch 'origin/2.20.x' into 3.5.x 2025-11-19 00:01:30 +01:00
Adrian Brajkovic
e38278bfca Fix eager fetch composite foreign key (#11397)
I think #11289 did not completely fix problem for eager fetch.
Change in that PR checked if primary key of target class is composite but that does not matter when loading collection by foreign key.
It should check if foreign key on target class is composite.

Fix from that PR did not work for me because i had entity with regular autogenerated id (single column), but foreign key referenced entity with composite primary key, like SecondLevelWithoutCompositePrimaryKey in this PR.

Checking if foreign key is composite fixed the problem for me.
2025-11-18 21:40:50 +01:00
Grégoire Paris
f18de9d569 Merge pull request #12269 from greg0ire/3.5.x
Undo merge from 3.6.x into 3.5.x
2025-11-11 19:27:40 +01:00
Grégoire Paris
37f76a8381 Undo merge from 3.6.x into 3.5.x 2025-11-11 19:11:32 +01:00
Grégoire Paris
b62292256a Merge pull request #12265 from greg0ire/3.5.x
Merge 2.20.x up into 3.5.x
2025-11-10 22:11:37 +01:00
Grégoire Paris
b138395194 Merge remote-tracking branch 'origin/2.20.x' into 3.5.x 2025-11-10 21:32:47 +01:00
Grégoire Paris
5bff0919a7 Merge pull request #12254 from elliotbruneel/fix/empty-array-query
fix: handling of empty array in SQL condition generation
2025-11-10 14:35:45 +01:00
Elliot Bruneel
9ef0f5301b fix: update SQL condition for empty array to 1=0 instead of IN (NULL) 2025-11-10 10:44:48 +01:00
Elliot Bruneel
4989ca6f15 test: add test for finding by nullable field with empty array 2025-11-05 10:03:09 +01:00
Elliot Bruneel
32d1e97ce7 chore: improve empty array check in SQL condition generation 2025-11-05 09:51:33 +01:00
Grégoire Paris
ca8147b148 Merge pull request #12257 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-12.2.0
Bump doctrine/.github from 12.1.0 to 12.2.0
2025-11-03 09:32:58 +01:00
dependabot[bot]
c8ebea77f0 Bump doctrine/.github from 12.1.0 to 12.2.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 12.1.0 to 12.2.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/12.1.0...v12.2.0)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-version: 12.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 06:11:36 +00:00
Elliot Bruneel
23f22860f1 chore: update phpstan version and regenerate baseline 2025-10-31 09:04:03 +01:00
Elliot Bruneel
b24586b1b5 fix: handling of empty array in SQL condition generation 2025-10-30 17:31:04 +01:00
95 changed files with 430 additions and 1060 deletions

View File

@@ -24,4 +24,4 @@ on:
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@12.1.0"
uses: "doctrine/.github/.github/workflows/coding-standards.yml@13.0.0"

View File

@@ -17,4 +17,4 @@ on:
jobs:
composer-lint:
name: "Composer Lint"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@12.1.0"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@13.0.0"

View File

@@ -44,43 +44,31 @@ jobs:
- "pdo_sqlite"
deps:
- "highest"
stability:
- "stable"
native_lazy:
- "0"
include:
- php-version: "8.2"
dbal-version: "4@dev"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "0"
- php-version: "8.2"
dbal-version: "4@dev"
extension: "sqlite3"
stability: "stable"
native_lazy: "0"
- php-version: "8.1"
dbal-version: "default"
deps: "lowest"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "0"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "1"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "sqlite3"
stability: "dev"
native_lazy: "1"
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -92,18 +80,14 @@ jobs:
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Allow dev dependencies"
run: |
composer config minimum-stability dev
composer remove --no-update --dev phpbench/phpbench phpdocumentor/guides-cli
composer require --no-update symfony/console:^8 symfony/var-exporter:^8 doctrine/dbal:^4.4
composer require --dev --no-update symfony/cache:^8
if: "${{ matrix.stability == 'dev' }}"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Downgrade VarExporter"
run: 'composer require --no-update "symfony/var-exporter:^6.4 || ^7.4"'
if: "${{ matrix.native_lazy == '0' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
@@ -141,7 +125,7 @@ jobs:
- name: "Upload coverage file"
uses: "actions/upload-artifact@v5"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.stability }}-${{ matrix.native_lazy }}-coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.native_lazy }}-coverage"
path: "coverage*.xml"
@@ -223,7 +207,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -291,7 +275,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -367,7 +351,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -431,7 +415,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2

View File

@@ -17,4 +17,4 @@ on:
jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@12.1.0"
uses: "doctrine/.github/.github/workflows/documentation.yml@13.0.0"

View File

@@ -36,7 +36,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2

View File

@@ -7,7 +7,7 @@ on:
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@12.1.0"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@13.0.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}

View File

@@ -35,7 +35,7 @@ jobs:
steps:
- name: "Checkout code"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
- name: Install PHP
uses: shivammathur/setup-php@v2

View File

@@ -27,54 +27,6 @@ At this point, we recommend upgrading to PHP 8.4 first and then directly from
ORM 2.19 to 3.5 and up so that you can skip the lazy ghost proxy generation
and directly start using native lazy objects.
# Upgrade to 3.6
## Deprecate specifying `nullable` on columns that end up being used in a primary key
Specifying `nullable` on join columns that are part of a primary key is
deprecated and will be an error in 4.0.
This can happen when using a join column mapping together with an id mapping,
or when using a join column mapping or an inverse join column mapping on a
many-to-many relationship.
```diff
class User
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Id]
#[ORM\ManyToOne(targetEntity: Family::class, inversedBy: 'users')]
- #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id', nullable: true)]
+ #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id')]
private ?Family $family;
#[ORM\ManyToMany(targetEntity: Group::class)]
#[ORM\JoinTable(name: 'user_group')]
- #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: true)]
- #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id', nullable: true)]
+ #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id')]
+ #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
private Collection $groups;
}
```
## Deprecate `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
Using `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
is deprecated in favor of using an associative array of join parts with the
root alias as key.
## Deprecate using the `WITH` keyword for arbitrary DQL joins
Using the `WITH` keyword to specify the condition for an arbitrary DQL join is
deprecated in favor of using the `ON` keyword (similar to the SQL syntax for
joins).
The `WITH` keyword is now meant to be used only for filtering conditions in
association joins.
# Upgrade to 3.5
See the General notes to upgrading to 3.x versions above.

View File

@@ -49,9 +49,8 @@
"require-dev": {
"doctrine/coding-standard": "^14.0",
"phpbench/phpbench": "^1.0",
"phpdocumentor/guides-cli": "^1.4",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "2.1.22",
"phpstan/phpstan": "2.1.23",
"phpstan/phpstan-deprecation-rules": "^2",
"phpunit/phpunit": "^10.5.0 || ^11.5",
"psr/log": "^1 || ^2 || ^3",

View File

@@ -29,7 +29,7 @@ steps of configuration.
$config = new Configuration;
$config->setMetadataCache($metadataCache);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
@@ -156,59 +156,15 @@ The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
<?php
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the attribute
driver, because otherwise mass-operations on all entities through
the console could not work correctly. Metadata drivers can accept either
a single directory as a string or an array of directories.
AttributeDriver also accepts ``Doctrine\Persistence\Mapping\Driver\ClassLocator``,
allowing one to customize file discovery logic. You may choose to use Symfony Finder, or
utilize directory scan with ``FileClassLocator::createFromDirectories()``:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
$paths = ['/path/to/lib/MyProject/Entities'];
$classLocator = FileClassLocator::createFromDirectories($paths);
$driverImpl = new AttributeDriver($classLocator);
$config->setMetadataDriverImpl($driverImpl);
With this feature, you're empowered to provide a fine-grained iterator of only necessary
files to the Driver. For example, if you are using Vertical Slice architecture, you can
exclude ``*Test.php``, ``*Controller.php``, ``*Service.php``, etc.:
.. code-block:: php
<?php
use Symfony\Component\Finder\Finder;
$finder = new Finder()->files()->in($paths)
->name('*.php')
->notName(['*Test.php', '*Controller.php', '*Service.php']);
$classLocator = new FileClassLocator($finder);
If you know the list of class names you want to track, use
``Doctrine\Persistence\Mapping\Driver\ClassNames``:
.. code-block:: php
<?php
use Doctrine\Persistence\Mapping\Driver\ClassNames;
use App\Entity\{Article, Book};
$entityClasses = [Article::class, Book::class];
$classLocator = new ClassNames($entityClasses);
$driverImpl = new AttributeDriver($classLocator);
$config->setMetadataDriverImpl($driverImpl);
the console could not work correctly. All of metadata drivers
accept either a single directory as a string or an array of
directories. With this feature a single driver can support multiple
directories of Entities.
Metadata Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -182,6 +182,21 @@ Here is a complete list of ``Column``s attributes (all optional):
- ``options``: Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
Specifying default values
~~~~~~~~~~~~~~~~~~~~~~~~~
While it is possible to specify default values for properties in your
PHP class, Doctrine also allows you to specify default values for
database columns using the ``default`` key in the ``options`` array of
the ``Column`` attribute.
.. configuration-block::
.. literalinclude:: basic-mapping/DefaultValues.php
:language: attribute
.. literalinclude:: basic-mapping/default-values.xml
:language: xml
.. _reference-php-mapping-types:
PHP Types Mapping

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
#[Entity]
class Message
{
#[Column(options: ['default' => 'Hello World!'])]
private string $text;
}

View File

@@ -0,0 +1,9 @@
<doctrine-mapping>
<entity name="Message">
<field name="text">
<options>
<option name="default">Hello World!</option>
</options>
</field>
</entity>
</doctrine-mapping>

View File

@@ -490,7 +490,7 @@ where you can generate an arbitrary join with the following syntax:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b ON u.email = b.email');
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b WITH u.email = b.email');
With an arbitrary join the result differs from the joins using a mapped property.
The result of an arbitrary join is an one dimensional array with a mix of the entity from the ``SELECT``
@@ -513,15 +513,13 @@ it loads all the related ``Banlist`` objects corresponding to this ``User``. Thi
when the DQL is switched to an arbitrary join.
.. note::
The differences between WHERE, WITH, ON and HAVING clauses may be
The differences between WHERE, WITH and HAVING clauses may be
confusing.
- WHERE is applied to the results of an entire query
- ON is applied to arbitrary joins as the join condition. For
arbitrary joins (SELECT f, b FROM Foo f, Bar b ON f.id = b.id)
the ON is required, even if it is 1 = 1. WITH is also
supported as alternative keyword for that case for BC reasons.
- WITH is applied to an association join as an additional condition.
- WITH is applied to a join as an additional condition. For
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
the WITH is required, even if it is 1 = 1
- HAVING is applied to the results of a query after
aggregation (GROUP BY)
@@ -1701,14 +1699,9 @@ From, Join and Index by
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration ["WITH" ConditionalExpression] | RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression])
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration | RangeVariableDeclaration) ["WITH" ConditionalExpression]
IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
.. note::
Using the ``WITH`` keyword for the ``ConditionalExpression`` of a
``RangeVariableDeclaration`` is deprecated and will be removed in
ORM 4.0. Use the ``ON`` keyword instead.
Select Expressions
~~~~~~~~~~~~~~~~~~

View File

@@ -18,30 +18,6 @@ In your mapping configuration, the column definition (for example, the
the ``charset`` and ``collation``. The default values are ``utf8`` and
``utf8_unicode_ci``, respectively.
Entity Classes
--------------
How can I add default values to a column?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine does not support to set the default values in columns through the "DEFAULT" keyword in SQL.
This is not necessary however, you can just use your class properties as default values. These are then used
upon insert:
.. code-block:: php
class User
{
private const STATUS_DISABLED = 0;
private const STATUS_ENABLED = 1;
private string $algorithm = "sha1";
/** @var self::STATUS_* */
private int $status = self::STATUS_DISABLED;
}
.
Mapping
-------

View File

@@ -696,12 +696,6 @@ parameters:
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) should return array\{data\: array\<array\>, newObjects\?\: array\<array\{class\: ReflectionClass, args\: array, obj\: object\}\>, scalars\?\: array\} but returns array\{data\: array, newObjects\: array\<array\{class\: ReflectionClass\<object\>, args\: array, obj\?\: object\}\>, scalars\?\: non\-empty\-array\}\.$#'
identifier: return.type
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
@@ -822,6 +816,12 @@ parameters:
count: 1
path: src/Internal/HydrationCompleteHandler.php
-
message: '#^Offset int\|null might not exist on array\<int, object\>\.$#'
identifier: offsetAccess.notFound
count: 1
path: src/Internal/StronglyConnectedComponents.php
-
message: '#^Property Doctrine\\ORM\\Internal\\StronglyConnectedComponents\:\:\$representingNodes \(array\<int, object\>\) does not accept array\<int\|string, object\>\.$#'
identifier: assign.propertyType
@@ -1410,12 +1410,6 @@ parameters:
count: 2
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#1 \$asset of static method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) expects Doctrine\\DBAL\\Schema\\AbstractAsset, Doctrine\\DBAL\\Schema\\Column\|false given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#2 \$columnName of method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getFieldNameForColumn\(\) expects string, string\|false given\.$#'
identifier: argument.type
@@ -1536,6 +1530,12 @@ parameters:
count: 1
path: src/Mapping/LegacyReflectionFields.php
-
message: '#^Strict comparison using \!\=\= between array\<string, string\> and null will always evaluate to true\.$#'
identifier: notIdentical.alwaysTrue
count: 1
path: src/Mapping/ManyToManyOwningSideMapping.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\MappedSuperclass\:\:__construct\(\) has parameter \$repositoryClass with generic class Doctrine\\ORM\\EntityRepository but does not specify its types\: T$#'
identifier: missingType.generics
@@ -2557,7 +2557,7 @@ parameters:
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '#^Parameter \#3 \$types of method Doctrine\\DBAL\\Connection\:\:executeStatement\(\) expects array\<int\<0, max\>\|string, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|string\>, list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|Doctrine\\DBAL\\Types\\Type\|int\|string\> given\.$#'
message: '#^Parameter \#3 \$types of method Doctrine\\DBAL\\Connection\:\:executeStatement\(\) expects array\<int\<0, max\>\|string, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|string\>, list\<Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|int\|string\> given\.$#'
identifier: argument.type
count: 1
path: src/Query/Exec/MultiTableUpdateExecutor.php
@@ -2604,6 +2604,12 @@ parameters:
count: 1
path: src/Query/Expr/Select.php
-
message: '#^Property Doctrine\\ORM\\Query\\Filter\\SQLFilter\:\:\$parameters \(array\<string, array\{type\: string, value\: mixed, is_list\: bool\}\>\) does not accept non\-empty\-array\<string, array\{value\: mixed, type\: Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|int\|string, is_list\: bool\}\>\.$#'
identifier: assign.propertyType
count: 2
path: src/Query/Filter/SQLFilter.php
-
message: '#^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns int so it can be removed from the return type\.$#'
identifier: return.unusedType
@@ -2874,12 +2880,6 @@ parameters:
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Parameter \#2 \$proxyDir of method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateProxyClasses\(\) expects string\|null, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\Command\\MappingDescribeCommand\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
@@ -3345,6 +3345,12 @@ parameters:
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\AssociationMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$inversedBy\.$#'
identifier: property.notFound
@@ -3360,7 +3366,7 @@ parameters:
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
identifier: property.notFound
count: 1
count: 3
path: src/UnitOfWork.php
-

View File

@@ -98,6 +98,17 @@ parameters:
identifier: argument.unresolvableType
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#1 \$asset of static method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) expects Doctrine\\DBAL\\Schema\\AbstractAsset, Doctrine\\DBAL\\Schema\\Column\|false given\.$#'
identifier: argument.type
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Instantiated class Doctrine\\DBAL\\Schema\\DefaultExpression\\\w+ not found\.$#'
identifier: class.notFound
path: src/Tools/SchemaTool.php
# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
@@ -149,8 +160,3 @@ parameters:
-
message: '~inferType.*never returns~'
path: src/Query/ParameterTypeInferer.php
# Compatibility with Symfony 8
-
message: '#^Call to function method_exists\(\) with ''Symfony\\\\Component\\\\VarExporter\\\\ProxyHelper'' and ''generateLazyGhost'' will always evaluate to true\.$#'
path: src/Proxy/ProxyFactory.php

View File

@@ -12,10 +12,6 @@ parameters:
message: '~^Match expression does not handle remaining values:~'
path: src/Utility/PersisterHelper.php
# The return type is already narrow enough.
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns ''[a-z_]+'' so it can be removed from the return type\.$~'
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns Doctrine\\DBAL\\(?:Array)?ParameterType\:\:[A-Z_]+ so it can be removed from the return type\.$~'
# DBAL 4 compatibility
-
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
@@ -54,8 +50,3 @@ parameters:
-
message: '#Expression on left side of \?\? is not nullable.#'
path: src/Mapping/Driver/AttributeDriver.php
# Compatibility with Symfony 8
-
message: '#^Call to function method_exists\(\) with ''Symfony\\\\Component\\\\VarExporter\\\\ProxyHelper'' and ''generateLazyGhost'' will always evaluate to true\.$#'
path: src/Proxy/ProxyFactory.php

View File

@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\ORM\Exception\MultipleSelectorsFoundException;
use function array_column;
use function count;
/**
@@ -26,6 +27,8 @@ final class ScalarColumnHydrator extends AbstractHydrator
throw MultipleSelectorsFoundException::create($this->resultSetMapping()->fieldMappings);
}
return $this->statement()->fetchFirstColumn();
$result = $this->statement()->fetchAllNumeric();
return array_column($result, 0);
}
}

View File

@@ -113,10 +113,6 @@ class AssociationBuilder
string|null $onDelete = null,
string|null $columnDef = null,
): static {
if ($this->mapping['id'] ?? false) {
$nullable = null;
}
$this->joinColumns[] = [
'name' => $columnName,
'referencedColumnName' => $referencedColumnName,
@@ -137,9 +133,6 @@ class AssociationBuilder
public function makePrimaryKey(): static
{
$this->mapping['id'] = true;
foreach ($this->joinColumns ?? [] as $i => $joinColumn) {
$this->joinColumns[$i]['nullable'] = null;
}
return $this;
}

View File

@@ -24,30 +24,6 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
return $this;
}
/**
* Add Join Columns.
*
* @return $this
*/
public function addJoinColumn(
string $columnName,
string $referencedColumnName,
bool $nullable = true,
bool $unique = false,
string|null $onDelete = null,
string|null $columnDef = null,
): static {
$this->joinColumns[] = [
'name' => $columnName,
'referencedColumnName' => $referencedColumnName,
'unique' => $unique,
'onDelete' => $onDelete,
'columnDefinition' => $columnDef,
];
return $this;
}
/**
* Adds Inverse Join Columns.
*
@@ -64,6 +40,7 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
$this->inverseJoinColumns[] = [
'name' => $columnName,
'referencedColumnName' => $referencedColumnName,
'nullable' => $nullable,
'unique' => $unique,
'onDelete' => $onDelete,
'columnDefinition' => $columnDef,

View File

@@ -546,7 +546,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*/
public LegacyReflectionFields|array $reflFields = [];
/** @var array<string, PropertyAccessor> */
/** @var array<string, PropertyAccessors\PropertyAccessor> */
public array $propertyAccessors = [];
private InstantiatorInterface|null $instantiator = null;
@@ -584,7 +584,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* Gets the ReflectionProperties of the mapped class.
*
* @return array<string, PropertyAccessor> An array of PropertyAccessor instances by name.
* @return PropertyAccessor[] An array of PropertyAccessor instances.
*/
public function getPropertyAccessors(): array
{

View File

@@ -10,7 +10,6 @@ use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use InvalidArgumentException;
@@ -36,10 +35,10 @@ class AttributeDriver implements MappingDriver
private readonly AttributeReader $reader;
/**
* @param string[]|ClassLocator $paths a ClassLocator, or an array of directories.
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
* @param array<string> $paths
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
*/
public function __construct(array|ClassLocator $paths, bool $reportFieldsWhereDeclared = true)
public function __construct(array $paths, bool $reportFieldsWhereDeclared = true)
{
if (! $reportFieldsWhereDeclared) {
throw new InvalidArgumentException(sprintf(
@@ -49,12 +48,7 @@ class AttributeDriver implements MappingDriver
}
$this->reader = new AttributeReader();
if ($paths instanceof ClassLocator) {
$this->classLocator = $paths;
} else {
$this->addPaths($paths);
}
$this->addPaths($paths);
}
public function isTransient(string $className): bool

View File

@@ -4,6 +4,8 @@ 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;
@@ -19,6 +21,7 @@ use function assert;
use function constant;
use function count;
use function defined;
use function enum_exists;
use function explode;
use function extension_loaded;
use function file_get_contents;
@@ -406,7 +409,10 @@ class XmlDriver extends FileDriver
if (isset($oneToManyElement->{'order-by'})) {
$orderBy = [];
foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
$orderBy[(string) $orderByField['name']] = (string) ($orderByField['direction'] ?? 'ASC');
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
? (string) $orderByField['direction']
// @phpstan-ignore classConstant.deprecated
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
}
$mapping['orderBy'] = $orderBy;
@@ -532,7 +538,10 @@ class XmlDriver extends FileDriver
if (isset($manyToManyElement->{'order-by'})) {
$orderBy = [];
foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
$orderBy[(string) $orderByField['name']] = (string) ($orderByField['direction'] ?? 'ASC');
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
? (string) $orderByField['direction']
// @phpstan-ignore classConstant.deprecated
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
}
$mapping['orderBy'] = $orderBy;

View File

@@ -84,14 +84,9 @@ final class JoinTableMapping implements ArrayAccess
/** @return mixed[] */
public function toArray(): array
{
$array = (array) $this;
$toArray = static function (JoinColumnMapping $column) {
$array = (array) $column;
$array = (array) $this;
unset($array['nullable']);
return $array;
};
$toArray = static fn (JoinColumnMapping $column): array => (array) $column;
$array['joinColumns'] = array_map($toArray, $array['joinColumns']);
$array['inverseJoinColumns'] = array_map($toArray, $array['inverseJoinColumns']);

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Deprecations\Deprecation;
use function strtolower;
use function trim;
@@ -129,20 +127,6 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
$mapping->joinTableColumns = [];
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
if ($joinColumn->nullable !== null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12126',
<<<'DEPRECATION'
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
The ORM will always set it to false.
Doing so is deprecated and will be an error in 4.0.
DEPRECATION,
$mapping->sourceEntity,
$mapping->fieldName,
);
}
$joinColumn->nullable = false;
if (empty($joinColumn->referencedColumnName)) {
@@ -168,20 +152,6 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
}
foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) {
if ($inverseJoinColumn->nullable !== null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12126',
<<<'DEPRECATION'
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
The ORM will always set it to false.
Doing so is deprecated and will be an error in 4.0.
DEPRECATION,
$mapping->targetEntity,
$mapping->fieldName,
);
}
$inverseJoinColumn->nullable = false;
if (empty($inverseJoinColumn->referencedColumnName)) {

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Deprecations\Deprecation;
use RuntimeException;
use function array_flip;
@@ -132,20 +131,6 @@ abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOne
foreach ($mapping->joinColumns as $joinColumn) {
if ($mapping->id) {
if ($joinColumn->nullable !== null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12126',
<<<'DEPRECATION'
Specifying the "nullable" attribute for join columns in to-one associations (here, %s::$%s) that are part of the identifier is a no-op.
The ORM will always set it to false.
Doing so is deprecated and will be an error in 4.0.
DEPRECATION,
$mapping->sourceEntity,
$mapping->fieldName,
);
}
$joinColumn->nullable = false;
} elseif ($joinColumn->nullable === null) {
$joinColumn->nullable = true;
@@ -215,12 +200,7 @@ abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOne
$joinColumns = [];
foreach ($array['joinColumns'] as $column) {
$columnArray = (array) $column;
if ($this->id) {
unset($columnArray['nullable']);
}
$joinColumns[] = $columnArray;
$joinColumns[] = (array) $column;
}
$array['joinColumns'] = $joinColumns;

View File

@@ -7,7 +7,6 @@ namespace Doctrine\ORM;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Psr\Cache\CacheItemPoolInterface;
use Redis;
use RuntimeException;
@@ -29,10 +28,10 @@ final class ORMSetup
/**
* Creates a configuration with an attribute metadata driver.
*
* @param string[]|ClassLocator $paths
* @param string[] $paths
*/
public static function createAttributeMetadataConfiguration(
array|ClassLocator $paths,
array $paths,
bool $isDevMode = false,
string|null $proxyDir = null,
CacheItemPoolInterface|null $cache = null,
@@ -56,10 +55,10 @@ final class ORMSetup
/**
* Creates a configuration with an attribute metadata driver.
*
* @param string[]|ClassLocator $paths
* @param string[] $paths
*/
public static function createAttributeMetadataConfig(
array|ClassLocator $paths,
array $paths,
bool $isDevMode = false,
string|null $cacheNamespaceSeed = null,
CacheItemPoolInterface|null $cache = null,

View File

@@ -1668,6 +1668,11 @@ class BasicEntityPersister implements EntityPersister
$value = [$value];
}
if ($value === []) {
$selectedColumns[] = '1=0';
continue;
}
$nullKeys = array_keys($value, null, true);
$nonNullValues = array_diff_key($value, array_flip($nullKeys));

View File

@@ -162,6 +162,7 @@ EOPHP;
);
}
// @phpstan-ignore function.impossibleType (This method has been removed in Symfony 8)
if (! method_exists(ProxyHelper::class, 'generateLazyGhost')) {
throw ORMInvalidArgumentException::lazyGhostUnavailable();
}
@@ -469,7 +470,7 @@ EOPHP;
private function generateUseLazyGhostTrait(ClassMetadata $class): string
{
// @phpstan-ignore staticMethod.deprecated (Because we support Symfony < 7.3)
// @phpstan-ignore staticMethod.notFound (This method has been removed in Symfony 8)
$code = ProxyHelper::generateLazyGhost($class->getReflectionClass());
$code = substr($code, 7 + (int) strpos($code, "\n{"));
$code = substr($code, 0, (int) strpos($code, "\n}"));

View File

@@ -6,28 +6,18 @@ namespace Doctrine\ORM\Query\Exec;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Query\AST\SelectStatement;
use Doctrine\ORM\Query\SqlWalker;
/**
* Executor that executes the SQL statement for simple DQL SELECT statements.
*
* @deprecated This class is no longer needed by the ORM and will be removed in 4.0.
*
* @link www.doctrine-project.org
*/
class SingleSelectExecutor extends AbstractSqlExecutor
{
public function __construct(SelectStatement $AST, SqlWalker $sqlWalker)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11188/',
'The %s is no longer needed by the ORM and will be removed in 4.0',
self::class,
);
$this->sqlStatements = $sqlWalker->walkSelectStatement($AST);
}

View File

@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Query\Filter;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\ParameterType;
/** @internal */
final class Parameter
{
/** @param ParameterType::*|ArrayParameterType::*|string $type */
public function __construct(
public readonly mixed $value,
public readonly ParameterType|ArrayParameterType|int|string $type,
public readonly bool $isList,
) {
}
}

View File

@@ -29,7 +29,7 @@ abstract class SQLFilter implements Stringable
/**
* Parameters for the filter.
*
* @phpstan-var array<string, Parameter>
* @phpstan-var array<string,array{type: string, value: mixed, is_list: bool}>
*/
private array $parameters = [];
@@ -49,7 +49,7 @@ abstract class SQLFilter implements Stringable
*/
final public function setParameterList(string $name, array $values, string $type = Types::STRING): static
{
$this->parameters[$name] = new Parameter(value: $values, type: $type, isList: true);
$this->parameters[$name] = ['value' => $values, 'type' => $type, 'is_list' => true];
// Keep the parameters sorted for the hash
ksort($this->parameters);
@@ -71,11 +71,11 @@ abstract class SQLFilter implements Stringable
*/
final public function setParameter(string $name, mixed $value, string|null $type = null): static
{
$this->parameters[$name] = new Parameter(
value: $value,
type: $type ?? ParameterTypeInferer::inferType($value),
isList: false,
);
if ($type === null) {
$type = ParameterTypeInferer::inferType($value);
}
$this->parameters[$name] = ['value' => $value, 'type' => $type, 'is_list' => false];
// Keep the parameters sorted for the hash
ksort($this->parameters);
@@ -102,11 +102,11 @@ abstract class SQLFilter implements Stringable
throw new InvalidArgumentException("Parameter '" . $name . "' does not exist.");
}
if ($this->parameters[$name]->isList) {
if ($this->parameters[$name]['is_list']) {
throw FilterException::cannotConvertListParameterIntoSingleValue($name);
}
return $this->em->getConnection()->quote((string) $this->parameters[$name]->value);
return $this->em->getConnection()->quote((string) $this->parameters[$name]['value']);
}
/**
@@ -124,7 +124,7 @@ abstract class SQLFilter implements Stringable
throw new InvalidArgumentException("Parameter '" . $name . "' does not exist.");
}
if (! $this->parameters[$name]->isList) {
if ($this->parameters[$name]['is_list'] === false) {
throw FilterException::cannotConvertSingleParameterIntoListValue($name);
}
@@ -133,7 +133,7 @@ abstract class SQLFilter implements Stringable
$quoted = array_map(
static fn (mixed $value): string => $connection->quote((string) $value),
$param->value,
$param['value'],
);
return implode(',', $quoted);
@@ -152,14 +152,7 @@ abstract class SQLFilter implements Stringable
*/
final public function __toString(): string
{
return serialize(array_map(
static fn (Parameter $value): array => [
'value' => $value->value,
'type' => $value->type,
'is_list' => $value->isList,
],
$this->parameters,
));
return serialize($this->parameters);
}
/**

View File

@@ -25,9 +25,9 @@ use function is_int;
final class ParameterTypeInferer
{
/**
* Infers the type of a given value
*
* @return ParameterType::*|ArrayParameterType::*|Types::*
* Infers type of a given value, returning a compatible constant:
* - Type (\Doctrine\DBAL\Types\Type::*)
* - Connection (\Doctrine\DBAL\Connection::PARAM_*)
*/
public static function inferType(mixed $value): ParameterType|ArrayParameterType|int|string
{

View File

@@ -1609,7 +1609,8 @@ final class Parser
/**
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
* (JoinAssociationDeclaration ["WITH" ConditionalExpression] | RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression])
* (JoinAssociationDeclaration | RangeVariableDeclaration)
* ["WITH" ConditionalExpression]
*/
public function Join(): AST\Join
{
@@ -1643,31 +1644,21 @@ final class Parser
$next = $this->lexer->glimpse();
assert($next !== null);
$conditionalExpression = null;
$joinDeclaration = $next->type === TokenType::T_DOT ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
$adhocConditions = $this->lexer->isNextToken(TokenType::T_WITH);
$join = new AST\Join($joinType, $joinDeclaration);
if ($next->type === TokenType::T_DOT) {
$joinDeclaration = $this->JoinAssociationDeclaration();
if ($this->lexer->isNextToken(TokenType::T_WITH)) {
$this->match(TokenType::T_WITH);
$conditionalExpression = $this->ConditionalExpression();
}
} else {
$joinDeclaration = $this->RangeVariableDeclaration();
// Describe non-root join declaration
if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
$joinDeclaration->isRoot = false;
if ($this->lexer->isNextToken(TokenType::T_ON)) {
$this->match(TokenType::T_ON);
$conditionalExpression = $this->ConditionalExpression();
} elseif ($this->lexer->isNextToken(TokenType::T_WITH)) {
$this->match(TokenType::T_WITH);
$conditionalExpression = $this->ConditionalExpression();
Deprecation::trigger('doctrine/orm', 'https://github.com/doctrine/orm/issues/12192', 'Using WITH for the join condition of arbitrary joins is deprecated. Use ON instead.');
}
}
$join = new AST\Join($joinType, $joinDeclaration);
$join->conditionalExpression = $conditionalExpression;
// Check for ad-hoc Join conditions
if ($adhocConditions) {
$this->match(TokenType::T_WITH);
$join->conditionalExpression = $this->ConditionalExpression();
}
return $join;
}

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
use Doctrine\ORM\Query\Exec\SqlFinalizer;
@@ -72,36 +71,20 @@ class ParserResult
/**
* Sets the SQL executor that should be used for this ParserResult.
*
* @deprecated The SqlExecutor will be removed from ParserResult in 4.0. Provide a SqlFinalizer instead that can create the executor.
* @deprecated
*/
public function setSqlExecutor(AbstractSqlExecutor $executor): void
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11188',
'The SqlExecutor will be removed from %s in 4.0. Provide a %s instead that can create the executor.',
self::class,
SqlFinalizer::class,
);
$this->sqlExecutor = $executor;
}
/**
* Gets the SQL executor used by this ParserResult.
*
* @deprecated The SqlExecutor will be removed from ParserResult in 4.0. Provide a SqlFinalizer instead that can create the executor.
* @deprecated
*/
public function getSqlExecutor(): AbstractSqlExecutor
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11188',
'The SqlExecutor will be removed from %s in 4.0. Provide a %s instead that can create the executor.',
self::class,
SqlFinalizer::class,
);
if ($this->sqlExecutor === null) {
throw new LogicException(sprintf(
'Executor not set yet. Call %s::setSqlExecutor() first.',

View File

@@ -9,7 +9,6 @@ use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\QuoteStrategy;
@@ -231,14 +230,6 @@ class SqlWalker
*/
public function getExecutor(AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement $statement): Exec\AbstractSqlExecutor
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11188/',
'Output walkers should implement %s. That way, the %s method is no longer needed and will be removed in 4.0',
OutputWalker::class,
__METHOD__,
);
return match (true) {
$statement instanceof AST\UpdateStatement => $this->createUpdateStatementExecutor($statement),
$statement instanceof AST\DeleteStatement => $this->createDeleteStatementExecutor($statement),

View File

@@ -90,5 +90,4 @@ enum TokenType: int
case T_WHERE = 255;
case T_WITH = 256;
case T_NAMED = 257;
case T_ON = 258;
}

View File

@@ -8,7 +8,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Parameter;
@@ -306,13 +305,8 @@ class QueryBuilder implements Stringable
} else {
// Should never happen with correct joining order. Might be
// thoughtful to throw exception instead.
$aliases = $this->getRootAliases();
if (! isset($aliases[0])) {
throw new RuntimeException('No alias was set before invoking getRootAlias().');
}
$rootAlias = $aliases[0];
// @phpstan-ignore method.deprecated
$rootAlias = $this->getRootAlias();
}
$this->joinRootAliases[$alias] = $rootAlias;
@@ -547,10 +541,6 @@ class QueryBuilder implements Stringable
*/
public function setMaxResults(int|null $maxResults): static
{
if ($this->type === QueryType::Delete || $this->type === QueryType::Update) {
throw new RuntimeException('Setting a limit is not supported for delete or update queries.');
}
$this->maxResults = $maxResults;
return $this;
@@ -592,25 +582,14 @@ class QueryBuilder implements Stringable
$dqlPart = reset($dqlPart);
}
// This is introduced for backwards compatibility reasons.
// TODO: Remove for 3.0
if ($dqlPartName === 'join') {
$newDqlPart = [];
foreach ($dqlPart as $k => $v) {
if (is_numeric($k)) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12051',
'Using numeric keys in %s for join parts is deprecated and will not be supported in 4.0. Use an associative array with the root alias as key instead.',
__METHOD__,
);
$aliases = $this->getRootAliases();
if (! isset($aliases[0])) {
throw new RuntimeException('No alias was set before invoking add().');
}
$k = $aliases[0];
}
// @phpstan-ignore method.deprecated
$k = is_numeric($k) ? $this->getRootAlias() : $k;
$newDqlPart[$k] = $v;
}

View File

@@ -18,11 +18,12 @@ trait ApplicationCompatibility
{
private static function addCommandToApplication(Application $application, Command $command): Command|null
{
// @phpstan-ignore function.alreadyNarrowedType (This method did not exist before Symfony 7.4)
if (method_exists(Application::class, 'addCommand')) {
// @phpstan-ignore method.notFound (This method will be added in Symfony 7.4)
return $application->addCommand($command);
}
// @phpstan-ignore method.notFound
return $application->add($command);
}
}

View File

@@ -10,7 +10,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\FieldMapping;
use Doctrine\Persistence\Mapping\MappingException;
use InvalidArgumentException;
use JsonException;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
@@ -53,17 +52,9 @@ final class MappingDescribeCommand extends AbstractEntityManagerCommand
protected function configure(): void
{
$this->setName('orm:mapping:describe')
->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity')
->setDescription('Display information about mapped objects')
->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on')
->addOption(
'format',
null,
InputOption::VALUE_REQUIRED,
'Output format (text, json)',
MappingDescribeCommandFormat::TEXT->value,
array_map(static fn (MappingDescribeCommandFormat $format) => $format->value, MappingDescribeCommandFormat::cases()),
)
->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity')
->setDescription('Display information about mapped objects')
->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on')
->setHelp(<<<'EOT'
The %command.full_name% command describes the metadata for the given full or partial entity class name.
@@ -72,13 +63,6 @@ The %command.full_name% command describes the metadata for the given full or par
Or:
<info>%command.full_name%</info> MyEntity
To output the metadata in JSON format, use the <info>--format</info> option:
<info>%command.full_name% My\Namespace\Entity\MyEntity --format=json</info>
To use a specific entity manager (e.g., for multi-DB projects), use the <info>--em</info> option:
<info>%command.full_name% My\Namespace\Entity\MyEntity --em=my_custom_entity_manager</info>
EOT);
}
@@ -86,11 +70,9 @@ EOT);
{
$ui = new SymfonyStyle($input, $output);
$format = MappingDescribeCommandFormat::from($input->getOption('format'));
$entityManager = $this->getEntityManager($input);
$this->displayEntity($input->getArgument('entityName'), $entityManager, $ui, $format);
$this->displayEntity($input->getArgument('entityName'), $entityManager, $ui);
return 0;
}
@@ -107,10 +89,6 @@ EOT);
$suggestions->suggestValues(array_values($entities));
}
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues(array_map(static fn (MappingDescribeCommandFormat $format) => $format->value, MappingDescribeCommandFormat::cases()));
}
}
/**
@@ -122,47 +100,9 @@ EOT);
string $entityName,
EntityManagerInterface $entityManager,
SymfonyStyle $ui,
MappingDescribeCommandFormat $format,
): void {
$metadata = $this->getClassMetadata($entityName, $entityManager);
if ($format === MappingDescribeCommandFormat::JSON) {
$ui->text(json_encode(
[
'name' => $metadata->name,
'rootEntityName' => $metadata->rootEntityName,
'customGeneratorDefinition' => $this->formatValueAsJson($metadata->customGeneratorDefinition),
'customRepositoryClassName' => $metadata->customRepositoryClassName,
'isMappedSuperclass' => $metadata->isMappedSuperclass,
'isEmbeddedClass' => $metadata->isEmbeddedClass,
'parentClasses' => $metadata->parentClasses,
'subClasses' => $metadata->subClasses,
'embeddedClasses' => $metadata->embeddedClasses,
'identifier' => $metadata->identifier,
'inheritanceType' => $metadata->inheritanceType,
'discriminatorColumn' => $this->formatValueAsJson($metadata->discriminatorColumn),
'discriminatorValue' => $metadata->discriminatorValue,
'discriminatorMap' => $metadata->discriminatorMap,
'generatorType' => $metadata->generatorType,
'table' => $this->formatValueAsJson($metadata->table),
'isIdentifierComposite' => $metadata->isIdentifierComposite,
'containsForeignIdentifier' => $metadata->containsForeignIdentifier,
'containsEnumIdentifier' => $metadata->containsEnumIdentifier,
'sequenceGeneratorDefinition' => $this->formatValueAsJson($metadata->sequenceGeneratorDefinition),
'changeTrackingPolicy' => $metadata->changeTrackingPolicy,
'isVersioned' => $metadata->isVersioned,
'versionField' => $metadata->versionField,
'isReadOnly' => $metadata->isReadOnly,
'entityListeners' => $metadata->entityListeners,
'associationMappings' => $this->formatMappingsAsJson($metadata->associationMappings),
'fieldMappings' => $this->formatMappingsAsJson($metadata->fieldMappings),
],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR,
));
return;
}
$ui->table(
['Field', 'Value'],
array_merge(
@@ -300,22 +240,6 @@ EOT);
throw new InvalidArgumentException(sprintf('Do not know how to format value "%s"', print_r($value, true)));
}
/** @throws JsonException */
private function formatValueAsJson(mixed $value): mixed
{
if (is_object($value)) {
$value = (array) $value;
}
if (is_array($value)) {
foreach ($value as $k => $v) {
$value[$k] = $this->formatValueAsJson($v);
}
}
return $value;
}
/**
* Add the given label and value to the two column table output
*
@@ -357,22 +281,6 @@ EOT);
return $output;
}
/**
* @param array<string, FieldMapping|AssociationMapping> $propertyMappings
*
* @return array<string, mixed>
*/
private function formatMappingsAsJson(array $propertyMappings): array
{
$output = [];
foreach ($propertyMappings as $propertyName => $mapping) {
$output[$propertyName] = $this->formatValueAsJson((array) $mapping);
}
return $output;
}
/**
* Format the entity listeners
*

View File

@@ -1,11 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Tools\Console\Command;
enum MappingDescribeCommandFormat: string
{
case TEXT = 'text';
case JSON = 'json';
}

View File

@@ -505,7 +505,6 @@ class SchemaTool
], true)
&& $options['default'] === $this->platform->getCurrentTimestampSQL()
) {
/** @phpstan-ignore class.notFound (if DefaultExpression exists, CurrentTimestamp exists as well) */
$options['default'] = new CurrentTimestamp();
}
@@ -513,7 +512,6 @@ class SchemaTool
in_array($mapping->type, [Types::TIME_MUTABLE, Types::TIME_IMMUTABLE], true)
&& $options['default'] === $this->platform->getCurrentTimeSQL()
) {
/** @phpstan-ignore class.notFound (if DefaultExpression exists, CurrentTime exists as well) */
$options['default'] = new CurrentTime();
}
@@ -521,7 +519,6 @@ class SchemaTool
in_array($mapping->type, [Types::DATE_MUTABLE, Types::DATE_IMMUTABLE], true)
&& $options['default'] === $this->platform->getCurrentDateSQL()
) {
/** @phpstan-ignore class.notFound (if DefaultExpression exists, CurrentDate exists as well) */
$options['default'] = new CurrentDate();
}
}
@@ -1046,7 +1043,7 @@ class SchemaTool
{
return $asset instanceof NamedObject
? $asset->getObjectName()->toString()
// DBAL < 4.4
// @phpstan-ignore method.deprecated (DBAL < 4.4)
: $asset->getName();
}
}

View File

@@ -62,6 +62,7 @@ use function array_map;
use function array_sum;
use function array_values;
use function assert;
use function count;
use function current;
use function get_debug_type;
use function implode;
@@ -2594,8 +2595,14 @@ class UnitOfWork implements PropertyChangedListener
$reflField->setValue($entity, $pColl);
if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite && ! $assoc->isIndexed()) {
if (
$assoc->isOneToMany()
// is iteration
&& ! (isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION])
// is foreign key composite
&& ! ($targetClass->hasAssociation($assoc->mappedBy) && count($targetClass->getAssociationMapping($assoc->mappedBy)->joinColumns) > 1)
&& ! $assoc->isIndexed()
) {
$this->scheduleCollectionForBatchLoading($pColl, $class);
} else {
$this->loadCollection($pColl);

View File

@@ -13,13 +13,14 @@ use Doctrine\DBAL\Result;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\TestUtil;
use function array_map;
use function realpath;
final class EntityManagerFactory
{
@@ -29,9 +30,9 @@ final class EntityManagerFactory
TestUtil::configureProxies($config);
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver([
__DIR__ . '/../Tests/Models/Cache',
__DIR__ . '/../Tests/Models/GeoNames',
$config->setMetadataDriverImpl(new AttributeDriver([
realpath(__DIR__ . '/Models/Cache'),
realpath(__DIR__ . '/Models/GeoNames'),
]));
$entityManager = new EntityManager(
@@ -54,10 +55,10 @@ final class EntityManagerFactory
TestUtil::configureProxies($config);
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver([
__DIR__ . '/../Tests/Models/Cache',
__DIR__ . '/../Tests/Models/Generic',
__DIR__ . '/../Tests/Models/GeoNames',
$config->setMetadataDriverImpl(new AttributeDriver([
realpath(__DIR__ . '/Models/Cache'),
realpath(__DIR__ . '/Models/Generic'),
realpath(__DIR__ . '/Models/GeoNames'),
]));
// A connection that doesn't really do anything

View File

@@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Mocks;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
use function interface_exists;
final class AttributeDriverFactory
{
/** @param list<string> $paths */
public static function createAttributeDriver(array $paths = []): AttributeDriver
{
if (! self::isClassLocatorSupported()) {
// Persistence < 4.1
return new AttributeDriver($paths);
}
// Persistence >= 4.1
$classLocator = FileClassLocator::createFromDirectories($paths);
return new AttributeDriver($classLocator);
}
/** Supported since doctrine/persistence >= 4.1 */
public static function isClassLocatorSupported(): bool
{
return interface_exists(ClassLocator::class);
}
}

View File

@@ -8,6 +8,7 @@ use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\TestUtil;
@@ -25,7 +26,7 @@ class EntityManagerMock extends EntityManager
if ($config === null) {
$config = new Configuration();
TestUtil::configureProxies($config);
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver());
$config->setMetadataDriverImpl(new AttributeDriver([]));
}
parent::__construct($conn, $config, $eventManager);

View File

@@ -6,14 +6,13 @@ namespace Doctrine\Tests\Models\DDC3579;
use Doctrine\ORM\Mapping\AssociationOverride;
use Doctrine\ORM\Mapping\AssociationOverrides;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Entity;
#[Entity]
#[AssociationOverrides([new AssociationOverride(name: 'groups', inversedBy: 'admins')])]
class DDC3579Admin extends DDC3579User
{
public static function loadMetadata(ClassMetadata $metadata): void
public static function loadMetadata($metadata): void
{
$metadata->setAssociationOverride('groups', ['inversedBy' => 'admins']);
}

View File

@@ -58,7 +58,7 @@ class DDC3579User
return $this->groups;
}
public static function loadMetadata(ClassMetadata $metadata): void
public static function loadMetadata($metadata): void
{
$metadata->isMappedSuperclass = true;

View File

@@ -24,11 +24,16 @@ class RootEntity
#[ORM\OneToMany(mappedBy: 'root', targetEntity: SecondLevel::class, fetch: 'EAGER')]
private Collection $secondLevel;
/** @var Collection<int, SecondLevelWithoutCompositePrimaryKey> */
#[ORM\OneToMany(mappedBy: 'root', targetEntity: SecondLevelWithoutCompositePrimaryKey::class, fetch: 'EAGER')]
private Collection $anotherSecondLevel;
public function __construct(int $id, string $other)
{
$this->otherKey = $other;
$this->secondLevel = new ArrayCollection();
$this->id = $id;
$this->otherKey = $other;
$this->secondLevel = new ArrayCollection();
$this->anotherSecondLevel = new ArrayCollection();
$this->id = $id;
}
public function getId(): int|null

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class SecondLevelWithoutCompositePrimaryKey
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', nullable: false)]
private int|null $id;
#[ORM\ManyToOne(targetEntity: RootEntity::class, inversedBy: 'anotherSecondLevel')]
#[ORM\JoinColumn(name: 'root_id', referencedColumnName: 'id')]
#[ORM\JoinColumn(name: 'root_other_key', referencedColumnName: 'other_key')]
private RootEntity $root;
public function __construct(RootEntity $upper)
{
$this->root = $upper;
}
public function getId(): int|null
{
return $this->id;
}
}

View File

@@ -16,7 +16,7 @@ class GH10334Foo
{
#[Id]
#[ManyToOne(targetEntity: GH10334FooCollection::class, inversedBy: 'foos')]
#[JoinColumn(name: 'foo_collection_id', referencedColumnName: 'id')]
#[JoinColumn(name: 'foo_collection_id', referencedColumnName: 'id', nullable: false)]
#[GeneratedValue]
protected GH10334FooCollection $collection;

View File

@@ -17,7 +17,7 @@ class InverseSide
/** Associative id (owning identifier) */
#[Id]
#[OneToOne(targetEntity: InverseSideIdTarget::class, inversedBy: 'inverseSide')]
#[JoinColumn(name: 'associativeId')]
#[JoinColumn(nullable: false, name: 'associativeId')]
public InverseSideIdTarget $associativeId;
#[OneToOne(targetEntity: OwningSide::class, mappedBy: 'inverse')]

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\RootEntity;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\SecondLevel;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\SecondLevelWithoutCompositePrimaryKey;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
@@ -14,7 +15,7 @@ final class EagerFetchOneToManyWithCompositeKeyTest extends OrmFunctionalTestCas
#[Group('GH11154')]
public function testItDoesNotThrowAnExceptionWhenTriggeringALoad(): void
{
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class]);
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class, SecondLevelWithoutCompositePrimaryKey::class]);
$a1 = new RootEntity(1, 'A');

View File

@@ -9,10 +9,10 @@ use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\DBAL\Types\EnumType;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Query\Expr\Func;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Models\DataTransferObjects\DtoWithArrayOfEnums;
use Doctrine\Tests\Models\DataTransferObjects\DtoWithEnum;
use Doctrine\Tests\Models\Enums\BookCategory;
@@ -35,6 +35,7 @@ use Generator;
use PHPUnit\Framework\Attributes\DataProvider;
use function class_exists;
use function dirname;
use function sprintf;
use function uniqid;
@@ -44,9 +45,7 @@ class EnumTest extends OrmFunctionalTestCase
{
parent::setUp();
$mappingDriver = AttributeDriverFactory::createAttributeDriver([__DIR__ . '/../../Models/Enums']);
$this->_em = $this->getEntityManager(null, $mappingDriver);
$this->_em = $this->getEntityManager(null, new AttributeDriver([dirname(__DIR__, 2) . '/Models/Enums'], true));
$this->_schemaTool = new SchemaTool($this->_em);
if ($this->isSecondLevelCacheEnabled) {

View File

@@ -9,7 +9,6 @@ use Doctrine\DBAL\Connection;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\ORM\Functional\Locking\Doctrine\ORM\Query;
use Doctrine\Tests\TestUtil;
use GearmanWorker;
@@ -117,8 +116,8 @@ class LockAgentWorker
TestUtil::configureProxies($config);
$config->setAutoGenerateProxyClasses(true);
$attributeDriver = AttributeDriverFactory::createAttributeDriver([__DIR__ . '/../../../Models']);
$config->setMetadataDriverImpl($attributeDriver);
$annotDriver = new AttributeDriver([__DIR__ . '/../../../Models/']);
$config->setMetadataDriverImpl($annotDriver);
$config->setMetadataCache(new ArrayAdapter());
$config->setQueryCache(new ArrayAdapter());

View File

@@ -17,6 +17,7 @@ use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
use function assert;
use function class_exists;
use function get_class;
/**
@@ -437,7 +438,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
$user = $this->_em->find($user::class, $user->id);
$criteria = Criteria::create(true)
->orderBy(['name' => Order::Ascending]);
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
self::assertEquals(
['A', 'B', 'C', 'Developers_0'],
@@ -477,7 +478,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
$user = $this->_em->find($user::class, $user->id);
$criteria = Criteria::create(true)
->orderBy(['name' => Order::Ascending]);
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
self::assertEquals(
['A', 'B', 'C'],

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Closure;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Exec\FinalizedSelectExecutor;
use Doctrine\ORM\Query\Exec\PreparedExecutorFinalizer;
@@ -17,7 +16,6 @@ use Generator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use ReflectionMethod;
use Symfony\Component\VarExporter\Instantiator;
use Symfony\Component\VarExporter\VarExporter;
use function file_get_contents;
@@ -27,8 +25,6 @@ use function unserialize;
class ParserResultSerializationTest extends OrmFunctionalTestCase
{
use VerifyDeprecations;
protected function setUp(): void
{
$this->useModelSet('company');
@@ -81,11 +77,6 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
},
];
$instantiatorMethod = new ReflectionMethod(Instantiator::class, 'instantiate');
if ($instantiatorMethod->getReturnType() === null) {
self::markTestSkipped('symfony/var-exporter 5.4+ is required.');
}
yield 'symfony/var-exporter' => [
static function (ParserResult $parserResult): ParserResult {
return eval('return ' . VarExporter::export($parserResult) . ';');
@@ -101,8 +92,6 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
$this->assertInstanceOf(ParserResult::class, $unserialized);
$this->assertInstanceOf(ResultSetMapping::class, $unserialized->getResultSetMapping());
$this->assertEquals(['name' => [0]], $unserialized->getParameterMappings());
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11188');
$this->assertInstanceOf(SingleSelectExecutor::class, $unserialized->getSqlExecutor());
$this->assertIsString($unserialized->getSqlExecutor()->getSqlStatements());
}

View File

@@ -7,7 +7,6 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
use Doctrine\ORM\Query\Exec\SqlFinalizer;
use Doctrine\ORM\Query\ParserResult;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Depends;
@@ -131,11 +130,8 @@ class QueryCacheTest extends OrmFunctionalTestCase
}
};
$sqlFinalizerMock = $this->createMock(SqlFinalizer::class);
$sqlFinalizerMock->method('createExecutor')->with($query)->willReturn($sqlExecutorStub);
$parserResultMock = new ParserResult();
$parserResultMock->setSqlFinalizer($sqlFinalizerMock);
$parserResultMock->setSqlExecutor($sqlExecutorStub);
$cache = $this->createMock(CacheItemPoolInterface::class);

View File

@@ -390,7 +390,7 @@ class QueryTest extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('select a, u from ' . CmsArticle::class . ' a JOIN ' . CmsUser::class . ' u ON a.user = u');
$query = $this->_em->createQuery('select a, u from ' . CmsArticle::class . ' a JOIN ' . CmsUser::class . ' u WITH a.user = u');
$result = iterator_to_array($query->toIterable());
@@ -474,7 +474,9 @@ class QueryTest extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('select a, u, a.topic, a.text from ' . CmsArticle::class . ' a, ' . CmsUser::class . ' u WHERE a.user = u ');
$query = $this->_em->createQuery(
'select a, u, a.topic, a.text from ' . CmsArticle::class . ' a, ' . CmsUser::class . ' u WHERE a.user = u order by a.id asc',
);
$result = $query->toIterable();
$it = iterator_to_array($result);
@@ -517,7 +519,9 @@ class QueryTest extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('select a.topic, a.text from ' . CmsArticle::class . ' a ');
$query = $this->_em->createQuery(
'select a.topic, a.text from ' . CmsArticle::class . ' a order by a.id asc',
);
$result = $query->toIterable();
$it = iterator_to_array($result);
@@ -545,7 +549,9 @@ class QueryTest extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('select a from Doctrine\Tests\Models\CMS\CmsArticle a');
$query = $this->_em->createQuery(
'select a from Doctrine\Tests\Models\CMS\CmsArticle a order by a.id asc',
);
$articles = $query->toIterable();
$iteratedCount = 0;
@@ -1061,7 +1067,7 @@ class QueryTest extends OrmFunctionalTestCase
$query = $this->_em->createQuery('
SELECT u, p
FROM Doctrine\Tests\Models\CMS\CmsUser u
INNER JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p ON u = p.user
INNER JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p WITH u = p.user
');
$users = $query->execute();
@@ -1094,7 +1100,7 @@ class QueryTest extends OrmFunctionalTestCase
$query = $this->_em->createQuery('
SELECT u, p
FROM Doctrine\Tests\Models\CMS\CmsUser u
LEFT JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p ON u = p.user
LEFT JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p WITH u = p.user
');
$users = $query->execute();

View File

@@ -4,14 +4,16 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Models\ReadonlyProperties\Author;
use Doctrine\Tests\Models\ReadonlyProperties\Book;
use Doctrine\Tests\Models\ReadonlyProperties\SimpleBook;
use Doctrine\Tests\OrmFunctionalTestCase;
use Doctrine\Tests\TestUtil;
use function dirname;
class ReadonlyPropertiesTest extends OrmFunctionalTestCase
{
protected function setUp(): void
@@ -20,9 +22,10 @@ class ReadonlyPropertiesTest extends OrmFunctionalTestCase
static::$sharedConn = TestUtil::getConnection();
}
$attributeDriver = AttributeDriverFactory::createAttributeDriver([__DIR__ . '/../../Models/ReadonlyProperties']);
$this->_em = $this->getEntityManager(null, $attributeDriver);
$this->_em = $this->getEntityManager(null, new AttributeDriver(
[dirname(__DIR__, 2) . '/Models/ReadonlyProperties'],
true,
));
$this->_schemaTool = new SchemaTool($this->_em);
parent::setUp();

View File

@@ -142,6 +142,8 @@ class MySqlSchemaToolTest extends OrmFunctionalTestCase
self::equalTo('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.4 (see https://github.com/doctrine/dbal/pull/7221)
self::equalTo('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
}

View File

@@ -102,7 +102,7 @@ class DDC1209Two
public function __construct(
#[Id]
#[ManyToOne(targetEntity: 'DDC1209One')]
#[JoinColumn(referencedColumnName: 'id')]
#[JoinColumn(referencedColumnName: 'id', nullable: false)]
private DDC1209One $future1,
) {
$this->startingDatetime = new DateTime2();

View File

@@ -50,7 +50,7 @@ class DDC1225TestEntity1
{
#[Id]
#[ManyToOne(targetEntity: 'Doctrine\Tests\ORM\Functional\Ticket\DDC1225TestEntity2')]
#[JoinColumn(name: 'test_entity2_id', referencedColumnName: 'id')]
#[JoinColumn(name: 'test_entity2_id', referencedColumnName: 'id', nullable: false)]
private DDC1225TestEntity2|null $testEntity2 = null;
public function setTestEntity2(DDC1225TestEntity2 $testEntity2): void

View File

@@ -80,7 +80,7 @@ class MyEntity1
public function __construct(
#[Id]
#[OneToOne(targetEntity: 'MyEntity2')]
#[JoinColumn(name: 'entity2_id', referencedColumnName: 'id')]
#[JoinColumn(name: 'entity2_id', referencedColumnName: 'id', nullable: false)]
private MyEntity2 $entity2,
) {
}

View File

@@ -119,10 +119,10 @@ class DDC2575A
public function __construct(
#[Id]
#[OneToOne(targetEntity: 'DDC2575Root', inversedBy: 'aRelation')]
#[JoinColumn(name: 'root_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
#[JoinColumn(name: 'root_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
public DDC2575Root $rootRelation,
#[ManyToOne(targetEntity: 'DDC2575B')]
#[JoinColumn(name: 'b_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
#[JoinColumn(name: 'b_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
public DDC2575B $bRelation,
) {
}

View File

@@ -31,7 +31,7 @@ class DDC3042Test extends OrmFunctionalTestCase
$this
->_em
->createQuery(
'SELECT f, b FROM ' . __NAMESPACE__ . '\DDC3042Foo f JOIN ' . __NAMESPACE__ . '\DDC3042Bar b ON 1 = 1',
'SELECT f, b FROM ' . __NAMESPACE__ . '\DDC3042Foo f JOIN ' . __NAMESPACE__ . '\DDC3042Bar b WITH 1 = 1',
)
->getSQL(),
'field_11',

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH12254Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH12254EntityA::class,
]);
$this->_em->persist(new GH12254EntityA());
$this->_em->flush();
$this->_em->clear();
}
public function testFindByEmptyArrayShouldReturnEmptyArray(): void
{
// pretend we are starting afresh
$this->_em = $this->getEntityManager();
$result = $this->_em->getRepository(GH12254EntityA::class)->findBy(['id' => []]);
$this->assertEmpty($result);
}
public function testFindByInNullableField(): void
{
$this->_em = $this->getEntityManager();
$result = $this->_em->getRepository(GH12254EntityA::class)->findBy(['name' => []]);
$this->assertEmpty($result);
}
}
#[Entity]
class GH12254EntityA
{
#[Column(type: 'integer')]
#[Id]
#[GeneratedValue(strategy: 'AUTO')]
public int $id;
#[Column(type: 'string', nullable: true)]
public string|null $name = null;
}

View File

@@ -39,7 +39,7 @@ final class GH6362Test extends OrmFunctionalTestCase
* SELECT a as base, b, c, d
* FROM Start a
* LEFT JOIN a.bases b
* LEFT JOIN Child c ON b.id = c.id
* LEFT JOIN Child c WITH b.id = c.id
* LEFT JOIN c.joins d
*/
#[Group('GH-6362')]

View File

@@ -39,7 +39,7 @@ class GH6464Test extends OrmFunctionalTestCase
$query = $this->_em->createQueryBuilder()
->select('p')
->from(GH6464Post::class, 'p')
->innerJoin(GH6464Author::class, 'a', 'ON', 'p.authorId = a.id')
->innerJoin(GH6464Author::class, 'a', 'WITH', 'p.authorId = a.id')
->getQuery();
self::assertDoesNotMatchRegularExpression(

View File

@@ -40,7 +40,7 @@ final class GH7496WithToIterableTest extends OrmFunctionalTestCase
public function testNonUniqueObjectHydrationDuringIteration(): void
{
$q = $this->_em->createQuery(
'SELECT b FROM ' . GH7496EntityAinB::class . ' aib JOIN ' . GH7496EntityB::class . ' b ON aib.eB = b',
'SELECT b FROM ' . GH7496EntityAinB::class . ' aib JOIN ' . GH7496EntityB::class . ' b WITH aib.eB = b',
);
$bs = IterableTester::iterableToArray(

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\Type;
@@ -145,7 +146,7 @@ class GH7820Test extends OrmFunctionalTestCase
{
$query = $this->_em->getRepository(GH7820Line::class)
->createQueryBuilder('l')
->orderBy('l.lineNumber', 'ASC')
->orderBy('l.lineNumber', Criteria::ASC)
->setMaxResults(100);
return array_map(static fn (GH7820Line $line): string => $line->toString(), iterator_to_array(new Paginator($query)));

View File

@@ -10,10 +10,7 @@ use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\JoinColumnMapping;
use Doctrine\ORM\Mapping\MappingAttribute;
use Doctrine\Persistence\Mapping\Driver\ClassNames;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Models\Cache\City;
use Doctrine\Tests\ORM\Mapping\Fixtures\AttributeEntityWithNestedJoinColumns;
use InvalidArgumentException;
use stdClass;
@@ -22,21 +19,9 @@ class AttributeDriverTest extends MappingDriverTestCase
{
protected function loadDriver(): MappingDriver
{
return AttributeDriverFactory::createAttributeDriver();
}
$paths = [];
public function testDriverCanAcceptClassLocator(): void
{
if (! AttributeDriverFactory::isClassLocatorSupported()) {
self::markTestSkipped('This test is only relevant for versions of doctrine/persistence >= 4.1');
}
$classLocator = new ClassNames([City::class]);
$driver = new AttributeDriver($classLocator);
self::assertSame([], $driver->getPaths(), 'Directory paths must be empty, since file paths are used');
self::assertSame([City::class], $driver->getAllClassNames());
return new AttributeDriver($paths, true);
}
public function testOriginallyNestedAttributesDeclaredWithoutOriginalParent(): void

View File

@@ -539,6 +539,7 @@ class ClassMetadataBuilderTest extends OrmTestCase
[
'name' => 'group_id',
'referencedColumnName' => 'id',
'nullable' => false,
'unique' => false,
'onDelete' => 'CASCADE',
'columnDefinition' => null,
@@ -550,6 +551,7 @@ class ClassMetadataBuilderTest extends OrmTestCase
[
'name' => 'user_id',
'referencedColumnName' => 'id',
'nullable' => false,
'unique' => false,
'onDelete' => null,
'columnDefinition' => null,
@@ -740,6 +742,7 @@ class ClassMetadataBuilderTest extends OrmTestCase
0 => [
'name' => 'group_id',
'referencedColumnName' => 'id',
'nullable' => false,
'unique' => false,
'onDelete' => 'CASCADE',
'columnDefinition' => null,

View File

@@ -34,17 +34,4 @@ final class JoinTableMappingTest extends TestCase
self::assertSame('bar', $resurrectedMapping->name);
self::assertSame(['foo' => 'bar'], $resurrectedMapping->options);
}
public function testConvertingItToAMappingArrayDoesNotContainNullableInformation(): void
{
$mapping = new JoinTableMapping('bar');
$mapping->joinColumns = [new JoinColumnMapping('foo_id', 'id')];
$mapping->inverseJoinColumns = [new JoinColumnMapping('bar_id', 'id')];
$mappingArray = $mapping->toArray();
foreach ($mappingArray['joinColumns'] as $joinColumn) {
self::assertArrayNotHasKey('nullable', $joinColumn);
}
}
}

View File

@@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Mapping;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
use Doctrine\ORM\Mapping\JoinTableMapping;
use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use PHPUnit\Framework\TestCase;
use function assert;
@@ -18,8 +16,6 @@ use function unserialize;
final class ManyToManyOwningSideMappingTest extends TestCase
{
use VerifyDeprecations;
public function testItSurvivesSerialization(): void
{
$mapping = new ManyToManyOwningSideMapping(
@@ -42,42 +38,22 @@ final class ManyToManyOwningSideMappingTest extends TestCase
self::assertSame(['bar' => 'baz'], $resurrectedMapping->relationToTargetKeyColumns);
}
/** @param array<string,mixed> $mappingArray */
#[DataProvider('mappingsProvider')]
#[WithoutErrorHandler]
public function testNullableDefaults(
bool $expectDeprecation,
bool $expectedValue,
array $mappingArray,
): void {
$namingStrategy = new DefaultNamingStrategy();
if ($expectDeprecation) {
$this->expectDeprecationWithIdentifier(
'https://github.com/doctrine/orm/pull/12126',
);
} else {
$this->expectNoDeprecationWithIdentifier(
'https://github.com/doctrine/orm/pull/12126',
);
}
$mapping = ManyToManyOwningSideMapping::fromMappingArrayAndNamingStrategy(
$mappingArray,
$namingStrategy,
);
public function testNullableDefaults(bool $expectedValue, ManyToManyOwningSideMapping $mapping): void
{
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
self::assertSame($expectedValue, $joinColumn->nullable);
}
}
/** @return iterable<string, array{bool, bool, array<string,mixed>}> */
/** @return iterable<string, array{bool, ManyToManyOwningSideMapping}> */
public static function mappingsProvider(): iterable
{
$namingStrategy = new DefaultNamingStrategy();
yield 'defaults to false' => [
false,
false,
[
ManyToManyOwningSideMapping::fromMappingArrayAndNamingStrategy([
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
@@ -91,13 +67,12 @@ final class ManyToManyOwningSideMappingTest extends TestCase
['name' => 'foo_id', 'referencedColumnName' => 'id'],
],
],
],
], $namingStrategy),
];
yield 'explicitly marked as nullable' => [
true,
false, // user's intent is ignored at the ORM level
[
ManyToManyOwningSideMapping::fromMappingArrayAndNamingStrategy([
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
@@ -107,75 +82,12 @@ final class ManyToManyOwningSideMappingTest extends TestCase
'joinColumns' => [
['name' => 'bar_id', 'referencedColumnName' => 'id', 'nullable' => true],
],
'inverseJoinColumns' => [
['name' => 'foo_id', 'referencedColumnName' => 'id'],
],
],
'id' => true,
],
];
yield 'explicitly marked as nullable (inverse column)' => [
true,
false, // user's intent is ignored at the ORM level
[
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
'isOwningSide' => true,
'joinTable' => [
'name' => 'bar',
'joinColumns' => [
['name' => 'bar_id', 'referencedColumnName' => 'id'],
],
'inverseJoinColumns' => [
['name' => 'foo_id', 'referencedColumnName' => 'id', 'nullable' => true],
],
],
'id' => true,
],
];
yield 'explicitly marked as not nullable' => [
true,
false,
[
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
'isOwningSide' => true,
'joinTable' => [
'name' => 'bar',
'joinColumns' => [
['name' => 'bar_id', 'referencedColumnName' => 'id', 'nullable' => false],
],
'inverseJoinColumns' => [
['name' => 'foo_id', 'referencedColumnName' => 'id'],
],
],
'id' => true,
],
];
yield 'explicitly marked as not nullable (inverse column)' => [
true,
false,
[
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
'isOwningSide' => true,
'joinTable' => [
'name' => 'bar',
'joinColumns' => [
['name' => 'bar_id', 'referencedColumnName' => 'id'],
],
'inverseJoinColumns' => [
['name' => 'foo_id', 'referencedColumnName' => 'id', 'nullable' => false],
],
],
'id' => true,
],
], $namingStrategy),
];
}
}

View File

@@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Mapping;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
use Doctrine\ORM\Mapping\JoinColumnMapping;
use Doctrine\ORM\Mapping\ManyToOneAssociationMapping;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use PHPUnit\Framework\TestCase;
use function assert;
@@ -18,8 +16,6 @@ use function unserialize;
final class ManyToOneAssociationMappingTest extends TestCase
{
use VerifyDeprecations;
public function testItSurvivesSerialization(): void
{
$mapping = new ManyToOneAssociationMapping(
@@ -42,45 +38,22 @@ final class ManyToOneAssociationMappingTest extends TestCase
self::assertSame(['bar' => 'foo'], $resurrectedMapping->targetToSourceKeyColumns);
}
/** @param array<string, mixed> $mappingArray */
#[DataProvider('mappingsProvider')]
#[WithoutErrorHandler]
public function testNullableDefaults(
bool $expectDeprecation,
bool $expectedValue,
array $mappingArray,
): void {
$namingStrategy = new DefaultNamingStrategy();
if ($expectDeprecation) {
$this->expectDeprecationWithIdentifier(
'https://github.com/doctrine/orm/pull/12126',
);
} else {
$this->expectNoDeprecationWithIdentifier(
'https://github.com/doctrine/orm/pull/12126',
);
}
$mapping = ManyToOneAssociationMapping::fromMappingArrayAndName(
$mappingArray,
$namingStrategy,
self::class,
null,
false,
);
public function testNullableDefaults(bool $expectedValue, ManyToOneAssociationMapping $mapping): void
{
foreach ($mapping->joinColumns as $joinColumn) {
self::assertSame($expectedValue, $joinColumn->nullable);
}
}
/** @return iterable<string, array{bool, bool, array<string, mixed>}> */
/** @return iterable<string, array{bool, ManyToOneAssociationMapping}> */
public static function mappingsProvider(): iterable
{
$namingStrategy = new DefaultNamingStrategy();
yield 'not part of the identifier' => [
false,
true,
[
ManyToOneAssociationMapping::fromMappingArrayAndName([
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
@@ -89,13 +62,12 @@ final class ManyToOneAssociationMappingTest extends TestCase
['name' => 'foo_id', 'referencedColumnName' => 'id'],
],
'id' => false,
],
], $namingStrategy, self::class, null, false),
];
yield 'part of the identifier' => [
false,
false,
[
ManyToOneAssociationMapping::fromMappingArrayAndName([
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
@@ -104,13 +76,12 @@ final class ManyToOneAssociationMappingTest extends TestCase
['name' => 'foo_id', 'referencedColumnName' => 'id'],
],
'id' => true,
],
], $namingStrategy, self::class, null, false),
];
yield 'part of the identifier, but explicitly marked as nullable' => [
true,
false, // user's intent is ignored at the ORM level
[
ManyToOneAssociationMapping::fromMappingArrayAndName([
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
@@ -119,22 +90,7 @@ final class ManyToOneAssociationMappingTest extends TestCase
['name' => 'foo_id', 'referencedColumnName' => 'id', 'nullable' => true],
],
'id' => true,
],
];
yield 'part of the identifier, but explicitly marked as not nullable' => [
true,
false,
[
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
'isOwningSide' => true,
'joinColumns' => [
['name' => 'foo_id', 'referencedColumnName' => 'id', 'nullable' => true],
],
'id' => true,
],
], $namingStrategy, self::class, null, false),
];
}
}

View File

@@ -999,7 +999,7 @@ class User
/** @var Collection<int, Group> */
#[ORM\ManyToMany(targetEntity: 'Group', cascade: ['all'])]
#[ORM\JoinTable(name: 'cms_user_groups')]
#[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', unique: false)]
#[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: false, unique: false)]
#[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id', columnDefinition: 'INT NULL')]
public $groups;

View File

@@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Mapping;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
use Doctrine\ORM\Mapping\JoinColumnMapping;
use Doctrine\ORM\Mapping\OneToOneOwningSideMapping;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use PHPUnit\Framework\TestCase;
use function assert;
@@ -18,8 +16,6 @@ use function unserialize;
final class OneToOneOwningSideMappingTest extends TestCase
{
use VerifyDeprecations;
public function testItSurvivesSerialization(): void
{
$mapping = new OneToOneOwningSideMapping(
@@ -42,33 +38,9 @@ final class OneToOneOwningSideMappingTest extends TestCase
self::assertSame(['bar' => 'foo'], $resurrectedMapping->targetToSourceKeyColumns);
}
/** @param array<string, mixed> $mappingArray */
#[DataProvider('mappingsProvider')]
#[WithoutErrorHandler]
public function testNullableDefaults(
bool $expectDeprecation,
bool $expectedValue,
array $mappingArray,
): void {
$namingStrategy = new DefaultNamingStrategy();
if ($expectDeprecation) {
$this->expectDeprecationWithIdentifier(
'https://github.com/doctrine/orm/pull/12126',
);
} else {
$this->expectNoDeprecationWithIdentifier(
'https://github.com/doctrine/orm/pull/12126',
);
}
$mapping = OneToOneOwningSideMapping::fromMappingArrayAndName(
$mappingArray,
$namingStrategy,
self::class,
null,
false,
);
public function testNullableDefaults(bool $expectedValue, OneToOneOwningSideMapping $mapping): void
{
foreach ($mapping->joinColumns as $joinColumn) {
self::assertSame($expectedValue, $joinColumn->nullable);
}
@@ -77,10 +49,11 @@ final class OneToOneOwningSideMappingTest extends TestCase
/** @return iterable<string, array{bool, OneToOneOwningSideMapping}> */
public static function mappingsProvider(): iterable
{
$namingStrategy = new DefaultNamingStrategy();
yield 'not part of the identifier' => [
false,
true,
[
OneToOneOwningSideMapping::fromMappingArrayAndName([
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
@@ -89,13 +62,12 @@ final class OneToOneOwningSideMappingTest extends TestCase
['name' => 'foo_id', 'referencedColumnName' => 'id'],
],
'id' => false,
],
], $namingStrategy, self::class, null, false),
];
yield 'part of the identifier' => [
false,
false,
[
OneToOneOwningSideMapping::fromMappingArrayAndName([
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
@@ -104,13 +76,12 @@ final class OneToOneOwningSideMappingTest extends TestCase
['name' => 'foo_id', 'referencedColumnName' => 'id'],
],
'id' => true,
],
], $namingStrategy, self::class, null, false),
];
yield 'part of the identifier, but explicitly marked as nullable' => [
true,
false, // user's intent ignored at the ORM level
[
OneToOneOwningSideMapping::fromMappingArrayAndName([
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
@@ -119,66 +90,7 @@ final class OneToOneOwningSideMappingTest extends TestCase
['name' => 'foo_id', 'referencedColumnName' => 'id', 'nullable' => true],
],
'id' => true,
],
];
yield 'part of the identifier, but explicitly marked as not nullable' => [
true,
false,
[
'fieldName' => 'foo',
'sourceEntity' => self::class,
'targetEntity' => self::class,
'isOwningSide' => true,
'joinColumns' => [
['name' => 'foo_id', 'referencedColumnName' => 'id', 'nullable' => false],
],
'id' => true,
],
];
}
#[DataProvider('convertToArrayProvider')]
public function testConvertToArray(
bool $shouldHaveNullableKey,
bool|null $id,
): void {
$mapping = new OneToOneOwningSideMapping(
fieldName: 'foo',
sourceEntity: self::class,
targetEntity: self::class,
);
$mapping->joinColumns = [new JoinColumnMapping('foo_id', 'id')];
$mapping->id = $id;
$mappingArray = $mapping->toArray();
foreach ($mappingArray['joinColumns'] as $joinColumn) {
if ($shouldHaveNullableKey) {
self::assertArrayHasKey('nullable', $joinColumn);
} else {
self::assertArrayNotHasKey('nullable', $joinColumn);
}
}
}
/** @return iterable<string, array{shouldHaveNullableKey: bool, id: bool|null}> */
public static function convertToArrayProvider(): iterable
{
yield 'not part of the identifier' => [
'shouldHaveNullableKey' => true,
'id' => false,
];
yield 'still not part of the identifier' => [
'shouldHaveNullableKey' => true,
'id' => null,
];
yield 'part of the identifier' => [
'shouldHaveNullableKey' => false,
'id' => true,
], $namingStrategy, self::class, null, false),
];
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Mapping;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Cache\Exception\CacheException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
@@ -254,8 +255,8 @@ class XmlMappingDriverTest extends MappingDriverTestCase
$class->initializeReflection(new RuntimeReflectionService());
$driver->loadMetadataForClass(GH7141Article::class, $class);
self::assertSame(
'ASC',
self::assertEquals(
Criteria::ASC,
$class->getMetadataValue('associationMappings')['tags']->orderBy['position'],
);
}
@@ -268,8 +269,8 @@ class XmlMappingDriverTest extends MappingDriverTestCase
$driver = $this->loadDriver();
$driver->loadMetadataForClass(GH7316Article::class, $class);
self::assertSame(
'ASC',
self::assertEquals(
Criteria::ASC,
$class->getMetadataValue('associationMappings')['tags']->orderBy['position'],
);
}

View File

@@ -78,7 +78,7 @@
</cascade>
<join-table name="cms_users_groups">
<join-columns>
<join-column name="user_id" referenced-column-name="id" unique="false" />
<join-column name="user_id" referenced-column-name="id" nullable="false" unique="false" />
</join-columns>
<inverse-join-columns>
<join-column name="group_id" referenced-column-name="id" column-definition="INT NULL" />

View File

@@ -19,6 +19,7 @@ use ReflectionProperty;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\NullAdapter;
use function sys_get_temp_dir;
@@ -68,7 +69,7 @@ class ORMSetupTest extends TestCase
{
$this->expectNotToPerformAssertions();
ORMSetup::createXMLMetadataConfig(paths: [], isXsdValidationEnabled: false);
ORMSetup::createXMLMetadataConfig(paths: [], cache: new NullAdapter(), isXsdValidationEnabled: false);
}
#[RequiresPhpExtension('apcu')]

View File

@@ -147,6 +147,7 @@ class BasicEntityPersisterTypeValueSqlTest extends OrmTestCase
}
#[Group('DDC-3056')]
#[Group('GH12254')]
public function testSelectConditionStatementWithMultipleValuesContainingNull(): void
{
self::assertEquals(
@@ -168,6 +169,11 @@ class BasicEntityPersisterTypeValueSqlTest extends OrmTestCase
'(t0.id IN (?, ?) OR t0.id IS NULL)',
$this->persister->getSelectConditionStatementSQL('id', [123, null, 234]),
);
self::assertEquals(
'1=0',
$this->persister->getSelectConditionStatementSQL('id', []),
);
}
public function testCountCondition(): void

View File

@@ -11,9 +11,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\SchemaValidator;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\Models\BinaryPrimaryKey\BinaryIdType;
use Doctrine\Tests\Models\BinaryPrimaryKey\Category;
@@ -68,10 +65,7 @@ final class BinaryIdPersisterTest extends OrmTestCase
return $this->entityManager;
}
$config = ORMSetup::createAttributeMetadataConfiguration(
$this->getClassLocator(),
isDevMode: true,
);
$config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../../Models/BinaryPrimaryKey'], isDevMode: true);
$config->enableNativeLazyObjects(PHP_VERSION_ID >= 80400);
if (! DbalType::hasType(BinaryIdType::NAME)) {
@@ -91,16 +85,4 @@ final class BinaryIdPersisterTest extends OrmTestCase
return $entityManager;
}
/** @return list<string>|ClassLocator */
private function getClassLocator(): array|ClassLocator
{
$paths = [__DIR__ . '/../../Models/BinaryPrimaryKey'];
if (! AttributeDriverFactory::isClassLocatorSupported()) {
return $paths;
}
return FileClassLocator::createFromDirectories($paths);
}
}

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Query;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Column;
@@ -21,12 +20,9 @@ use Doctrine\Tests\Mocks\NullSqlWalker;
use Doctrine\Tests\OrmTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
class LanguageRecognitionTest extends OrmTestCase
{
use VerifyDeprecations;
private EntityManagerInterface $entityManager;
private int $hydrationMode = AbstractQuery::HYDRATE_OBJECT;
@@ -266,15 +262,8 @@ class LanguageRecognitionTest extends OrmTestCase
$this->assertValidDQL('SELECT u.name, a.topic, p.phonenumber FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles a LEFT JOIN u.phonenumbers p');
}
public function testJoinClassPathUsingON(): void
{
$this->assertValidDQL('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN Doctrine\Tests\Models\CMS\CmsArticle a ON a.user = u.id');
}
#[IgnoreDeprecations]
public function testJoinClassPathUsingWITH(): void
{
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issues/12192');
$this->assertValidDQL('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN Doctrine\Tests\Models\CMS\CmsArticle a WITH a.user = u.id');
}
@@ -649,7 +638,7 @@ class LanguageRecognitionTest extends OrmTestCase
#[Group('DDC-3085')]
public function testHavingSupportResultVariableInNullComparisonExpression(): void
{
$this->assertValidDQL('SELECT u AS user, SUM(a.id) AS score FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN Doctrine\Tests\Models\CMS\CmsAddress a ON a.user = u GROUP BY u HAVING score IS NOT NULL AND score >= 5');
$this->assertValidDQL('SELECT u AS user, SUM(a.id) AS score FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN Doctrine\Tests\Models\CMS\CmsAddress a WITH a.user = u GROUP BY u HAVING score IS NOT NULL AND score >= 5');
}
#[Group('DDC-1858')]

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Query;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
use Doctrine\ORM\Query\ParserResult;
use Doctrine\ORM\Query\ResultSetMapping;
@@ -13,8 +12,6 @@ use PHPUnit\Framework\TestCase;
class ParserResultTest extends TestCase
{
use VerifyDeprecations;
/** @var ParserResult */
public $parserResult;
@@ -40,8 +37,6 @@ class ParserResultTest extends TestCase
public function testSetGetSqlExecutor(): void
{
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11188');
$executor = $this->createMock(AbstractSqlExecutor::class);
$this->parserResult->setSqlExecutor($executor);
self::assertSame($executor, $this->parserResult->getSqlExecutor());

View File

@@ -191,7 +191,7 @@ class SelectSqlGenerationTest extends OrmTestCase
public function testSupportsJoinOnMultipleComponents(): void
{
$this->assertSqlGeneration(
'SELECT u, p FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p ON u = p.user',
'SELECT u, p FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p WITH u = p.user',
'SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3, c1_.phonenumber AS phonenumber_4, c0_.email_id AS email_id_5, c1_.user_id AS user_id_6 FROM cms_users c0_ INNER JOIN cms_phonenumbers c1_ ON (c0_.id = c1_.user_id)',
);
}
@@ -199,17 +199,17 @@ class SelectSqlGenerationTest extends OrmTestCase
public function testSupportsJoinOnMultipleComponentsWithJoinedInheritanceType(): void
{
$this->assertSqlGeneration(
'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e JOIN Doctrine\Tests\Models\Company\CompanyManager m ON e.id = m.id',
'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e JOIN Doctrine\Tests\Models\Company\CompanyManager m WITH e.id = m.id',
'SELECT c0_.id AS id_0, c0_.name AS name_1, c1_.salary AS salary_2, c1_.department AS department_3, c1_.startDate AS startDate_4, c2_.title AS title_5, c0_.discr AS discr_6, c0_.spouse_id AS spouse_id_7, c2_.car_id AS car_id_8 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id INNER JOIN (company_managers c3_ INNER JOIN company_employees c5_ ON c3_.id = c5_.id INNER JOIN company_persons c4_ ON c3_.id = c4_.id) ON (c0_.id = c4_.id)',
);
$this->assertSqlGeneration(
'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e LEFT JOIN Doctrine\Tests\Models\Company\CompanyManager m ON e.id = m.id',
'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e LEFT JOIN Doctrine\Tests\Models\Company\CompanyManager m WITH e.id = m.id',
'SELECT c0_.id AS id_0, c0_.name AS name_1, c1_.salary AS salary_2, c1_.department AS department_3, c1_.startDate AS startDate_4, c2_.title AS title_5, c0_.discr AS discr_6, c0_.spouse_id AS spouse_id_7, c2_.car_id AS car_id_8 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id LEFT JOIN (company_managers c3_ INNER JOIN company_employees c5_ ON c3_.id = c5_.id INNER JOIN company_persons c4_ ON c3_.id = c4_.id) ON (c0_.id = c4_.id)',
);
$this->assertSqlGeneration(
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c JOIN c.salesPerson s LEFT JOIN Doctrine\Tests\Models\Company\CompanyEvent e ON s.id = e.id',
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c JOIN c.salesPerson s LEFT JOIN Doctrine\Tests\Models\Company\CompanyEvent e WITH s.id = e.id',
"SELECT c0_.id AS id_0, c0_.completed AS completed_1, c0_.fixPrice AS fixPrice_2, c0_.hoursWorked AS hoursWorked_3, c0_.pricePerHour AS pricePerHour_4, c0_.maxPrice AS maxPrice_5, c0_.discr AS discr_6, c0_.salesPerson_id AS salesPerson_id_7 FROM company_contracts c0_ INNER JOIN company_employees c1_ ON c0_.salesPerson_id = c1_.id LEFT JOIN company_persons c2_ ON c1_.id = c2_.id LEFT JOIN company_managers c3_ ON c1_.id = c3_.id LEFT JOIN (company_events c4_ LEFT JOIN company_auctions c5_ ON c4_.id = c5_.id LEFT JOIN company_raffles c6_ ON c4_.id = c6_.id) ON (c2_.id = c4_.id) WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')",
);
}
@@ -2025,7 +2025,7 @@ SQL,
{
// Regression test for the bug
$this->assertSqlGeneration(
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyEmployee e LEFT JOIN Doctrine\Tests\Models\Company\CompanyContract c ON c.salesPerson = e.id',
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyEmployee e LEFT JOIN Doctrine\Tests\Models\Company\CompanyContract c WITH c.salesPerson = e.id',
"SELECT c0_.id AS id_0, c0_.completed AS completed_1, c0_.fixPrice AS fixPrice_2, c0_.hoursWorked AS hoursWorked_3, c0_.pricePerHour AS pricePerHour_4, c0_.maxPrice AS maxPrice_5, c0_.discr AS discr_6, c0_.salesPerson_id AS salesPerson_id_7 FROM company_employees c1_ INNER JOIN company_persons c2_ ON c1_.id = c2_.id LEFT JOIN company_managers c3_ ON c1_.id = c3_.id LEFT JOIN company_contracts c0_ ON (c0_.salesPerson_id = c2_.id) AND c0_.discr IN ('fix', 'flexible', 'flexultra')",
);
}
@@ -2035,7 +2035,7 @@ SQL,
{
// Ensure other WHERE predicates are passed through to the main WHERE clause
$this->assertSqlGeneration(
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyEmployee e LEFT JOIN Doctrine\Tests\Models\Company\CompanyContract c ON c.salesPerson = e.id WHERE e.salary > 1000',
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyEmployee e LEFT JOIN Doctrine\Tests\Models\Company\CompanyContract c WITH c.salesPerson = e.id WHERE e.salary > 1000',
"SELECT c0_.id AS id_0, c0_.completed AS completed_1, c0_.fixPrice AS fixPrice_2, c0_.hoursWorked AS hoursWorked_3, c0_.pricePerHour AS pricePerHour_4, c0_.maxPrice AS maxPrice_5, c0_.discr AS discr_6, c0_.salesPerson_id AS salesPerson_id_7 FROM company_employees c1_ INNER JOIN company_persons c2_ ON c1_.id = c2_.id LEFT JOIN company_managers c3_ ON c1_.id = c3_.id LEFT JOIN company_contracts c0_ ON (c0_.salesPerson_id = c2_.id) AND c0_.discr IN ('fix', 'flexible', 'flexultra') WHERE c1_.salary > 1000",
);
}
@@ -2045,7 +2045,7 @@ SQL,
{
// Test inner joins too
$this->assertSqlGeneration(
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyEmployee e INNER JOIN Doctrine\Tests\Models\Company\CompanyContract c ON c.salesPerson = e.id',
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyEmployee e INNER JOIN Doctrine\Tests\Models\Company\CompanyContract c WITH c.salesPerson = e.id',
"SELECT c0_.id AS id_0, c0_.completed AS completed_1, c0_.fixPrice AS fixPrice_2, c0_.hoursWorked AS hoursWorked_3, c0_.pricePerHour AS pricePerHour_4, c0_.maxPrice AS maxPrice_5, c0_.discr AS discr_6, c0_.salesPerson_id AS salesPerson_id_7 FROM company_employees c1_ INNER JOIN company_persons c2_ ON c1_.id = c2_.id LEFT JOIN company_managers c3_ ON c1_.id = c3_.id INNER JOIN company_contracts c0_ ON (c0_.salesPerson_id = c2_.id) AND c0_.discr IN ('fix', 'flexible', 'flexultra')",
);
}
@@ -2056,7 +2056,7 @@ SQL,
// Test that the discriminator IN() predicate is still added into
// the where clause when not joining onto that table
$this->assertSqlGeneration(
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c LEFT JOIN Doctrine\Tests\Models\Company\CompanyEmployee e ON e.id = c.salesPerson WHERE c.completed = true',
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c LEFT JOIN Doctrine\Tests\Models\Company\CompanyEmployee e WITH e.id = c.salesPerson WHERE c.completed = true',
"SELECT c0_.id AS id_0, c0_.completed AS completed_1, c0_.fixPrice AS fixPrice_2, c0_.hoursWorked AS hoursWorked_3, c0_.pricePerHour AS pricePerHour_4, c0_.maxPrice AS maxPrice_5, c0_.discr AS discr_6, c0_.salesPerson_id AS salesPerson_id_7 FROM company_contracts c0_ LEFT JOIN (company_employees c1_ INNER JOIN company_persons c2_ ON c1_.id = c2_.id LEFT JOIN company_managers c3_ ON c1_.id = c3_.id) ON (c2_.id = c0_.salesPerson_id) WHERE (c0_.completed = 1) AND c0_.discr IN ('fix', 'flexible', 'flexultra')",
);
}
@@ -2109,7 +2109,7 @@ SQL,
public function testHavingSupportResultVariableNullComparisonExpression(): void
{
$this->assertSqlGeneration(
'SELECT u AS user, SUM(a.id) AS score FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN Doctrine\Tests\Models\CMS\CmsAddress a ON a.user = u GROUP BY u HAVING score IS NOT NULL AND score >= 5',
'SELECT u AS user, SUM(a.id) AS score FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN Doctrine\Tests\Models\CMS\CmsAddress a WITH a.user = u GROUP BY u HAVING score IS NOT NULL AND score >= 5',
'SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3, SUM(c1_.id) AS sclr_4, c0_.email_id AS email_id_5 FROM cms_users c0_ LEFT JOIN cms_addresses c1_ ON (c1_.user_id = c0_.id) GROUP BY c0_.id, c0_.status, c0_.username, c0_.name, c0_.email_id HAVING sclr_4 IS NOT NULL AND sclr_4 >= 5',
);
}

View File

@@ -9,7 +9,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Expr\Join;
@@ -25,11 +24,10 @@ use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\OrmTestCase;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use function array_filter;
use function class_exists;
/**
* Test case for the QueryBuilder class used to build DQL query string in a
@@ -37,8 +35,6 @@ use function array_filter;
*/
class QueryBuilderTest extends OrmTestCase
{
use VerifyDeprecations;
private EntityManagerMock $entityManager;
protected function setUp(): void
@@ -72,26 +68,6 @@ class QueryBuilderTest extends OrmTestCase
$this->assertValidQueryBuilder($qb, 'DELETE Doctrine\Tests\Models\CMS\CmsUser u');
}
public function testDeleteWithLimitNotSupported(): void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Setting a limit is not supported for delete or update queries.');
$this->entityManager->createQueryBuilder()
->delete(CmsUser::class, 'c')
->setMaxResults(1);
}
public function testUpdateWithLimitNotSupported(): void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Setting a limit is not supported for delete or update queries.');
$this->entityManager->createQueryBuilder()
->update(CmsUser::class, 'c')
->setMaxResults(1);
}
public function testUpdateSetsType(): void
{
$qb = $this->entityManager->createQueryBuilder()
@@ -614,7 +590,7 @@ class QueryBuilderTest extends OrmTestCase
->from(CmsUser::class, 'u');
$criteria = Criteria::create(true);
$criteria->orderBy(['field' => Order::Descending]);
$criteria->orderBy(['field' => class_exists(Order::class) ? Order::Descending : Criteria::DESC]);
$qb->addCriteria($criteria);
@@ -631,7 +607,7 @@ class QueryBuilderTest extends OrmTestCase
->join('u.article', 'a');
$criteria = Criteria::create(true);
$criteria->orderBy(['a.field' => Order::Descending]);
$criteria->orderBy(['a.field' => class_exists(Order::class) ? Order::Descending : Criteria::DESC]);
$qb->addCriteria($criteria);
@@ -1055,10 +1031,8 @@ class QueryBuilderTest extends OrmTestCase
self::assertEquals('u', $qb->getRootAlias());
}
#[WithoutErrorHandler]
public function testBCAddJoinWithoutRootAlias(): void
{
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12051');
$qb = $this->entityManager->createQueryBuilder()
->select('u')
->from(CmsUser::class, 'u')

View File

@@ -16,8 +16,6 @@ use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandCompletionTester;
use Symfony\Component\Console\Tester\CommandTester;
use function json_decode;
/**
* Tests for {@see \Doctrine\ORM\Tools\Console\Command\MappingDescribeCommand}
*/
@@ -58,25 +56,6 @@ class MappingDescribeCommandTest extends OrmFunctionalTestCase
self::assertStringContainsString('Root entity name', $display);
}
public function testShowSpecificFuzzySingleJson(): void
{
$this->tester->execute([
'command' => $this->command->getName(),
'entityName' => 'AttractionInfo',
'--format' => 'json',
]);
$display = $this->tester->getDisplay();
$decodedJson = json_decode($display, true);
self::assertJson($display);
self::assertSame(AttractionInfo::class, $decodedJson['name']);
self::assertArrayHasKey('rootEntityName', $decodedJson);
self::assertArrayHasKey('fieldMappings', $decodedJson);
self::assertArrayHasKey('associationMappings', $decodedJson);
self::assertArrayHasKey('id', $decodedJson['fieldMappings']);
}
public function testShowSpecificFuzzyAmbiguous(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -132,10 +111,5 @@ class MappingDescribeCommandTest extends OrmFunctionalTestCase
'Doctrine\\\\Tests\\\\Models\\\\Cache\\\\Bar',
],
];
yield 'format option value' => [
['--format='],
['text', 'json'],
];
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Tools\Console\Command\SchemaTool;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Tools\Console\Command\SchemaTool\AbstractCommand;
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\OrmFunctionalTestCase;
use Symfony\Component\Console\Tester\CommandTester;
@@ -16,8 +16,9 @@ abstract class CommandTestCase extends OrmFunctionalTestCase
/** @param class-string<AbstractCommand> $commandClass */
protected function getCommandTester(string $commandClass, string|null $commandName = null): CommandTester
{
$attributeDriver = AttributeDriverFactory::createAttributeDriver([__DIR__ . '/Models']);
$entityManager = $this->getEntityManager(null, $attributeDriver);
$entityManager = $this->getEntityManager(null, new AttributeDriver([
__DIR__ . '/Models',
]));
if (! $entityManager->getConnection()->getDatabasePlatform() instanceof SQLitePlatform) {
self::markTestSkipped('We are testing the symfony/console integration');

View File

@@ -121,7 +121,7 @@ class CountWalkerTest extends PaginationTestCase
public function testCountQueryWithArbitraryJoin(): void
{
$query = $this->entityManager->createQuery(
'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p LEFT JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c ON p.category = c',
'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p LEFT JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c WITH p.category = c',
);
$query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [CountWalker::class]);
$query->setHint(CountWalker::HINT_DISTINCT, true);

View File

@@ -131,7 +131,7 @@ class LimitSubqueryWalkerTest extends PaginationTestCase
*/
public function testLimitSubqueryWithArbitraryJoin(): void
{
$dql = 'SELECT p, c FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c ON p.category = c';
$dql = 'SELECT p, c FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c WITH p.category = c';
$query = $this->entityManager->createQuery($dql);
$limitQuery = clone $query;
@@ -145,7 +145,7 @@ class LimitSubqueryWalkerTest extends PaginationTestCase
public function testLimitSubqueryWithSortWithArbitraryJoin(): void
{
$dql = 'SELECT p, c FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c ON p.category = c ORDER BY p.title';
$dql = 'SELECT p, c FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c WITH p.category = c ORDER BY p.title';
$query = $this->entityManager->createQuery($dql);
$limitQuery = clone $query;

View File

@@ -71,12 +71,12 @@ class WhereInWalkerTest extends PaginationTestCase
];
yield 'arbitary join with no WHERE' => [
'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c ON p.category = c',
'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c WITH p.category = c',
'SELECT b0_.id AS id_0, b0_.author_id AS author_id_1, b0_.category_id AS category_id_2 FROM BlogPost b0_ INNER JOIN Category c1_ ON (b0_.category_id = c1_.id) WHERE b0_.id IN (?)',
];
yield 'arbitary join with single WHERE' => [
'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c ON p.category = c WHERE 1 = 1',
'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c WITH p.category = c WHERE 1 = 1',
'SELECT b0_.id AS id_0, b0_.author_id AS author_id_1, b0_.category_id AS category_id_2 FROM BlogPost b0_ INNER JOIN Category c1_ ON (b0_.category_id = c1_.id) WHERE 1 = 1 AND b0_.id IN (?)',
];
}

View File

@@ -22,6 +22,7 @@ use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Exception\ORMException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Tools\DebugUnitOfWorkListener;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\ToolsException;
@@ -30,7 +31,6 @@ use Doctrine\Tests\DbalExtensions\Connection;
use Doctrine\Tests\DbalExtensions\QueryLog;
use Doctrine\Tests\DbalTypes\Rot13Type;
use Doctrine\Tests\EventListener\CacheMetadataListener;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Models\Cache\Action;
use Doctrine\Tests\Models\Cache\Address;
use Doctrine\Tests\Models\Cache\Attraction;
@@ -187,6 +187,7 @@ use function getenv;
use function implode;
use function is_object;
use function method_exists;
use function realpath;
use function sprintf;
use function str_contains;
use function strtolower;
@@ -951,12 +952,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
$config->enableNativeLazyObjects(true);
}
$mappingDriver ??= AttributeDriverFactory::createAttributeDriver([
__DIR__ . '/Models/Cache',
__DIR__ . '/Models/GeoNames',
]);
$config->setMetadataDriverImpl($mappingDriver);
$config->setMetadataDriverImpl(
$mappingDriver ?? new AttributeDriver([
realpath(__DIR__ . '/Models/Cache'),
realpath(__DIR__ . '/Models/GeoNames'),
], true),
);
$conn = $connection ?: static::$sharedConn;
assert($conn !== null);

View File

@@ -15,7 +15,6 @@ use Doctrine\ORM\Cache\DefaultCacheFactory;
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Mocks\EntityManagerMock;
use PHPUnit\Framework\TestCase;
use Psr\Cache\CacheItemPoolInterface;
@@ -23,6 +22,7 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter;
use function class_exists;
use function method_exists;
use function realpath;
use function sprintf;
// DBAL 3 compatibility
@@ -57,10 +57,9 @@ abstract class OrmTestCase extends TestCase
private CacheItemPoolInterface|null $secondLevelCache = null;
/** @param list<string> $paths */
protected function createAttributeDriver(array $paths = []): AttributeDriver
{
return AttributeDriverFactory::createAttributeDriver($paths);
return new AttributeDriver($paths);
}
/**
@@ -97,7 +96,9 @@ abstract class OrmTestCase extends TestCase
TestUtil::configureProxies($config);
$config->setMetadataCache($metadataCache);
$config->setQueryCache(self::getSharedQueryCache());
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver([__DIR__ . '/Models/Cache']));
$config->setMetadataDriverImpl(new AttributeDriver([
realpath(__DIR__ . '/Models/Cache'),
], true));
if ($this->isSecondLevelCacheEnabled) {
$cacheConfig = new CacheConfiguration();