mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
223 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbfe47b07b | ||
|
|
d31aabb40c | ||
|
|
d66884403f | ||
|
|
a90ee5c495 | ||
|
|
11270425e5 | ||
|
|
552eae37a3 | ||
|
|
ee4b03aa78 | ||
|
|
f1246d57c2 | ||
|
|
54c29140fa | ||
|
|
daa99f197b | ||
|
|
2b04cc2e3f | ||
|
|
3d9af3187f | ||
|
|
e83d8a80ba | ||
|
|
c5291b4de8 | ||
|
|
029ca611f0 | ||
|
|
831d86548c | ||
|
|
f26b3b9cf9 | ||
|
|
9e7715f678 | ||
|
|
9ab84f7478 | ||
|
|
e6bb4ef20e | ||
|
|
0e26e3ed50 | ||
|
|
8ca99fdfdc | ||
|
|
2d8e466636 | ||
|
|
94986af284 | ||
|
|
ad5c8e4bdc | ||
|
|
c363f55ad1 | ||
|
|
c973a62272 | ||
|
|
8d3446015a | ||
|
|
4e335f4044 | ||
|
|
bb36d49b38 | ||
|
|
2b81a8e260 | ||
|
|
7d3b3f28e9 | ||
|
|
cbec236e8b | ||
|
|
306963fe79 | ||
|
|
fb4578406f | ||
|
|
bdc41e2b5e | ||
|
|
90376a6431 | ||
|
|
f79d166a4e | ||
|
|
9c22814cfa | ||
|
|
b274893486 | ||
|
|
e0e55dc9c5 | ||
|
|
010b1e0886 | ||
|
|
93eb8a1bcb | ||
|
|
1464827220 | ||
|
|
8709fb38b0 | ||
|
|
cbb6c897de | ||
|
|
e9e60f2fbc | ||
|
|
5f3c1dbab8 | ||
|
|
753bc16c0b | ||
|
|
6090141e0b | ||
|
|
e4a6c041b5 | ||
|
|
c54c557e02 | ||
|
|
46d0865339 | ||
|
|
9c56071392 | ||
|
|
0a1988b349 | ||
|
|
1a5a4c674a | ||
|
|
95795c87a8 | ||
|
|
db6e702088 | ||
|
|
4175edf311 | ||
|
|
67ac5a82da | ||
|
|
e384978e0b | ||
|
|
5ccbc201bf | ||
|
|
d15624f72f | ||
|
|
9d1a4973ae | ||
|
|
55c4845d57 | ||
|
|
a38f473a92 | ||
|
|
40a0964f06 | ||
|
|
08a9e60ed0 | ||
|
|
3e3c023c95 | ||
|
|
5e6d5c06a9 | ||
|
|
1622b7877d | ||
|
|
80aae2796d | ||
|
|
528ef40fc4 | ||
|
|
4b4b9b7b6f | ||
|
|
ae842259f5 | ||
|
|
7178b9d6b7 | ||
|
|
8a14eee67a | ||
|
|
c5315f86fb | ||
|
|
5820bb8f49 | ||
|
|
820a0da4c1 | ||
|
|
fcd02b1ee2 | ||
|
|
b0d07ffaba | ||
|
|
196d3a6996 | ||
|
|
abcad6fa45 | ||
|
|
1b6cf58a1a | ||
|
|
6501890ab5 | ||
|
|
e399d21fb3 | ||
|
|
16f355f0cc | ||
|
|
94d45a036f | ||
|
|
9acca2252f | ||
|
|
716fc97b70 | ||
|
|
a809a71aa6 | ||
|
|
4617a5e310 | ||
|
|
e77c5a3a5e | ||
|
|
c3cc0fdd8c | ||
|
|
bd4449c462 | ||
|
|
e3e96745cc | ||
|
|
12e0cefba1 | ||
|
|
21221f73cc | ||
|
|
ab5e9e393b | ||
|
|
507c73c073 | ||
|
|
ba0ea8953b | ||
|
|
e62571c8f4 | ||
|
|
53763d432b | ||
|
|
154920a0b3 | ||
|
|
b8d0a85017 | ||
|
|
98f9de2af6 | ||
|
|
52a6a21387 | ||
|
|
cb497826be | ||
|
|
ba0d3842a9 | ||
|
|
bf49055a1f | ||
|
|
29e1935c65 | ||
|
|
694413a888 | ||
|
|
33e02b2796 | ||
|
|
26f7588479 | ||
|
|
20a6efdff6 | ||
|
|
83c81f6c41 | ||
|
|
4fc8629414 | ||
|
|
791667a9e4 | ||
|
|
95da667862 | ||
|
|
feb27f00c1 | ||
|
|
c02ddd692f | ||
|
|
151a3fba9d | ||
|
|
b187bc8588 | ||
|
|
1e056842fe | ||
|
|
ebb0c67ecc | ||
|
|
abd9186d00 | ||
|
|
719d007a81 | ||
|
|
08d3f72755 | ||
|
|
3f7a3333ad | ||
|
|
2a8802af12 | ||
|
|
9cc11d2541 | ||
|
|
ee5b2ce5b0 | ||
|
|
d54c9678d0 | ||
|
|
859e6af972 | ||
|
|
8c3c9f115d | ||
|
|
3907872046 | ||
|
|
779781173a | ||
|
|
54cd70002c | ||
|
|
76c4539ffa | ||
|
|
0f8d193512 | ||
|
|
cc314d0fb7 | ||
|
|
2df4d75565 | ||
|
|
dc21ab63ac | ||
|
|
2a250b5814 | ||
|
|
c9c493b2fe | ||
|
|
e6eef1a97d | ||
|
|
8d4718f875 | ||
|
|
44fa5d340a | ||
|
|
708146bbbc | ||
|
|
a5bf9bb96a | ||
|
|
3eace16e85 | ||
|
|
e4c27092cd | ||
|
|
adadf1fb90 | ||
|
|
380b5b62ef | ||
|
|
a0e7a59572 | ||
|
|
fb6c0c1d8b | ||
|
|
fcf1116e33 | ||
|
|
78dc63df27 | ||
|
|
bc5efd4bfe | ||
|
|
c0dfba2ef3 | ||
|
|
0efac09141 | ||
|
|
b6b4cbcb93 | ||
|
|
b1f553eba3 | ||
|
|
0c4aac5a35 | ||
|
|
e0081b59be | ||
|
|
4bd574daee | ||
|
|
efb6cebd41 | ||
|
|
e4769d3191 | ||
|
|
cf408ad9ae | ||
|
|
7c29078051 | ||
|
|
d5ba106803 | ||
|
|
b59189ab48 | ||
|
|
f9a4adc8ab | ||
|
|
401a0c4fe9 | ||
|
|
dba9d72b2d | ||
|
|
fe0647053a | ||
|
|
7b3db4a037 | ||
|
|
6672aaf165 | ||
|
|
aa3b331cae | ||
|
|
e5e3166747 | ||
|
|
3918dcfb42 | ||
|
|
6290747bf9 | ||
|
|
b6f4220493 | ||
|
|
bfb033fe3c | ||
|
|
afbf293c94 | ||
|
|
bf86155dc2 | ||
|
|
1d218bae30 | ||
|
|
9acc70d5b8 | ||
|
|
b7860c782b | ||
|
|
7baef1e120 | ||
|
|
9a24ce5fad | ||
|
|
9fcb8f1305 | ||
|
|
5a40b99e11 | ||
|
|
5049b615c5 | ||
|
|
94144e1227 | ||
|
|
599dd58fe1 | ||
|
|
aff543a4ff | ||
|
|
1854ce2d32 | ||
|
|
b00f0c258e | ||
|
|
13a79b068c | ||
|
|
27c9e9cab3 | ||
|
|
1051817d92 | ||
|
|
40fbbf4429 | ||
|
|
00ed2ca991 | ||
|
|
54b7ad2073 | ||
|
|
517d038e5b | ||
|
|
3db79ebbf3 | ||
|
|
6f98147d09 | ||
|
|
a2faeb9a26 | ||
|
|
3764ebf7a3 | ||
|
|
a7d5adb3ce | ||
|
|
6f507c322a | ||
|
|
54013671a7 | ||
|
|
f5dea25b6c | ||
|
|
7527b788de | ||
|
|
cfadb5499d | ||
|
|
e52bc846f0 | ||
|
|
9ce9ae2818 | ||
|
|
f259754b7c | ||
|
|
3bc2cb6b15 | ||
|
|
fdb9d44538 | ||
|
|
a9fcaf1d18 |
@@ -6,22 +6,52 @@
|
||||
"docsSlug": "doctrine-orm",
|
||||
"versions": [
|
||||
{
|
||||
"name": "3.0",
|
||||
"branchName": "3.0.x",
|
||||
"name": "4.0",
|
||||
"branchName": "4.0.x",
|
||||
"slug": "latest",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.2",
|
||||
"branchName": "3.2.x",
|
||||
"slug": "3.2",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.1",
|
||||
"branchName": "3.1.x",
|
||||
"slug": "3.1",
|
||||
"current": true
|
||||
},
|
||||
{
|
||||
"name": "3.0",
|
||||
"branchName": "3.0.x",
|
||||
"slug": "3.0",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.20",
|
||||
"branchName": "2.20.x",
|
||||
"slug": "2.20",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.19",
|
||||
"branchName": "2.19.x",
|
||||
"slug": "2.19",
|
||||
"maintained": true
|
||||
},
|
||||
{
|
||||
"name": "2.18",
|
||||
"branchName": "2.18.x",
|
||||
"slug": "2.18",
|
||||
"upcoming": true
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.17",
|
||||
"branchName": "2.17.x",
|
||||
"slug": "2.17",
|
||||
"current": true
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.16",
|
||||
@@ -64,42 +94,6 @@
|
||||
"branchName": "2.10.x",
|
||||
"slug": "2.10",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.9",
|
||||
"branchName": "2.9.x",
|
||||
"slug": "2.9",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.8",
|
||||
"branchName": "2.8.x",
|
||||
"slug": "2.8",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.7",
|
||||
"branchName": "2.7",
|
||||
"slug": "2.7",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.6",
|
||||
"branchName": "2.6",
|
||||
"slug": "2.6",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.5",
|
||||
"branchName": "2.5",
|
||||
"slug": "2.5",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.4",
|
||||
"branchName": "2.4",
|
||||
"slug": "2.4",
|
||||
"maintained": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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.0.1"
|
||||
|
||||
26
.github/workflows/continuous-integration.yml
vendored
26
.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 }}"
|
||||
@@ -91,9 +91,9 @@ jobs:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
@@ -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+"
|
||||
|
||||
@@ -164,9 +164,9 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
|
||||
path: "coverage.xml"
|
||||
|
||||
|
||||
@@ -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+"
|
||||
|
||||
@@ -230,7 +230,7 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
@@ -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+"
|
||||
|
||||
@@ -311,7 +311,7 @@ jobs:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage files"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
@@ -332,11 +332,13 @@ jobs:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Download coverage files"
|
||||
uses: "actions/download-artifact@v3"
|
||||
uses: "actions/download-artifact@v4"
|
||||
with:
|
||||
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 }}"
|
||||
|
||||
11
.github/workflows/documentation.yml
vendored
11
.github/workflows/documentation.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
|
||||
- name: "Remove existing composer file"
|
||||
run: "rm composer.json"
|
||||
@@ -36,13 +36,14 @@ jobs:
|
||||
run: "composer require --dev phpdocumentor/guides-cli --no-update"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
with:
|
||||
dependency-versions: "highest"
|
||||
|
||||
- name: "Add dummy title to the sidebar"
|
||||
- name: "Add orphan metadata where needed"
|
||||
run: |
|
||||
printf '%s\n%s\n\n%s\n' "Dummy title" "===========" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
|
||||
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 'Unknown directive' | ( ! grep WARNING )"
|
||||
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )"
|
||||
|
||||
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@3.0.0"
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.0.1"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
|
||||
14
.github/workflows/static-analysis.yml
vendored
14
.github/workflows/static-analysis.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
- src/**
|
||||
- phpstan*
|
||||
- psalm*
|
||||
- tests/Doctrine/StaticAnalysis/**
|
||||
- tests/StaticAnalysis/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
@@ -20,7 +20,7 @@ on:
|
||||
- src/**
|
||||
- phpstan*
|
||||
- psalm*
|
||||
- tests/Doctrine/StaticAnalysis/**
|
||||
- tests/StaticAnalysis/**
|
||||
|
||||
jobs:
|
||||
static-analysis-phpstan:
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
include:
|
||||
- dbal-version: default
|
||||
config: phpstan.neon
|
||||
- dbal-version: 3.7
|
||||
- dbal-version: 3.8.2
|
||||
config: phpstan-dbal3.neon
|
||||
|
||||
steps:
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
coverage: none
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
tools: cs2pr
|
||||
|
||||
- name: Require specific DBAL version
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
matrix:
|
||||
dbal-version:
|
||||
- default
|
||||
- 3.7
|
||||
- 3.8.2
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
coverage: none
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
tools: cs2pr
|
||||
|
||||
- name: Require specific DBAL version
|
||||
@@ -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
|
||||
|
||||
42
README.md
42
README.md
@@ -1,11 +1,11 @@
|
||||
| [3.0.x][3.0] | [2.18.x][2.18] | [2.17.x][2.17] |
|
||||
|:----------------:|:----------------:|:----------:|
|
||||
| [![Build status][3.0 image]][3.0] | [![Build status][2.18 image]][2.18] | [![Build status][2.17 image]][2.17] |
|
||||
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.18 coverage image]][2.18 coverage] | [![Coverage Status][2.17 coverage image]][2.17 coverage] |
|
||||
| [4.0.x][4.0] | [3.2.x][3.2] | [3.1.x][3.1] | [2.20.x][2.20] | [2.19.x][2.19] |
|
||||
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
|
||||
| [![Build status][4.0 image]][4.0] | [![Build status][3.2 image]][3.2] | [![Build status][3.1 image]][3.1] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][3.1 coverage image]][3.1 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] |
|
||||
|
||||
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
|
||||
|
||||
Doctrine ORM is an object-relational mapper for PHP 7.1+ that provides transparent persistence
|
||||
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
|
||||
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
|
||||
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
|
||||
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
|
||||
@@ -18,15 +18,23 @@ without requiring unnecessary code duplication.
|
||||
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html)
|
||||
|
||||
|
||||
[3.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.0.x
|
||||
[3.0]: https://github.com/doctrine/orm/tree/3.0.x
|
||||
[3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg
|
||||
[3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x
|
||||
[2.18 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.18.x
|
||||
[2.18]: https://github.com/doctrine/orm/tree/2.18.x
|
||||
[2.18 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.18.x/graph/badge.svg
|
||||
[2.18 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.18.x
|
||||
[2.17 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.17.x
|
||||
[2.17]: https://github.com/doctrine/orm/tree/2.17.x
|
||||
[2.17 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.17.x/graph/badge.svg
|
||||
[2.17 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.17.x
|
||||
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
|
||||
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
|
||||
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
|
||||
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
|
||||
[3.2 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.2.x
|
||||
[3.2]: https://github.com/doctrine/orm/tree/3.2.x
|
||||
[3.2 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.2.x/graph/badge.svg
|
||||
[3.2 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.2.x
|
||||
[3.1 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.1.x
|
||||
[3.1]: https://github.com/doctrine/orm/tree/3.1.x
|
||||
[3.1 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.1.x/graph/badge.svg
|
||||
[3.1 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.1.x
|
||||
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
|
||||
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
|
||||
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
|
||||
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
|
||||
[2.19 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.19.x
|
||||
[2.19]: https://github.com/doctrine/orm/tree/2.19.x
|
||||
[2.19 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.19.x/graph/badge.svg
|
||||
[2.19 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.19.x
|
||||
|
||||
63
UPGRADE.md
63
UPGRADE.md
@@ -1,5 +1,39 @@
|
||||
# Upgrade to 3.1
|
||||
|
||||
## Deprecate `Doctrine\ORM\Mapping\ReflectionEnumProperty`
|
||||
|
||||
This class is deprecated and will be removed in 4.0.
|
||||
Instead, use `Doctrine\Persistence\Reflection\EnumReflectionProperty` from
|
||||
`doctrine/persistence`.
|
||||
|
||||
## Deprecate passing null to `ClassMetadata::fullyQualifiedClassName()`
|
||||
|
||||
Passing `null` to `Doctrine\ORM\ClassMetadata::fullyQualifiedClassName()` is
|
||||
deprecated and will no longer be possible in 4.0.
|
||||
|
||||
## Deprecate array access
|
||||
|
||||
Using array access on instances of the following classes is deprecated:
|
||||
|
||||
- `Doctrine\ORM\Mapping\DiscriminatorColumnMapping`
|
||||
- `Doctrine\ORM\Mapping\EmbedClassMapping`
|
||||
- `Doctrine\ORM\Mapping\FieldMapping`
|
||||
- `Doctrine\ORM\Mapping\JoinColumnMapping`
|
||||
- `Doctrine\ORM\Mapping\JoinTableMapping`
|
||||
|
||||
# Upgrade to 3.0
|
||||
|
||||
## BC BREAK: Calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association now throws an exception
|
||||
|
||||
Previously, calling
|
||||
`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with
|
||||
the owning side of an association returned `null`, which was undocumented, and
|
||||
wrong according to the phpdoc of the parent method.
|
||||
|
||||
If you do not know whether you are on the owning or inverse side of an association,
|
||||
you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()`
|
||||
to find out.
|
||||
|
||||
## BC BREAK: `Doctrine\ORM\Proxy\Autoloader` no longer extends `Doctrine\Common\Proxy\Autoloader`
|
||||
|
||||
Make sure to use the former when writing a type declaration or an `instanceof` check.
|
||||
@@ -13,9 +47,9 @@ so `$targetEntity` is a first argument now. This change affects only non-named a
|
||||
|
||||
When using the `AUTO` strategy to let Doctrine determine the identity generation mechanism for
|
||||
an entity, and when using `doctrine/dbal` 4, PostgreSQL now uses `IDENTITY`
|
||||
instead of `SEQUENCE`. When upgrading from ORM 2.x and preference is on keeping
|
||||
the `SEQUENCE` based identity generation, then configure the ORM this way:
|
||||
|
||||
instead of `SEQUENCE` or `SERIAL`.
|
||||
* If you want to upgrade your existing tables to identity columns, you will need to follow [migration to identity columns on PostgreSQL](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.0/how-to/postgresql-identity-migration.html)
|
||||
* If you want to keep using SQL sequences, you need to configure the ORM this way:
|
||||
```php
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\ORM\Configuration;
|
||||
@@ -495,8 +529,8 @@ The methods have been replaced by PSR-6 compatible counterparts
|
||||
|
||||
## BC BREAK: Remove `Doctrine\ORM\Configuration::newDefaultAnnotationDriver`
|
||||
|
||||
This functionality has been moved to the new `DoctrineSetup` class. Call
|
||||
`Doctrine\ORM\Tools\DoctrineSetup::createDefaultAnnotationDriver()` to create
|
||||
This functionality has been moved to the new `ORMSetup` class. Call
|
||||
`Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create
|
||||
a new annotation driver.
|
||||
|
||||
## BC BREAK: Remove `Doctrine\ORM\Tools\Setup`
|
||||
@@ -504,7 +538,7 @@ a new annotation driver.
|
||||
In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which
|
||||
accepted a Doctrine Cache instance in each method has been removed.
|
||||
|
||||
The replacement is `Doctrine\ORM\Tools\DoctrineSetup` which accepts a PSR-6
|
||||
The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6
|
||||
cache instead.
|
||||
|
||||
## BC BREAK: Removed named queries
|
||||
@@ -663,6 +697,23 @@ following classes and methods:
|
||||
|
||||
Use `toIterable()` instead.
|
||||
|
||||
# Upgrade to 2.19
|
||||
|
||||
## Deprecate calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association
|
||||
|
||||
Calling
|
||||
`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with
|
||||
the owning side of an association returns `null`, which is undocumented, and
|
||||
wrong according to the phpdoc of the parent method.
|
||||
|
||||
If you do not know whether you are on the owning or inverse side of an association,
|
||||
you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()`
|
||||
to find out.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Query\Lexer::T_*` constants
|
||||
|
||||
Use `Doctrine\ORM\Query\TokenType::T_*` instead.
|
||||
|
||||
# Upgrade to 2.17
|
||||
|
||||
## Deprecate annotations classes for named queries
|
||||
|
||||
@@ -23,29 +23,28 @@
|
||||
"php": "^8.1",
|
||||
"composer-runtime-api": "^2",
|
||||
"ext-ctype": "*",
|
||||
"doctrine/collections": "^2.1",
|
||||
"doctrine/dbal": "^3.6 || ^4",
|
||||
"doctrine/collections": "^2.2",
|
||||
"doctrine/dbal": "^3.8.2 || ^4",
|
||||
"doctrine/deprecations": "^0.5.3 || ^1",
|
||||
"doctrine/event-manager": "^1.2 || ^2",
|
||||
"doctrine/inflector": "^1.4 || ^2.0",
|
||||
"doctrine/instantiator": "^1.3 || ^2",
|
||||
"doctrine/lexer": "^3",
|
||||
"doctrine/persistence": "^3.1.1",
|
||||
"doctrine/persistence": "^3.3.1",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"symfony/console": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/var-exporter": "~6.2.13 || ^6.3.2 || ^7.0"
|
||||
"symfony/var-exporter": "^6.3.9 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12.0",
|
||||
"phpbench/phpbench": "^1.0",
|
||||
"phpstan/phpstan": "1.10.35",
|
||||
"phpstan/phpstan": "1.11.1",
|
||||
"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.16.0"
|
||||
"vimeo/psalm": "5.24.0"
|
||||
},
|
||||
"minimum-stability": "RC",
|
||||
"suggest": {
|
||||
"ext-dom": "Provides support for XSD validation for XML mapping files",
|
||||
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0"
|
||||
|
||||
4
docs/.gitignore
vendored
4
docs/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
en/_exts/configurationblock.pyc
|
||||
build
|
||||
en/_build
|
||||
.idea
|
||||
3
docs/.gitmodules
vendored
3
docs/.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "en/_theme"]
|
||||
path = en/_theme
|
||||
url = https://github.com/doctrine/doctrine-sphinx-theme.git
|
||||
@@ -1,7 +1,7 @@
|
||||
Implementing ArrayAccess for Domain Objects
|
||||
===========================================
|
||||
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
.. sectionauthor:: Roman Borschel <roman@code-factory.org>
|
||||
|
||||
This recipe will show you how to implement ArrayAccess for your
|
||||
domain objects in order to allow more uniform access, for example
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Welcome to Doctrine 2 ORM's documentation!
|
||||
Welcome to Doctrine ORM's documentation!
|
||||
==========================================
|
||||
|
||||
The Doctrine documentation is comprised of tutorials, a reference section and
|
||||
@@ -93,7 +93,7 @@ Tutorials
|
||||
Changelogs
|
||||
----------
|
||||
|
||||
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
|
||||
* `Upgrade <https://github.com/doctrine/orm/blob/HEAD/UPGRADE.md>`_
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -228,50 +228,12 @@ and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
|
||||
Doctrine Mapping Types
|
||||
----------------------
|
||||
|
||||
The ``type`` option used in the ``@Column`` accepts any of the existing
|
||||
Doctrine types or even your own custom types. A Doctrine type defines
|
||||
The ``type`` option used in the ``@Column`` accepts any of the
|
||||
`existing Doctrine DBAL types <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/types.html#reference>`_
|
||||
or :doc:`your own custom mapping types
|
||||
<../cookbook/custom-mapping-types>`. A Doctrine type defines
|
||||
the conversion between PHP and SQL types, independent from the database vendor
|
||||
you are using. All Mapping Types that ship with Doctrine are fully portable
|
||||
between the supported database systems.
|
||||
|
||||
As an example, the Doctrine Mapping Type ``string`` defines the
|
||||
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
|
||||
depending on the RDBMS brand). Here is a quick overview of the
|
||||
built-in mapping types:
|
||||
|
||||
- ``string``: Type that maps a SQL VARCHAR to a PHP string.
|
||||
- ``integer``: Type that maps a SQL INT to a PHP integer.
|
||||
- ``smallint``: Type that maps a database SMALLINT to a PHP
|
||||
integer.
|
||||
- ``bigint``: Type that maps a database BIGINT to a PHP string.
|
||||
- ``boolean``: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
|
||||
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
|
||||
- ``date``: Type that maps a SQL DATETIME to a PHP DateTime
|
||||
object.
|
||||
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
|
||||
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
|
||||
DateTime object.
|
||||
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
|
||||
DateTime object with timezone.
|
||||
- ``text``: Type that maps a SQL CLOB to a PHP string.
|
||||
- ``object``: Type that maps a SQL CLOB to a PHP object using
|
||||
``serialize()`` and ``unserialize()``
|
||||
- ``array``: Type that maps a SQL CLOB to a PHP array using
|
||||
``serialize()`` and ``unserialize()``
|
||||
- ``simple_array``: Type that maps a SQL CLOB to a PHP array using
|
||||
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
|
||||
Only use this type if you are sure that your values cannot contain a ",".
|
||||
- ``json_array``: Type that maps a SQL CLOB to a PHP array using
|
||||
``json_encode()`` and ``json_decode()``
|
||||
- ``float``: Type that maps a SQL Float (Double Precision) to a
|
||||
PHP double. *IMPORTANT*: Works only with locale settings that use
|
||||
decimal points as separator.
|
||||
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
|
||||
varchar but uses a specific type if the platform supports it.
|
||||
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
|
||||
|
||||
A cookbook article shows how to define :doc:`your own custom mapping types
|
||||
<../cookbook/custom-mapping-types>`.
|
||||
you are using.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -377,7 +339,7 @@ Here is the list of possible generation strategies:
|
||||
a new entity is passed to ``EntityManager#persist``. NONE is the
|
||||
same as leaving off the ``#[GeneratedValue]`` entirely.
|
||||
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
|
||||
It will allow you to pass a :ref:`class of your own to generate the identifiers.<attrref_customidgenerator>`
|
||||
It will allow you to pass a :ref:`class of your own to generate the identifiers. <attrref_customidgenerator>`
|
||||
|
||||
Sequence Generator
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -18,14 +18,20 @@ especially what the strategies presented here provide help with.
|
||||
|
||||
.. note::
|
||||
|
||||
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
|
||||
To avoid that you should remove the corresponding middleware.
|
||||
To remove all middlewares, you can use this line:
|
||||
Having an SQL logger enabled when processing batches can have a
|
||||
serious impact on performance and resource usage.
|
||||
To avoid that, you should use a PSR logger implementation that can be
|
||||
disabled at runtime.
|
||||
For example, with Monolog, you can use ``Logger::pushHandler()``
|
||||
to push a ``NullHandler`` to the logger instance, and then pop it
|
||||
when you need to enable logging again.
|
||||
|
||||
With DBAL 2, you can disable the SQL logger like below:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConnection()->getConfiguration()->setMiddlewares([]); // DBAL 3
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null); // DBAL 2
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null);
|
||||
|
||||
Bulk Inserts
|
||||
------------
|
||||
@@ -188,6 +194,3 @@ problems using the following approach:
|
||||
Iterating results is not possible with queries that
|
||||
fetch-join a collection-valued association. The nature of such SQL
|
||||
result sets is not suitable for incremental hydration.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -131,47 +131,47 @@ There are two ways to set up an event handler:
|
||||
* For *all events* you can create a Lifecycle Event Listener or Subscriber class and register
|
||||
it by calling ``$eventManager->addEventListener()`` or ``eventManager->addEventSubscriber()``,
|
||||
see
|
||||
:ref:`Listening and subscribing to Lifecycle Events<listening-and-subscribing-to-lifecycle-events>`
|
||||
:ref:`Listening and subscribing to Lifecycle Events <listening-and-subscribing-to-lifecycle-events>`
|
||||
* For *some events* (see table below), you can create a *Lifecycle Callback* method in the
|
||||
entity, see :ref:`Lifecycle Callbacks<lifecycle-callbacks>`.
|
||||
entity, see :ref:`Lifecycle Callbacks <lifecycle-callbacks>`.
|
||||
|
||||
.. _reference-events-lifecycle-events:
|
||||
|
||||
Events Overview
|
||||
---------------
|
||||
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| Event | Dispatched by | Lifecycle | Passed |
|
||||
| | | Callback | Argument |
|
||||
+=================================================================+=======================+===========+=====================================+
|
||||
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
|
||||
| | on *initial* persist | | |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preUpdate<reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
|
||||
| | metadata | | |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preFlush<reference-events-pre-flush>` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onFlush<reference-events-on-flush>` | ``$em->flush()`` | No | `OnFlushEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postFlush<reference-events-post-flush>` | ``$em->flush()`` | No | `PostFlushEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onClear<reference-events-on-clear>` | ``$em->clear()`` | No | `OnClearEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| Event | Dispatched by | Lifecycle | Passed |
|
||||
| | | Callback | Argument |
|
||||
+==================================================================+=======================+===========+=====================================+
|
||||
| :ref:`preRemove <reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postRemove <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`prePersist <reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
|
||||
| | on *initial* persist | | |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postPersist <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preUpdate <reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postUpdate <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postLoad <reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`loadClassMetadata <reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
|
||||
| | metadata | | |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preFlush <reference-events-pre-flush>` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onFlush <reference-events-on-flush>` | ``$em->flush()`` | No | `OnFlushEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postFlush <reference-events-post-flush>` | ``$em->flush()`` | No | `PostFlushEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onClear <reference-events-on-clear>` | ``$em->clear()`` | No | `OnClearEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -313,7 +313,7 @@ behaviors across different entity classes.
|
||||
|
||||
Note that they require much more detailed knowledge about the inner
|
||||
workings of the ``EntityManager`` and ``UnitOfWork`` classes. Please
|
||||
read the :ref:`Implementing Event Listeners<reference-events-implementing-listeners>` section
|
||||
read the :ref:`Implementing Event Listeners <reference-events-implementing-listeners>` section
|
||||
carefully if you are trying to write your own listener.
|
||||
|
||||
For event subscribers, there are no surprises. They declare the
|
||||
@@ -426,11 +426,11 @@ prePersist
|
||||
There are two ways for the ``prePersist`` event to be triggered:
|
||||
|
||||
- One is when you call ``EntityManager::persist()``. The
|
||||
event is also called for all :ref:`cascaded associations<transitive-persistence>`.
|
||||
event is also called for all :ref:`cascaded associations <transitive-persistence>`.
|
||||
- The other is inside the ``flush()`` method when changes to associations are computed and
|
||||
this association is marked as :ref:`cascade: persist<transitive-persistence>`. Any new entity found
|
||||
this association is marked as :ref:`cascade: persist <transitive-persistence>`. Any new entity found
|
||||
during this operation is also persisted and ``prePersist`` called
|
||||
on it. This is called :ref:`persistence by reachability<persistence-by-reachability>`.
|
||||
on it. This is called :ref:`persistence by reachability <persistence-by-reachability>`.
|
||||
|
||||
In both cases you get passed a ``PrePersistEventArgs`` instance
|
||||
which has access to the entity and the entity manager.
|
||||
@@ -454,7 +454,7 @@ preRemove
|
||||
|
||||
The ``preRemove`` event is called on every entity immediately when it is passed
|
||||
to the ``EntityManager::remove()`` method. It is cascaded for all
|
||||
associations that are marked as :ref:`cascade: remove<transitive-persistence>`
|
||||
associations that are marked as :ref:`cascade: remove <transitive-persistence>`
|
||||
|
||||
It is not called for a DQL ``DELETE`` statement.
|
||||
|
||||
@@ -502,7 +502,7 @@ entities and their associations have been computed. This means, the
|
||||
- Collections scheduled for removal
|
||||
|
||||
To make use of the ``onFlush`` event you have to be familiar with the
|
||||
internal :ref:`UnitOfWork<unit-of-work>` API, which grants you access to the previously
|
||||
internal :ref:`UnitOfWork <unit-of-work>` API, which grants you access to the previously
|
||||
mentioned sets. See this example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -101,7 +101,7 @@ The many-to-many association is only supporting foreign keys in the table defini
|
||||
To work with many-to-many tables containing extra columns you have to use the
|
||||
foreign keys as primary keys feature of Doctrine ORM.
|
||||
|
||||
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
|
||||
See :doc:`the tutorial on composite primary keys for more information <../tutorials/composite-primary-keys>`.
|
||||
|
||||
|
||||
How can i paginate fetch-joined collections?
|
||||
|
||||
@@ -342,7 +342,7 @@ It is not supported to use overrides in entity inheritance scenarios.
|
||||
.. note::
|
||||
|
||||
When using traits, make sure not to miss the warnings given in the
|
||||
:doc:`Limitations and Known Issues</reference/limitations-and-known-issues>` chapter.
|
||||
:doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
|
||||
|
||||
|
||||
Association Override
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
reference/inheritance-mapping
|
||||
reference/working-with-objects
|
||||
reference/working-with-associations
|
||||
reference/typedfieldmapper
|
||||
reference/events
|
||||
reference/unitofwork
|
||||
reference/unitofwork-associations
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -82,9 +82,9 @@ that directory with the following contents:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"doctrine/orm": "^2.11.0",
|
||||
"doctrine/dbal": "^3.2",
|
||||
"symfony/cache": "^5.4"
|
||||
"doctrine/orm": "^3",
|
||||
"doctrine/dbal": "^4",
|
||||
"symfony/cache": "^7"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {"": "src/"}
|
||||
|
||||
@@ -115,21 +115,11 @@ 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
|
||||
path: src/EntityRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata\\:\\:fullyQualifiedClassName\\(\\) should return class\\-string\\|null but returns string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Mapping/ClassMetadata.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always true\\.$#"
|
||||
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,17 +20,13 @@ 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
|
||||
-
|
||||
message: '~^Result of method Doctrine\\DBAL\\Connection::commit\(\) \(void\) is used\.$~'
|
||||
path: src/UnitOfWork.php
|
||||
-
|
||||
message: '~^Strict comparison using === between void and false will always evaluate to false\.$~'
|
||||
message: '~^Strict comparison using === between null and false will always evaluate to false\.$~'
|
||||
path: src/UnitOfWork.php
|
||||
-
|
||||
message: '~^Variable \$e on left side of \?\? always exists and is not nullable\.$~'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -170,6 +170,12 @@
|
||||
<file name="src/Mapping/ClassMetadataFactory.php"/>
|
||||
</errorLevel>
|
||||
</ReferenceConstraintViolation>
|
||||
<RiskyTruthyFalsyComparison>
|
||||
<!-- TODO: Enable this new rule on higher branches. -->
|
||||
<errorLevel type="suppress">
|
||||
<directory name="src" />
|
||||
</errorLevel>
|
||||
</RiskyTruthyFalsyComparison>
|
||||
<TooManyArguments>
|
||||
<errorLevel type="suppress">
|
||||
<!-- Symfony cache supports passing a key prefix to the clear method. -->
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Cache\Persister\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
@@ -201,8 +202,8 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* Generates a string of currently query
|
||||
*
|
||||
* @param string[]|Criteria $criteria
|
||||
* @param string[]|null $orderBy
|
||||
* @param string[]|Criteria $criteria
|
||||
* @param array<string, Order>|null $orderBy
|
||||
*/
|
||||
protected function getHash(
|
||||
string $query,
|
||||
@@ -426,7 +427,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria): array
|
||||
{
|
||||
$orderBy = $criteria->getOrderings();
|
||||
$orderBy = $criteria->orderings();
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->persister->getSelectSQL($criteria);
|
||||
|
||||
@@ -67,7 +67,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
$time = $this->getLockTime($filename);
|
||||
$content = $this->getLockContent($filename);
|
||||
|
||||
if (! $content || ! $time) {
|
||||
if ($content === false || $time === false) {
|
||||
@unlink($filename);
|
||||
|
||||
return false;
|
||||
@@ -156,12 +156,10 @@ class FileLockRegion implements ConcurrentRegion
|
||||
{
|
||||
// The check below is necessary because on some platforms glob returns false
|
||||
// when nothing matched (even though no errors occurred)
|
||||
$filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION));
|
||||
$filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION)) ?: [];
|
||||
|
||||
if ($filenames) {
|
||||
foreach ($filenames as $filename) {
|
||||
@unlink($filename);
|
||||
}
|
||||
foreach ($filenames as $filename) {
|
||||
@unlink($filename);
|
||||
}
|
||||
|
||||
return $this->region->evictAll();
|
||||
@@ -176,7 +174,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
$lock = Lock::createLockRead();
|
||||
$filename = $this->getLockFileName($key);
|
||||
|
||||
if (! @file_put_contents($filename, $lock->value, LOCK_EX)) {
|
||||
if (@file_put_contents($filename, $lock->value, LOCK_EX) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,7 @@ use function method_exists;
|
||||
* The EntityManager is the central access point to ORM functionality.
|
||||
*
|
||||
* It is a facade to all different ORM subsystems such as UnitOfWork,
|
||||
* Query Language and Repository API. Instantiation is done through
|
||||
* the static create() method. The quickest way to obtain a fully
|
||||
* Query Language and Repository API. The quickest way to obtain a fully
|
||||
* configured EntityManager is:
|
||||
*
|
||||
* use Doctrine\ORM\Tools\ORMSetup;
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
}
|
||||
|
||||
// handle fetch-joined owning side bi-directional one-to-one associations
|
||||
if ($assoc->inversedBy) {
|
||||
if ($assoc->inversedBy !== null) {
|
||||
$class = $this->getClassMetadata($className);
|
||||
$inverseAssoc = $class->associationMappings[$assoc->inversedBy];
|
||||
|
||||
@@ -439,7 +439,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
if ($relation->isOwningSide()) {
|
||||
// TODO: Just check hints['fetched'] here?
|
||||
// If there is an inverse mapping on the target class its bidirectional
|
||||
if ($relation->inversedBy) {
|
||||
if ($relation->inversedBy !== null) {
|
||||
$inverseAssoc = $targetClass->associationMappings[$relation->inversedBy];
|
||||
if ($inverseAssoc->isToOne()) {
|
||||
$targetClass->reflFields[$inverseAssoc->fieldName]->setValue($element, $parentObject);
|
||||
|
||||
55
src/Internal/NoUnknownNamedArguments.php
Normal file
55
src/Internal/NoUnknownNamedArguments.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal;
|
||||
|
||||
use BadMethodCallException;
|
||||
|
||||
use function array_filter;
|
||||
use function array_is_list;
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function debug_backtrace;
|
||||
use function implode;
|
||||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
|
||||
/**
|
||||
* Checks if a variadic parameter contains unexpected named arguments.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait NoUnknownNamedArguments
|
||||
{
|
||||
/**
|
||||
* @param TItem[] $parameter
|
||||
*
|
||||
* @template TItem
|
||||
* @psalm-assert list<TItem> $parameter
|
||||
*/
|
||||
private static function validateVariadicParameter(array $parameter): void
|
||||
{
|
||||
if (array_is_list($parameter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
[, $trace] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
assert(isset($trace['class']));
|
||||
|
||||
$additionalArguments = array_values(array_filter(
|
||||
array_keys($parameter),
|
||||
is_string(...),
|
||||
));
|
||||
|
||||
throw new BadMethodCallException(sprintf(
|
||||
'Invalid call to %s::%s(), unknown named arguments: %s',
|
||||
$trace['class'],
|
||||
$trace['function'],
|
||||
implode(', ', $additionalArguments),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use InvalidArgumentException;
|
||||
|
||||
use function property_exists;
|
||||
@@ -14,12 +15,26 @@ trait ArrayAccessImplementation
|
||||
/** @param string $offset */
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11211',
|
||||
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
|
||||
static::class,
|
||||
);
|
||||
|
||||
return isset($this->$offset);
|
||||
}
|
||||
|
||||
/** @param string $offset */
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11211',
|
||||
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
|
||||
static::class,
|
||||
);
|
||||
|
||||
if (! property_exists($this, $offset)) {
|
||||
throw new InvalidArgumentException('Undefined property: ' . $offset);
|
||||
}
|
||||
@@ -30,12 +45,26 @@ trait ArrayAccessImplementation
|
||||
/** @param string $offset */
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11211',
|
||||
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
|
||||
static::class,
|
||||
);
|
||||
|
||||
$this->$offset = $value;
|
||||
}
|
||||
|
||||
/** @param string $offset */
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11211',
|
||||
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
|
||||
static::class,
|
||||
);
|
||||
|
||||
$this->$offset = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -288,7 +288,7 @@ class ClassMetadataBuilder
|
||||
): ClassMetadataBuilder {
|
||||
$builder = $this->createManyToOne($name, $targetEntity);
|
||||
|
||||
if ($inversedBy) {
|
||||
if ($inversedBy !== null) {
|
||||
$builder->inversedBy($inversedBy);
|
||||
}
|
||||
|
||||
@@ -348,7 +348,7 @@ class ClassMetadataBuilder
|
||||
): ClassMetadataBuilder {
|
||||
$builder = $this->createOneToOne($name, $targetEntity);
|
||||
|
||||
if ($inversedBy) {
|
||||
if ($inversedBy !== null) {
|
||||
$builder->inversedBy($inversedBy);
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ class ClassMetadataBuilder
|
||||
): ClassMetadataBuilder {
|
||||
$builder = $this->createManyToMany($name, $targetEntity);
|
||||
|
||||
if ($inversedBy) {
|
||||
if ($inversedBy !== null) {
|
||||
$builder->inversedBy($inversedBy);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,18 +4,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
|
||||
use ReflectionProperty;
|
||||
|
||||
final class ChainTypedFieldMapper implements TypedFieldMapper
|
||||
{
|
||||
/**
|
||||
* @readonly
|
||||
* @var TypedFieldMapper[] $typedFieldMappers
|
||||
*/
|
||||
use NoUnknownNamedArguments;
|
||||
|
||||
/** @var list<TypedFieldMapper> $typedFieldMappers */
|
||||
private readonly array $typedFieldMappers;
|
||||
|
||||
public function __construct(TypedFieldMapper ...$typedFieldMappers)
|
||||
{
|
||||
self::validateVariadicParameter($typedFieldMappers);
|
||||
|
||||
$this->typedFieldMappers = $typedFieldMappers;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Mapping;
|
||||
use BackedEnum;
|
||||
use BadMethodCallException;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\Instantiator\Instantiator;
|
||||
use Doctrine\Instantiator\InstantiatorInterface;
|
||||
use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation;
|
||||
@@ -14,6 +15,7 @@ use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Id\AbstractIdGenerator;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\ReflectionService;
|
||||
use Doctrine\Persistence\Reflection\EnumReflectionProperty;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use ReflectionClass;
|
||||
@@ -41,6 +43,7 @@ use function is_subclass_of;
|
||||
use function ltrim;
|
||||
use function method_exists;
|
||||
use function spl_object_id;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
use function strtolower;
|
||||
@@ -820,7 +823,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
assert($childProperty !== null);
|
||||
|
||||
if (isset($mapping->enumType)) {
|
||||
$childProperty = new ReflectionEnumProperty(
|
||||
$childProperty = new EnumReflectionProperty(
|
||||
$childProperty,
|
||||
$mapping->enumType,
|
||||
);
|
||||
@@ -839,7 +842,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
: $this->getAccessibleProperty($reflService, $this->name, $field);
|
||||
|
||||
if (isset($mapping->enumType) && $this->reflFields[$field] !== null) {
|
||||
$this->reflFields[$field] = new ReflectionEnumProperty(
|
||||
$this->reflFields[$field] = new EnumReflectionProperty(
|
||||
$this->reflFields[$field],
|
||||
$mapping->enumType,
|
||||
);
|
||||
@@ -1149,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.
|
||||
*
|
||||
@@ -2003,6 +2006,12 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
*/
|
||||
public function setCustomRepositoryClass(string|null $repositoryClassName): void
|
||||
{
|
||||
if ($repositoryClassName === null) {
|
||||
$this->customRepositoryClassName = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
|
||||
}
|
||||
|
||||
@@ -2108,13 +2117,12 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
* @param DiscriminatorColumnMapping|mixed[]|null $columnDef
|
||||
* @psalm-param DiscriminatorColumnMapping|array{
|
||||
* name: string|null,
|
||||
* fieldName?: string,
|
||||
* type?: string,
|
||||
* length?: int,
|
||||
* fieldName?: string|null,
|
||||
* type?: string|null,
|
||||
* length?: int|null,
|
||||
* columnDefinition?: string|null,
|
||||
* enumType?: class-string<BackedEnum>|null,
|
||||
* options?:array<string,
|
||||
* mixed>|null
|
||||
* options?: array<string, mixed>|null
|
||||
* }|null $columnDef
|
||||
*
|
||||
* @throws MappingException
|
||||
@@ -2136,13 +2144,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
|
||||
}
|
||||
|
||||
if (! isset($columnDef['fieldName'])) {
|
||||
$columnDef['fieldName'] = $columnDef['name'];
|
||||
}
|
||||
|
||||
if (! isset($columnDef['type'])) {
|
||||
$columnDef['type'] = 'string';
|
||||
}
|
||||
$columnDef['fieldName'] ??= $columnDef['name'];
|
||||
$columnDef['type'] ??= 'string';
|
||||
$columnDef['options'] ??= [];
|
||||
|
||||
if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) {
|
||||
throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
|
||||
@@ -2462,21 +2466,43 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
|
||||
public function getAssociationMappedByTargetField(string $assocName): string
|
||||
{
|
||||
$assoc = $this->associationMappings[$assocName];
|
||||
$assoc = $this->getAssociationMapping($assocName);
|
||||
|
||||
assert($assoc instanceof InverseSideMapping);
|
||||
if (! $assoc instanceof InverseSideMapping) {
|
||||
throw new LogicException(sprintf(
|
||||
<<<'EXCEPTION'
|
||||
Context: Calling %s() with "%s", which is the owning side of an association.
|
||||
Problem: The owning side of an association has no "mappedBy" field.
|
||||
Solution: Call %s::isAssociationInverseSide() to check first.
|
||||
EXCEPTION,
|
||||
__METHOD__,
|
||||
$assocName,
|
||||
self::class,
|
||||
));
|
||||
}
|
||||
|
||||
return $assoc->mappedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null null if the input value is null
|
||||
* @psalm-return class-string|null
|
||||
* @param C $className
|
||||
*
|
||||
* @return string|null null if and only if the input value is null
|
||||
* @psalm-return (C is class-string ? class-string : (C is string ? string : null))
|
||||
*
|
||||
* @template C of string|null
|
||||
*/
|
||||
public function fullyQualifiedClassName(string|null $className): string|null
|
||||
{
|
||||
if (empty($className)) {
|
||||
return $className;
|
||||
if ($className === null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11294',
|
||||
'Passing null to %s is deprecated and will not be supported in Doctrine ORM 4.0',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! str_contains($className, '\\') && $this->namespace) {
|
||||
@@ -2519,12 +2545,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
throw MappingException::missingEmbeddedClass($mapping['fieldName']);
|
||||
}
|
||||
|
||||
$fqcn = $this->fullyQualifiedClassName($mapping['class']);
|
||||
|
||||
assert($fqcn !== null);
|
||||
|
||||
$this->embeddedClasses[$mapping['fieldName']] = EmbeddedClassMapping::fromMappingArray([
|
||||
'class' => $fqcn,
|
||||
'class' => $this->fullyQualifiedClassName($mapping['class']),
|
||||
'columnPrefix' => $mapping['columnPrefix'] ?? null,
|
||||
'declaredField' => $mapping['declaredField'] ?? null,
|
||||
'originalField' => $mapping['originalField'] ?? null,
|
||||
|
||||
@@ -642,7 +642,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
configuration.
|
||||
We currently recommend "SEQUENCE" for "%s", when using DBAL 3,
|
||||
and "IDENTITY" when using DBAL 4,
|
||||
so you should use probably use the following configuration before upgrading to DBAL 4,
|
||||
so you should probably use the following configuration before upgrading to DBAL 4,
|
||||
and remove it after deploying that upgrade:
|
||||
|
||||
$configuration->setIdentityGenerationPreferences([
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use BackedEnum;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
@@ -16,6 +17,7 @@ use ReflectionProperty;
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function enum_exists;
|
||||
use function is_a;
|
||||
|
||||
/** @psalm-type ScalarName = 'array'|'bool'|'float'|'int'|'string' */
|
||||
final class DefaultTypedFieldMapper implements TypedFieldMapper
|
||||
@@ -52,18 +54,18 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
) {
|
||||
if (! $type->isBuiltin() && enum_exists($type->getName())) {
|
||||
$mapping['enumType'] = $type->getName();
|
||||
|
||||
$reflection = new ReflectionEnum($type->getName());
|
||||
if (! $reflection->isBacked()) {
|
||||
throw MappingException::backedEnumTypeRequired(
|
||||
$field->class,
|
||||
$mapping['fieldName'],
|
||||
$mapping['enumType'],
|
||||
$type->getName(),
|
||||
);
|
||||
}
|
||||
|
||||
$type = $reflection->getBackingType();
|
||||
assert(is_a($type->getName(), BackedEnum::class, true));
|
||||
$mapping['enumType'] = $type->getName();
|
||||
$type = $reflection->getBackingType();
|
||||
|
||||
assert($type instanceof ReflectionNamedType);
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@ final class DiscriminatorColumnMapping implements ArrayAccess
|
||||
* type: string,
|
||||
* fieldName: string,
|
||||
* name: string,
|
||||
* length?: int,
|
||||
* columnDefinition?: string,
|
||||
* enumType?: class-string<BackedEnum>,
|
||||
* options?: array<string, mixed>,
|
||||
* length?: int|null,
|
||||
* columnDefinition?: string|null,
|
||||
* enumType?: class-string<BackedEnum>|null,
|
||||
* options?: array<string, mixed>|null,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): self
|
||||
@@ -58,7 +58,7 @@ final class DiscriminatorColumnMapping implements ArrayAccess
|
||||
}
|
||||
|
||||
if (property_exists($mapping, $key)) {
|
||||
$mapping->$key = $value;
|
||||
$mapping->$key = $value ?? $mapping->$key;
|
||||
} else {
|
||||
throw new Exception('Unknown property ' . $key . ' on class ' . static::class);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
@@ -20,6 +21,7 @@ use function assert;
|
||||
use function constant;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function enum_exists;
|
||||
use function explode;
|
||||
use function extension_loaded;
|
||||
use function file_get_contents;
|
||||
@@ -403,9 +405,10 @@ class XmlDriver extends FileDriver
|
||||
if (isset($oneToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
|
||||
/** @psalm-suppress DeprecatedConstant */
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
: Criteria::ASC;
|
||||
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
|
||||
}
|
||||
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
@@ -531,9 +534,10 @@ class XmlDriver extends FileDriver
|
||||
if (isset($manyToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
|
||||
/** @psalm-suppress DeprecatedConstant */
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
: Criteria::ASC;
|
||||
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
|
||||
}
|
||||
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ final class ReflectionEmbeddedProperty extends ReflectionProperty
|
||||
private readonly ReflectionProperty $childProperty,
|
||||
private readonly string $embeddedClass,
|
||||
) {
|
||||
parent::__construct($childProperty->class, $childProperty->name);
|
||||
parent::__construct($childProperty->getDeclaringClass()->name, $childProperty->getName());
|
||||
}
|
||||
|
||||
public function getValue(object|null $object = null): mixed
|
||||
|
||||
@@ -11,6 +11,7 @@ use ValueError;
|
||||
use function array_map;
|
||||
use function is_array;
|
||||
|
||||
/** @deprecated use Doctrine\Persistence\Reflection\EnumReflectionProperty instead */
|
||||
final class ReflectionEnumProperty extends ReflectionProperty
|
||||
{
|
||||
/** @param class-string<BackedEnum> $enumType */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -8,6 +8,7 @@ use Doctrine\Common\Collections\AbstractLazyCollection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -23,6 +24,7 @@ use function array_walk;
|
||||
use function assert;
|
||||
use function is_object;
|
||||
use function spl_object_id;
|
||||
use function strtoupper;
|
||||
|
||||
/**
|
||||
* A PersistentCollection represents a collection of elements that have persistent state.
|
||||
@@ -348,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) {
|
||||
@@ -585,7 +582,12 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
|
||||
$criteria = clone $criteria;
|
||||
$criteria->where($expression);
|
||||
$criteria->orderBy($criteria->getOrderings() ?: $association->orderBy());
|
||||
$criteria->orderBy(
|
||||
$criteria->orderings() ?: array_map(
|
||||
static fn (string $order): Order => Order::from(strtoupper($order)),
|
||||
$association->orderBy(),
|
||||
),
|
||||
);
|
||||
|
||||
$persister = $this->getUnitOfWork()->getEntityPersister($association->targetEntity);
|
||||
|
||||
|
||||
@@ -732,7 +732,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
|
||||
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string
|
||||
{
|
||||
$orderings = $criteria->getOrderings();
|
||||
$orderings = $criteria->orderings();
|
||||
if ($orderings) {
|
||||
$orderBy = [];
|
||||
foreach ($orderings as $name => $direction) {
|
||||
@@ -741,7 +741,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
$targetClass,
|
||||
$this->platform,
|
||||
);
|
||||
$orderBy[] = $field . ' ' . $direction;
|
||||
$orderBy[] = $field . ' ' . $direction->value;
|
||||
}
|
||||
|
||||
return ' ORDER BY ' . implode(', ', $orderBy);
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Persisters\Entity;
|
||||
use BackedEnum;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Expr\Comparison;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
@@ -764,7 +765,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
$targetClass = $this->em->getClassMetadata($assoc->targetEntity);
|
||||
|
||||
if ($assoc->isOwningSide()) {
|
||||
$isInverseSingleValued = $assoc->inversedBy && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy);
|
||||
$isInverseSingleValued = $assoc->inversedBy !== null && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy);
|
||||
|
||||
// Mark inverse side as fetched in the hints, otherwise the UoW would
|
||||
// try to load it in a separate query (remember: to-one inverse sides can not be lazy).
|
||||
@@ -842,7 +843,10 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria): array
|
||||
{
|
||||
$orderBy = $criteria->getOrderings();
|
||||
$orderBy = array_map(
|
||||
static fn (Order $order): string => $order->value,
|
||||
$criteria->orderings(),
|
||||
);
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
|
||||
@@ -1151,7 +1155,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
|
||||
if (isset($this->class->fieldMappings[$fieldName])) {
|
||||
$tableAlias = isset($this->class->fieldMappings[$fieldName]->inherited)
|
||||
? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]['inherited'])
|
||||
? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]->inherited)
|
||||
: $baseTableAlias;
|
||||
|
||||
$columnName = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform);
|
||||
|
||||
@@ -466,7 +466,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|| isset($this->class->associationMappings[$name]->inherited)
|
||||
|| ($this->class->isVersioned && $this->class->versionField === $name)
|
||||
|| isset($this->class->embeddedClasses[$name])
|
||||
|| isset($this->class->fieldMappings[$name]['notInsertable'])
|
||||
|| isset($this->class->fieldMappings[$name]->notInsertable)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -519,9 +519,9 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
$class = null;
|
||||
if ($this->class->isVersioned && $key === $versionedClass->versionField) {
|
||||
$class = $versionedClass;
|
||||
} elseif (isset($column['generated'])) {
|
||||
$class = isset($column['inherited'])
|
||||
? $this->em->getClassMetadata($column['inherited'])
|
||||
} elseif (isset($column->generated)) {
|
||||
$class = isset($column->inherited)
|
||||
? $this->em->getClassMetadata($column->inherited)
|
||||
: $this->class;
|
||||
} else {
|
||||
continue;
|
||||
|
||||
@@ -216,11 +216,11 @@ EOPHP;
|
||||
*/
|
||||
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
|
||||
{
|
||||
return static function (InternalProxy $proxy, InternalProxy $original) use ($entityPersister, $classMetadata, $identifierFlattener): void {
|
||||
$identifier = $classMetadata->getIdentifierValues($original);
|
||||
$entity = $entityPersister->loadById($identifier, $original);
|
||||
return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
|
||||
$identifier = $classMetadata->getIdentifierValues($proxy);
|
||||
$original = $entityPersister->loadById($identifier);
|
||||
|
||||
if ($entity === null) {
|
||||
if ($original === null) {
|
||||
throw EntityNotFoundException::fromClassNameAndIdentifier(
|
||||
$classMetadata->getName(),
|
||||
$identifierFlattener->flattenIdentifier($classMetadata, $identifier),
|
||||
@@ -234,11 +234,11 @@ EOPHP;
|
||||
$class = $entityPersister->getClassMetadata();
|
||||
|
||||
foreach ($class->getReflectionProperties() as $property) {
|
||||
if (! $property || ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
|
||||
if (! $property || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$property->setValue($proxy, $property->getValue($entity));
|
||||
$property->setValue($proxy, $property->getValue($original));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -283,9 +283,7 @@ EOPHP;
|
||||
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);
|
||||
|
||||
$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {
|
||||
$proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
|
||||
$initializer($object, $proxy);
|
||||
}, $skippedProperties);
|
||||
$proxy = self::createLazyGhost($initializer, $skippedProperties);
|
||||
|
||||
foreach ($identifierFields as $idField => $reflector) {
|
||||
if (! isset($identifier[$idField])) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
use Doctrine\ORM\Query\TokenType;
|
||||
|
||||
use function assert;
|
||||
use function is_numeric;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
@@ -63,17 +61,10 @@ class DateAddFunction extends FunctionNode
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return numeric-string
|
||||
*
|
||||
* @throws ASTException
|
||||
*/
|
||||
/** @throws ASTException */
|
||||
private function dispatchIntervalExpression(SqlWalker $sqlWalker): string
|
||||
{
|
||||
$sql = $this->intervalExpression->dispatch($sqlWalker);
|
||||
assert(is_numeric($sql));
|
||||
|
||||
return $sql;
|
||||
return $this->intervalExpression->dispatch($sqlWalker);
|
||||
}
|
||||
|
||||
public function parse(Parser $parser): void
|
||||
|
||||
@@ -8,8 +8,6 @@ use Doctrine\ORM\Query\AST\ASTException;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
use function assert;
|
||||
use function is_numeric;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
@@ -56,16 +54,9 @@ class DateSubFunction extends DateAddFunction
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return numeric-string
|
||||
*
|
||||
* @throws ASTException
|
||||
*/
|
||||
/** @throws ASTException */
|
||||
private function dispatchIntervalExpression(SqlWalker $sqlWalker): string
|
||||
{
|
||||
$sql = $this->intervalExpression->dispatch($sqlWalker);
|
||||
assert(is_numeric($sql));
|
||||
|
||||
return $sql;
|
||||
return $this->intervalExpression->dispatch($sqlWalker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class TrimFunction extends FunctionNode
|
||||
$this->trimChar = $lexer->token->value;
|
||||
}
|
||||
|
||||
if ($this->leading || $this->trailing || $this->both || $this->trimChar) {
|
||||
if ($this->leading || $this->trailing || $this->both || ($this->trimChar !== false)) {
|
||||
$parser->match(TokenType::T_FROM);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
|
||||
use Traversable;
|
||||
|
||||
use function implode;
|
||||
@@ -23,6 +24,8 @@ use function str_replace;
|
||||
*/
|
||||
class Expr
|
||||
{
|
||||
use NoUnknownNamedArguments;
|
||||
|
||||
/**
|
||||
* Creates a conjunction of the given boolean expressions.
|
||||
*
|
||||
@@ -38,6 +41,8 @@ class Expr
|
||||
*/
|
||||
public function andX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Andx
|
||||
{
|
||||
self::validateVariadicParameter($x);
|
||||
|
||||
return new Expr\Andx($x);
|
||||
}
|
||||
|
||||
@@ -56,6 +61,8 @@ class Expr
|
||||
*/
|
||||
public function orX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Orx
|
||||
{
|
||||
self::validateVariadicParameter($x);
|
||||
|
||||
return new Expr\Orx($x);
|
||||
}
|
||||
|
||||
@@ -225,6 +232,8 @@ class Expr
|
||||
*/
|
||||
public function countDistinct(mixed ...$x): string
|
||||
{
|
||||
self::validateVariadicParameter($x);
|
||||
|
||||
return 'COUNT(DISTINCT ' . implode(', ', $x) . ')';
|
||||
}
|
||||
|
||||
@@ -470,6 +479,8 @@ class Expr
|
||||
*/
|
||||
public function concat(mixed ...$x): Expr\Func
|
||||
{
|
||||
self::validateVariadicParameter($x);
|
||||
|
||||
return new Expr\Func('CONCAT', $x);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ namespace Doctrine\ORM\Query\Expr;
|
||||
use InvalidArgumentException;
|
||||
use Stringable;
|
||||
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function get_debug_type;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
@@ -33,6 +35,10 @@ abstract class Base implements Stringable
|
||||
|
||||
public function __construct(mixed $args = [])
|
||||
{
|
||||
if (is_array($args) && array_key_exists(0, $args) && is_array($args[0])) {
|
||||
$args = $args[0];
|
||||
}
|
||||
|
||||
$this->addMultiple($args);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ class ResultSetMappingBuilder extends ResultSetMapping implements Stringable
|
||||
|
||||
$this->addFieldResult($alias, $columnAlias, $propertyName);
|
||||
|
||||
$enumType = $classMetadata->getFieldMapping($propertyName)['enumType'] ?? null;
|
||||
$enumType = $classMetadata->getFieldMapping($propertyName)->enumType ?? null;
|
||||
if (! empty($enumType)) {
|
||||
$this->addEnumResult($columnAlias, $enumType);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -376,22 +377,38 @@ class SqlWalker
|
||||
continue;
|
||||
}
|
||||
|
||||
$conn = $this->em->getConnection();
|
||||
$values = [];
|
||||
|
||||
if ($class->discriminatorValue !== null) { // discriminators can be 0
|
||||
$values[] = $conn->quote($class->discriminatorValue);
|
||||
}
|
||||
|
||||
foreach ($class->subClasses as $subclassName) {
|
||||
$values[] = $conn->quote((string) $this->em->getClassMetadata($subclassName)->discriminatorValue);
|
||||
}
|
||||
|
||||
$sqlTableAlias = $this->useSqlTableAliases
|
||||
? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
|
||||
: '';
|
||||
|
||||
$sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()->name . ' IN (' . implode(', ', $values) . ')';
|
||||
$conn = $this->em->getConnection();
|
||||
$values = [];
|
||||
|
||||
if ($class->discriminatorValue !== null) { // discriminators can be 0
|
||||
$values[] = $class->getDiscriminatorColumn()->type === 'integer' && is_int($class->discriminatorValue)
|
||||
? $class->discriminatorValue
|
||||
: $conn->quote((string) $class->discriminatorValue);
|
||||
}
|
||||
|
||||
foreach ($class->subClasses as $subclassName) {
|
||||
$subclassMetadata = $this->em->getClassMetadata($subclassName);
|
||||
|
||||
// Abstract entity classes show up in the list of subClasses, but may be omitted
|
||||
// from the discriminator map. In that case, they have a null discriminator value.
|
||||
if ($subclassMetadata->discriminatorValue === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$values[] = $subclassMetadata->getDiscriminatorColumn()->type === 'integer' && is_int($subclassMetadata->discriminatorValue)
|
||||
? $subclassMetadata->discriminatorValue
|
||||
: $conn->quote((string) $subclassMetadata->discriminatorValue);
|
||||
}
|
||||
|
||||
if ($values !== []) {
|
||||
$sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()->name . ' IN (' . implode(', ', $values) . ')';
|
||||
} else {
|
||||
$sqlParts[] = '1=0'; // impossible condition
|
||||
}
|
||||
}
|
||||
|
||||
$sql = implode(' AND ', $sqlParts);
|
||||
@@ -786,7 +803,7 @@ class SqlWalker
|
||||
$class = $this->getMetadataForDqlAlias($alias);
|
||||
|
||||
if (isset($class->associationMappings[$fieldName]->inherited)) {
|
||||
$class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
|
||||
$class = $this->em->getClassMetadata($class->associationMappings[$fieldName]->inherited);
|
||||
}
|
||||
|
||||
$association = $class->associationMappings[$fieldName];
|
||||
@@ -2234,8 +2251,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) . ')';
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
|
||||
use Doctrine\ORM\Internal\QueryType;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
@@ -38,6 +41,8 @@ use function substr;
|
||||
*/
|
||||
class QueryBuilder implements Stringable
|
||||
{
|
||||
use NoUnknownNamedArguments;
|
||||
|
||||
/**
|
||||
* The array of DQL parts collected.
|
||||
*
|
||||
@@ -428,12 +433,12 @@ class QueryBuilder implements Stringable
|
||||
* ->setParameter('user_id', 1);
|
||||
* </code>
|
||||
*
|
||||
* @param string|int $key The parameter position or name.
|
||||
* @param string|int|null $type ParameterType::* or \Doctrine\DBAL\Types\Type::* constant
|
||||
* @param string|int $key The parameter position or name.
|
||||
* @param ParameterType|ArrayParameterType|string|int|null $type ParameterType::*, ArrayParameterType::* or \Doctrine\DBAL\Types\Type::* constant
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParameter(string|int $key, mixed $value, string|int|null $type = null): static
|
||||
public function setParameter(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|int|null $type = null): static
|
||||
{
|
||||
$existingParameter = $this->getParameter($key);
|
||||
|
||||
@@ -611,6 +616,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function select(mixed ...$select): static
|
||||
{
|
||||
self::validateVariadicParameter($select);
|
||||
|
||||
$this->type = QueryType::Select;
|
||||
|
||||
if ($select === []) {
|
||||
@@ -657,6 +664,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function addSelect(mixed ...$select): static
|
||||
{
|
||||
self::validateVariadicParameter($select);
|
||||
|
||||
$this->type = QueryType::Select;
|
||||
|
||||
if ($select === []) {
|
||||
@@ -951,6 +960,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function where(mixed ...$predicates): static
|
||||
{
|
||||
self::validateVariadicParameter($predicates);
|
||||
|
||||
if (! (count($predicates) === 1 && $predicates[0] instanceof Expr\Composite)) {
|
||||
$predicates = new Expr\Andx($predicates);
|
||||
}
|
||||
@@ -976,6 +987,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function andWhere(mixed ...$where): static
|
||||
{
|
||||
self::validateVariadicParameter($where);
|
||||
|
||||
$dql = $this->getDQLPart('where');
|
||||
|
||||
if ($dql instanceof Expr\Andx) {
|
||||
@@ -1006,6 +1019,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function orWhere(mixed ...$where): static
|
||||
{
|
||||
self::validateVariadicParameter($where);
|
||||
|
||||
$dql = $this->getDQLPart('where');
|
||||
|
||||
if ($dql instanceof Expr\Orx) {
|
||||
@@ -1033,6 +1048,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function groupBy(string ...$groupBy): static
|
||||
{
|
||||
self::validateVariadicParameter($groupBy);
|
||||
|
||||
return $this->add('groupBy', new Expr\GroupBy($groupBy));
|
||||
}
|
||||
|
||||
@@ -1051,6 +1068,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function addGroupBy(string ...$groupBy): static
|
||||
{
|
||||
self::validateVariadicParameter($groupBy);
|
||||
|
||||
return $this->add('groupBy', new Expr\GroupBy($groupBy), true);
|
||||
}
|
||||
|
||||
@@ -1062,6 +1081,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function having(mixed ...$having): static
|
||||
{
|
||||
self::validateVariadicParameter($having);
|
||||
|
||||
if (! (count($having) === 1 && ($having[0] instanceof Expr\Andx || $having[0] instanceof Expr\Orx))) {
|
||||
$having = new Expr\Andx($having);
|
||||
}
|
||||
@@ -1077,6 +1098,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function andHaving(mixed ...$having): static
|
||||
{
|
||||
self::validateVariadicParameter($having);
|
||||
|
||||
$dql = $this->getDQLPart('having');
|
||||
|
||||
if ($dql instanceof Expr\Andx) {
|
||||
@@ -1097,6 +1120,8 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function orHaving(mixed ...$having): static
|
||||
{
|
||||
self::validateVariadicParameter($having);
|
||||
|
||||
$dql = $this->getDQLPart('having');
|
||||
|
||||
if ($dql instanceof Expr\Orx) {
|
||||
@@ -1162,22 +1187,20 @@ class QueryBuilder implements Stringable
|
||||
}
|
||||
}
|
||||
|
||||
if ($criteria->getOrderings()) {
|
||||
foreach ($criteria->getOrderings() as $sort => $order) {
|
||||
$hasValidAlias = false;
|
||||
foreach ($allAliases as $alias) {
|
||||
if (str_starts_with($sort . '.', $alias . '.')) {
|
||||
$hasValidAlias = true;
|
||||
break;
|
||||
}
|
||||
foreach ($criteria->orderings() as $sort => $order) {
|
||||
$hasValidAlias = false;
|
||||
foreach ($allAliases as $alias) {
|
||||
if (str_starts_with($sort . '.', $alias . '.')) {
|
||||
$hasValidAlias = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $hasValidAlias) {
|
||||
$sort = $allAliases[0] . '.' . $sort;
|
||||
}
|
||||
|
||||
$this->addOrderBy($sort, $order);
|
||||
}
|
||||
|
||||
if (! $hasValidAlias) {
|
||||
$sort = $allAliases[0] . '.' . $sort;
|
||||
}
|
||||
|
||||
$this->addOrderBy($sort, $order->value);
|
||||
}
|
||||
|
||||
// Overwrite limits only if they was set in criteria
|
||||
|
||||
@@ -5,8 +5,10 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Tools;
|
||||
|
||||
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
|
||||
use function assert;
|
||||
use function ltrim;
|
||||
|
||||
/**
|
||||
@@ -14,16 +16,22 @@ use function ltrim;
|
||||
*/
|
||||
class AttachEntityListenersListener
|
||||
{
|
||||
/** @var mixed[][] */
|
||||
/**
|
||||
* @var array<class-string, list<array{
|
||||
* event: Events::*|null,
|
||||
* class: class-string,
|
||||
* method: string|null,
|
||||
* }>>
|
||||
*/
|
||||
private array $entityListeners = [];
|
||||
|
||||
/**
|
||||
* Adds an entity listener for a specific entity.
|
||||
*
|
||||
* @param string $entityClass The entity to attach the listener.
|
||||
* @param string $listenerClass The listener class.
|
||||
* @param string|null $eventName The entity lifecycle event.
|
||||
* @param string|null $listenerCallback The listener callback method or NULL to use $eventName.
|
||||
* @param class-string $entityClass The entity to attach the listener.
|
||||
* @param class-string $listenerClass The listener class.
|
||||
* @param Events::*|null $eventName The entity lifecycle event.
|
||||
* @param non-falsy-string|null $listenerCallback The listener callback method or NULL to use $eventName.
|
||||
*/
|
||||
public function addEntityListener(
|
||||
string $entityClass,
|
||||
@@ -34,7 +42,7 @@ class AttachEntityListenersListener
|
||||
$this->entityListeners[ltrim($entityClass, '\\')][] = [
|
||||
'event' => $eventName,
|
||||
'class' => $listenerClass,
|
||||
'method' => $listenerCallback ?: $eventName,
|
||||
'method' => $listenerCallback ?? $eventName,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -53,6 +61,7 @@ class AttachEntityListenersListener
|
||||
if ($listener['event'] === null) {
|
||||
EntityListenerBuilder::bindEntityListener($metadata, $listener['class']);
|
||||
} else {
|
||||
assert($listener['method'] !== null);
|
||||
$metadata->addEntityListener($listener['event'], $listener['class'], $listener['method']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ class SchemaTool
|
||||
|
||||
if (isset($class->table['uniqueConstraints'])) {
|
||||
foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) {
|
||||
$uniqIndex = new Index($indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []);
|
||||
$uniqIndex = new Index('tmp__' . $indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []);
|
||||
|
||||
foreach ($table->getIndexes() as $tableIndexName => $tableIndex) {
|
||||
if ($tableIndex->isFulfilledBy($uniqIndex)) {
|
||||
|
||||
@@ -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(
|
||||
@@ -162,7 +162,7 @@ class SchemaValidator
|
||||
}
|
||||
}
|
||||
|
||||
if ($assoc->isOwningSide() && $assoc->inversedBy) {
|
||||
if ($assoc->isOwningSide() && $assoc->inversedBy !== null) {
|
||||
if ($targetMetadata->hasField($assoc->inversedBy)) {
|
||||
$ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the inverse side ' .
|
||||
'field ' . $assoc->targetEntity . '#' . $assoc->inversedBy . ' which is not defined as association.';
|
||||
@@ -343,7 +343,7 @@ class SchemaValidator
|
||||
return null;
|
||||
}
|
||||
|
||||
$metadataFieldType = $this->findBuiltInType(Type::getType($fieldMapping['type']));
|
||||
$metadataFieldType = $this->findBuiltInType(Type::getType($fieldMapping->type));
|
||||
|
||||
//If the metadata field type is not a mapped built-in type, we cannot check it
|
||||
if ($metadataFieldType === null) {
|
||||
@@ -353,25 +353,25 @@ 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),
|
||||
);
|
||||
}
|
||||
|
||||
if (! isset($fieldMapping['enumType']) || $propertyType === $fieldMapping['enumType']) {
|
||||
if (! isset($fieldMapping->enumType) || $propertyType === $fieldMapping->enumType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -380,19 +380,19 @@ class SchemaValidator
|
||||
$class->name,
|
||||
$fieldName,
|
||||
$propertyType,
|
||||
$fieldMapping['enumType'],
|
||||
$fieldMapping->enumType,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isset($fieldMapping['enumType'])
|
||||
&& $propertyType !== $fieldMapping['enumType']
|
||||
isset($fieldMapping->enumType)
|
||||
&& $propertyType !== $fieldMapping->enumType
|
||||
&& interface_exists($propertyType)
|
||||
&& is_a($fieldMapping['enumType'], $propertyType, true)
|
||||
&& is_a($fieldMapping->enumType, $propertyType, true)
|
||||
) {
|
||||
$backingType = (string) (new ReflectionEnum($fieldMapping['enumType']))->getBackingType();
|
||||
$backingType = (string) (new ReflectionEnum($fieldMapping->enumType))->getBackingType();
|
||||
|
||||
if ($metadataFieldType === $backingType) {
|
||||
if (in_array($backingType, $metadataFieldType, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -400,14 +400,14 @@ class SchemaValidator
|
||||
"The field '%s#%s' has the metadata enumType '%s' with a backing type of '%s' that differs from the metadata field type '%s'.",
|
||||
$class->name,
|
||||
$fieldName,
|
||||
$fieldMapping['enumType'],
|
||||
$fieldMapping->enumType,
|
||||
$backingType,
|
||||
$metadataFieldType,
|
||||
implode('|', $metadataFieldType),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
$fieldMapping['type'] === 'json'
|
||||
$fieldMapping->type === 'json'
|
||||
&& in_array($propertyType, ['string', 'int', 'float', 'bool', 'true', 'false', 'null'], true)
|
||||
) {
|
||||
return null;
|
||||
@@ -418,8 +418,8 @@ class SchemaValidator
|
||||
$class->name,
|
||||
$fieldName,
|
||||
$propertyType,
|
||||
$metadataFieldType,
|
||||
$fieldMapping['type'],
|
||||
implode('|', $metadataFieldType),
|
||||
$fieldMapping->type,
|
||||
);
|
||||
},
|
||||
$class->fieldMappings,
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -997,7 +997,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
foreach ($actualData as $propName => $actualValue) {
|
||||
$orgValue = $originalData[$propName] ?? null;
|
||||
|
||||
if (isset($class->fieldMappings[$propName]['enumType'])) {
|
||||
if (isset($class->fieldMappings[$propName]->enumType)) {
|
||||
if (is_array($orgValue)) {
|
||||
foreach ($orgValue as $id => $val) {
|
||||
if ($val instanceof BackedEnum) {
|
||||
@@ -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);
|
||||
@@ -1267,16 +1269,16 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
$joinColumns = reset($assoc->joinColumns);
|
||||
if (! isset($joinColumns['onDelete'])) {
|
||||
if (! isset($joinColumns->onDelete)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$onDeleteOption = strtolower($joinColumns['onDelete']);
|
||||
$onDeleteOption = strtolower($joinColumns->onDelete);
|
||||
if ($onDeleteOption !== 'cascade') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetEntity = $class->getFieldValue($entity, $assoc['fieldName']);
|
||||
$targetEntity = $class->getFieldValue($entity, $assoc->fieldName);
|
||||
|
||||
// If the association does not refer to another entity or that entity
|
||||
// is not to be deleted, there is no ordering problem and we can
|
||||
@@ -1484,8 +1486,6 @@ class UnitOfWork implements PropertyChangedListener
|
||||
return;
|
||||
}
|
||||
|
||||
$this->removeFromIdentityMap($entity);
|
||||
|
||||
unset($this->entityUpdates[$oid]);
|
||||
|
||||
if (! isset($this->entityDeletions[$oid])) {
|
||||
@@ -1557,18 +1557,15 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
final public static function getIdHashByIdentifier(array $identifier): string
|
||||
{
|
||||
foreach ($identifier as $k => $value) {
|
||||
if ($value instanceof BackedEnum) {
|
||||
$identifier[$k] = $value->value;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(
|
||||
' ',
|
||||
array_map(
|
||||
static function ($value) {
|
||||
if ($value instanceof BackedEnum) {
|
||||
return $value->value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
$identifier,
|
||||
),
|
||||
$identifier,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2550,7 +2547,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$this->originalEntityData[$oid][$field] = $newValue;
|
||||
$class->reflFields[$field]->setValue($entity, $newValue);
|
||||
|
||||
if ($assoc->inversedBy && $assoc->isOneToOne() && $newValue !== null) {
|
||||
if ($assoc->inversedBy !== null && $assoc->isOneToOne() && $newValue !== null) {
|
||||
$inverseAssoc = $targetClass->associationMappings[$assoc->inversedBy];
|
||||
$targetClass->reflFields[$inverseAssoc->fieldName]->setValue($newValue, $entity);
|
||||
}
|
||||
@@ -2584,9 +2581,9 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
|
||||
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
|
||||
if (! $isIteration && $assoc->isOneToMany()) {
|
||||
if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite && ! $assoc->isIndexed()) {
|
||||
$this->scheduleCollectionForBatchLoading($pColl, $class);
|
||||
} elseif (($isIteration && $assoc->isOneToMany()) || $assoc->isManyToMany()) {
|
||||
} else {
|
||||
$this->loadCollection($pColl);
|
||||
$pColl->takeSnapshot();
|
||||
}
|
||||
@@ -2656,7 +2653,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);
|
||||
@@ -2665,7 +2662,19 @@ class UnitOfWork implements PropertyChangedListener
|
||||
foreach ($found as $targetValue) {
|
||||
$sourceEntity = $targetProperty->getValue($targetValue);
|
||||
|
||||
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
|
||||
if ($sourceEntity === null && isset($targetClass->associationMappings[$mappedBy]->joinColumns)) {
|
||||
// case where the hydration $targetValue itself has not yet fully completed, for example
|
||||
// in case a bi-directional association is being hydrated and deferring eager loading is
|
||||
// not possible due to subclassing.
|
||||
$data = $this->getOriginalEntityData($targetValue);
|
||||
$id = [];
|
||||
foreach ($targetClass->associationMappings[$mappedBy]->joinColumns as $joinColumn) {
|
||||
$id[] = $data[$joinColumn->name];
|
||||
}
|
||||
} else {
|
||||
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
|
||||
}
|
||||
|
||||
$idHash = implode(' ', $id);
|
||||
|
||||
if ($mapping->indexBy !== null) {
|
||||
@@ -2713,7 +2722,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
private function scheduleCollectionForBatchLoading(PersistentCollection $collection, ClassMetadata $sourceClass): void
|
||||
{
|
||||
$mapping = $collection->getMapping();
|
||||
$name = $mapping['sourceEntity'] . '#' . $mapping['fieldName'];
|
||||
$name = $mapping->sourceEntity . '#' . $mapping->fieldName;
|
||||
|
||||
if (! isset($this->eagerLoadingCollections[$name])) {
|
||||
$this->eagerLoadingCollections[$name] = [
|
||||
|
||||
@@ -4,8 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance\Mock;
|
||||
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
|
||||
|
||||
/**
|
||||
@@ -13,22 +12,14 @@ use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
|
||||
*/
|
||||
class NonLoadingPersister extends BasicEntityPersister
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
public function __construct(
|
||||
ClassMetadata $class,
|
||||
) {
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function load(
|
||||
array $criteria,
|
||||
object|null $entity = null,
|
||||
AssociationMapping|null $assoc = null,
|
||||
array $hints = [],
|
||||
LockMode|int|null $lockMode = null,
|
||||
int|null $limit = null,
|
||||
array|null $orderBy = null,
|
||||
): object|null {
|
||||
return $entity;
|
||||
public function loadById(array $identifier, object|null $entity = null): object|null
|
||||
{
|
||||
return $entity ?? new ($this->class->name)();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class NonProxyLoadingEntityManager implements EntityManagerInterface
|
||||
|
||||
public function getUnitOfWork(): UnitOfWork
|
||||
{
|
||||
return new NonProxyLoadingUnitOfWork();
|
||||
return new NonProxyLoadingUnitOfWork($this);
|
||||
}
|
||||
|
||||
public function getCache(): Cache|null
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance\Mock;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
|
||||
/**
|
||||
@@ -11,15 +12,17 @@ use Doctrine\ORM\UnitOfWork;
|
||||
*/
|
||||
class NonProxyLoadingUnitOfWork extends UnitOfWork
|
||||
{
|
||||
private NonLoadingPersister $entityPersister;
|
||||
/** @var array<class-string, NonLoadingPersister> */
|
||||
private array $entityPersisters = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->entityPersister = new NonLoadingPersister();
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEntityPersister(string $entityName): NonLoadingPersister
|
||||
{
|
||||
return $this->entityPersister;
|
||||
return $this->entityPersisters[$entityName]
|
||||
??= new NonLoadingPersister($this->entityManager->getClassMetadata($entityName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\AbstractFetchEager;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'abstract_fetch_eager_remote_control')]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
|
||||
#[ORM\DiscriminatorMap(['mobile' => 'MobileRemoteControl'])]
|
||||
abstract class AbstractRemoteControl
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
public int $id;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
public string $name;
|
||||
|
||||
/** @var Collection<User> */
|
||||
#[ORM\OneToMany(targetEntity: User::class, mappedBy: 'remoteControl', fetch: 'EAGER')]
|
||||
public Collection $users;
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->users = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\AbstractFetchEager;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class MobileRemoteControl extends AbstractRemoteControl
|
||||
{
|
||||
}
|
||||
26
tests/Tests/Models/AbstractFetchEager/User.php
Normal file
26
tests/Tests/Models/AbstractFetchEager/User.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\AbstractFetchEager;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'abstract_fetch_eager_user')]
|
||||
class User
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
public int $id;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: AbstractRemoteControl::class, inversedBy: 'users')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
public AbstractRemoteControl $remoteControl;
|
||||
|
||||
public function __construct(AbstractRemoteControl $control)
|
||||
{
|
||||
$this->remoteControl = $control;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'eager_composite_join_root')]
|
||||
class RootEntity
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer', nullable: false)]
|
||||
private int|null $id = null;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'string', nullable: false, name: 'other_key', length: 42)]
|
||||
private string $otherKey;
|
||||
|
||||
/** @var Collection<int, SecondLevel> */
|
||||
#[ORM\OneToMany(mappedBy: 'root', targetEntity: SecondLevel::class, fetch: 'EAGER')]
|
||||
private Collection $secondLevel;
|
||||
|
||||
public function __construct(int $id, string $other)
|
||||
{
|
||||
$this->otherKey = $other;
|
||||
$this->secondLevel = new ArrayCollection();
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getOtherKey(): string
|
||||
{
|
||||
return $this->otherKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'eager_composite_join_second_level')]
|
||||
#[ORM\Index(name: 'root_other_key_idx', columns: ['root_other_key', 'root_id'])]
|
||||
class SecondLevel
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer', nullable: false)]
|
||||
private int|null $upperId;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'string', nullable: false, name: 'other_key')]
|
||||
private string $otherKey;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: RootEntity::class, inversedBy: 'secondLevel')]
|
||||
#[ORM\JoinColumn(name: 'root_id', referencedColumnName: 'id')]
|
||||
#[ORM\JoinColumn(name: 'root_other_key', referencedColumnName: 'other_key')]
|
||||
private RootEntity $root;
|
||||
|
||||
public function __construct(RootEntity $upper)
|
||||
{
|
||||
$this->upperId = $upper->getId();
|
||||
$this->otherKey = $upper->getOtherKey();
|
||||
$this->root = $upper;
|
||||
}
|
||||
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
@@ -26,8 +25,6 @@ use Psr\Cache\CacheItemPoolInterface;
|
||||
*/
|
||||
class ConfigurationTest extends TestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
private Configuration $configuration;
|
||||
|
||||
protected function setUp(): void
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Exception\EntityManagerClosed;
|
||||
@@ -27,8 +26,6 @@ use TypeError;
|
||||
|
||||
class EntityManagerTest extends OrmTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
private EntityManagerMock $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
|
||||
37
tests/Tests/ORM/Functional/AbstractFetchEagerTest.php
Normal file
37
tests/Tests/ORM/Functional/AbstractFetchEagerTest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Tests\Models\AbstractFetchEager\AbstractRemoteControl;
|
||||
use Doctrine\Tests\Models\AbstractFetchEager\MobileRemoteControl;
|
||||
use Doctrine\Tests\Models\AbstractFetchEager\User;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
final class AbstractFetchEagerTest extends OrmFunctionalTestCase
|
||||
{
|
||||
public function testWithAbstractFetchEager(): void
|
||||
{
|
||||
$this->createSchemaForModels(
|
||||
AbstractRemoteControl::class,
|
||||
User::class,
|
||||
);
|
||||
|
||||
$control = new MobileRemoteControl('smart');
|
||||
$user = new User($control);
|
||||
|
||||
$entityManage = $this->getEntityManager();
|
||||
|
||||
$entityManage->persist($control);
|
||||
$entityManage->persist($user);
|
||||
$entityManage->flush();
|
||||
$entityManage->clear();
|
||||
|
||||
$user = $entityManage->find(User::class, $user->id);
|
||||
|
||||
self::assertNotNull($user);
|
||||
self::assertEquals('smart', $user->remoteControl->name);
|
||||
self::assertTrue($user->remoteControl->users->contains($user));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\RootEntity;
|
||||
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\SecondLevel;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
final class EagerFetchOneToManyWithCompositeKeyTest extends OrmFunctionalTestCase
|
||||
{
|
||||
/** @ticket 11154 */
|
||||
public function testItDoesNotThrowAnExceptionWhenTriggeringALoad(): void
|
||||
{
|
||||
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class]);
|
||||
|
||||
$a1 = new RootEntity(1, 'A');
|
||||
|
||||
$this->_em->persist($a1);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
self::assertCount(1, $this->_em->getRepository(RootEntity::class)->findAll());
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
@@ -16,6 +17,7 @@ use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
|
||||
/**
|
||||
* Basic many-to-many association tests.
|
||||
@@ -435,7 +437,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$user = $this->_em->find($user::class, $user->id);
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->orderBy(['name' => Criteria::ASC]);
|
||||
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
|
||||
|
||||
self::assertEquals(
|
||||
['A', 'B', 'C', 'Developers_0'],
|
||||
@@ -475,7 +477,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$user = $this->_em->find($user::class, $user->id);
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->orderBy(['name' => Criteria::ASC]);
|
||||
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
|
||||
|
||||
self::assertEquals(
|
||||
['A', 'B', 'C'],
|
||||
|
||||
@@ -8,7 +8,6 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type as DBALType;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Internal\Hydration\HydrationException;
|
||||
use Doctrine\ORM\Internal\SQLResultCasing;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
@@ -33,7 +32,6 @@ use PHPUnit\Framework\Attributes\Group;
|
||||
class NativeQueryTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use SQLResultCasing;
|
||||
use VerifyDeprecations;
|
||||
|
||||
private AbstractPlatform|null $platform = null;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform;
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\Tests\Models\Company\CompanyEmployee;
|
||||
use Doctrine\Tests\Models\Company\CompanyManager;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
@@ -251,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')
|
||||
@@ -487,4 +487,34 @@ SQL;
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
#[Group('GH-11240')]
|
||||
public function testDateAddWithColumnInterval(): void
|
||||
{
|
||||
$query = sprintf(
|
||||
'SELECT DATE_ADD(CURRENT_TIMESTAMP(), m.salary, \'day\') AS add FROM %s m',
|
||||
CompanyEmployee::class,
|
||||
);
|
||||
|
||||
$result = $this->_em->createQuery($query)
|
||||
->setMaxResults(1)
|
||||
->getSingleResult(AbstractQuery::HYDRATE_ARRAY);
|
||||
|
||||
self::assertArrayHasKey('add', $result);
|
||||
}
|
||||
|
||||
#[Group('GH-11240')]
|
||||
public function testDateSubWithColumnInterval(): void
|
||||
{
|
||||
$query = sprintf(
|
||||
'SELECT DATE_SUB(CURRENT_TIMESTAMP(), m.salary, \'day\') AS add FROM %s m',
|
||||
CompanyEmployee::class,
|
||||
);
|
||||
|
||||
$result = $this->_em->createQuery($query)
|
||||
->setMaxResults(1)
|
||||
->getSingleResult(AbstractQuery::HYDRATE_ARRAY);
|
||||
|
||||
self::assertArrayHasKey('add', $result);
|
||||
}
|
||||
}
|
||||
|
||||
124
tests/Tests/ORM/Functional/QueryParameterTest.php
Normal file
124
tests/Tests/ORM/Functional/QueryParameterTest.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
#[Group('GH-11278')]
|
||||
final class QueryParameterTest extends OrmFunctionalTestCase
|
||||
{
|
||||
private int $userId;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->useModelSet('cms');
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$user = new CmsUser();
|
||||
$user->name = 'John Doe';
|
||||
$user->username = 'john';
|
||||
$user2 = new CmsUser();
|
||||
$user2->name = 'Jane Doe';
|
||||
$user2->username = 'jane';
|
||||
$user3 = new CmsUser();
|
||||
$user3->name = 'Just Bill';
|
||||
$user3->username = 'bill';
|
||||
|
||||
$this->_em->persist($user);
|
||||
$this->_em->persist($user2);
|
||||
$this->_em->persist($user3);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->userId = $user->id;
|
||||
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
public function testParameterTypeInBuilder(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.id = :id')
|
||||
->setParameter('id', $this->userId, ParameterType::INTEGER)
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testParameterTypeInQuery(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.id = :id')
|
||||
->getQuery()
|
||||
->setParameter('id', $this->userId, ParameterType::INTEGER)
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testDbalTypeStringInBuilder(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.id = :id')
|
||||
->setParameter('id', $this->userId, Types::INTEGER)
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testDbalTypeStringInQuery(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.id = :id')
|
||||
->getQuery()
|
||||
->setParameter('id', $this->userId, Types::INTEGER)
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testArrayParameterTypeInBuilder(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.username IN (:usernames)')
|
||||
->orderBy('u.username')
|
||||
->setParameter('usernames', ['john', 'jane'], ArrayParameterType::STRING)
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'Jane Doe'], ['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testArrayParameterTypeInQuery(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.username IN (:usernames)')
|
||||
->orderBy('u.username')
|
||||
->getQuery()
|
||||
->setParameter('usernames', ['john', 'jane'], ArrayParameterType::STRING)
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'Jane Doe'], ['name' => 'John Doe']], $result);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\Models\DDC117\DDC117ApproveChanges;
|
||||
use Doctrine\Tests\Models\DDC117\DDC117Article;
|
||||
@@ -23,8 +22,6 @@ use function count;
|
||||
#[Group('DDC-117')]
|
||||
class DDC117Test extends OrmFunctionalTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
private DDC117Article|null $article1;
|
||||
|
||||
private DDC117Article|null $article2;
|
||||
|
||||
@@ -25,8 +25,8 @@ class GH11135Test extends OrmFunctionalTestCase
|
||||
$cm1 = $this->_em->getClassMetadata(GH11135EntityWithOverride::class);
|
||||
$cm2 = $this->_em->getClassMetadata(GH11135EntityWithoutOverride::class);
|
||||
|
||||
self::assertSame($cm1->getFieldMapping('id')['declared'], $cm2->getFieldMapping('id')['declared']);
|
||||
self::assertSame($cm1->getAssociationMapping('ref')['declared'], $cm2->getAssociationMapping('ref')['declared']);
|
||||
self::assertSame($cm1->getFieldMapping('id')->declared, $cm2->getFieldMapping('id')->declared);
|
||||
self::assertSame($cm1->getAssociationMapping('ref')->declared, $cm2->getAssociationMapping('ref')->declared);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
tests/Tests/ORM/Functional/Ticket/GH11149/EagerProduct.php
Normal file
33
tests/Tests/ORM/Functional/Ticket/GH11149/EagerProduct.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('gh11149_eager_product')]
|
||||
class EagerProduct
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
public int $id;
|
||||
|
||||
/** @var Collection<string, EagerProductTranslation> */
|
||||
#[ORM\OneToMany(
|
||||
targetEntity: EagerProductTranslation::class,
|
||||
mappedBy: 'product',
|
||||
fetch: 'EAGER',
|
||||
indexBy: 'locale_code',
|
||||
)]
|
||||
public Collection $translations;
|
||||
|
||||
public function __construct(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->translations = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('gh11149_eager_product_translation')]
|
||||
class EagerProductTranslation
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
private int $id;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'translations')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
public EagerProduct $product;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(name: 'locale_code', referencedColumnName: 'code', nullable: false)]
|
||||
public Locale $locale;
|
||||
|
||||
public function __construct(int $id, EagerProduct $product, Locale $locale)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->product = $product;
|
||||
$this->locale = $locale;
|
||||
}
|
||||
}
|
||||
47
tests/Tests/ORM/Functional/Ticket/GH11149/GH11149Test.php
Normal file
47
tests/Tests/ORM/Functional/Ticket/GH11149/GH11149Test.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
|
||||
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH11149Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
Locale::class,
|
||||
EagerProduct::class,
|
||||
EagerProductTranslation::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testFetchEagerModeWithIndexBy(): void
|
||||
{
|
||||
// Load entities into database
|
||||
$this->_em->persist($product = new EagerProduct(11149));
|
||||
$this->_em->persist($locale = new Locale('fr_FR'));
|
||||
$this->_em->persist(new EagerProductTranslation(11149, $product, $locale));
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
// Fetch entity from database
|
||||
$product = $this->_em->find(EagerProduct::class, 11149);
|
||||
|
||||
// Assert associated entity is loaded eagerly
|
||||
static::assertInstanceOf(EagerProduct::class, $product);
|
||||
static::assertInstanceOf(PersistentCollection::class, $product->translations);
|
||||
static::assertTrue($product->translations->isInitialized());
|
||||
static::assertCount(1, $product->translations);
|
||||
|
||||
// Assert associated entity is indexed by given property
|
||||
$translation = $product->translations->get('fr_FR');
|
||||
static::assertInstanceOf(EagerProductTranslation::class, $translation);
|
||||
static::assertNotInstanceOf(Proxy::class, $translation);
|
||||
}
|
||||
}
|
||||
21
tests/Tests/ORM/Functional/Ticket/GH11149/Locale.php
Normal file
21
tests/Tests/ORM/Functional/Ticket/GH11149/Locale.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('gh11149_locale')]
|
||||
class Locale
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(length: 5)]
|
||||
public string $code;
|
||||
|
||||
public function __construct(string $code)
|
||||
{
|
||||
$this->code = $code;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user