mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
522863116a | ||
|
|
b44774285b | ||
|
|
bc37f75b41 | ||
|
|
191a5366b1 | ||
|
|
44dddb2eee | ||
|
|
0c0c61c51b | ||
|
|
cc28fed9f5 | ||
|
|
b13564c6c0 | ||
|
|
d18126aac5 | ||
|
|
b7fd8241cf | ||
|
|
2432939e4f | ||
|
|
93ce84fa6e | ||
|
|
1bf4603422 | ||
|
|
e6961bd968 | ||
|
|
25d5bc5b46 | ||
|
|
cfc0655a1c | ||
|
|
6cde337777 | ||
|
|
831a1eb7d2 | ||
|
|
3a82b153f3 | ||
|
|
168ac31084 | ||
|
|
fe4a2e83cf | ||
|
|
205b2f5f20 | ||
|
|
3f550c19e3 | ||
|
|
8ac6a13ca0 | ||
|
|
2707b09a07 | ||
|
|
121158f92c | ||
|
|
51ad860a25 | ||
|
|
9bd51aaeb6 | ||
|
|
1fe1a6a048 | ||
|
|
c37b115450 | ||
|
|
19129e9f8a | ||
|
|
722cea6536 | ||
|
|
c1bb2ccf4b | ||
|
|
e3d7c6076c | ||
|
|
ce7d93f14d | ||
|
|
1153b9468c | ||
|
|
40f299f1eb | ||
|
|
428032ca7c | ||
|
|
68af854f46 | ||
|
|
77467cd824 | ||
|
|
ca3319c2f6 | ||
|
|
c06f6b9376 | ||
|
|
802f20b8e7 | ||
|
|
96d13ac62a | ||
|
|
2ea6a1a5fb | ||
|
|
41cb5fbbbf | ||
|
|
cc2ad1993c | ||
|
|
e4d46c4276 | ||
|
|
858a1adc3b | ||
|
|
3b499132d9 | ||
|
|
39153fd88a | ||
|
|
bdc9679e37 | ||
|
|
87a8ee21c9 | ||
|
|
59c8bc09ab | ||
|
|
3a7d7c9f57 | ||
|
|
06eca40134 | ||
|
|
23b35e9554 | ||
|
|
e063926cbd | ||
|
|
4a01a76a17 | ||
|
|
93c2dd9d4b | ||
|
|
75bc22980e | ||
|
|
9696c3434d | ||
|
|
9d4f54b9a4 | ||
|
|
37946d3a21 | ||
|
|
baf96cdad4 | ||
|
|
ce09c96427 | ||
|
|
ae659fe650 | ||
|
|
0a177d5074 | ||
|
|
dbfe47b07b | ||
|
|
d31aabb40c | ||
|
|
22b1f52c1c | ||
|
|
d66884403f | ||
|
|
a90ee5c495 | ||
|
|
11270425e5 | ||
|
|
552eae37a3 | ||
|
|
ee4b03aa78 | ||
|
|
f1246d57c2 | ||
|
|
a14ef7c279 | ||
|
|
54c29140fa | ||
|
|
daa99f197b | ||
|
|
2b04cc2e3f | ||
|
|
3d9af3187f | ||
|
|
e83d8a80ba | ||
|
|
c5291b4de8 | ||
|
|
029ca611f0 | ||
|
|
831d86548c | ||
|
|
f26b3b9cf9 | ||
|
|
9e7715f678 | ||
|
|
9ab84f7478 | ||
|
|
e6bb4ef20e | ||
|
|
0e26e3ed50 | ||
|
|
63315c8e4a | ||
|
|
8ca99fdfdc | ||
|
|
2d8e466636 | ||
|
|
94986af284 | ||
|
|
ad5c8e4bdc | ||
|
|
c363f55ad1 | ||
|
|
c973a62272 | ||
|
|
8d3446015a | ||
|
|
4e335f4044 | ||
|
|
bb36d49b38 | ||
|
|
2b81a8e260 | ||
|
|
7d3b3f28e9 | ||
|
|
cbec236e8b | ||
|
|
306963fe79 | ||
|
|
fb4578406f | ||
|
|
bdc41e2b5e | ||
|
|
90376a6431 | ||
|
|
97634ae6a1 | ||
|
|
f79d166a4e | ||
|
|
9c22814cfa | ||
|
|
b274893486 | ||
|
|
e0e55dc9c5 | ||
|
|
010b1e0886 | ||
|
|
93eb8a1bcb | ||
|
|
1464827220 | ||
|
|
8709fb38b0 | ||
|
|
cbb6c897de | ||
|
|
e9e60f2fbc | ||
|
|
5f3c1dbab8 | ||
|
|
753bc16c0b | ||
|
|
6090141e0b | ||
|
|
e4a6c041b5 | ||
|
|
c54c557e02 | ||
|
|
46d0865339 | ||
|
|
4672d284ff | ||
|
|
4b4b9b7b6f | ||
|
|
ae842259f5 | ||
|
|
69f51cc794 | ||
|
|
f9331ee2b9 | ||
|
|
cb05f1aadf | ||
|
|
ab616f1a1d | ||
|
|
7d1444e5b6 | ||
|
|
25d5936337 | ||
|
|
68f9bf5dfa |
@@ -11,17 +11,23 @@
|
||||
"slug": "latest",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.3",
|
||||
"branchName": "3.3.x",
|
||||
"slug": "3.3",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.2",
|
||||
"branchName": "3.2.x",
|
||||
"slug": "3.2",
|
||||
"upcoming": true
|
||||
"current": true
|
||||
},
|
||||
{
|
||||
"name": "3.1",
|
||||
"branchName": "3.1.x",
|
||||
"slug": "3.1",
|
||||
"current": true
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "3.0",
|
||||
@@ -29,17 +35,23 @@
|
||||
"slug": "3.0",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.21",
|
||||
"branchName": "2.21.x",
|
||||
"slug": "2.21",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.20",
|
||||
"branchName": "2.20.x",
|
||||
"slug": "2.20",
|
||||
"upcoming": true
|
||||
"maintained": true
|
||||
},
|
||||
{
|
||||
"name": "2.19",
|
||||
"branchName": "2.19.x",
|
||||
"slug": "2.19",
|
||||
"maintained": true
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.18",
|
||||
|
||||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- "CI"
|
||||
target-branch: "2.19.x"
|
||||
2
.github/workflows/coding-standards.yml
vendored
2
.github/workflows/coding-standards.yml
vendored
@@ -24,4 +24,4 @@ on:
|
||||
|
||||
jobs:
|
||||
coding-standards:
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@3.0.0"
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.1.0"
|
||||
|
||||
20
.github/workflows/continuous-integration.yml
vendored
20
.github/workflows/continuous-integration.yml
vendored
@@ -75,7 +75,7 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
dependency-versions: "${{ matrix.deps }}"
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
- "3.7"
|
||||
- "4@dev"
|
||||
mariadb-version:
|
||||
- "10.9"
|
||||
- "11.4"
|
||||
extension:
|
||||
- "mysqli"
|
||||
- "pdo_mysql"
|
||||
@@ -194,11 +194,11 @@ jobs:
|
||||
mariadb:
|
||||
image: "mariadb:${{ matrix.mariadb-version }}"
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: "doctrine_tests"
|
||||
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes
|
||||
MARIADB_DATABASE: "doctrine_tests"
|
||||
|
||||
options: >-
|
||||
--health-cmd "mysqladmin ping --silent"
|
||||
--health-cmd "healthcheck.sh --connect --innodb_initialized"
|
||||
|
||||
ports:
|
||||
- "3306:3306"
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
extensions: "${{ matrix.extension }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
@@ -296,7 +296,7 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
@@ -337,6 +337,8 @@ jobs:
|
||||
path: "reports"
|
||||
|
||||
- name: "Upload to Codecov"
|
||||
uses: "codecov/codecov-action@v3"
|
||||
uses: "codecov/codecov-action@v4"
|
||||
with:
|
||||
directory: reports
|
||||
env:
|
||||
CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"
|
||||
|
||||
43
.github/workflows/documentation.yml
vendored
43
.github/workflows/documentation.yml
vendored
@@ -5,45 +5,16 @@ on:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/documentation.yml
|
||||
- docs/**
|
||||
- ".github/workflows/documentation.yml"
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/documentation.yml
|
||||
- docs/**
|
||||
- ".github/workflows/documentation.yml"
|
||||
- "docs/**"
|
||||
|
||||
jobs:
|
||||
validate-with-guides:
|
||||
name: "Validate documentation with phpDocumentor/guides"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: "actions/checkout@v4"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.3"
|
||||
|
||||
- name: "Remove existing composer file"
|
||||
run: "rm composer.json"
|
||||
|
||||
- name: "Require phpdocumentor/guides-cli"
|
||||
run: "composer require --dev phpdocumentor/guides-cli --no-update"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
dependency-versions: "highest"
|
||||
|
||||
- name: "Add orphan metadata where needed"
|
||||
run: |
|
||||
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
|
||||
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/reference/installation.rst)" > docs/en/reference/installation.rst
|
||||
|
||||
- name: "Run guides-cli"
|
||||
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )"
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
uses: "doctrine/.github/.github/workflows/documentation.yml@5.1.0"
|
||||
|
||||
11
.github/workflows/phpbench.yml
vendored
11
.github/workflows/phpbench.yml
vendored
@@ -47,15 +47,8 @@ jobs:
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
- name: "Cache dependencies installed with composer"
|
||||
uses: "actions/cache@v3"
|
||||
with:
|
||||
path: "~/.composer/cache"
|
||||
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
|
||||
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
|
||||
|
||||
- name: "Install dependencies with composer"
|
||||
run: "composer update --no-interaction --no-progress"
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
|
||||
- name: "Run PHPBench"
|
||||
run: "vendor/bin/phpbench run --report=default"
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@4.0.0"
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.1.0"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
|
||||
2
.github/workflows/static-analysis.yml
vendored
2
.github/workflows/static-analysis.yml
vendored
@@ -83,7 +83,7 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: Install dependencies with Composer
|
||||
uses: ramsey/composer-install@v2
|
||||
uses: ramsey/composer-install@v3
|
||||
|
||||
- name: Run static analysis with Vimeo Psalm
|
||||
run: vendor/bin/psalm --shepherd
|
||||
|
||||
14
README.md
14
README.md
@@ -1,7 +1,7 @@
|
||||
| [4.0.x][4.0] | [3.2.x][3.2] | [3.1.x][3.1] | [2.20.x][2.20] | [2.19.x][2.19] |
|
||||
| [4.0.x][4.0] | [3.3.x][3.3] | [3.2.x][3.2] | [2.20.x][2.20] | [2.19.x][2.19] |
|
||||
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
|
||||
| [![Build status][4.0 image]][4.0] | [![Build status][3.2 image]][3.2] | [![Build status][3.1 image]][3.1] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][3.1 coverage image]][3.1 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] |
|
||||
| [![Build status][4.0 image]][4.0] | [![Build status][3.3 image]][3.3] | [![Build status][3.2 image]][3.2] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.3 coverage image]][3.3 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] |
|
||||
|
||||
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
|
||||
|
||||
@@ -22,14 +22,14 @@ without requiring unnecessary code duplication.
|
||||
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
|
||||
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
|
||||
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
|
||||
[3.3 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.3.x
|
||||
[3.3]: https://github.com/doctrine/orm/tree/3.3.x
|
||||
[3.3 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.3.x/graph/badge.svg
|
||||
[3.3 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.3.x
|
||||
[3.2 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.2.x
|
||||
[3.2]: https://github.com/doctrine/orm/tree/3.2.x
|
||||
[3.2 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.2.x/graph/badge.svg
|
||||
[3.2 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.2.x
|
||||
[3.1 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.1.x
|
||||
[3.1]: https://github.com/doctrine/orm/tree/3.1.x
|
||||
[3.1 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.1.x/graph/badge.svg
|
||||
[3.1 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.1.x
|
||||
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
|
||||
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
|
||||
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
|
||||
|
||||
26
UPGRADE.md
26
UPGRADE.md
@@ -1,3 +1,29 @@
|
||||
# Upgrade to 3.2
|
||||
|
||||
## Deprecate the `NotSupported` exception
|
||||
|
||||
The class `Doctrine\ORM\Exception\NotSupported` is deprecated without replacement.
|
||||
|
||||
## Deprecate remaining `Serializable` implementation
|
||||
|
||||
Relying on `SequenceGenerator` implementing the `Serializable` is deprecated
|
||||
because that interface won't be implemented in ORM 4 anymore.
|
||||
|
||||
The following methods are deprecated:
|
||||
|
||||
* `SequenceGenerator::serialize()`
|
||||
* `SequenceGenerator::unserialize()`
|
||||
|
||||
## `orm:schema-tool:update` option `--complete` is deprecated
|
||||
|
||||
That option behaves as a no-op, and is deprecated. It will be removed in 4.0.
|
||||
|
||||
## Deprecate properties `$indexes` and `$uniqueConstraints` of `Doctrine\ORM\Mapping\Table`
|
||||
|
||||
The properties `$indexes` and `$uniqueConstraints` have been deprecated since they had no effect at all.
|
||||
The preferred way of defining indices and unique constraints is by
|
||||
using the `\Doctrine\ORM\Mapping\UniqueConstraint` and `\Doctrine\ORM\Mapping\Index` attributes.
|
||||
|
||||
# Upgrade to 3.1
|
||||
|
||||
## Deprecate `Doctrine\ORM\Mapping\ReflectionEnumProperty`
|
||||
|
||||
@@ -38,12 +38,13 @@
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12.0",
|
||||
"phpbench/phpbench": "^1.0",
|
||||
"phpstan/phpstan": "1.10.59",
|
||||
"phpdocumentor/guides-cli": "^1.4",
|
||||
"phpstan/phpstan": "1.12.6",
|
||||
"phpunit/phpunit": "^10.4.0",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"squizlabs/php_codesniffer": "3.7.2",
|
||||
"symfony/cache": "^5.4 || ^6.2 || ^7.0",
|
||||
"vimeo/psalm": "5.22.2"
|
||||
"vimeo/psalm": "5.24.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "Provides support for XSD validation for XML mapping files",
|
||||
|
||||
@@ -232,6 +232,33 @@ vendors SQL parser to show us further errors in the parsing
|
||||
process, for example if the Unit would not be one of the supported
|
||||
values by MySql.
|
||||
|
||||
Typed functions
|
||||
---------------
|
||||
By default, result of custom functions is fetched as-is from the database driver.
|
||||
If you want to be sure that the type is always the same, then your custom function needs to
|
||||
implement ``Doctrine\ORM\Query\AST\TypedExpression``. Then, the result is wired
|
||||
through ``Doctrine\DBAL\Types\Type::convertToPhpValue()`` of the ``Type`` returned in ``getReturnType()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\AST\TypedExpression;
|
||||
|
||||
class DateDiff extends FunctionNode implements TypedExpression
|
||||
{
|
||||
// ...
|
||||
|
||||
public function getReturnType(): Type
|
||||
{
|
||||
return Type::getType(Types::INTEGER);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ What we offer are hooks to execute any kind of validation.
|
||||
.. note::
|
||||
|
||||
You don't need to validate your entities in the lifecycle
|
||||
events. Its only one of many options. Of course you can also
|
||||
events. It is only one of many options. Of course you can also
|
||||
perform validations in value setters or any other method of your
|
||||
entities that are used in your code.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ well.
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Doctrine ORM requires a minimum of PHP 7.1. For greatly improved
|
||||
Doctrine ORM requires a minimum of PHP 8.1. For greatly improved
|
||||
performance it is also recommended that you use APC with PHP.
|
||||
|
||||
Doctrine ORM Packages
|
||||
|
||||
@@ -870,8 +870,8 @@ This is essentially the same as the following, more verbose, mapping:
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
#[JoinTable(name: 'User_Group')]
|
||||
#[JoinColumn(name: 'User_id', referencedColumnName: 'id')]
|
||||
#[InverseJoinColumn(name: 'Group_id', referencedColumnName: 'id')]
|
||||
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
|
||||
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
|
||||
#[ManyToMany(targetEntity: Group::class)]
|
||||
private Collection $groups;
|
||||
// ...
|
||||
@@ -884,10 +884,10 @@ This is essentially the same as the following, more verbose, mapping:
|
||||
<many-to-many field="groups" target-entity="Group">
|
||||
<join-table name="User_Group">
|
||||
<join-columns>
|
||||
<join-column id="User_id" referenced-column-name="id" />
|
||||
<join-column id="user_id" referenced-column-name="id" />
|
||||
</join-columns>
|
||||
<inverse-join-columns>
|
||||
<join-column id="Group_id" referenced-column-name="id" />
|
||||
<join-column id="group_id" referenced-column-name="id" />
|
||||
</inverse-join-columns>
|
||||
</join-table>
|
||||
</many-to-many>
|
||||
|
||||
@@ -15,7 +15,7 @@ Index
|
||||
- :ref:`#[AttributeOverride] <attrref_attributeoverride>`
|
||||
- :ref:`#[Column] <attrref_column>`
|
||||
- :ref:`#[Cache] <attrref_cache>`
|
||||
- :ref:`#[ChangeTrackingPolicy <attrref_changetrackingpolicy>`
|
||||
- :ref:`#[ChangeTrackingPolicy] <attrref_changetrackingpolicy>`
|
||||
- :ref:`#[CustomIdGenerator] <attrref_customidgenerator>`
|
||||
- :ref:`#[DiscriminatorColumn] <attrref_discriminatorcolumn>`
|
||||
- :ref:`#[DiscriminatorMap] <attrref_discriminatormap>`
|
||||
|
||||
@@ -976,7 +976,7 @@ The Query class
|
||||
---------------
|
||||
|
||||
An instance of the ``Doctrine\ORM\Query`` class represents a DQL
|
||||
query. You create a Query instance be calling
|
||||
query. You create a Query instance by calling
|
||||
``EntityManager#createQuery($dql)``, passing the DQL query string.
|
||||
Alternatively you can create an empty ``Query`` instance and invoke
|
||||
``Query#setDQL($dql)`` afterwards. Here are some examples:
|
||||
@@ -993,58 +993,146 @@ Alternatively you can create an empty ``Query`` instance and invoke
|
||||
$q = $em->createQuery();
|
||||
$q->setDQL('select u from MyProject\Model\User u');
|
||||
|
||||
Query Result Formats
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Query Result Formats (Hydration Modes)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The format in which the result of a DQL SELECT query is returned
|
||||
can be influenced by a so-called ``hydration mode``. A hydration
|
||||
mode specifies a particular way in which a SQL result set is
|
||||
transformed. Each hydration mode has its own dedicated method on
|
||||
the Query class. Here they are:
|
||||
The way in which the SQL result set of a DQL SELECT query is transformed
|
||||
to PHP is determined by the so-called "hydration mode".
|
||||
|
||||
``getResult()``
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- ``Query#getResult()``: Retrieves a collection of objects. The
|
||||
result is either a plain collection of objects (pure) or an array
|
||||
where the objects are nested in the result rows (mixed).
|
||||
- ``Query#getSingleResult()``: Retrieves a single object. If the
|
||||
result contains more than one object, an ``NonUniqueResultException``
|
||||
is thrown. If the result contains no objects, an ``NoResultException``
|
||||
is thrown. The pure/mixed distinction does not apply.
|
||||
- ``Query#getOneOrNullResult()``: Retrieve a single object. If the
|
||||
result contains more than one object, a ``NonUniqueResultException``
|
||||
is thrown. If no object is found null will be returned.
|
||||
- ``Query#getArrayResult()``: Retrieves an array graph (a nested
|
||||
array) that is largely interchangeable with the object graph
|
||||
generated by ``Query#getResult()`` for read-only purposes.
|
||||
Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array
|
||||
where the objects are nested in the result rows (mixed):
|
||||
|
||||
.. note::
|
||||
.. code-block:: php
|
||||
|
||||
An array graph can differ from the corresponding object
|
||||
graph in certain scenarios due to the difference of the identity
|
||||
semantics between arrays and objects.
|
||||
<?php
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
|
||||
$query = $em->createQuery('SELECT u FROM User u');
|
||||
$users = $query->getResult();
|
||||
// same as:
|
||||
$users = $query->getResult(AbstractQuery::HYDRATE_OBJECT);
|
||||
|
||||
- Objects fetched in a FROM clause are returned as a Set, that means every
|
||||
object is only ever included in the resulting array once. This is the case
|
||||
even when using JOIN or GROUP BY in ways that return the same row for an
|
||||
object multiple times. If the hydrator sees the same object multiple times,
|
||||
then it makes sure it is only returned once.
|
||||
|
||||
- ``Query#getScalarResult()``: Retrieves a flat/rectangular result
|
||||
set of scalar values that can contain duplicate data. The
|
||||
pure/mixed distinction does not apply.
|
||||
- ``Query#getSingleScalarResult()``: Retrieves a single scalar
|
||||
value from the result returned by the dbms. If the result contains
|
||||
more than a single scalar value, an exception is thrown. The
|
||||
pure/mixed distinction does not apply.
|
||||
- If an object is already in memory from a previous query of any kind, then
|
||||
then the previous object is used, even if the database may contain more
|
||||
recent data. This even happens if the previous object is still an unloaded proxy.
|
||||
|
||||
Instead of using these methods, you can alternatively use the
|
||||
general-purpose method
|
||||
``Query#execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``.
|
||||
Using this method you can directly supply the hydration mode as the
|
||||
second parameter via one of the Query constants. In fact, the
|
||||
methods mentioned earlier are just convenient shortcuts for the
|
||||
execute method. For example, the method ``Query#getResult()``
|
||||
internally invokes execute, passing in ``Query::HYDRATE_OBJECT`` as
|
||||
the hydration mode.
|
||||
``getArrayResult()``
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The use of the methods mentioned earlier is generally preferred as
|
||||
it leads to more concise code.
|
||||
Retrieves an array graph (a nested array) for read-only purposes.
|
||||
|
||||
.. note::
|
||||
|
||||
An array graph can differ from the corresponding object
|
||||
graph in certain scenarios due to the difference of the identity
|
||||
semantics between arrays and objects.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$users = $query->getArrayResult();
|
||||
// same as:
|
||||
$users = $query->getResult(AbstractQuery::HYDRATE_ARRAY);
|
||||
|
||||
``getScalarResult()``
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The
|
||||
pure/mixed distinction does not apply.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$users = $query->getScalarResult();
|
||||
// same as:
|
||||
$users = $query->getResult(AbstractQuery::HYDRATE_SCALAR);
|
||||
|
||||
Fields from classes are prefixed by the DQL alias in the result.
|
||||
A query of the kind `SELECT u.name ...` returns a key `u_name` in the result rows.
|
||||
|
||||
``getSingleScalarResult()``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Retrieves a single scalar value from the result returned by the database. If the result contains
|
||||
more than a single scalar value, a ``NonUniqueResultException`` is thrown. The pure/mixed distinction does not apply.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT COUNT(u.id) FROM User u');
|
||||
$numUsers = $query->getSingleScalarResult();
|
||||
// same as:
|
||||
$numUsers = $query->getResult(AbstractQuery::HYDRATE_SINGLE_SCALAR);
|
||||
|
||||
``getSingleColumnResult()``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Retrieves an array from a one-dimensional array of scalar values:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT a.id FROM User u');
|
||||
$ids = $query->getSingleColumnResult();
|
||||
// same as:
|
||||
$ids = $query->getResult(AbstractQuery::HYDRATE_SCALAR_COLUMN);
|
||||
|
||||
``getSingleResult()``
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Retrieves a single object. If the result contains more than one object, a ``NonUniqueResultException``
|
||||
is thrown. If the result contains no objects, a ``NoResultException`` is thrown. The pure/mixed distinction does not apply.
|
||||
|
||||
``getOneOrNullResult()``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Retrieves a single object. If the result contains more than one object, a ``NonUniqueResultException``
|
||||
is thrown. If no object is found, ``null`` will be returned.
|
||||
|
||||
Custom Hydration Modes
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can easily add your own custom hydration modes by first
|
||||
creating a class which extends ``AbstractHydrator``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Hydrators;
|
||||
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
|
||||
class CustomHydrator extends AbstractHydrator
|
||||
{
|
||||
protected function _hydrateAll()
|
||||
{
|
||||
return $this->_stmt->fetchAllAssociative();
|
||||
}
|
||||
}
|
||||
|
||||
Next you just need to add the class to the ORM configuration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator');
|
||||
|
||||
Now the hydrator is ready to be used in your queries:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$results = $query->getResult('CustomHydrator');
|
||||
|
||||
Pure and Mixed Results
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -1148,165 +1236,6 @@ will return the rows iterating the different top-level entities.
|
||||
[2] => Object (User)
|
||||
[3] => Object (Group)
|
||||
|
||||
|
||||
Hydration Modes
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Each of the Hydration Modes makes assumptions about how the result
|
||||
is returned to user land. You should know about all the details to
|
||||
make best use of the different result formats:
|
||||
|
||||
The constants for the different hydration modes are:
|
||||
|
||||
|
||||
- ``Query::HYDRATE_OBJECT``
|
||||
- ``Query::HYDRATE_ARRAY``
|
||||
- ``Query::HYDRATE_SCALAR``
|
||||
- ``Query::HYDRATE_SINGLE_SCALAR``
|
||||
- ``Query::HYDRATE_SCALAR_COLUMN``
|
||||
|
||||
Object Hydration
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Object hydration hydrates the result set into the object graph:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$users = $query->getResult(Query::HYDRATE_OBJECT);
|
||||
|
||||
Sometimes the behavior in the object hydrator can be confusing, which is
|
||||
why we are listing as many of the assumptions here for reference:
|
||||
|
||||
- Objects fetched in a FROM clause are returned as a Set, that means every
|
||||
object is only ever included in the resulting array once. This is the case
|
||||
even when using JOIN or GROUP BY in ways that return the same row for an
|
||||
object multiple times. If the hydrator sees the same object multiple times,
|
||||
then it makes sure it is only returned once.
|
||||
|
||||
- If an object is already in memory from a previous query of any kind, then
|
||||
then the previous object is used, even if the database may contain more
|
||||
recent data. Data from the database is discarded. This even happens if the
|
||||
previous object is still an unloaded proxy.
|
||||
|
||||
This list might be incomplete.
|
||||
|
||||
Array Hydration
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
You can run the same query with array hydration and the result set
|
||||
is hydrated into an array that represents the object graph:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$users = $query->getResult(Query::HYDRATE_ARRAY);
|
||||
|
||||
You can use the ``getArrayResult()`` shortcut as well:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$users = $query->getArrayResult();
|
||||
|
||||
Scalar Hydration
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
If you want to return a flat rectangular result set instead of an
|
||||
object graph you can use scalar hydration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$users = $query->getResult(Query::HYDRATE_SCALAR);
|
||||
echo $users[0]['u_id'];
|
||||
|
||||
The following assumptions are made about selected fields using
|
||||
Scalar Hydration:
|
||||
|
||||
|
||||
1. Fields from classes are prefixed by the DQL alias in the result.
|
||||
A query of the kind 'SELECT u.name ..' returns a key 'u_name' in
|
||||
the result rows.
|
||||
|
||||
Single Scalar Hydration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you have a query which returns just a single scalar value you can use
|
||||
single scalar hydration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id');
|
||||
$query->setParameter(1, 'jwage');
|
||||
$numArticles = $query->getResult(Query::HYDRATE_SINGLE_SCALAR);
|
||||
|
||||
You can use the ``getSingleScalarResult()`` shortcut as well:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$numArticles = $query->getSingleScalarResult();
|
||||
|
||||
Scalar Column Hydration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you have a query which returns a one-dimensional array of scalar values
|
||||
you can use scalar column hydration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT a.id FROM CmsUser u');
|
||||
$ids = $query->getResult(Query::HYDRATE_SCALAR_COLUMN);
|
||||
|
||||
You can use the ``getSingleColumnResult()`` shortcut as well:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$ids = $query->getSingleColumnResult();
|
||||
|
||||
Custom Hydration Modes
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can easily add your own custom hydration modes by first
|
||||
creating a class which extends ``AbstractHydrator``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Hydrators;
|
||||
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
|
||||
class CustomHydrator extends AbstractHydrator
|
||||
{
|
||||
protected function _hydrateAll()
|
||||
{
|
||||
return $this->_stmt->fetchAllAssociative();
|
||||
}
|
||||
}
|
||||
|
||||
Next you just need to add the class to the ORM configuration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator');
|
||||
|
||||
Now the hydrator is ready to be used in your queries:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$results = $query->getResult('CustomHydrator');
|
||||
|
||||
Iterating Large Result Sets
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
:orphan:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ requirement.
|
||||
|
||||
A more convenient alternative for explicit transaction demarcation is the use
|
||||
of provided control abstractions in the form of
|
||||
``Connection#transactional($func)`` and ``EntityManager#transactional($func)``.
|
||||
``Connection#transactional($func)`` and ``EntityManager#wrapInTransaction($func)``.
|
||||
When used, these control abstractions ensure that you never forget to rollback
|
||||
the transaction, in addition to the obvious code reduction. An example that is
|
||||
functionally equivalent to the previously shown code looks as follows:
|
||||
@@ -96,21 +96,23 @@ functionally equivalent to the previously shown code looks as follows:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// transactional with Connection instance
|
||||
// $conn instanceof Connection
|
||||
$conn->transactional(function($conn) {
|
||||
// ... do some work
|
||||
$user = new User;
|
||||
$user->setName('George');
|
||||
});
|
||||
|
||||
// transactional with EntityManager instance
|
||||
// $em instanceof EntityManager
|
||||
$em->transactional(function($em) {
|
||||
$em->wrapInTransaction(function($em) {
|
||||
// ... do some work
|
||||
$user = new User;
|
||||
$user->setName('George');
|
||||
$em->persist($user);
|
||||
});
|
||||
|
||||
.. warning::
|
||||
|
||||
For historical reasons, ``EntityManager#transactional($func)`` will return
|
||||
``true`` whenever the return value of ``$func`` is loosely false.
|
||||
Some examples of this include ``array()``, ``"0"``, ``""``, ``0``, and
|
||||
``null``.
|
||||
|
||||
The difference between ``Connection#transactional($func)`` and
|
||||
``EntityManager#transactional($func)`` is that the latter
|
||||
abstraction flushes the ``EntityManager`` prior to transaction
|
||||
|
||||
@@ -338,10 +338,11 @@ Performance of different deletion strategies
|
||||
Deleting an object with all its associated objects can be achieved
|
||||
in multiple ways with very different performance impacts.
|
||||
|
||||
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM
|
||||
will fetch this association. If its a Single association it will
|
||||
pass this entity to
|
||||
``EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()``.
|
||||
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM will
|
||||
fetch this association. If it's a Single association it will pass
|
||||
this entity to ``EntityManager#remove()``. If the association is a
|
||||
collection, Doctrine will loop over all its elements and pass them to
|
||||
``EntityManager#remove()``.
|
||||
In both cases the cascade remove semantics are applied recursively.
|
||||
For large object graphs this removal strategy can be very costly.
|
||||
2. Using a DQL ``DELETE`` statement allows you to delete multiple
|
||||
|
||||
@@ -1,80 +1,73 @@
|
||||
.. toc::
|
||||
:orphan:
|
||||
|
||||
.. tocheader:: Tutorials
|
||||
.. toctree::
|
||||
:caption: Tutorials
|
||||
:depth: 3
|
||||
|
||||
.. toctree::
|
||||
:depth: 3
|
||||
tutorials/getting-started
|
||||
tutorials/getting-started-database
|
||||
tutorials/getting-started-models
|
||||
tutorials/working-with-indexed-associations
|
||||
tutorials/extra-lazy-associations
|
||||
tutorials/composite-primary-keys
|
||||
tutorials/ordered-associations
|
||||
tutorials/override-field-association-mappings-in-subclasses
|
||||
tutorials/pagination
|
||||
tutorials/embeddables
|
||||
|
||||
tutorials/getting-started
|
||||
tutorials/getting-started-database
|
||||
tutorials/getting-started-models
|
||||
tutorials/working-with-indexed-associations
|
||||
tutorials/extra-lazy-associations
|
||||
tutorials/composite-primary-keys
|
||||
tutorials/ordered-associations
|
||||
tutorials/override-field-association-mappings-in-subclasses
|
||||
tutorials/pagination
|
||||
tutorials/embeddables
|
||||
.. toctree::
|
||||
:caption: Reference
|
||||
:depth: 3
|
||||
|
||||
.. toc::
|
||||
reference/architecture
|
||||
reference/configuration
|
||||
reference/faq
|
||||
reference/basic-mapping
|
||||
reference/association-mapping
|
||||
reference/inheritance-mapping
|
||||
reference/working-with-objects
|
||||
reference/working-with-associations
|
||||
reference/typedfieldmapper
|
||||
reference/events
|
||||
reference/unitofwork
|
||||
reference/unitofwork-associations
|
||||
reference/transactions-and-concurrency
|
||||
reference/batch-processing
|
||||
reference/dql-doctrine-query-language
|
||||
reference/query-builder
|
||||
reference/native-sql
|
||||
reference/change-tracking-policies
|
||||
reference/attributes-reference
|
||||
reference/xml-mapping
|
||||
reference/php-mapping
|
||||
reference/caching
|
||||
reference/improving-performance
|
||||
reference/tools
|
||||
reference/metadata-drivers
|
||||
reference/best-practices
|
||||
reference/limitations-and-known-issues
|
||||
tutorials/pagination
|
||||
reference/filters
|
||||
reference/namingstrategy
|
||||
reference/advanced-configuration
|
||||
reference/second-level-cache
|
||||
reference/security
|
||||
|
||||
.. tocheader:: Reference
|
||||
.. toctree::
|
||||
:caption: Cookbook
|
||||
:depth: 3
|
||||
|
||||
.. toctree::
|
||||
:depth: 3
|
||||
|
||||
reference/architecture
|
||||
reference/configuration
|
||||
reference/faq
|
||||
reference/basic-mapping
|
||||
reference/association-mapping
|
||||
reference/inheritance-mapping
|
||||
reference/working-with-objects
|
||||
reference/working-with-associations
|
||||
reference/typedfieldmapper
|
||||
reference/events
|
||||
reference/unitofwork
|
||||
reference/unitofwork-associations
|
||||
reference/transactions-and-concurrency
|
||||
reference/batch-processing
|
||||
reference/dql-doctrine-query-language
|
||||
reference/query-builder
|
||||
reference/native-sql
|
||||
reference/change-tracking-policies
|
||||
reference/attributes-reference
|
||||
reference/xml-mapping
|
||||
reference/php-mapping
|
||||
reference/caching
|
||||
reference/improving-performance
|
||||
reference/tools
|
||||
reference/metadata-drivers
|
||||
reference/best-practices
|
||||
reference/limitations-and-known-issues
|
||||
tutorials/pagination
|
||||
reference/filters
|
||||
reference/namingstrategy
|
||||
reference/advanced-configuration
|
||||
reference/second-level-cache
|
||||
reference/security
|
||||
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Cookbook
|
||||
|
||||
.. toctree::
|
||||
:depth: 3
|
||||
|
||||
cookbook/aggregate-fields
|
||||
cookbook/custom-mapping-types
|
||||
cookbook/decorator-pattern
|
||||
cookbook/dql-custom-walkers
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
cookbook/validation-of-entities
|
||||
cookbook/working-with-datetime
|
||||
cookbook/mysql-enums
|
||||
cookbook/advanced-field-value-conversion-using-custom-mapping-types
|
||||
cookbook/entities-in-session
|
||||
cookbook/aggregate-fields
|
||||
cookbook/custom-mapping-types
|
||||
cookbook/decorator-pattern
|
||||
cookbook/dql-custom-walkers
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
cookbook/validation-of-entities
|
||||
cookbook/working-with-datetime
|
||||
cookbook/mysql-enums
|
||||
cookbook/advanced-field-value-conversion-using-custom-mapping-types
|
||||
cookbook/entities-in-session
|
||||
|
||||
@@ -145,7 +145,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
|
||||
private Collection $attributes;
|
||||
|
||||
public function addAttribute(string $name, ArticleAttribute $value): void
|
||||
public function addAttribute(string $name, string $value): void
|
||||
{
|
||||
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ What is Doctrine?
|
||||
-----------------
|
||||
|
||||
Doctrine ORM is an `object-relational mapper (ORM) <https://en.wikipedia.org/wiki/Object-relational_mapping>`_
|
||||
for PHP 7.1+ that provides transparent persistence for PHP objects. It uses the Data Mapper
|
||||
for PHP that provides transparent persistence for PHP objects. It uses the Data Mapper
|
||||
pattern at the heart, aiming for a complete separation of your domain/business
|
||||
logic from the persistence in a relational database management system.
|
||||
|
||||
@@ -139,12 +139,12 @@ step:
|
||||
|
||||
// Create a simple "default" Doctrine ORM configuration for Attributes
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration(
|
||||
paths: array(__DIR__."/src"),
|
||||
paths: [__DIR__ . '/src'],
|
||||
isDevMode: true,
|
||||
);
|
||||
// or if you prefer XML
|
||||
// $config = ORMSetup::createXMLMetadataConfiguration(
|
||||
// paths: array(__DIR__."/config/xml"),
|
||||
// paths: [__DIR__ . '/config/xml'],
|
||||
// isDevMode: true,
|
||||
//);
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<file>src</file>
|
||||
<file>tests</file>
|
||||
|
||||
<exclude-pattern>*/src/Mapping/InverseJoinColumn.php</exclude-pattern>
|
||||
<exclude-pattern>*/tests/Tests/Proxies/__CG__*</exclude-pattern>
|
||||
<exclude-pattern>*/tests/Tests/ORM/Tools/Export/export/*</exclude-pattern>
|
||||
|
||||
|
||||
@@ -115,11 +115,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/EntityManagerInterface.php
|
||||
|
||||
-
|
||||
message: "#^Template type T of method Doctrine\\\\ORM\\\\EntityManagerInterface\\:\\:getClassMetadata\\(\\) is not referenced in a parameter\\.$#"
|
||||
count: 1
|
||||
path: src/EntityManagerInterface.php
|
||||
|
||||
-
|
||||
message: "#^Method Doctrine\\\\ORM\\\\EntityRepository\\:\\:matching\\(\\) should return Doctrine\\\\Common\\\\Collections\\\\AbstractLazyCollection\\<int, T of object\\>&Doctrine\\\\Common\\\\Collections\\\\Selectable\\<int, T of object\\> but returns Doctrine\\\\ORM\\\\LazyCriteriaCollection\\<\\(int\\|string\\), object\\>\\.$#"
|
||||
count: 1
|
||||
|
||||
@@ -20,10 +20,6 @@ parameters:
|
||||
message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '~^Unreachable statement \- code above always terminates\.$~'
|
||||
path: src/Mapping/AssociationMapping.php
|
||||
|
||||
- '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~'
|
||||
|
||||
# To be removed in 4.0
|
||||
|
||||
@@ -20,10 +20,6 @@ parameters:
|
||||
message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '~^Unreachable statement \- code above always terminates\.$~'
|
||||
path: src/Mapping/AssociationMapping.php
|
||||
|
||||
# Compatibility with DBAL 3
|
||||
# See https://github.com/doctrine/dbal/pull/3480
|
||||
-
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="5.22.2@d768d914152dbbf3486c36398802f74e80cfde48">
|
||||
<files psalm-version="5.24.0@462c80e31c34e58cc4f750c656be3927e80e550e">
|
||||
<file src="src/AbstractQuery.php">
|
||||
<FalsableReturnStatement>
|
||||
<code><![CDATA[! $filteredParameters->isEmpty() ? $filteredParameters->first() : null]]></code>
|
||||
@@ -154,14 +154,6 @@
|
||||
<code><![CDATA[$className]]></code>
|
||||
</ArgumentTypeCoercion>
|
||||
</file>
|
||||
<file src="src/Decorator/EntityManagerDecorator.php">
|
||||
<InvalidReturnStatement>
|
||||
<code><![CDATA[$this->wrapped->getClassMetadata($className)]]></code>
|
||||
</InvalidReturnStatement>
|
||||
<InvalidReturnType>
|
||||
<code><![CDATA[ClassMetadata]]></code>
|
||||
</InvalidReturnType>
|
||||
</file>
|
||||
<file src="src/EntityManager.php">
|
||||
<ArgumentTypeCoercion>
|
||||
<code><![CDATA[$className]]></code>
|
||||
@@ -174,11 +166,9 @@
|
||||
<code><![CDATA[$persister->load($sortedId, null, null, [], $lockMode)]]></code>
|
||||
<code><![CDATA[$persister->loadById($sortedId)]]></code>
|
||||
<code><![CDATA[$this->metadataFactory]]></code>
|
||||
<code><![CDATA[$this->metadataFactory->getMetadataFor($className)]]></code>
|
||||
</InvalidReturnStatement>
|
||||
<InvalidReturnType>
|
||||
<code><![CDATA[ClassMetadataFactory]]></code>
|
||||
<code><![CDATA[Mapping\ClassMetadata]]></code>
|
||||
</InvalidReturnType>
|
||||
<PossiblyNullArgument>
|
||||
<code><![CDATA[$config->getProxyDir()]]></code>
|
||||
@@ -216,6 +206,11 @@
|
||||
<code><![CDATA[$entity]]></code>
|
||||
</PossiblyNullArgument>
|
||||
</file>
|
||||
<file src="src/Id/SequenceGenerator.php">
|
||||
<ParamNameMismatch>
|
||||
<code><![CDATA[$serialized]]></code>
|
||||
</ParamNameMismatch>
|
||||
</file>
|
||||
<file src="src/Internal/Hydration/AbstractHydrator.php">
|
||||
<ReferenceConstraintViolation>
|
||||
<code><![CDATA[return $rowData;]]></code>
|
||||
@@ -251,9 +246,6 @@
|
||||
</UnsupportedReferenceUsage>
|
||||
</file>
|
||||
<file src="src/Internal/Hydration/ObjectHydrator.php">
|
||||
<InvalidArgument>
|
||||
<code><![CDATA[$element]]></code>
|
||||
</InvalidArgument>
|
||||
<PossiblyFalseArgument>
|
||||
<code><![CDATA[$index]]></code>
|
||||
</PossiblyFalseArgument>
|
||||
@@ -298,6 +290,10 @@
|
||||
<code><![CDATA[$this->columnNames]]></code>
|
||||
</DeprecatedProperty>
|
||||
<InvalidArgument>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
@@ -501,13 +497,8 @@
|
||||
<InvalidPropertyAssignmentValue>
|
||||
<code><![CDATA[$metadata->table]]></code>
|
||||
</InvalidPropertyAssignmentValue>
|
||||
<InvalidPropertyFetch>
|
||||
<code><![CDATA[$xmlRoot->{'discriminator-column'}]]></code>
|
||||
<code><![CDATA[$xmlRoot->{'discriminator-map'}]]></code>
|
||||
</InvalidPropertyFetch>
|
||||
<InvalidReturnStatement>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$result]]></code>
|
||||
<code><![CDATA[[
|
||||
'usage' => $usage,
|
||||
'region' => $region,
|
||||
@@ -531,20 +522,10 @@
|
||||
* options?: array
|
||||
* }]]></code>
|
||||
<code><![CDATA[array{usage: int|null, region?: string}]]></code>
|
||||
<code><![CDATA[loadMappingFile]]></code>
|
||||
</InvalidReturnType>
|
||||
<MoreSpecificImplementedParamType>
|
||||
<code><![CDATA[$metadata]]></code>
|
||||
</MoreSpecificImplementedParamType>
|
||||
<NoInterfaceProperties>
|
||||
<code><![CDATA[$xmlRoot->{'discriminator-column'}]]></code>
|
||||
<code><![CDATA[$xmlRoot->{'discriminator-map'}]]></code>
|
||||
</NoInterfaceProperties>
|
||||
<TypeDoesNotContainType>
|
||||
<code><![CDATA[$xmlRoot->getName() === 'embeddable']]></code>
|
||||
<code><![CDATA[$xmlRoot->getName() === 'entity']]></code>
|
||||
<code><![CDATA[$xmlRoot->getName() === 'mapped-superclass']]></code>
|
||||
</TypeDoesNotContainType>
|
||||
</file>
|
||||
<file src="src/Mapping/ManyToManyInverseSideMapping.php">
|
||||
<PropertyNotSetInConstructor>
|
||||
@@ -760,7 +741,9 @@
|
||||
<code><![CDATA[$autoGenerate > 4]]></code>
|
||||
</TypeDoesNotContainType>
|
||||
<UndefinedMethod>
|
||||
<code><![CDATA[self::createLazyGhost($initializer, $skippedProperties)]]></code>
|
||||
<code><![CDATA[self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void {
|
||||
$initializer($object, $identifier);
|
||||
}, $skippedProperties)]]></code>
|
||||
</UndefinedMethod>
|
||||
<UnresolvableInclude>
|
||||
<code><![CDATA[require $fileName]]></code>
|
||||
|
||||
@@ -63,27 +63,27 @@ class EntityManager implements EntityManagerInterface
|
||||
/**
|
||||
* The metadata factory, used to retrieve the ORM metadata of entity classes.
|
||||
*/
|
||||
private readonly ClassMetadataFactory $metadataFactory;
|
||||
private ClassMetadataFactory $metadataFactory;
|
||||
|
||||
/**
|
||||
* The UnitOfWork used to coordinate object-level transactions.
|
||||
*/
|
||||
private readonly UnitOfWork $unitOfWork;
|
||||
private UnitOfWork $unitOfWork;
|
||||
|
||||
/**
|
||||
* The event manager that is the central point of the event system.
|
||||
*/
|
||||
private readonly EventManager $eventManager;
|
||||
private EventManager $eventManager;
|
||||
|
||||
/**
|
||||
* The proxy factory used to create dynamic proxies.
|
||||
*/
|
||||
private readonly ProxyFactory $proxyFactory;
|
||||
private ProxyFactory $proxyFactory;
|
||||
|
||||
/**
|
||||
* The repository factory used to create dynamic repositories.
|
||||
*/
|
||||
private readonly RepositoryFactory $repositoryFactory;
|
||||
private RepositoryFactory $repositoryFactory;
|
||||
|
||||
/**
|
||||
* The expression builder instance used to generate query expressions.
|
||||
@@ -112,8 +112,8 @@ class EntityManager implements EntityManagerInterface
|
||||
* @param Connection $conn The database connection used by the EntityManager.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Connection $conn,
|
||||
private readonly Configuration $config,
|
||||
private Connection $conn,
|
||||
private Configuration $config,
|
||||
EventManager|null $eventManager = null,
|
||||
) {
|
||||
if (! $config->getMetadataDriverImpl()) {
|
||||
|
||||
@@ -234,7 +234,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
*
|
||||
* @psalm-param string|class-string<T> $className
|
||||
*
|
||||
* @psalm-return Mapping\ClassMetadata<T>
|
||||
* @psalm-return ($className is class-string<T> ? Mapping\ClassMetadata<T> : Mapping\ClassMetadata<object>)
|
||||
*
|
||||
* @psalm-template T of object
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ use LogicException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @deprecated */
|
||||
final class NotSupported extends LogicException implements ORMException
|
||||
{
|
||||
public static function create(): self
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Id;
|
||||
|
||||
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Serializable;
|
||||
|
||||
@@ -65,8 +66,17 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
|
||||
return $this->nextValue;
|
||||
}
|
||||
|
||||
/** @deprecated without replacement. */
|
||||
final public function serialize(): string
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11468',
|
||||
'%s() is deprecated, use __serialize() instead. %s won\'t implement the Serializable interface anymore in ORM 4.',
|
||||
__METHOD__,
|
||||
self::class,
|
||||
);
|
||||
|
||||
return serialize($this->__serialize());
|
||||
}
|
||||
|
||||
@@ -79,8 +89,17 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
|
||||
];
|
||||
}
|
||||
|
||||
/** @deprecated without replacement. */
|
||||
final public function unserialize(string $serialized): void
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11468',
|
||||
'%s() is deprecated, use __unserialize() instead. %s won\'t implement the Serializable interface anymore in ORM 4.',
|
||||
__METHOD__,
|
||||
self::class,
|
||||
);
|
||||
|
||||
$this->__unserialize(unserialize($serialized));
|
||||
}
|
||||
|
||||
|
||||
@@ -104,29 +104,31 @@ abstract class AbstractHydrator
|
||||
|
||||
$this->prepare();
|
||||
|
||||
while (true) {
|
||||
$row = $this->statement()->fetchAssociative();
|
||||
try {
|
||||
while (true) {
|
||||
$row = $this->statement()->fetchAssociative();
|
||||
|
||||
if ($row === false) {
|
||||
$this->cleanup();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
$this->hydrateRowData($row, $result);
|
||||
|
||||
$this->cleanupAfterRowIteration();
|
||||
if (count($result) === 1) {
|
||||
if (count($resultSetMapping->indexByMap) === 0) {
|
||||
yield end($result);
|
||||
} else {
|
||||
yield from $result;
|
||||
if ($row === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
$this->hydrateRowData($row, $result);
|
||||
|
||||
$this->cleanupAfterRowIteration();
|
||||
if (count($result) === 1) {
|
||||
if (count($resultSetMapping->indexByMap) === 0) {
|
||||
yield end($result);
|
||||
} else {
|
||||
yield from $result;
|
||||
}
|
||||
} else {
|
||||
yield $result;
|
||||
}
|
||||
} else {
|
||||
yield $result;
|
||||
}
|
||||
} finally {
|
||||
$this->cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -356,11 +356,15 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$parentObject = $this->resultPointers[$parentAlias];
|
||||
} else {
|
||||
// Parent object of relation not found, mark as not-fetched again
|
||||
$element = $this->getEntity($data, $dqlAlias);
|
||||
if (isset($nonemptyComponents[$dqlAlias])) {
|
||||
$element = $this->getEntity($data, $dqlAlias);
|
||||
|
||||
// Update result pointer and provide initial fetch data for parent
|
||||
$this->resultPointers[$dqlAlias] = $element;
|
||||
$rowData['data'][$parentAlias][$relationField] = $element;
|
||||
// Update result pointer and provide initial fetch data for parent
|
||||
$this->resultPointers[$dqlAlias] = $element;
|
||||
$rowData['data'][$parentAlias][$relationField] = $element;
|
||||
} else {
|
||||
$element = null;
|
||||
}
|
||||
|
||||
// Mark as not-fetched again
|
||||
unset($this->hints['fetched'][$parentAlias][$relationField]);
|
||||
|
||||
@@ -64,11 +64,11 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Do an optimized search of an element
|
||||
*
|
||||
* @template TMaybeContained
|
||||
* @param mixed $element The element to search for.
|
||||
*
|
||||
* @return bool TRUE if the collection contains $element, FALSE otherwise.
|
||||
*/
|
||||
public function contains(mixed $element): bool
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ abstract class AssociationMapping implements ArrayAccess
|
||||
/**
|
||||
* The fetching strategy to use for the association, usually defaults to FETCH_LAZY.
|
||||
*
|
||||
* @var ClassMetadata::FETCH_*
|
||||
* @var ClassMetadata::FETCH_*|null
|
||||
*/
|
||||
public int|null $fetch = null;
|
||||
|
||||
@@ -96,13 +96,26 @@ abstract class AssociationMapping implements ArrayAccess
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $mappingArray
|
||||
* @psalm-param array{
|
||||
* fieldName: string,
|
||||
* sourceEntity: class-string,
|
||||
* targetEntity: class-string,
|
||||
* cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>,
|
||||
* fetch?: ClassMetadata::FETCH_*|null,
|
||||
* inherited?: class-string|null,
|
||||
* declared?: class-string|null,
|
||||
* cache?: array<mixed>|null,
|
||||
* id?: bool|null,
|
||||
* isOnDeleteCascade?: bool|null,
|
||||
* originalClass?: class-string|null,
|
||||
* originalField?: string|null,
|
||||
* orphanRemoval?: bool,
|
||||
* unique?: bool|null,
|
||||
* joinTable?: mixed[]|null,
|
||||
* type?: int,
|
||||
* isOwningSide: bool, ...} $mappingArray
|
||||
* isOwningSide: bool,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): static
|
||||
{
|
||||
|
||||
@@ -1152,7 +1152,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
* fieldName?: string,
|
||||
* columnName?: string,
|
||||
* id?: bool,
|
||||
* generated?: int,
|
||||
* generated?: self::GENERATED_*,
|
||||
* enumType?: class-string,
|
||||
* } $mapping The field mapping to validate & complete.
|
||||
*
|
||||
|
||||
@@ -390,7 +390,7 @@ class AttributeDriver implements MappingDriver
|
||||
$metadata->mapOneToMany($mapping);
|
||||
} elseif ($manyToOneAttribute !== null) {
|
||||
if ($metadata->isEmbeddedClass) {
|
||||
throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class);
|
||||
throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ManyToOne::class);
|
||||
}
|
||||
|
||||
$idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class);
|
||||
|
||||
@@ -38,6 +38,8 @@ use function strtoupper;
|
||||
* XmlDriver is a metadata driver that enables mapping through XML files.
|
||||
*
|
||||
* @link www.doctrine-project.org
|
||||
*
|
||||
* @template-extends FileDriver<SimpleXMLElement>
|
||||
*/
|
||||
class XmlDriver extends FileDriver
|
||||
{
|
||||
@@ -78,7 +80,6 @@ class XmlDriver extends FileDriver
|
||||
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
|
||||
{
|
||||
$xmlRoot = $this->getElement($className);
|
||||
assert($xmlRoot instanceof SimpleXMLElement);
|
||||
|
||||
if ($xmlRoot->getName() === 'entity') {
|
||||
if (isset($xmlRoot['repository-class'])) {
|
||||
@@ -134,6 +135,7 @@ class XmlDriver extends FileDriver
|
||||
];
|
||||
|
||||
if (isset($discrColumn['options'])) {
|
||||
assert($discrColumn['options'] instanceof SimpleXMLElement);
|
||||
$columnDef['options'] = $this->parseOptions($discrColumn['options']->children());
|
||||
}
|
||||
|
||||
@@ -145,6 +147,7 @@ class XmlDriver extends FileDriver
|
||||
// Evaluate <discriminator-map...>
|
||||
if (isset($xmlRoot->{'discriminator-map'})) {
|
||||
$map = [];
|
||||
assert($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} instanceof SimpleXMLElement);
|
||||
foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
|
||||
$map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
|
||||
}
|
||||
|
||||
@@ -49,10 +49,12 @@ final class EmbeddedClassMapping implements ArrayAccess
|
||||
|
||||
/**
|
||||
* @psalm-param array{
|
||||
* class: class-string,
|
||||
* columnPrefix?: false|string|null,
|
||||
* declaredField?: string|null,
|
||||
* originalField?: string|null
|
||||
* class: class-string,
|
||||
* columnPrefix?: false|string|null,
|
||||
* declaredField?: string|null,
|
||||
* originalField?: string|null,
|
||||
* inherited?: class-string|null,
|
||||
* declared?: class-string|null,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): self
|
||||
|
||||
@@ -26,7 +26,7 @@ final class FieldMapping implements ArrayAccess
|
||||
public bool|null $notInsertable = null;
|
||||
public bool|null $notUpdatable = null;
|
||||
public string|null $columnDefinition = null;
|
||||
/** @psalm-var ClassMetadata::GENERATED_* */
|
||||
/** @psalm-var ClassMetadata::GENERATED_*|null */
|
||||
public int|null $generated = null;
|
||||
/** @var class-string<BackedEnum>|null */
|
||||
public string|null $enumType = null;
|
||||
@@ -83,7 +83,34 @@ final class FieldMapping implements ArrayAccess
|
||||
) {
|
||||
}
|
||||
|
||||
/** @param array{type: string, fieldName: string, columnName: string} $mappingArray */
|
||||
/**
|
||||
* @param array<string, mixed> $mappingArray
|
||||
* @psalm-param array{
|
||||
* type: string,
|
||||
* fieldName: string,
|
||||
* columnName: string,
|
||||
* length?: int|null,
|
||||
* id?: bool|null,
|
||||
* nullable?: bool|null,
|
||||
* notInsertable?: bool|null,
|
||||
* notUpdatable?: bool|null,
|
||||
* columnDefinition?: string|null,
|
||||
* generated?: ClassMetadata::GENERATED_*|null,
|
||||
* enumType?: string|null,
|
||||
* precision?: int|null,
|
||||
* scale?: int|null,
|
||||
* unique?: bool|null,
|
||||
* inherited?: string|null,
|
||||
* originalClass?: string|null,
|
||||
* originalField?: string|null,
|
||||
* quoted?: bool|null,
|
||||
* declared?: string|null,
|
||||
* declaredField?: string|null,
|
||||
* options?: array<string, mixed>|null,
|
||||
* version?: bool|null,
|
||||
* default?: string|int|null,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): self
|
||||
{
|
||||
$mapping = new self(
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
@@ -31,7 +31,17 @@ final class JoinColumnMapping implements ArrayAccess
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $mappingArray
|
||||
* @psalm-param array{name: string, referencedColumnName: string, ...} $mappingArray
|
||||
* @psalm-param array{
|
||||
* name: string,
|
||||
* referencedColumnName: string,
|
||||
* unique?: bool|null,
|
||||
* quoted?: bool|null,
|
||||
* fieldName?: string|null,
|
||||
* onDelete?: string|null,
|
||||
* columnDefinition?: string|null,
|
||||
* nullable?: bool|null,
|
||||
* options?: array<string, mixed>|null,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): self
|
||||
{
|
||||
|
||||
@@ -35,10 +35,10 @@ final class JoinTableMapping implements ArrayAccess
|
||||
* @param mixed[] $mappingArray
|
||||
* @psalm-param array{
|
||||
* name: string,
|
||||
* quoted?: bool,
|
||||
* quoted?: bool|null,
|
||||
* joinColumns?: mixed[],
|
||||
* inverseJoinColumns?: mixed[],
|
||||
* schema?: string,
|
||||
* schema?: string|null,
|
||||
* options?: array<string, mixed>
|
||||
* } $mappingArray
|
||||
*/
|
||||
|
||||
@@ -41,9 +41,21 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
|
||||
* fieldName: string,
|
||||
* sourceEntity: class-string,
|
||||
* targetEntity: class-string,
|
||||
* cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>,
|
||||
* fetch?: ClassMetadata::FETCH_*|null,
|
||||
* inherited?: class-string|null,
|
||||
* declared?: class-string|null,
|
||||
* cache?: array<mixed>|null,
|
||||
* id?: bool|null,
|
||||
* isOnDeleteCascade?: bool|null,
|
||||
* originalClass?: class-string|null,
|
||||
* originalField?: string|null,
|
||||
* orphanRemoval?: bool,
|
||||
* unique?: bool|null,
|
||||
* joinTable?: mixed[]|null,
|
||||
* type?: int,
|
||||
* isOwningSide: bool, ...} $mappingArray
|
||||
* isOwningSide: bool,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArrayAndNamingStrategy(array $mappingArray, NamingStrategy $namingStrategy): self
|
||||
{
|
||||
|
||||
@@ -12,9 +12,21 @@ final class OneToManyAssociationMapping extends ToManyInverseSideMapping
|
||||
* fieldName: string,
|
||||
* sourceEntity: class-string,
|
||||
* targetEntity: class-string,
|
||||
* cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>,
|
||||
* fetch?: ClassMetadata::FETCH_*|null,
|
||||
* inherited?: class-string|null,
|
||||
* declared?: class-string|null,
|
||||
* cache?: array<mixed>|null,
|
||||
* id?: bool|null,
|
||||
* isOnDeleteCascade?: bool|null,
|
||||
* originalClass?: class-string|null,
|
||||
* originalField?: string|null,
|
||||
* orphanRemoval?: bool,
|
||||
* unique?: bool|null,
|
||||
* joinTable?: mixed[]|null,
|
||||
* type?: int,
|
||||
* isOwningSide: bool, ...} $mappingArray
|
||||
* isOwningSide: bool,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): static
|
||||
{
|
||||
@@ -33,9 +45,21 @@ final class OneToManyAssociationMapping extends ToManyInverseSideMapping
|
||||
* fieldName: string,
|
||||
* sourceEntity: class-string,
|
||||
* targetEntity: class-string,
|
||||
* cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>,
|
||||
* fetch?: ClassMetadata::FETCH_*|null,
|
||||
* inherited?: class-string|null,
|
||||
* declared?: class-string|null,
|
||||
* cache?: array<mixed>|null,
|
||||
* id?: bool|null,
|
||||
* isOnDeleteCascade?: bool|null,
|
||||
* originalClass?: class-string|null,
|
||||
* originalField?: string|null,
|
||||
* orphanRemoval?: bool,
|
||||
* unique?: bool|null,
|
||||
* joinTable?: mixed[]|null,
|
||||
* type?: int,
|
||||
* isOwningSide: bool, ...} $mappingArray
|
||||
* isOwningSide: bool,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArrayAndName(array $mappingArray, string $name): static
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class Table implements MappingAttribute
|
||||
@@ -21,5 +22,24 @@ final class Table implements MappingAttribute
|
||||
public readonly array|null $uniqueConstraints = null,
|
||||
public readonly array $options = [],
|
||||
) {
|
||||
if ($this->indexes !== null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11357',
|
||||
'Providing the property $indexes on %s does not have any effect and will be removed in Doctrine ORM 4.0. Please use the %s attribute instead.',
|
||||
self::class,
|
||||
Index::class,
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->uniqueConstraints !== null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11357',
|
||||
'Providing the property $uniqueConstraints on %s does not have any effect and will be removed in Doctrine ORM 4.0. Please use the %s attribute instead.',
|
||||
self::class,
|
||||
UniqueConstraint::class,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,21 @@ abstract class ToOneInverseSideMapping extends InverseSideMapping
|
||||
* fieldName: string,
|
||||
* sourceEntity: class-string,
|
||||
* targetEntity: class-string,
|
||||
* cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>,
|
||||
* fetch?: ClassMetadata::FETCH_*|null,
|
||||
* inherited?: class-string|null,
|
||||
* declared?: class-string|null,
|
||||
* cache?: array<mixed>|null,
|
||||
* id?: bool|null,
|
||||
* isOnDeleteCascade?: bool|null,
|
||||
* originalClass?: class-string|null,
|
||||
* originalField?: string|null,
|
||||
* orphanRemoval?: bool,
|
||||
* unique?: bool|null,
|
||||
* joinTable?: mixed[]|null,
|
||||
* type?: int,
|
||||
* isOwningSide: bool,
|
||||
* } $mappingArray
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArrayAndName(
|
||||
array $mappingArray,
|
||||
|
||||
@@ -31,8 +31,22 @@ abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOne
|
||||
* fieldName: string,
|
||||
* sourceEntity: class-string,
|
||||
* targetEntity: class-string,
|
||||
* cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>,
|
||||
* fetch?: ClassMetadata::FETCH_*|null,
|
||||
* inherited?: class-string|null,
|
||||
* declared?: class-string|null,
|
||||
* cache?: array<mixed>|null,
|
||||
* id?: bool|null,
|
||||
* isOnDeleteCascade?: bool|null,
|
||||
* originalClass?: class-string|null,
|
||||
* originalField?: string|null,
|
||||
* orphanRemoval?: bool,
|
||||
* unique?: bool|null,
|
||||
* joinTable?: mixed[]|null,
|
||||
* type?: int,
|
||||
* isOwningSide: bool,
|
||||
* joinColumns?: mixed[]|null,
|
||||
* isOwningSide: bool, ...} $mappingArray
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): static
|
||||
{
|
||||
@@ -64,8 +78,22 @@ abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOne
|
||||
* fieldName: string,
|
||||
* sourceEntity: class-string,
|
||||
* targetEntity: class-string,
|
||||
* cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>,
|
||||
* fetch?: ClassMetadata::FETCH_*|null,
|
||||
* inherited?: class-string|null,
|
||||
* declared?: class-string|null,
|
||||
* cache?: array<mixed>|null,
|
||||
* id?: bool|null,
|
||||
* isOnDeleteCascade?: bool|null,
|
||||
* originalClass?: class-string|null,
|
||||
* originalField?: string|null,
|
||||
* orphanRemoval?: bool,
|
||||
* unique?: bool|null,
|
||||
* joinTable?: mixed[]|null,
|
||||
* type?: int,
|
||||
* isOwningSide: bool,
|
||||
* joinColumns?: mixed[]|null,
|
||||
* isOwningSide: bool, ...} $mappingArray
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArrayAndName(
|
||||
array $mappingArray,
|
||||
|
||||
@@ -40,7 +40,15 @@ class NativeQuery extends AbstractQuery
|
||||
$types = [];
|
||||
|
||||
foreach ($this->getParameters() as $parameter) {
|
||||
$name = $parameter->getName();
|
||||
$name = $parameter->getName();
|
||||
|
||||
if ($parameter->typeWasSpecified()) {
|
||||
$parameters[$name] = $parameter->getValue();
|
||||
$types[$name] = $parameter->getType();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->processParameterValue($parameter->getValue());
|
||||
$type = $parameter->getValue() === $value
|
||||
? $parameter->getType()
|
||||
|
||||
@@ -350,11 +350,6 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
return parent::containsKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @template TMaybeContained
|
||||
*/
|
||||
public function contains(mixed $element): bool
|
||||
{
|
||||
if (! $this->initialized && $this->getMapping()->fetch === ClassMetadata::FETCH_EXTRA_LAZY) {
|
||||
|
||||
@@ -8,13 +8,18 @@ use BadMethodCallException;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Mapping\OneToManyAssociationMapping;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
|
||||
use function array_fill;
|
||||
use function array_keys;
|
||||
use function array_reverse;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
@@ -146,7 +151,11 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.');
|
||||
}
|
||||
|
||||
/** @throws DBALException */
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws EntityNotFoundException
|
||||
* @throws MappingException
|
||||
*/
|
||||
private function deleteEntityCollection(PersistentCollection $collection): int
|
||||
{
|
||||
$mapping = $this->getMapping($collection);
|
||||
@@ -166,6 +175,16 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
$statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform)
|
||||
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
|
||||
|
||||
if ($targetClass->isInheritanceTypeSingleTable()) {
|
||||
$discriminatorColumn = $targetClass->getDiscriminatorColumn();
|
||||
$discriminatorValues = $targetClass->discriminatorValue ? [$targetClass->discriminatorValue] : array_keys($targetClass->discriminatorMap);
|
||||
$statement .= ' AND ' . $discriminatorColumn->name . ' IN (' . implode(', ', array_fill(0, count($discriminatorValues), '?')) . ')';
|
||||
foreach ($discriminatorValues as $discriminatorValue) {
|
||||
$parameters[] = $discriminatorValue;
|
||||
$types[] = $discriminatorColumn->type;
|
||||
}
|
||||
}
|
||||
|
||||
$numAffected = $this->conn->executeStatement($statement, $parameters, $types);
|
||||
|
||||
assert(is_int($numAffected));
|
||||
|
||||
@@ -792,17 +792,42 @@ class BasicEntityPersister implements EntityPersister
|
||||
|
||||
$computedIdentifier = [];
|
||||
|
||||
/** @var array<string,mixed>|null $sourceEntityData */
|
||||
$sourceEntityData = null;
|
||||
|
||||
// TRICKY: since the association is specular source and target are flipped
|
||||
foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
|
||||
if (! isset($sourceClass->fieldNames[$sourceKeyColumn])) {
|
||||
throw MappingException::joinColumnMustPointToMappedField(
|
||||
$sourceClass->name,
|
||||
$sourceKeyColumn,
|
||||
);
|
||||
}
|
||||
// The likely case here is that the column is a join column
|
||||
// in an association mapping. However, there is no guarantee
|
||||
// at this point that a corresponding (generally identifying)
|
||||
// association has been mapped in the source entity. To handle
|
||||
// this case we directly reference the column-keyed data used
|
||||
// to initialize the source entity before throwing an exception.
|
||||
$resolvedSourceData = false;
|
||||
if (! isset($sourceEntityData)) {
|
||||
$sourceEntityData = $this->em->getUnitOfWork()->getOriginalEntityData($sourceEntity);
|
||||
}
|
||||
|
||||
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
|
||||
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
||||
if (isset($sourceEntityData[$sourceKeyColumn])) {
|
||||
$dataValue = $sourceEntityData[$sourceKeyColumn];
|
||||
if ($dataValue !== null) {
|
||||
$resolvedSourceData = true;
|
||||
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
|
||||
$dataValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $resolvedSourceData) {
|
||||
throw MappingException::joinColumnMustPointToMappedField(
|
||||
$sourceClass->name,
|
||||
$sourceKeyColumn,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
|
||||
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
||||
}
|
||||
}
|
||||
|
||||
$targetEntity = $this->load($computedIdentifier, null, $assoc);
|
||||
|
||||
@@ -210,15 +210,14 @@ EOPHP;
|
||||
/**
|
||||
* Creates a closure capable of initializing a proxy
|
||||
*
|
||||
* @return Closure(InternalProxy, InternalProxy):void
|
||||
* @return Closure(InternalProxy, array):void
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
*/
|
||||
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
|
||||
{
|
||||
return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
|
||||
$identifier = $classMetadata->getIdentifierValues($proxy);
|
||||
$original = $entityPersister->loadById($identifier);
|
||||
return static function (InternalProxy $proxy, array $identifier) use ($entityPersister, $classMetadata, $identifierFlattener): void {
|
||||
$original = $entityPersister->loadById($identifier);
|
||||
|
||||
if ($original === null) {
|
||||
throw EntityNotFoundException::fromClassNameAndIdentifier(
|
||||
@@ -234,7 +233,7 @@ EOPHP;
|
||||
$class = $entityPersister->getClassMetadata();
|
||||
|
||||
foreach ($class->getReflectionProperties() as $property) {
|
||||
if (! $property || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
|
||||
if (! $property || isset($identifier[$property->getName()]) || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -283,7 +282,9 @@ EOPHP;
|
||||
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);
|
||||
|
||||
$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {
|
||||
$proxy = self::createLazyGhost($initializer, $skippedProperties);
|
||||
$proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void {
|
||||
$initializer($object, $identifier);
|
||||
}, $skippedProperties);
|
||||
|
||||
foreach ($identifierFields as $idField => $reflector) {
|
||||
if (! isset($identifier[$idField])) {
|
||||
@@ -386,12 +387,18 @@ EOPHP;
|
||||
$code = substr($code, 7 + (int) strpos($code, "\n{"));
|
||||
$code = substr($code, 0, (int) strpos($code, "\n}"));
|
||||
$code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait {
|
||||
initializeLazyObject as __load;
|
||||
initializeLazyObject as private;
|
||||
setLazyObjectAsInitialized as public __setInitialized;
|
||||
isLazyObjectInitialized as private;
|
||||
createLazyGhost as private;
|
||||
resetLazyObject as private;
|
||||
}'), $code);
|
||||
}
|
||||
|
||||
public function __load(): void
|
||||
{
|
||||
$this->initializeLazyObject();
|
||||
}
|
||||
'), $code);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
@@ -635,9 +635,10 @@ class Query extends AbstractQuery
|
||||
/**
|
||||
* Get the current lock mode for this query.
|
||||
*
|
||||
* @return int|null The current lock mode of this query or NULL if no specific lock mode is set.
|
||||
* @return LockMode|int|null The current lock mode of this query or NULL if no specific lock mode is set.
|
||||
* @psalm-return LockMode::*|null
|
||||
*/
|
||||
public function getLockMode(): int|null
|
||||
public function getLockMode(): LockMode|int|null
|
||||
{
|
||||
$lockMode = $this->getHint(self::HINT_LOCK_MODE);
|
||||
|
||||
|
||||
@@ -2563,7 +2563,10 @@ final class Parser
|
||||
return new AST\ParenthesisExpression($expr);
|
||||
}
|
||||
|
||||
assert($this->lexer->lookahead !== null);
|
||||
if ($this->lexer->lookahead === null) {
|
||||
$this->syntaxError('ArithmeticPrimary');
|
||||
}
|
||||
|
||||
switch ($this->lexer->lookahead->type) {
|
||||
case TokenType::T_COALESCE:
|
||||
case TokenType::T_NULLIF:
|
||||
|
||||
@@ -30,6 +30,7 @@ use function count;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_float;
|
||||
use function is_int;
|
||||
use function is_numeric;
|
||||
use function is_string;
|
||||
use function preg_match;
|
||||
@@ -384,7 +385,9 @@ class SqlWalker
|
||||
$values = [];
|
||||
|
||||
if ($class->discriminatorValue !== null) { // discriminators can be 0
|
||||
$values[] = $conn->quote($class->discriminatorValue);
|
||||
$values[] = $class->getDiscriminatorColumn()->type === 'integer' && is_int($class->discriminatorValue)
|
||||
? $class->discriminatorValue
|
||||
: $conn->quote((string) $class->discriminatorValue);
|
||||
}
|
||||
|
||||
foreach ($class->subClasses as $subclassName) {
|
||||
@@ -396,7 +399,9 @@ class SqlWalker
|
||||
continue;
|
||||
}
|
||||
|
||||
$values[] = $conn->quote((string) $subclassMetadata->discriminatorValue);
|
||||
$values[] = $subclassMetadata->getDiscriminatorColumn()->type === 'integer' && is_int($subclassMetadata->discriminatorValue)
|
||||
? $subclassMetadata->discriminatorValue
|
||||
: $conn->quote((string) $subclassMetadata->discriminatorValue);
|
||||
}
|
||||
|
||||
if ($values !== []) {
|
||||
@@ -906,7 +911,9 @@ class SqlWalker
|
||||
}
|
||||
}
|
||||
|
||||
if ($relation->fetch === ClassMetadata::FETCH_EAGER && $condExpr !== null) {
|
||||
$fetchMode = $this->query->getHint('fetchMode')[$assoc->sourceEntity][$assoc->fieldName] ?? $relation->fetch;
|
||||
|
||||
if ($fetchMode === ClassMetadata::FETCH_EAGER && $condExpr !== null) {
|
||||
throw QueryException::eagerFetchJoinWithNotAllowed($assoc->sourceEntity, $assoc->fieldName);
|
||||
}
|
||||
|
||||
@@ -2246,8 +2253,10 @@ class SqlWalker
|
||||
$discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($metadata, $this->em);
|
||||
}
|
||||
|
||||
foreach (array_keys($discriminators) as $dis) {
|
||||
$sqlParameterList[] = $this->conn->quote($dis);
|
||||
foreach (array_keys($discriminators) as $discriminatorValue) {
|
||||
$sqlParameterList[] = $rootClass->getDiscriminatorColumn()->type === 'integer' && is_int($discriminatorValue)
|
||||
? $discriminatorValue
|
||||
: $this->conn->quote((string) $discriminatorValue);
|
||||
}
|
||||
|
||||
return '(' . implode(', ', $sqlParameterList) . ')';
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command\SchemaTool;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@@ -28,7 +29,7 @@ class UpdateCommand extends AbstractCommand
|
||||
$this->setName($this->name)
|
||||
->setDescription('Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata')
|
||||
->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on')
|
||||
->addOption('complete', null, InputOption::VALUE_NONE, 'This option is a no-op and will be removed in 4.0')
|
||||
->addOption('complete', null, InputOption::VALUE_NONE, 'This option is a no-op, is deprecated and will be removed in 4.0')
|
||||
->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Dumps the generated SQL statements to the screen (does not execute them).')
|
||||
->addOption('force', 'f', InputOption::VALUE_NONE, 'Causes the generated SQL statements to be physically executed against your database.')
|
||||
->setHelp(<<<'EOT'
|
||||
@@ -75,6 +76,15 @@ EOT);
|
||||
{
|
||||
$notificationUi = $ui->getErrorStyle();
|
||||
|
||||
if ($input->getOption('complete') === true) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11354',
|
||||
'The --complete option is a no-op, is deprecated and will be removed in Doctrine ORM 4.0.',
|
||||
);
|
||||
$notificationUi->warning('The --complete option is a no-op, is deprecated and will be removed in Doctrine ORM 4.0.');
|
||||
}
|
||||
|
||||
$sqls = $schemaTool->getUpdateSchemaSql($metadatas);
|
||||
|
||||
if (empty($sqls)) {
|
||||
|
||||
@@ -52,18 +52,18 @@ class SchemaValidator
|
||||
* It maps built-in Doctrine types to PHP types
|
||||
*/
|
||||
private const BUILTIN_TYPES_MAP = [
|
||||
AsciiStringType::class => 'string',
|
||||
BigIntType::class => 'string',
|
||||
BooleanType::class => 'bool',
|
||||
DecimalType::class => 'string',
|
||||
FloatType::class => 'float',
|
||||
GuidType::class => 'string',
|
||||
IntegerType::class => 'int',
|
||||
JsonType::class => 'array',
|
||||
SimpleArrayType::class => 'array',
|
||||
SmallIntType::class => 'int',
|
||||
StringType::class => 'string',
|
||||
TextType::class => 'string',
|
||||
AsciiStringType::class => ['string'],
|
||||
BigIntType::class => ['int', 'string'],
|
||||
BooleanType::class => ['bool'],
|
||||
DecimalType::class => ['string'],
|
||||
FloatType::class => ['float'],
|
||||
GuidType::class => ['string'],
|
||||
IntegerType::class => ['int'],
|
||||
JsonType::class => ['array'],
|
||||
SimpleArrayType::class => ['array'],
|
||||
SmallIntType::class => ['int'],
|
||||
StringType::class => ['string'],
|
||||
TextType::class => ['string'],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
@@ -353,21 +353,21 @@ class SchemaValidator
|
||||
$propertyType = $propertyType->getName();
|
||||
|
||||
// If the property type is the same as the metadata field type, we are ok
|
||||
if ($propertyType === $metadataFieldType) {
|
||||
if (in_array($propertyType, $metadataFieldType, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_a($propertyType, BackedEnum::class, true)) {
|
||||
$backingType = (string) (new ReflectionEnum($propertyType))->getBackingType();
|
||||
|
||||
if ($metadataFieldType !== $backingType) {
|
||||
if (! in_array($backingType, $metadataFieldType, true)) {
|
||||
return sprintf(
|
||||
"The field '%s#%s' has the property type '%s' with a backing type of '%s' that differs from the metadata field type '%s'.",
|
||||
$class->name,
|
||||
$fieldName,
|
||||
$propertyType,
|
||||
$backingType,
|
||||
$metadataFieldType,
|
||||
implode('|', $metadataFieldType),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -392,7 +392,7 @@ class SchemaValidator
|
||||
) {
|
||||
$backingType = (string) (new ReflectionEnum($fieldMapping->enumType))->getBackingType();
|
||||
|
||||
if ($metadataFieldType === $backingType) {
|
||||
if (in_array($backingType, $metadataFieldType, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ class SchemaValidator
|
||||
$fieldName,
|
||||
$fieldMapping->enumType,
|
||||
$backingType,
|
||||
$metadataFieldType,
|
||||
implode('|', $metadataFieldType),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ class SchemaValidator
|
||||
$class->name,
|
||||
$fieldName,
|
||||
$propertyType,
|
||||
$metadataFieldType,
|
||||
implode('|', $metadataFieldType),
|
||||
$fieldMapping->type,
|
||||
);
|
||||
},
|
||||
@@ -431,8 +431,10 @@ class SchemaValidator
|
||||
/**
|
||||
* The exact DBAL type must be used (no subclasses), since consumers of doctrine/orm may have their own
|
||||
* customization around field types.
|
||||
*
|
||||
* @return list<string>|null
|
||||
*/
|
||||
private function findBuiltInType(Type $type): string|null
|
||||
private function findBuiltInType(Type $type): array|null
|
||||
{
|
||||
$typeName = $type::class;
|
||||
|
||||
|
||||
@@ -1143,6 +1143,8 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$eventsToDispatch = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$this->removeFromIdentityMap($entity);
|
||||
|
||||
$oid = spl_object_id($entity);
|
||||
$class = $this->em->getClassMetadata($entity::class);
|
||||
$persister = $this->getEntityPersister($class->name);
|
||||
@@ -1484,8 +1486,6 @@ class UnitOfWork implements PropertyChangedListener
|
||||
return;
|
||||
}
|
||||
|
||||
$this->removeFromIdentityMap($entity);
|
||||
|
||||
unset($this->entityUpdates[$oid]);
|
||||
|
||||
if (! isset($this->entityDeletions[$oid])) {
|
||||
@@ -2467,10 +2467,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
} else {
|
||||
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
|
||||
}
|
||||
} elseif (
|
||||
$targetClass->containsForeignIdentifier
|
||||
&& in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifier, true)
|
||||
) {
|
||||
} elseif (in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifier, true)) {
|
||||
// the missing key is part of target's entity primary key
|
||||
$associatedId = [];
|
||||
break;
|
||||
@@ -2653,7 +2650,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$entities[] = $collection->getOwner();
|
||||
}
|
||||
|
||||
$found = $this->getEntityPersister($targetEntity)->loadAll([$mappedBy => $entities]);
|
||||
$found = $this->getEntityPersister($targetEntity)->loadAll([$mappedBy => $entities], $mapping->orderBy);
|
||||
|
||||
$targetClass = $this->em->getClassMetadata($targetEntity);
|
||||
$targetProperty = $targetClass->getReflectionProperty($mappedBy);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance;
|
||||
|
||||
use Doctrine\DBAL\Cache\ArrayResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\PDO\SQLite\Driver;
|
||||
use Doctrine\DBAL\Result;
|
||||
|
||||
final class ArrayResultFactory
|
||||
{
|
||||
public static function createFromArray(array $resultSet): Result
|
||||
{
|
||||
return new Result(new ArrayResult($resultSet), new Connection([], new Driver()));
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Performance;
|
||||
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Cache\ArrayResult;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\PDO\SQLite\Driver;
|
||||
@@ -17,6 +16,7 @@ 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\TestUtil;
|
||||
|
||||
use function array_map;
|
||||
@@ -67,7 +67,7 @@ final class EntityManagerFactory
|
||||
/** {@inheritDoc} */
|
||||
public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile|null $qcp = null): Result
|
||||
{
|
||||
return new Result(new ArrayResult([]), $this);
|
||||
return ArrayResultFactory::createWrapperResultFromArray([], $this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\ORM\Internal\Hydration\ArrayHydrator;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Performance\ArrayResultFactory;
|
||||
use Doctrine\Performance\EntityManagerFactory;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
|
||||
@@ -62,7 +62,7 @@ final class MixedQueryFetchJoinArrayHydrationPerformanceBench
|
||||
];
|
||||
}
|
||||
|
||||
$this->result = ArrayResultFactory::createFromArray($resultSet);
|
||||
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
|
||||
$this->hydrator = new ArrayHydrator(EntityManagerFactory::getEntityManager([]));
|
||||
$this->rsm = new ResultSetMapping();
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Performance\ArrayResultFactory;
|
||||
use Doctrine\Performance\EntityManagerFactory;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
@@ -49,7 +49,7 @@ final class MixedQueryFetchJoinFullObjectHydrationPerformanceBench
|
||||
];
|
||||
}
|
||||
|
||||
$this->result = ArrayResultFactory::createFromArray($resultSet);
|
||||
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
|
||||
$this->hydrator = new ObjectHydrator(EntityManagerFactory::getEntityManager([]));
|
||||
$this->rsm = new ResultSetMapping();
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\ORM\Internal\Hydration\ArrayHydrator;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Performance\ArrayResultFactory;
|
||||
use Doctrine\Performance\EntityManagerFactory;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
|
||||
|
||||
@@ -53,7 +53,7 @@ final class SimpleQueryArrayHydrationPerformanceBench
|
||||
];
|
||||
}
|
||||
|
||||
$this->result = ArrayResultFactory::createFromArray($resultSet);
|
||||
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
|
||||
$this->hydrator = new ArrayHydrator(EntityManagerFactory::getEntityManager([]));
|
||||
$this->rsm = new ResultSetMapping();
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Performance\ArrayResultFactory;
|
||||
use Doctrine\Performance\EntityManagerFactory;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
|
||||
@@ -44,7 +44,7 @@ final class SimpleQueryFullObjectHydrationPerformanceBench
|
||||
];
|
||||
}
|
||||
|
||||
$this->result = ArrayResultFactory::createFromArray($resultSet);
|
||||
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
|
||||
$this->hydrator = new ObjectHydrator(EntityManagerFactory::getEntityManager([]));
|
||||
$this->rsm = new ResultSetMapping();
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\ORM\Internal\Hydration\ScalarHydrator;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Performance\ArrayResultFactory;
|
||||
use Doctrine\Performance\EntityManagerFactory;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
|
||||
|
||||
@@ -53,7 +53,7 @@ final class SimpleQueryScalarHydrationPerformanceBench
|
||||
];
|
||||
}
|
||||
|
||||
$this->result = ArrayResultFactory::createFromArray($resultSet);
|
||||
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
|
||||
$this->hydrator = new ScalarHydrator(EntityManagerFactory::getEntityManager([]));
|
||||
$this->rsm = new ResultSetMapping();
|
||||
|
||||
|
||||
42
tests/Tests/Mocks/ArrayResultFactory.php
Normal file
42
tests/Tests/Mocks/ArrayResultFactory.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Mocks;
|
||||
|
||||
use Doctrine\DBAL\Cache\ArrayResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\PDO\SQLite\Driver;
|
||||
use Doctrine\DBAL\Result;
|
||||
use ReflectionMethod;
|
||||
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
|
||||
final class ArrayResultFactory
|
||||
{
|
||||
/** @param list<array<string, mixed>> $resultSet */
|
||||
public static function createDriverResultFromArray(array $resultSet): ArrayResult
|
||||
{
|
||||
if ((new ReflectionMethod(ArrayResult::class, '__construct'))->getNumberOfRequiredParameters() < 2) {
|
||||
// DBAL < 4.2
|
||||
return new ArrayResult($resultSet);
|
||||
}
|
||||
|
||||
// DBAL 4.2+
|
||||
return new ArrayResult(
|
||||
array_keys($resultSet[0] ?? []),
|
||||
array_map(array_values(...), $resultSet),
|
||||
);
|
||||
}
|
||||
|
||||
/** @param list<array<string, mixed>> $resultSet */
|
||||
public static function createWrapperResultFromArray(array $resultSet, Connection|null $connection = null): Result
|
||||
{
|
||||
return new Result(
|
||||
self::createDriverResultFromArray($resultSet),
|
||||
$connection ?? new Connection([], new Driver()),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
tests/Tests/Models/BigIntegers/BigIntegers.php
Normal file
26
tests/Tests/Models/BigIntegers/BigIntegers.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\BigIntegers;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class BigIntegers
|
||||
{
|
||||
#[ORM\Column]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
public int|null $id = null;
|
||||
|
||||
#[ORM\Column(type: Types::BIGINT)]
|
||||
public int $one = 1;
|
||||
|
||||
#[ORM\Column(type: Types::BIGINT)]
|
||||
public string $two = '2';
|
||||
|
||||
#[ORM\Column(type: Types::BIGINT)]
|
||||
public float $three = 3.0;
|
||||
}
|
||||
24
tests/Tests/Models/CompositeKeyRelations/CustomerClass.php
Normal file
24
tests/Tests/Models/CompositeKeyRelations/CustomerClass.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\CompositeKeyRelations;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
|
||||
#[Entity]
|
||||
class CustomerClass
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'string')]
|
||||
public string $companyCode;
|
||||
|
||||
#[Id]
|
||||
#[Column(type: 'string')]
|
||||
public string $code;
|
||||
|
||||
#[Column(type: 'string')]
|
||||
public string $name;
|
||||
}
|
||||
31
tests/Tests/Models/CompositeKeyRelations/InvoiceClass.php
Normal file
31
tests/Tests/Models/CompositeKeyRelations/InvoiceClass.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\CompositeKeyRelations;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
|
||||
#[Entity]
|
||||
class InvoiceClass
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'string')]
|
||||
public string $companyCode;
|
||||
|
||||
#[Id]
|
||||
#[Column(type: 'string')]
|
||||
public string $invoiceNumber;
|
||||
|
||||
#[ManyToOne(targetEntity: CustomerClass::class)]
|
||||
#[JoinColumn(name: 'companyCode', referencedColumnName: 'companyCode')]
|
||||
#[JoinColumn(name: 'customerCode', referencedColumnName: 'code')]
|
||||
public CustomerClass|null $customer;
|
||||
|
||||
#[Column(type: 'string', nullable: true)]
|
||||
public string|null $customerCode = null;
|
||||
}
|
||||
46
tests/Tests/Models/ECommerce/ECommerceProduct2.php
Normal file
46
tests/Tests/Models/ECommerce/ECommerceProduct2.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\ECommerce;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\Index;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
/**
|
||||
* ECommerceProduct2
|
||||
* Resets the id when being cloned.
|
||||
*/
|
||||
#[Entity]
|
||||
#[Table(name: 'ecommerce_products')]
|
||||
#[Index(name: 'name_idx', columns: ['name'])]
|
||||
class ECommerceProduct2
|
||||
{
|
||||
#[Column]
|
||||
#[Id]
|
||||
#[GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
#[Column(length: 50, nullable: true)]
|
||||
private string|null $name = null;
|
||||
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string|null
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->id = null;
|
||||
$this->name = 'Clone of ' . $this->name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad;
|
||||
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
#[Entity]
|
||||
#[Table(name: 'one_to_one_inverse_side_assoc_id_load_inverse')]
|
||||
class InverseSide
|
||||
{
|
||||
/** Associative id (owning identifier) */
|
||||
#[Id]
|
||||
#[OneToOne(targetEntity: InverseSideIdTarget::class, inversedBy: 'inverseSide')]
|
||||
#[JoinColumn(nullable: false, name: 'associativeId')]
|
||||
public InverseSideIdTarget $associativeId;
|
||||
|
||||
#[OneToOne(targetEntity: OwningSide::class, mappedBy: 'inverse')]
|
||||
public OwningSide $owning;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
#[Entity]
|
||||
#[Table(name: 'one_to_one_inverse_side_assoc_id_load_inverse_id_target')]
|
||||
class InverseSideIdTarget
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'string', length: 255)]
|
||||
#[GeneratedValue(strategy: 'NONE')]
|
||||
public string $id;
|
||||
|
||||
#[OneToOne(targetEntity: InverseSide::class, mappedBy: 'associativeId')]
|
||||
public InverseSide $inverseSide;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
#[Entity]
|
||||
#[Table(name: 'one_to_one_inverse_side_assoc_id_load_owning')]
|
||||
class OwningSide
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'string', length: 255)]
|
||||
#[GeneratedValue(strategy: 'NONE')]
|
||||
public string $id;
|
||||
|
||||
/** Owning side */
|
||||
#[OneToOne(targetEntity: InverseSide::class, inversedBy: 'owning')]
|
||||
#[JoinColumn(name: 'inverse', referencedColumnName: 'associativeId')]
|
||||
public InverseSide $inverse;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Cache\Persister\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\AbstractEntityPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
|
||||
@@ -97,7 +98,7 @@ abstract class EntityPersisterTestCase extends OrmTestCase
|
||||
->with(
|
||||
self::identicalTo(['name' => 'Foo']),
|
||||
self::identicalTo($associationMapping),
|
||||
self::identicalTo(1),
|
||||
self::identicalTo(LockMode::OPTIMISTIC),
|
||||
self::identicalTo(2),
|
||||
self::identicalTo(3),
|
||||
self::identicalTo([4]),
|
||||
@@ -107,7 +108,7 @@ abstract class EntityPersisterTestCase extends OrmTestCase
|
||||
self::assertSame('SELECT * FROM foo WERE name = ?', $persister->getSelectSQL(
|
||||
['name' => 'Foo'],
|
||||
$associationMapping,
|
||||
1,
|
||||
LockMode::OPTIMISTIC,
|
||||
2,
|
||||
3,
|
||||
[4],
|
||||
@@ -233,13 +234,21 @@ abstract class EntityPersisterTestCase extends OrmTestCase
|
||||
self::identicalTo($entity),
|
||||
self::identicalTo($associationMapping),
|
||||
self::identicalTo([1]),
|
||||
self::identicalTo(2),
|
||||
self::identicalTo(LockMode::PESSIMISTIC_READ),
|
||||
self::identicalTo(3),
|
||||
self::identicalTo([4]),
|
||||
)
|
||||
->willReturn($entity);
|
||||
|
||||
self::assertSame($entity, $persister->load(['id' => 1], $entity, $associationMapping, [1], 2, 3, [4]));
|
||||
self::assertSame($entity, $persister->load(
|
||||
['id' => 1],
|
||||
$entity,
|
||||
$associationMapping,
|
||||
[1],
|
||||
LockMode::PESSIMISTIC_READ,
|
||||
3,
|
||||
[4],
|
||||
));
|
||||
}
|
||||
|
||||
public function testInvokeLoadAll(): void
|
||||
@@ -402,9 +411,9 @@ abstract class EntityPersisterTestCase extends OrmTestCase
|
||||
|
||||
$this->entityPersister->expects(self::once())
|
||||
->method('lock')
|
||||
->with(self::identicalTo($identifier), self::identicalTo(1));
|
||||
->with(self::identicalTo($identifier), self::identicalTo(LockMode::OPTIMISTIC));
|
||||
|
||||
$persister->lock($identifier, 1);
|
||||
$persister->lock($identifier, LockMode::OPTIMISTIC);
|
||||
}
|
||||
|
||||
public function testInvokeExists(): void
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\Tests\ORM;
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Exception\EntityManagerClosed;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
@@ -21,7 +22,9 @@ use Doctrine\Tests\OrmTestCase;
|
||||
use Generator;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use ReflectionProperty;
|
||||
use stdClass;
|
||||
use Symfony\Component\VarExporter\LazyGhostTrait;
|
||||
use TypeError;
|
||||
|
||||
class EntityManagerTest extends OrmTestCase
|
||||
@@ -172,4 +175,36 @@ class EntityManagerTest extends OrmTestCase
|
||||
self::assertFalse($this->entityManager->isOpen());
|
||||
}
|
||||
}
|
||||
|
||||
/** Resetting the EntityManager relies on lazy objects until https://github.com/doctrine/orm/issues/5933 is resolved */
|
||||
public function testLazyGhostEntityManager(): void
|
||||
{
|
||||
$em = new class () extends EntityManager {
|
||||
use LazyGhostTrait;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$em = $em::createLazyGhost(static function ($em): void {
|
||||
$r = new ReflectionProperty(EntityManager::class, 'unitOfWork');
|
||||
$r->setValue($em, new class () extends UnitOfWork {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$this->assertTrue($em->isOpen());
|
||||
$em->close();
|
||||
$this->assertFalse($em->isOpen());
|
||||
|
||||
$em->resetLazyObject();
|
||||
$this->assertTrue($em->isOpen());
|
||||
}
|
||||
}
|
||||
|
||||
61
tests/Tests/ORM/Functional/CompositeKeyRelationsTest.php
Normal file
61
tests/Tests/ORM/Functional/CompositeKeyRelationsTest.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Tests\Models\CompositeKeyRelations\CustomerClass;
|
||||
use Doctrine\Tests\Models\CompositeKeyRelations\InvoiceClass;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class CompositeKeyRelationsTest extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->useModelSet('compositekeyrelations');
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testFindEntityWithNotNullRelation(): void
|
||||
{
|
||||
$this->_em->getConnection()->insert('CustomerClass', [
|
||||
'companyCode' => 'AA',
|
||||
'code' => 'CUST1',
|
||||
'name' => 'Customer 1',
|
||||
]);
|
||||
|
||||
$this->_em->getConnection()->insert('InvoiceClass', [
|
||||
'companyCode' => 'AA',
|
||||
'invoiceNumber' => 'INV1',
|
||||
'customerCode' => 'CUST1',
|
||||
]);
|
||||
|
||||
$entity = $this->findEntity('AA', 'INV1');
|
||||
self::assertSame('AA', $entity->companyCode);
|
||||
self::assertSame('INV1', $entity->invoiceNumber);
|
||||
self::assertInstanceOf(CustomerClass::class, $entity->customer);
|
||||
self::assertSame('Customer 1', $entity->customer->name);
|
||||
}
|
||||
|
||||
public function testFindEntityWithNullRelation(): void
|
||||
{
|
||||
$this->_em->getConnection()->insert('InvoiceClass', [
|
||||
'companyCode' => 'BB',
|
||||
'invoiceNumber' => 'INV1',
|
||||
]);
|
||||
|
||||
$entity = $this->findEntity('BB', 'INV1');
|
||||
self::assertSame('BB', $entity->companyCode);
|
||||
self::assertSame('INV1', $entity->invoiceNumber);
|
||||
self::assertNull($entity->customer);
|
||||
}
|
||||
|
||||
private function findEntity(string $companyCode, string $invoiceNumber): InvoiceClass
|
||||
{
|
||||
return $this->_em->find(
|
||||
InvoiceClass::class,
|
||||
['companyCode' => $companyCode, 'invoiceNumber' => $invoiceNumber],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,14 @@ class EagerFetchCollectionTest extends OrmFunctionalTestCase
|
||||
$query->getResult();
|
||||
}
|
||||
|
||||
public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEager(): void
|
||||
{
|
||||
$query = $this->_em->createQuery('SELECT o, c FROM ' . EagerFetchOwner::class . ' o JOIN o.children c WITH c.id = 1');
|
||||
$query->setFetchMode(EagerFetchChild::class, 'owner', ORM\ClassMetadata::FETCH_LAZY);
|
||||
|
||||
$this->assertIsString($query->getSql());
|
||||
}
|
||||
|
||||
public function testEagerFetchWithIterable(): void
|
||||
{
|
||||
$this->createOwnerWithChildren(2);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad\InverseSide;
|
||||
use Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad\InverseSideIdTarget;
|
||||
use Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad\OwningSide;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function assert;
|
||||
|
||||
class OneToOneInverseSideWithAssociativeIdLoadAfterDqlQueryTest extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createSchemaForModels(OwningSide::class, InverseSideIdTarget::class, InverseSide::class);
|
||||
}
|
||||
|
||||
#[Group('GH-11108')]
|
||||
public function testInverseSideWithAssociativeIdOneToOneLoadedAfterDqlQuery(): void
|
||||
{
|
||||
$owner = new OwningSide();
|
||||
$inverseId = new InverseSideIdTarget();
|
||||
$inverse = new InverseSide();
|
||||
|
||||
$owner->id = 'owner';
|
||||
$inverseId->id = 'inverseId';
|
||||
$inverseId->inverseSide = $inverse;
|
||||
$inverse->associativeId = $inverseId;
|
||||
$owner->inverse = $inverse;
|
||||
$inverse->owning = $owner;
|
||||
|
||||
$this->_em->persist($owner);
|
||||
$this->_em->persist($inverseId);
|
||||
$this->_em->persist($inverse);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$fetchedInverse = $this
|
||||
->_em
|
||||
->createQueryBuilder()
|
||||
->select('inverse')
|
||||
->from(InverseSide::class, 'inverse')
|
||||
->andWhere('inverse.associativeId = :associativeId')
|
||||
->setParameter('associativeId', 'inverseId')
|
||||
->getQuery()
|
||||
->getSingleResult();
|
||||
assert($fetchedInverse instanceof InverseSide);
|
||||
|
||||
self::assertInstanceOf(InverseSide::class, $fetchedInverse);
|
||||
self::assertInstanceOf(InverseSideIdTarget::class, $fetchedInverse->associativeId);
|
||||
self::assertInstanceOf(OwningSide::class, $fetchedInverse->owning);
|
||||
|
||||
$this->assertSQLEquals(
|
||||
'select o0_.associativeid as associativeid_0 from one_to_one_inverse_side_assoc_id_load_inverse o0_ where o0_.associativeid = ?',
|
||||
$this->getLastLoggedQuery(1)['sql'],
|
||||
);
|
||||
|
||||
$this->assertSQLEquals(
|
||||
'select t0.id as id_1, t0.inverse as inverse_2 from one_to_one_inverse_side_assoc_id_load_owning t0 where t0.inverse = ?',
|
||||
$this->getLastLoggedQuery()['sql'],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
public function testPersistUpdate(): void
|
||||
{
|
||||
// Considering case (a)
|
||||
$proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => 123]);
|
||||
$proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => $this->user->getId()]);
|
||||
|
||||
$proxy->id = null;
|
||||
$proxy->username = 'ocra';
|
||||
|
||||
@@ -252,7 +252,6 @@ class QueryDqlFunctionTest extends OrmFunctionalTestCase
|
||||
self::assertEquals(1_600_000, $result[3]['op']);
|
||||
}
|
||||
|
||||
#[Group('test')]
|
||||
public function testOperatorDiv(): void
|
||||
{
|
||||
$result = $this->_em->createQuery('SELECT m, (m.salary/0.5) AS op FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC')
|
||||
|
||||
@@ -9,6 +9,7 @@ use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
|
||||
use Doctrine\ORM\Proxy\InternalProxy;
|
||||
use Doctrine\Tests\Models\Company\CompanyAuction;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceProduct;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceProduct2;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceShipping;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
@@ -112,6 +113,24 @@ class ReferenceProxyTest extends OrmFunctionalTestCase
|
||||
self::assertFalse($entity->isCloned);
|
||||
}
|
||||
|
||||
public function testCloneProxyWithResetId(): void
|
||||
{
|
||||
$id = $this->createProduct();
|
||||
|
||||
$entity = $this->_em->getReference(ECommerceProduct2::class, $id);
|
||||
assert($entity instanceof ECommerceProduct2);
|
||||
|
||||
$clone = clone $entity;
|
||||
assert($clone instanceof ECommerceProduct2);
|
||||
|
||||
self::assertEquals($id, $entity->getId());
|
||||
self::assertEquals('Doctrine Cookbook', $entity->getName());
|
||||
|
||||
self::assertFalse($this->_em->contains($clone));
|
||||
self::assertNull($clone->getId());
|
||||
self::assertEquals('Clone of Doctrine Cookbook', $clone->getName());
|
||||
}
|
||||
|
||||
#[Group('DDC-733')]
|
||||
public function testInitializeProxy(): void
|
||||
{
|
||||
|
||||
79
tests/Tests/ORM/Functional/Ticket/GH10889Test.php
Normal file
79
tests/Tests/ORM/Functional/Ticket/GH10889Test.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
/** @see https://github.com/doctrine/orm/issues/10889 */
|
||||
#[Group('GH10889')]
|
||||
class GH10889Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createSchemaForModels(
|
||||
GH10889Person::class,
|
||||
GH10889Company::class,
|
||||
GH10889Resume::class,
|
||||
);
|
||||
}
|
||||
|
||||
public function testIssue(): void
|
||||
{
|
||||
$person = new GH10889Person();
|
||||
$resume = new GH10889Resume($person, null);
|
||||
|
||||
$this->_em->persist($person);
|
||||
$this->_em->persist($resume);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
/** @var list<GH10889Resume> $resumes */
|
||||
$resumes = $this->_em
|
||||
->getRepository(GH10889Resume::class)
|
||||
->createQueryBuilder('resume')
|
||||
->leftJoin('resume.currentCompany', 'company')->addSelect('company')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
$this->assertArrayHasKey(0, $resumes);
|
||||
$this->assertEquals(1, $resumes[0]->person->id);
|
||||
$this->assertNull($resumes[0]->currentCompany);
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH10889Person
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
#[ORM\GeneratedValue]
|
||||
public int|null $id = null;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH10889Company
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
#[ORM\GeneratedValue]
|
||||
public int|null $id = null;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH10889Resume
|
||||
{
|
||||
public function __construct(
|
||||
#[ORM\Id]
|
||||
#[ORM\OneToOne]
|
||||
public GH10889Person $person,
|
||||
#[ORM\ManyToOne]
|
||||
public GH10889Company|null $currentCompany,
|
||||
) {
|
||||
}
|
||||
}
|
||||
112
tests/Tests/ORM/Functional/Ticket/GH11163Test.php
Normal file
112
tests/Tests/ORM/Functional/Ticket/GH11163Test.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH11163Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
GH11163Bucket::class,
|
||||
GH11163BucketItem::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
$conn = static::$sharedConn;
|
||||
$conn->executeStatement('DELETE FROM GH11163BucketItem');
|
||||
$conn->executeStatement('DELETE FROM GH11163Bucket');
|
||||
}
|
||||
|
||||
public function testFetchEagerModeWithOrderBy(): void
|
||||
{
|
||||
// Load entities into database
|
||||
$this->_em->persist($bucket = new GH11163Bucket(11163));
|
||||
$this->_em->persist(new GH11163BucketItem(1, $bucket, 2));
|
||||
$this->_em->persist(new GH11163BucketItem(2, $bucket, 3));
|
||||
$this->_em->persist(new GH11163BucketItem(3, $bucket, 1));
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
// Fetch entity from database
|
||||
$dql = 'SELECT bucket FROM ' . GH11163Bucket::class . ' bucket WHERE bucket.id = :id';
|
||||
$bucket = $this->_em->createQuery($dql)
|
||||
->setParameter('id', 11163)
|
||||
->getSingleResult();
|
||||
|
||||
// Assert associated entity is loaded eagerly
|
||||
static::assertInstanceOf(GH11163Bucket::class, $bucket);
|
||||
static::assertInstanceOf(PersistentCollection::class, $bucket->items);
|
||||
static::assertTrue($bucket->items->isInitialized());
|
||||
|
||||
static::assertCount(3, $bucket->items);
|
||||
|
||||
// Assert order of entities
|
||||
static::assertSame(1, $bucket->items[0]->position);
|
||||
static::assertSame(3, $bucket->items[0]->id);
|
||||
|
||||
static::assertSame(2, $bucket->items[1]->position);
|
||||
static::assertSame(1, $bucket->items[1]->id);
|
||||
|
||||
static::assertSame(3, $bucket->items[2]->position);
|
||||
static::assertSame(2, $bucket->items[2]->id);
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11163Bucket
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $id;
|
||||
|
||||
/** @var Collection<int, GH11163BucketItem> */
|
||||
#[ORM\OneToMany(
|
||||
targetEntity: GH11163BucketItem::class,
|
||||
mappedBy: 'bucket',
|
||||
fetch: 'EAGER',
|
||||
)]
|
||||
#[ORM\OrderBy(['position' => 'ASC'])]
|
||||
public Collection $items;
|
||||
|
||||
public function __construct(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->items = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11163BucketItem
|
||||
{
|
||||
#[ORM\ManyToOne(targetEntity: GH11163Bucket::class, inversedBy: 'items')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private GH11163Bucket $bucket;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
public int $id;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
public int $position;
|
||||
|
||||
public function __construct(int $id, GH11163Bucket $bucket, int $position)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->bucket = $bucket;
|
||||
$this->position = $position;
|
||||
}
|
||||
}
|
||||
157
tests/Tests/ORM/Functional/Ticket/GH11341Test.php
Normal file
157
tests/Tests/ORM/Functional/Ticket/GH11341Test.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Generator;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
class GH11341Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
IntegerBaseClass::class,
|
||||
IntegerFooEntity::class,
|
||||
IntegerBarEntity::class,
|
||||
StringAsIntBaseClass::class,
|
||||
StringAsIntFooEntity::class,
|
||||
StringAsIntBarEntity::class,
|
||||
StringBaseClass::class,
|
||||
StringFooEntity::class,
|
||||
StringBarEntity::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function dqlStatements(): Generator
|
||||
{
|
||||
yield ['SELECT e FROM ' . IntegerBaseClass::class . ' e', '/WHERE [a-z]0_.type IN \(1, 2\)$/'];
|
||||
yield ['SELECT e FROM ' . IntegerFooEntity::class . ' e', '/WHERE [a-z]0_.type IN \(1\)$/'];
|
||||
yield ['SELECT e FROM ' . IntegerBarEntity::class . ' e', '/WHERE [a-z]0_.type IN \(2\)$/'];
|
||||
yield ['SELECT e FROM ' . StringAsIntBaseClass::class . ' e', '/WHERE [a-z]0_.type IN \(\'1\', \'2\'\)$/'];
|
||||
yield ['SELECT e FROM ' . StringAsIntFooEntity::class . ' e', '/WHERE [a-z]0_.type IN \(\'1\'\)$/'];
|
||||
yield ['SELECT e FROM ' . StringAsIntBarEntity::class . ' e', '/WHERE [a-z]0_.type IN \(\'2\'\)$/'];
|
||||
yield ['SELECT e FROM ' . StringBaseClass::class . ' e', '/WHERE [a-z]0_.type IN \(\'1\', \'2\'\)$/'];
|
||||
yield ['SELECT e FROM ' . StringFooEntity::class . ' e', '/WHERE [a-z]0_.type IN \(\'1\'\)$/'];
|
||||
yield ['SELECT e FROM ' . StringBarEntity::class . ' e', '/WHERE [a-z]0_.type IN \(\'2\'\)$/'];
|
||||
}
|
||||
|
||||
#[DataProvider('dqlStatements')]
|
||||
public function testDiscriminatorValue(string $dql, string $expectedDiscriminatorValues): void
|
||||
{
|
||||
$query = $this->_em->createQuery($dql);
|
||||
$sql = $query->getSQL();
|
||||
|
||||
self::assertMatchesRegularExpression($expectedDiscriminatorValues, $sql);
|
||||
}
|
||||
|
||||
public static function dqlStatementsForInstanceOf(): Generator
|
||||
{
|
||||
yield [IntegerBaseClass::class, IntegerFooEntity::class];
|
||||
yield [StringBaseClass::class, StringFooEntity::class];
|
||||
yield [StringAsIntBaseClass::class, StringAsIntFooEntity::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param class-string $baseClass
|
||||
* @psalm-param class-string $inheritedClass
|
||||
*/
|
||||
#[DataProvider('dqlStatementsForInstanceOf')]
|
||||
public function testInstanceOf(string $baseClass, string $inheritedClass): void
|
||||
{
|
||||
$this->_em->persist(new $inheritedClass());
|
||||
$this->_em->flush();
|
||||
|
||||
$dql = 'SELECT p FROM ' . $baseClass . ' p WHERE p INSTANCE OF ' . $baseClass;
|
||||
|
||||
$query = $this->_em->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(1, $result);
|
||||
self::assertContainsOnlyInstancesOf($baseClass, $result);
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'integer_discriminator')]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorColumn(name: 'type', type: 'integer')]
|
||||
#[ORM\DiscriminatorMap([
|
||||
1 => IntegerFooEntity::class,
|
||||
2 => IntegerBarEntity::class,
|
||||
])]
|
||||
class IntegerBaseClass
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class IntegerFooEntity extends IntegerBaseClass
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class IntegerBarEntity extends IntegerBaseClass
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'string_as_int_discriminator')]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
|
||||
#[ORM\DiscriminatorMap([
|
||||
1 => StringAsIntFooEntity::class,
|
||||
2 => StringAsIntBarEntity::class,
|
||||
])]
|
||||
class StringAsIntBaseClass
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class StringAsIntFooEntity extends StringAsIntBaseClass
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class StringAsIntBarEntity extends StringAsIntBaseClass
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'string_discriminator')]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
|
||||
#[ORM\DiscriminatorMap([
|
||||
'1' => StringFooEntity::class,
|
||||
'2' => StringBarEntity::class,
|
||||
])]
|
||||
class StringBaseClass
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class StringFooEntity extends StringBaseClass
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class StringBarEntity extends StringBaseClass
|
||||
{
|
||||
}
|
||||
34
tests/Tests/ORM/Functional/Ticket/GH11487Test.php
Normal file
34
tests/Tests/ORM/Functional/Ticket/GH11487Test.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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\ORM\Query\QueryException;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH11487Test extends OrmFunctionalTestCase
|
||||
{
|
||||
public function testItThrowsASyntaxErrorOnUnfinishedQuery(): void
|
||||
{
|
||||
$this->expectException(QueryException::class);
|
||||
$this->expectExceptionMessage('Syntax Error');
|
||||
$this->_em->createQuery('UPDATE Doctrine\Tests\ORM\Functional\Ticket\TaxType t SET t.default =')->execute();
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class TaxType
|
||||
{
|
||||
#[Column]
|
||||
#[Id]
|
||||
#[GeneratedValue]
|
||||
public int|null $id = null;
|
||||
|
||||
#[Column]
|
||||
public bool $default = false;
|
||||
}
|
||||
109
tests/Tests/ORM/Functional/Ticket/GH11500Test.php
Normal file
109
tests/Tests/ORM/Functional/Ticket/GH11500Test.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH11500Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
GH11500AbstractTestEntity::class,
|
||||
GH11500TestEntityOne::class,
|
||||
GH11500TestEntityTwo::class,
|
||||
GH11500TestEntityHolder::class,
|
||||
]);
|
||||
}
|
||||
|
||||
/** @throws ORMException */
|
||||
public function testDeleteOneToManyCollectionWithSingleTableInheritance(): void
|
||||
{
|
||||
$testEntityOne = new GH11500TestEntityOne();
|
||||
$testEntityTwo = new GH11500TestEntityTwo();
|
||||
$testEntityHolder = new GH11500TestEntityHolder();
|
||||
|
||||
$testEntityOne->testEntityHolder = $testEntityHolder;
|
||||
$testEntityHolder->testEntityOnes->add($testEntityOne);
|
||||
|
||||
$testEntityTwo->testEntityHolder = $testEntityHolder;
|
||||
$testEntityHolder->testEntityTwos->add($testEntityTwo);
|
||||
|
||||
$em = $this->getEntityManager();
|
||||
$em->persist($testEntityOne);
|
||||
$em->persist($testEntityTwo);
|
||||
$em->persist($testEntityHolder);
|
||||
$em->flush();
|
||||
|
||||
$testEntityTwosBeforeRemovalOfTestEntityOnes = $testEntityHolder->testEntityTwos->toArray();
|
||||
|
||||
$testEntityHolder->testEntityOnes = new ArrayCollection();
|
||||
$em->persist($testEntityHolder);
|
||||
$em->flush();
|
||||
$em->refresh($testEntityHolder);
|
||||
|
||||
static::assertEmpty($testEntityHolder->testEntityOnes->toArray(), 'All records should have been deleted');
|
||||
static::assertEquals($testEntityTwosBeforeRemovalOfTestEntityOnes, $testEntityHolder->testEntityTwos->toArray(), 'Different Entity\'s records should not have been deleted');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'one_to_many_single_table_inheritance_test_entities')]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
|
||||
#[ORM\DiscriminatorMap(['test_entity_one' => 'GH11500TestEntityOne', 'test_entity_two' => 'GH11500TestEntityTwo'])]
|
||||
class GH11500AbstractTestEntity
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
#[ORM\GeneratedValue]
|
||||
public int|null $id = null;
|
||||
}
|
||||
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11500TestEntityOne extends GH11500AbstractTestEntity
|
||||
{
|
||||
#[ORM\ManyToOne(inversedBy:'testEntityOnes')]
|
||||
#[ORM\JoinColumn(name:'test_entity_holder_id', referencedColumnName:'id')]
|
||||
public GH11500TestEntityHolder|null $testEntityHolder = null;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11500TestEntityTwo extends GH11500AbstractTestEntity
|
||||
{
|
||||
#[ORM\ManyToOne(inversedBy:'testEntityTwos')]
|
||||
#[ORM\JoinColumn(name:'test_entity_holder_id', referencedColumnName:'id')]
|
||||
public GH11500TestEntityHolder|null $testEntityHolder = null;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11500TestEntityHolder
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
#[ORM\GeneratedValue]
|
||||
public int|null $id = null;
|
||||
|
||||
#[ORM\OneToMany(targetEntity: 'GH11500TestEntityOne', mappedBy: 'testEntityHolder', orphanRemoval: true)]
|
||||
public Collection $testEntityOnes;
|
||||
|
||||
#[ORM\OneToMany(targetEntity: 'GH11500TestEntityTwo', mappedBy: 'testEntityHolder', orphanRemoval: true)]
|
||||
public Collection $testEntityTwos;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->testEntityOnes = new ArrayCollection();
|
||||
$this->testEntityTwos = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
105
tests/Tests/ORM/Functional/Ticket/GH11501Test.php
Normal file
105
tests/Tests/ORM/Functional/Ticket/GH11501Test.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH11501Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
GH11501AbstractTestEntity::class,
|
||||
GH11501TestEntityOne::class,
|
||||
GH11501TestEntityTwo::class,
|
||||
GH11501TestEntityHolder::class,
|
||||
]);
|
||||
}
|
||||
|
||||
/** @throws ORMException */
|
||||
public function testDeleteOneToManyCollectionWithSingleTableInheritance(): void
|
||||
{
|
||||
$testEntityOne = new GH11501TestEntityOne();
|
||||
$testEntityTwo = new GH11501TestEntityTwo();
|
||||
$testEntityHolder = new GH11501TestEntityHolder();
|
||||
|
||||
$testEntityOne->testEntityHolder = $testEntityHolder;
|
||||
$testEntityHolder->testEntities->add($testEntityOne);
|
||||
|
||||
$testEntityTwo->testEntityHolder = $testEntityHolder;
|
||||
$testEntityHolder->testEntities->add($testEntityTwo);
|
||||
|
||||
$em = $this->getEntityManager();
|
||||
$em->persist($testEntityOne);
|
||||
$em->persist($testEntityTwo);
|
||||
$em->persist($testEntityHolder);
|
||||
$em->flush();
|
||||
|
||||
$testEntityHolder->testEntities = new ArrayCollection();
|
||||
$em->persist($testEntityHolder);
|
||||
$em->flush();
|
||||
$em->refresh($testEntityHolder);
|
||||
|
||||
static::assertEmpty($testEntityHolder->testEntities->toArray(), 'All records should have been deleted');
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'one_to_many_single_table_inheritance_test_entities_parent_join')]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
|
||||
#[ORM\DiscriminatorMap([
|
||||
'test_entity_one' => 'GH11501TestEntityOne',
|
||||
'test_entity_two' => 'GH11501TestEntityTwo',
|
||||
])]
|
||||
class GH11501AbstractTestEntity
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\GeneratedValue]
|
||||
public int $id;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: 'GH11501TestEntityHolder', inversedBy: 'testEntities')]
|
||||
#[ORM\JoinColumn(name: 'test_entity_holder_id', referencedColumnName: 'id')]
|
||||
public GH11501TestEntityHolder $testEntityHolder;
|
||||
}
|
||||
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11501TestEntityOne extends GH11501AbstractTestEntity
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11501TestEntityTwo extends GH11501AbstractTestEntity
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11501TestEntityHolder
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\GeneratedValue]
|
||||
public int $id;
|
||||
|
||||
#[ORM\OneToMany(
|
||||
targetEntity: 'GH11501AbstractTestEntity',
|
||||
mappedBy: 'testEntityHolder',
|
||||
orphanRemoval: true,
|
||||
)]
|
||||
public Collection $testEntities;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->testEntities = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
79
tests/Tests/ORM/Functional/Ticket/GH6123Test.php
Normal file
79
tests/Tests/ORM/Functional/Ticket/GH6123Test.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH6123Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createSchemaForModels(
|
||||
GH6123Entity::class,
|
||||
);
|
||||
}
|
||||
|
||||
public function testLoadingRemovedEntityFromDatabaseDoesNotCreateNewManagedEntityInstance(): void
|
||||
{
|
||||
$entity = new GH6123Entity();
|
||||
$this->_em->persist($entity);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertSame(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($entity));
|
||||
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForDelete($entity));
|
||||
|
||||
$this->_em->remove($entity);
|
||||
|
||||
$freshEntity = $this->loadEntityFromDatabase($entity->id);
|
||||
self::assertSame($entity, $freshEntity);
|
||||
|
||||
self::assertSame(UnitOfWork::STATE_REMOVED, $this->_em->getUnitOfWork()->getEntityState($freshEntity));
|
||||
self::assertTrue($this->_em->getUnitOfWork()->isScheduledForDelete($freshEntity));
|
||||
}
|
||||
|
||||
public function testRemovedEntityCanBePersistedAgain(): void
|
||||
{
|
||||
$entity = new GH6123Entity();
|
||||
$this->_em->persist($entity);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->_em->remove($entity);
|
||||
self::assertSame(UnitOfWork::STATE_REMOVED, $this->_em->getUnitOfWork()->getEntityState($entity));
|
||||
self::assertTrue($this->_em->getUnitOfWork()->isScheduledForDelete($entity));
|
||||
|
||||
$this->loadEntityFromDatabase($entity->id);
|
||||
|
||||
$this->_em->persist($entity);
|
||||
self::assertSame(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($entity));
|
||||
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForDelete($entity));
|
||||
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
private function loadEntityFromDatabase(int $id): GH6123Entity|null
|
||||
{
|
||||
return $this->_em->createQueryBuilder()
|
||||
->select('e')
|
||||
->from(GH6123Entity::class, 'e')
|
||||
->where('e.id = :id')
|
||||
->setParameter('id', $id)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH6123Entity
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
public int $id;
|
||||
}
|
||||
@@ -5,9 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Cache\ArrayResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\DiscriminatorColumn;
|
||||
@@ -19,6 +17,7 @@ use Doctrine\ORM\Mapping\InheritanceType;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\OneToMany;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
@@ -78,7 +77,7 @@ final class GH6362Test extends OrmFunctionalTestCase
|
||||
],
|
||||
];
|
||||
|
||||
$stmt = new Result(new ArrayResult($resultSet), $this->createMock(Connection::class));
|
||||
$stmt = ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->createMock(Connection::class));
|
||||
$hydrator = new ObjectHydrator($this->_em);
|
||||
$result = $hydrator->hydrateAll($stmt, $rsm);
|
||||
|
||||
|
||||
@@ -5,9 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Cache\ArrayResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
@@ -15,6 +13,7 @@ use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
final class GH9807Test extends OrmFunctionalTestCase
|
||||
@@ -63,7 +62,7 @@ final class GH9807Test extends OrmFunctionalTestCase
|
||||
],
|
||||
];
|
||||
|
||||
$stmt = new Result(new ArrayResult($resultSet), $this->createMock(Connection::class));
|
||||
$stmt = ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->createMock(Connection::class));
|
||||
|
||||
/** @var GH9807Main[] $result */
|
||||
$result = $hydrator->hydrateAll($stmt, $rsm);
|
||||
|
||||
@@ -13,6 +13,7 @@ use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Tests\Models\Hydration\SimpleEntity;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
@@ -149,4 +150,33 @@ class AbstractHydratorTest extends OrmFunctionalTestCase
|
||||
$this->expectException(ORMException::class);
|
||||
$this->hydrator->hydrateAll($this->mockResult, $this->mockResultMapping);
|
||||
}
|
||||
|
||||
public function testToIterableIfYieldAndBreakBeforeFinishAlwaysCleansUp(): void
|
||||
{
|
||||
$this->setUpEntitySchema([SimpleEntity::class]);
|
||||
|
||||
$entity1 = new SimpleEntity();
|
||||
$this->_em->persist($entity1);
|
||||
$entity2 = new SimpleEntity();
|
||||
$this->_em->persist($entity2);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$evm = $this->_em->getEventManager();
|
||||
|
||||
$q = $this->_em->createQuery('SELECT e.id FROM ' . SimpleEntity::class . ' e');
|
||||
|
||||
// select two entities, but do no iterate
|
||||
$q->toIterable();
|
||||
self::assertCount(0, $evm->getListeners(Events::onClear));
|
||||
|
||||
// select two entities, but abort after first record
|
||||
foreach ($q->toIterable() as $result) {
|
||||
self::assertCount(1, $evm->getListeners(Events::onClear));
|
||||
break;
|
||||
}
|
||||
|
||||
self::assertCount(0, $evm->getListeners(Events::onClear));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Hydration;
|
||||
|
||||
use Doctrine\DBAL\Cache\ArrayResult;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
|
||||
class HydrationTestCase extends OrmTestCase
|
||||
@@ -22,6 +22,6 @@ class HydrationTestCase extends OrmTestCase
|
||||
|
||||
protected function createResultMock(array $resultSet): Result
|
||||
{
|
||||
return new Result(new ArrayResult($resultSet), $this->entityManager->getConnection());
|
||||
return ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->entityManager->getConnection());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Hydration;
|
||||
|
||||
use Doctrine\DBAL\Cache\ArrayResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\ORM\Internal\Hydration\HydrationException;
|
||||
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -14,6 +12,7 @@ use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Proxy\InternalProxy;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsArticle;
|
||||
use Doctrine\Tests\Models\CMS\CmsComment;
|
||||
@@ -1337,7 +1336,7 @@ class ObjectHydratorTest extends HydrationTestCase
|
||||
$hydrator = new ObjectHydrator($this->entityManager);
|
||||
$rowNum = 0;
|
||||
$iterableResult = $hydrator->toIterable(
|
||||
new Result(new ArrayResult($resultSet), $this->createMock(Connection::class)),
|
||||
ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->createMock(Connection::class)),
|
||||
$rsm,
|
||||
);
|
||||
|
||||
|
||||
28
tests/Tests/ORM/Mapping/TableMappingTest.php
Normal file
28
tests/Tests/ORM/Mapping/TableMappingTest.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class TableMappingTest extends TestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
public function testDeprecationOnIndexesPropertyIsTriggered(): void
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11357');
|
||||
|
||||
new Table(indexes: []);
|
||||
}
|
||||
|
||||
public function testDeprecationOnUniqueConstraintsPropertyIsTriggered(): void
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11357');
|
||||
|
||||
new Table(uniqueConstraints: []);
|
||||
}
|
||||
}
|
||||
42
tests/Tests/ORM/Query/NativeQueryTest.php
Normal file
42
tests/Tests/ORM/Query/NativeQueryTest.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Query;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\Mocks\EntityManagerMock;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
|
||||
class NativeQueryTest extends OrmTestCase
|
||||
{
|
||||
/** @var EntityManagerMock */
|
||||
protected $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->entityManager = $this->getTestEntityManager();
|
||||
}
|
||||
|
||||
public function testValuesAreNotBeingResolvedForSpecifiedParameterTypes(): void
|
||||
{
|
||||
$unitOfWork = $this->createMock(UnitOfWork::class);
|
||||
|
||||
$this->entityManager->setUnitOfWork($unitOfWork);
|
||||
|
||||
$unitOfWork
|
||||
->expects(self::never())
|
||||
->method('getSingleIdentifierValue');
|
||||
|
||||
$rsm = new ResultSetMapping();
|
||||
|
||||
$query = $this->entityManager->createNativeQuery('SELECT d.* FROM date_time_model d WHERE d.datetime = :value', $rsm);
|
||||
|
||||
$query->setParameter('value', new DateTime(), Types::DATETIME_MUTABLE);
|
||||
|
||||
self::assertEmpty($query->getResult());
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,20 @@ use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Cache\ArrayResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Result;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
use Doctrine\Tests\Mocks\EntityManagerMock;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsGroup;
|
||||
@@ -39,8 +42,7 @@ use function array_map;
|
||||
|
||||
class QueryTest extends OrmTestCase
|
||||
{
|
||||
/** @var EntityManagerMock */
|
||||
protected $entityManager;
|
||||
private EntityManagerMock $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -80,6 +82,30 @@ class QueryTest extends OrmTestCase
|
||||
self::assertEquals($parameters, $query->getParameters());
|
||||
}
|
||||
|
||||
/** @psalm-param LockMode::* $lockMode */
|
||||
#[DataProvider('provideLockModes')]
|
||||
public function testSetLockMode(LockMode|int $lockMode): void
|
||||
{
|
||||
$query = $this->entityManager->wrapInTransaction(static function (EntityManagerInterface $em) use ($lockMode): Query {
|
||||
$query = $em->createQuery('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username = ?1');
|
||||
$query->setLockMode($lockMode);
|
||||
|
||||
return $query;
|
||||
});
|
||||
|
||||
self::assertSame($lockMode, $query->getLockMode());
|
||||
self::assertSame($lockMode, $query->getHint(Query::HINT_LOCK_MODE));
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{LockMode::*}> */
|
||||
public static function provideLockModes(): array
|
||||
{
|
||||
return [
|
||||
[LockMode::PESSIMISTIC_READ],
|
||||
[LockMode::PESSIMISTIC_WRITE],
|
||||
];
|
||||
}
|
||||
|
||||
public function testFree(): void
|
||||
{
|
||||
$query = $this->entityManager->createQuery('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username = ?1');
|
||||
@@ -335,10 +361,10 @@ class QueryTest extends OrmTestCase
|
||||
{
|
||||
$entityManager = $this->createTestEntityManagerWithConnection(
|
||||
$this->createConnection(
|
||||
new ArrayResult([
|
||||
ArrayResultFactory::createDriverResultFromArray([
|
||||
['id_0' => 1],
|
||||
]),
|
||||
new ArrayResult([]),
|
||||
ArrayResultFactory::createDriverResultFromArray([]),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -373,14 +399,14 @@ class QueryTest extends OrmTestCase
|
||||
{
|
||||
$entityManager = $this->createTestEntityManagerWithConnection(
|
||||
$this->createConnection(
|
||||
new ArrayResult([
|
||||
ArrayResultFactory::createDriverResultFromArray([
|
||||
['id_0' => 1],
|
||||
]),
|
||||
new ArrayResult([
|
||||
ArrayResultFactory::createDriverResultFromArray([
|
||||
['id_0' => 1],
|
||||
['id_0' => 2],
|
||||
]),
|
||||
new ArrayResult([
|
||||
ArrayResultFactory::createDriverResultFromArray([
|
||||
['id_0' => 1],
|
||||
]),
|
||||
),
|
||||
|
||||
@@ -25,6 +25,7 @@ use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\ORM\Mapping\OrderBy;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
use Doctrine\ORM\Tools\SchemaValidator;
|
||||
use Doctrine\Tests\Models\BigIntegers\BigIntegers;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceCart;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
@@ -228,6 +229,16 @@ class SchemaValidatorTest extends OrmTestCase
|
||||
$ce,
|
||||
);
|
||||
}
|
||||
|
||||
public function testBigIntProperty(): void
|
||||
{
|
||||
$class = $this->em->getClassMetadata(BigIntegers::class);
|
||||
|
||||
self::assertSame(
|
||||
['The field \'Doctrine\Tests\Models\BigIntegers\BigIntegers#three\' has the property type \'float\' that differs from the metadata field type \'int|string\' returned by the \'bigint\' DBAL type.'],
|
||||
$this->validator->validateClass($class),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[MappedSuperclass]
|
||||
|
||||
@@ -288,12 +288,18 @@ class UnitOfWorkTest extends OrmTestCase
|
||||
$entity->id = 123;
|
||||
|
||||
$this->_unitOfWork->registerManaged($entity, ['id' => 123], []);
|
||||
self::assertSame(UnitOfWork::STATE_MANAGED, $this->_unitOfWork->getEntityState($entity));
|
||||
self::assertFalse($this->_unitOfWork->isScheduledForDelete($entity));
|
||||
self::assertTrue($this->_unitOfWork->isInIdentityMap($entity));
|
||||
|
||||
$this->_unitOfWork->remove($entity);
|
||||
self::assertFalse($this->_unitOfWork->isInIdentityMap($entity));
|
||||
self::assertSame(UnitOfWork::STATE_REMOVED, $this->_unitOfWork->getEntityState($entity));
|
||||
self::assertTrue($this->_unitOfWork->isScheduledForDelete($entity));
|
||||
self::assertTrue($this->_unitOfWork->isInIdentityMap($entity));
|
||||
|
||||
$this->_unitOfWork->persist($entity);
|
||||
self::assertSame(UnitOfWork::STATE_MANAGED, $this->_unitOfWork->getEntityState($entity));
|
||||
self::assertFalse($this->_unitOfWork->isScheduledForDelete($entity));
|
||||
self::assertTrue($this->_unitOfWork->isInIdentityMap($entity));
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user