Compare commits

...

80 Commits

Author SHA1 Message Date
Alexander M. Turek
099e51d899 Psalm 5.3.0 (#10317) 2022-12-19 15:04:28 +01:00
Alexander M. Turek
5df84d4ec0 PHPStan 1.9.4 (#10318) 2022-12-19 12:16:41 +01:00
aleksejs1
c23220b68a add apcu enabled check if apcu extension loaded (#10310) (#10311)
* add apcu enabled check if apcu extension loaded (#10310)

* Apply suggestions from code review

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>

* add use function apcu_enabled

* enable apcu_cli for cache tests

Co-authored-by: Aleksejs Kovalovs <kovalovs@co.inbox.lv>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2022-12-18 21:21:27 +01:00
Alexander M. Turek
83f6356f25 Bump coding standard to v11 (#10295) 2022-12-13 19:08:04 +01:00
Alexander M. Turek
4e6cb908f6 PHPStan 1.9.3 (#10298) 2022-12-13 18:51:58 +01:00
Alexander M. Turek
9b723a88aa Rename AbstractCommandTest (#10294) 2022-12-12 16:36:16 +01:00
michnovka
bd11475615 Fix changeset computation for enum arrays (#10277)
Previously, array of enums were incorrectly compared in UoW::computeChangeSet() resulting always in false positive, since the original data for comparison is fetched using ReflectionEnumProperty::getValue(), which returns the enum values, not enum objects.
This fix ensures comparing the individual enum array members' values.
2022-12-12 13:58:21 +01:00
Alexander M. Turek
90f1f54e73 Psalm 5.2.0 (#10291) 2022-12-12 13:54:50 +01:00
Alexander M. Turek
b25561ad96 Run tools on PHP 8.2 (#10287) 2022-12-11 11:58:09 +01:00
Grégoire Paris
b391431a0e Merge pull request #10274 from michnovka/2.13.x-complex-enum-ids
Fix enum IDs in association mappings
2022-12-08 08:59:52 +01:00
Tomas
d5a6b36e6f Fix association mapping with enum fields
Enum fields as ID have worked for some time, but referencing these fields from other entities in association mappings such as OneToOne, ManyToOne and ManyToMany resulted in fatal error as there was an attempt to convert enum value to string improperly.
2022-12-07 16:56:35 +01:00
Grégoire Paris
aeed977d6e Merge pull request #10271 from kalifg/patch-1
Correct spelling errors
2022-12-05 19:43:18 +01:00
Michael Dwyer
f66b008b8b Correct spelling errors 2022-12-05 12:31:10 -06:00
Grégoire Paris
2ed3f55c01 Merge pull request #10264 from greg0ire/psalm-5.1
Upgrade to Psalm 5.1.0
2022-12-04 00:00:18 +01:00
Grégoire Paris
2dfb4f44e2 Upgrade to Psalm 5.1.0 2022-12-03 10:58:00 +01:00
Alexander M. Turek
4b577e7a18 Bump Psalm to 5.0.0 and fix errors for Symfony 6.2 (#10261) 2022-11-30 22:00:40 +01:00
Grégoire Paris
65da1fe8cb Merge pull request #10257 from greg0ire/fix-phpdoc-parser
Leverage Lexer's Token type (follow up)
2022-11-27 21:53:55 +01:00
Grégoire Paris
1df221860f Leverage Lexer's Token type (follow up)
Follow-up of f82db6a894.
The previous value introduced in
dc37c2cd2f was too restrictive, hence the
changes to the Psalm baseline.
2022-11-26 14:24:02 +01:00
Grégoire Paris
57ac275137 Merge pull request #10252 from greg0ire/psalm-5
Upgrade to Psalm v5
2022-11-26 14:11:21 +01:00
Grégoire Paris
e958046c4a Upgrade to Psalm v5
It is not stable yet, but should be good enough, and this will help
taking care of #10118

Let us baseline all the new issues, just because they are new does not
mean they are more important than already-baselined errors. Besides, it
is more important to have higher standards for new code than to get an
increased baseline.
2022-11-26 13:41:02 +01:00
Grégoire Paris
3038f6aeef Psalm 5 fixes (#10248)
* Account for ClassMetadata::inheritanceType always being truthy

* Remove redundant cast to array
2022-11-24 21:28:23 +01:00
Grégoire Paris
28bf6cb1fa Merge pull request #10247 from derrabus/sa/paginator
Fix types for Paginator
2022-11-23 21:49:53 +01:00
Alexander M. Turek
e864c4cbc2 Fix types for Paginator 2022-11-23 21:40:17 +01:00
Grégoire Paris
a5a6cc6630 Remove fragile assertions (#10239)
RuntimePublicReflectionProperty has been deprecated in favor of
RuntimeReflectionProperty.

Let us use a more robust assertion.
2022-11-20 19:53:31 +01:00
Grégoire Paris
fc3201bded Merge pull request #10229 from greg0ire/fix-phpdoc-exception
Widen parameter type
2022-11-14 22:15:29 +01:00
Grégoire Paris
3178b4ec4f Widen parameter type
This exception is used in two places, one where $generatedMode is
clearly a string, and another where typing it as a string will cause a
type error, because $generatedMode is supposed to be an int there.
2022-11-13 19:13:40 +01:00
Grégoire Paris
465c02fe68 Reverse-engineer actual type from code (#10221)
The code contains tests for is_array(), is object(), and an else clause.
The type is wrong, and the variable name misleading.
2022-11-11 16:29:36 +01:00
Alexander M. Turek
7e45ad935c Psalm 4.30.0, PHPStan 1.9.2 (#10213) 2022-11-10 22:29:50 +01:00
Alexander M. Turek
0b14c01b93 PHPStan 1.9.0 (#10200) 2022-11-03 21:04:02 +01:00
Alexander M. Turek
552d98d554 Bump CI to PHP 8.2 and latest database versions (#10180) 2022-10-26 15:12:52 +02:00
Alexander M. Turek
8f605c652a Remove reference to deprecated DriverChain from docs (#10179) 2022-10-26 15:09:12 +02:00
Alexander M. Turek
75e42abfdf PHPStan 1.8.11 (#10182) 2022-10-26 11:36:22 +02:00
Romain Monteil
3133bf06c2 Add isMemberOf and isInstanceOf to Expr helper list (#10104)
* Add isMemberOf and isInstanceOf to Expr helper list

* Apply suggestions from code review

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-10-26 11:04:17 +02:00
Grégoire Paris
7cf4074d3a Migrate more references to annotations (#10176) 2022-10-26 10:42:42 +02:00
Jonny Eom
bb1deba510 Fix grammer in working-with-objects (#10120) 2022-10-26 10:39:05 +02:00
Grégoire Paris
a5553a0786 Adapt use statements to the code (#10174) 2022-10-25 23:25:36 +02:00
Alexander M. Turek
0b9c949590 Fix build with DBAL 3.5 (#10163) 2022-10-23 01:11:03 +02:00
Grégoire Paris
3ee7d96179 Adjust comments (#10160) 2022-10-22 15:34:10 +02:00
Grégoire Paris
9f926f04ba Merge pull request #10157 from greg0ire/followup-migrate-docs-to-attributes
Migrate more documentation towards attributes
2022-10-21 21:25:08 +02:00
Grégoire Paris
4e445feb6c Migrate more documentation towards attributes
The previous rework missed some details / left the documentation in an
inconsistent state. Searching for "annotation" case insensitively allows
to find discrepancies and forgotten things.
2022-10-21 12:31:10 +02:00
Grégoire Paris
d79e61f8d9 Remove wrong sentence 2022-10-21 12:31:10 +02:00
Simon Podlipsky
06eafd82ac Do not export phpstan stuff (#10154) 2022-10-19 13:36:12 +02:00
Grégoire Paris
5f4b76b88f Merge pull request #10126 from greg0ire/migrate-docs-to-attributes
Modernize documentation code
2022-10-17 23:11:33 +02:00
Grégoire Paris
794777b21f Modernize documentation code
- migrate to attributes;
- add helpful phpdoc annotations;
- use typed properties;
- add type declarations.

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-10-17 22:56:34 +02:00
Alexander M. Turek
119c378a3a Add CI jobs for SQLite3 driver (#10141) 2022-10-17 22:48:07 +02:00
Grégoire Paris
90bc6dc300 Merge pull request #10136 from greg0ire/trigger-sa-workflows-less
Stop triggering static analysis workflows on tests
2022-10-17 22:45:22 +02:00
Ondřej Mirtes
e93e8e0bdf Fix FieldMapping for generated key (#10144) 2022-10-17 15:37:06 +02:00
Grégoire Paris
bdf067b58a Stop triggering static analysis workflows on tests
In my opinion it is not great that we do not run static analysis tools on
tests, but since we do not, let us stop triggering extra jobs for no reason.
2022-10-15 23:59:57 +02:00
Grégoire Paris
f08b67f0cc Merge pull request #10135 from greg0ire/remove-extra-file
Remove file suffixed with singular
2022-10-15 11:59:05 +02:00
Grégoire Paris
5f8504b5cf Remove file suffixed with singular
It was wrongly preserved after a conflict that followed a rename.
2022-10-15 11:57:22 +02:00
Grégoire Paris
16e25656d9 Merge pull request #10134 from greg0ire/lighter-builds-💚🌍
Run only relevant workflows
2022-10-15 11:48:21 +02:00
Grégoire Paris
cacdc56b6e Run only relevant workflows
The downside of this is that we will have to tweak the settings so that
no job is required anymore.
The upside is that builds should be faster, and less resource-intensive.
2022-10-15 11:44:34 +02:00
Alexander M. Turek
13d1c7b286 Psalm 4.29 (#10128) 2022-10-13 10:03:12 +02:00
Pierre du Plessis
06c77cebb5 Make code blocks consistent (#10103) 2022-10-10 23:31:20 +02:00
HypeMC
1ed0057276 Fix change set computation with enums (#10074)
* add failing testcase test enum Change sets

* Fix change set computation with enums

Co-authored-by: Olda Salek <mzk@mozektevidi.net>
2022-10-10 11:59:04 +02:00
Alexander M. Turek
5b6f3cd598 PHPStan 1.8.8, Psalm 4.28.0 (#10115) 2022-10-09 23:31:52 +02:00
Grégoire Paris
38271d4aeb Merge pull request #10110 from davidromani/2.13.x
Fix deprecated trigger help comment
2022-10-08 22:42:56 +02:00
David Romaní
84213b9f05 fix deprecated trigger help comment 2022-10-07 18:03:42 +02:00
Grégoire Paris
e750360bd5 Merge pull request #10101 from greg0ire/followup-9488
Assert that serialization leaves PersistentCollection usable
2022-10-07 08:37:17 +02:00
Grégoire Paris
e8ac1169ad Bump CI workflows (#10106) 2022-10-06 16:57:00 +02:00
Grégoire Paris
dd8c7003b8 Assert that serialization leaves PersistentCollection usable
That feature is no longer covered since support for merging entities in
the entity manager was removed.
2022-10-06 10:40:06 +02:00
Grégoire Paris
9bec416bb0 Merge pull request #10100 from greg0ire/forward-compat-attributes
Forward compat attributes
2022-10-05 22:44:49 +02:00
Grégoire Paris
021722acc7 Remove ignored annotation
Making it a separate, valid annotation breaks the tests
2022-10-05 22:15:04 +02:00
Grégoire Paris
5a528bef5d Use short class name
It is more friendly with conversion to attributes
2022-10-05 22:00:44 +02:00
Grégoire Paris
5fbcb18334 Spell cities properly 2022-10-05 21:55:24 +02:00
Grégoire Paris
8280f41fa5 Merge pull request #10099 from greg0ire/fix-attribute-docs
Fix attribute reference
2022-10-05 21:48:46 +02:00
Grégoire Paris
d95d8d0a19 Fix broken links 2022-10-05 21:40:09 +02:00
Grégoire Paris
d36004f825 Remove trailing whitespace 2022-10-05 21:39:31 +02:00
Grégoire Paris
18366db578 Remove reference to JoinColumns
While that class can be an annotation, it cannot be an attribute because
it is not necessary when using attributes.

See https://github.com/doctrine/orm/issues/9986
2022-10-05 21:38:38 +02:00
Grégoire Paris
1b5bef3d4d Merge pull request #10094 from doctrine/fix_doc_toc 2022-10-04 15:55:16 +02:00
Christophe Coevoet
880b622fe6 Fix heading levels for the embeddable tutorial 2022-10-04 14:13:44 +02:00
Grégoire Paris
cbf141f7ed Add missing variable name in param phpdoc and switch to psalm-assert (#10092)
* Add missing variable name in param phpdoc

* Use psalm-assert instead of psalm-param

The parameter can in fact be any string, but if an exception is not
thrown, then it was a valid value.
2022-10-03 21:17:21 +02:00
Grégoire Paris
536bb4f678 Merge pull request #10090 from greg0ire/update-psalm-baseline
Update Psalm baseline
2022-10-02 16:23:14 +02:00
Grégoire Paris
3f2f42277e Update Psalm baseline 2022-10-02 16:02:57 +02:00
Grégoire Paris
757d950128 Merge pull request #10080 from greg0ire/add-missing-template-annotation
Address changes from doctrine/collections 1.8.0
2022-09-29 22:58:03 +02:00
Grégoire Paris
f07840b10c Address changes from doctrine/collections 1.8.0
Not quite sure why this annotation is not inherited.
2022-09-29 17:07:57 +02:00
Grégoire Paris
6e8afeeb60 Merge pull request #10072 from greg0ire/fix-return-type 2022-09-27 13:50:49 +02:00
Grégoire Paris
9130b27aca Remove return type override
In the parent class, all 3 methods are documented to return mixed.
This was not forward compatible with the addition of return types.
2022-09-27 13:41:09 +02:00
Grégoire Paris
2e7c2bb385 Merge pull request #10058 from HypeMC/enum-with-query-builder-fix
Fix using enums with the QueryBuilder
2022-09-23 22:08:15 +02:00
HypeMC
d69a0fa2cf Fix using enums with the QueryBuilder 2022-09-22 22:39:52 +02:00
292 changed files with 2878 additions and 1216 deletions

3
.gitattributes vendored
View File

@@ -16,5 +16,8 @@ phpcs.xml.dist export-ignore
phpbench.json export-ignore
phpstan.neon export-ignore
phpstan-baseline.neon export-ignore
phpstan-dbal2.neon export-ignore
phpstan-params.neon export-ignore
phpstan-persistence2.neon export-ignore
psalm.xml export-ignore
psalm-baseline.xml export-ignore

View File

@@ -1,13 +0,0 @@
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
push:
branches:
- "*.x"
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@2.0.0"

27
.github/workflows/coding-standards.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- lib/**
- phpcs.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- lib/**
- phpcs.xml.dist
- tests/**
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@3.0.0"

View File

@@ -4,9 +4,23 @@ on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- lib/**
- phpunit.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- lib/**
- phpunit.xml.dist
- tests/**
env:
fail-fast: true
@@ -24,19 +38,25 @@ jobs:
- "7.4"
- "8.0"
- "8.1"
- "8.2"
dbal-version:
- "default"
extension:
- "pdo_sqlite"
include:
- php-version: "8.0"
dbal-version: "2.13"
- php-version: "8.1"
dbal-version: "3@dev"
extension: "pdo_sqlite"
- php-version: "8.2"
dbal-version: "3@dev"
extension: "pdo_sqlite"
- php-version: "8.2"
dbal-version: "default"
extension: "sqlite3"
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -44,33 +64,33 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "apcu, pdo, pdo_sqlite"
extensions: "apcu, pdo, ${{ matrix.extension }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --coverage-clover=coverage-no-cache.xml"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
- name: "Run PHPUnit with Second Level Cache"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage file"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "phpunit-sqlite-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
@@ -82,19 +102,19 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
dbal-version:
- "default"
- "3@dev"
postgres-version:
- "9.6"
- "14"
- "15"
include:
- php-version: "8.0"
dbal-version: "2.13"
postgres-version: "14"
- php-version: "8.2"
dbal-version: "3@dev"
postgres-version: "14"
dbal-version: "default"
postgres-version: "9.6"
services:
postgres:
@@ -110,7 +130,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -119,14 +139,14 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
@@ -134,7 +154,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
@@ -148,11 +168,12 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
dbal-version:
- "default"
- "3@dev"
mariadb-version:
- "10.6"
- "10.9"
extension:
- "mysqli"
- "pdo_mysql"
@@ -161,14 +182,6 @@ jobs:
dbal-version: "2.13"
mariadb-version: "10.6"
extension: "pdo_mysql"
- php-version: "8.2"
dbal-version: "3@dev"
mariadb-version: "10.6"
extension: "pdo_mysql"
- php-version: "8.2"
dbal-version: "3@dev"
mariadb-version: "10.6"
extension: "mysqli"
services:
mariadb:
@@ -185,7 +198,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -198,11 +211,11 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
@@ -210,7 +223,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@v2"
uses: "actions/upload-artifact@v3"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
@@ -224,9 +237,10 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
dbal-version:
- "default"
- "3@dev"
mysql-version:
- "5.7"
- "8.0"
@@ -238,14 +252,6 @@ jobs:
dbal-version: "2.13"
mysql-version: "8.0"
extension: "pdo_mysql"
- php-version: "8.2"
dbal-version: "3@dev"
mysql-version: "8.0"
extension: "mysqli"
- php-version: "8.2"
dbal-version: "3@dev"
mysql-version: "8.0"
extension: "pdo_mysql"
services:
mysql:
@@ -261,7 +267,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -270,7 +276,7 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Require specific DBAL version"
@@ -278,7 +284,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
@@ -293,7 +299,7 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
@@ -313,7 +319,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -321,15 +327,15 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml"
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_sqlite.xml"
upload_coverage:
@@ -343,16 +349,16 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v2"
uses: "actions/download-artifact@v3"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v1"
uses: "codecov/codecov-action@v3"
with:
directory: reports

View File

@@ -5,9 +5,21 @@ on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- lib/**
- phpbench.json
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- lib/**
- phpbench.json
- tests/**
env:
fail-fast: true
@@ -24,7 +36,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -33,10 +45,10 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v2"
uses: "actions/cache@v3"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"

View File

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

View File

@@ -1,13 +1,26 @@
name: "Static Analysis"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- lib/**
- phpstan*
- psalm*
- tests/Doctrine/StaticAnalysis/**
push:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- lib/**
- phpstan*
- psalm*
- tests/Doctrine/StaticAnalysis/**
jobs:
static-analysis-phpstan:
@@ -17,29 +30,25 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version:
- "8.1"
dbal-version:
- "default"
persistence-version:
- "default"
include:
- php-version: "8.1"
dbal-version: "2.13"
- dbal-version: "2.13"
persistence-version: "default"
- php-version: "8.1"
dbal-version: "default"
- dbal-version: "default"
persistence-version: "2.5"
steps:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
php-version: "8.2"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
@@ -50,7 +59,7 @@ jobs:
if: "${{ matrix.persistence-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"
@@ -72,22 +81,19 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version:
- "8.1"
steps:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
php-version: "8.2"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"

View File

@@ -191,8 +191,8 @@ The following methods have been deprecated:
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryResultClassMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativQueryResultSetMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativQueryEntityResultMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryResultSetMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryEntityResultMapping()`
## Deprecated classes related to Doctrine 1 and reverse engineering

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
<!-- use an in-memory sqlite database -->
<var name="db_driver" value="sqlite3"/>
<var name="db_memory" value="true"/>
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">../../../lib/Doctrine</directory>
</whitelist>
</filter>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@@ -40,15 +40,15 @@
},
"require-dev": {
"doctrine/annotations": "^1.13",
"doctrine/coding-standard": "^9.0.2 || ^10.0",
"doctrine/coding-standard": "^9.0.2 || ^11.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.8.5",
"phpstan/phpstan": "~1.4.10 || 1.9.4",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.1",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.27.0"
"vimeo/psalm": "4.30.0 || 5.3.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 2.0"

View File

@@ -32,59 +32,39 @@ The entity class:
namespace Geo\Entity;
/**
* @Entity
*/
use Geo\ValueObject\Point;
#[Entity]
class Location
{
/**
* @Column(type="point")
*
* @var \Geo\ValueObject\Point
*/
private $point;
#[Column(type: 'point')]
private Point $point;
/**
* @Column(type="string")
*
* @var string
*/
private $address;
#[Column]
private string $address;
/**
* @param \Geo\ValueObject\Point $point
*/
public function setPoint(\Geo\ValueObject\Point $point)
public function setPoint(Point $point): void
{
$this->point = $point;
}
/**
* @return \Geo\ValueObject\Point
*/
public function getPoint()
public function getPoint(): Point
{
return $this->point;
}
/**
* @param string $address
*/
public function setAddress($address)
public function setAddress(string $address): void
{
$this->address = $address;
}
/**
* @return string
*/
public function getAddress()
public function getAddress(): string
{
return $this->address;
}
}
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
We use the custom type ``point`` in the ``#[Column]`` attribute of the
``$point`` field. We will create this custom mapping type in the next chapter.
The point class:
@@ -97,29 +77,18 @@ The point class:
class Point
{
/**
* @param float $latitude
* @param float $longitude
*/
public function __construct($latitude, $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
public function __construct(
private float $latitude,
private float $longitude,
) {
}
/**
* @return float
*/
public function getLatitude()
public function getLatitude(): float
{
return $this->latitude;
}
/**
* @return float
*/
public function getLongitude()
public function getLongitude(): float
{
return $this->longitude;
}

View File

@@ -23,48 +23,32 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
namespace Test;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
"cd" = "Test\Decorator\ConcreteDecorator"})
*/
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['cc' => Component\ConcreteComponent::class,
'cd' => Decorator\ConcreteDecorator::class])]
abstract class Component
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
#[Id, Column]
#[GeneratedValue(strategy: 'AUTO')]
protected int|null $id = null;
/** @Column(type="string", nullable=true) */
#[Column(type: 'string', nullable: true)]
protected $name;
/**
* Get id
* @return integer $id
*/
public function getId()
public function getId(): int|null
{
return $this->id;
}
/**
* Set name
* @param string $name
*/
public function setName($name)
public function setName(string $name): void
{
$this->name = $name;
}
/**
* Get name
* @return string $name
*/
public function getName()
public function getName(): string
{
return $this->name;
}
@@ -86,7 +70,7 @@ purpose of keeping this example simple).
use Test\Component;
/** @Entity */
#[Entity]
class ConcreteComponent extends Component
{}
@@ -103,14 +87,11 @@ use a ``MappedSuperclass`` for this.
namespace Test;
/** @MappedSuperclass */
#[MappedSuperclass]
abstract class Decorator extends Component
{
/**
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
* @JoinColumn(name="decorates", referencedColumnName="id")
*/
#[OneToOne(targetEntity: Component::class, cascade: ['all'])]
#[JoinColumn(name: 'decorates', referencedColumnName: 'id')]
protected $decorates;
/**
@@ -126,25 +107,19 @@ use a ``MappedSuperclass`` for this.
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName()
public function getName(): string
{
return 'Decorated ' . $this->getDecorates()->getName();
}
/**
* the component being decorated
* @return Component
*/
protected function getDecorates()
/** the component being decorated */
protected function getDecorates(): Component
{
return $this->decorates;
}
/**
* sets the component being decorated
* @param Component $c
*/
protected function setDecorates(Component $c)
/** sets the component being decorated */
protected function setDecorates(Component $c): void
{
$this->decorates = $c;
}
@@ -187,27 +162,19 @@ of the getSpecial() method to its return value.
use Test\Decorator;
/** @Entity */
#[Entity]
class ConcreteDecorator extends Decorator
{
/** @Column(type="string", nullable=true) */
protected $special;
#[Column(type: 'string', nullable: true)]
protected string|null $special = null;
/**
* Set special
* @param string $special
*/
public function setSpecial($special)
public function setSpecial(string|null $special): void
{
$this->special = $special;
}
/**
* Get special
* @return string $special
*/
public function getSpecial()
public function getSpecial(): string|null
{
return $this->special;
}
@@ -216,7 +183,7 @@ of the getSpecial() method to its return value.
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName()
public function getName(): string
{
return '[' . $this->getSpecial()
. '] ' . parent::getName();
@@ -270,4 +237,3 @@ objects
echo $d->getName();
// prints: [Really] Decorated Test Component 2

View File

@@ -29,15 +29,15 @@ implement the ``NotifyPropertyChanged`` interface from the
<?php
use Doctrine\Persistence\NotifyPropertyChanged;
use Doctrine\Persistence\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->listeners[] = $listener;
}
/** Notifies listeners of a change. */
protected function onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->listeners) {
@@ -55,12 +55,12 @@ listeners:
.. code-block:: php
<?php
// Mapping not shown, either in annotations, xml or yaml as usual
// Mapping not shown, either in attributes, annotations, xml or yaml as usual
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
@@ -73,5 +73,3 @@ The check whether the new value is different from the old one is
not mandatory but recommended. That way you can avoid unnecessary
updates and also have full control over when you consider a
property changed.

View File

@@ -154,8 +154,8 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
* This var contains the classname of the strategy
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine ORM)
*
* This is a doctrine field, so make sure that you use an @column annotation or setup your
* yaml or xml files correctly
* This is a doctrine field, so make sure that you use a
#[Column] attribute or setup your yaml or xml files correctly
* @var string
*/
protected $strategyClassName;
@@ -251,5 +251,3 @@ This might look like this:
In this example, even some variables are set - like a view object
or a specific configuration object.

View File

@@ -36,12 +36,12 @@ are allowed to:
public function assertCustomerAllowedBuying()
{
$orderLimit = $this->customer->getOrderLimit();
$amount = 0;
foreach ($this->orderLines as $line) {
$amount += $line->getAmount();
}
if ($amount > $orderLimit) {
throw new CustomerOrderLimitExceededException();
}
@@ -53,7 +53,21 @@ code, enforcing it at any time is important so that customers with
a unknown reputation don't owe your business too much money.
We can enforce this constraint in any of the metadata drivers.
First Annotations:
First Attributes:
.. code-block:: php
<?php
#[Entity]
#[HasLifecycleCallbacks]
class Order
{
#[PrePersist, PreUpdate]
public function assertCustomerAllowedBuying() {}
}
As Annotations:
.. code-block:: php
@@ -83,9 +97,6 @@ In XML Mappings:
</entity>
</doctrine-mapping>
YAML needs some little change yet, to allow multiple lifecycle
events for one method, this will happen before Beta 1 though.
Now validation is performed whenever you call
``EntityManager#persist($order)`` or when you call
``EntityManager#flush()`` and an order is about to be updated. Any
@@ -101,19 +112,17 @@ validation callbacks.
<?php
class Order
{
/**
* @PrePersist @PreUpdate
*/
#[PrePersist, PreUpdate]
public function validate()
{
if (!($this->plannedShipDate instanceof DateTime)) {
throw new ValidateException();
}
if ($this->plannedShipDate->format('U') < time()) {
throw new ValidateException();
}
if ($this->customer == null) {
throw new OrderRequiresCustomerException();
}

View File

@@ -15,13 +15,16 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
.. code-block:: php
<?php
/** @Entity */
use DateTime;
#[Entity]
class Article
{
/** @Column(type="datetime") */
private $updated;
#[Column(type='datetime')]
private DateTime $updated;
public function setUpdated()
public function setUpdated(): void
{
// will NOT be saved in the database
$this->updated->modify("now");
@@ -33,12 +36,14 @@ The way to go would be:
.. code-block:: php
<?php
use DateTime;
class Article
{
public function setUpdated()
public function setUpdated(): void
{
// WILL be saved in the database
$this->updated = new \DateTime("now");
$this->updated = new DateTime("now");
}
}
@@ -84,16 +89,14 @@ the UTC time at the time of the booking and the timezone the event happened in.
namespace DoctrineExtensions\DBAL\Types;
use DateTimeZone;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateTimeType;
class UTCDateTimeType extends DateTimeType
{
/**
* @var \DateTimeZone
*/
private static $utc;
private static DateTimeZone $utc;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
@@ -126,10 +129,10 @@ the UTC time at the time of the booking and the timezone the event happened in.
return $converted;
}
private static function getUtc(): \DateTimeZone
private static function getUtc(): DateTimeZone
{
return self::$utc ?: self::$utc = new \DateTimeZone('UTC');
return self::$utc ??= new DateTimeZone('UTC');
}
}

View File

@@ -12,6 +12,7 @@ steps of configuration.
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\ORMSetup;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
@@ -28,7 +29,7 @@ steps of configuration.
$config = new Configuration;
$config->setMetadataCache($metadataCache);
$driverImpl = ORMSetup::createDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
@@ -119,23 +120,22 @@ There are currently 5 available implementations:
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
Throughout the most part of this manual the AnnotationDriver is
used in the examples. For information on the usage of the XmlDriver
or YamlDriver please refer to the dedicated chapters
``XML Mapping`` and ``YAML Mapping``.
Throughout the most part of this manual the AttributeDriver is
used in the examples. For information on the usage of the
AnnotationDriver, XmlDriver or YamlDriver please refer to the dedicated
chapters ``Annotation Reference``, ``XML Mapping`` and ``YAML Mapping``.
The annotation driver can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
.. code-block:: php
<?php
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
$driverImpl = ORMSetup::createDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the annotation
The path information to the entities is required for the attribute
driver, because otherwise mass-operations on all entities through
the console could not work correctly. All of metadata drivers
accept either a single directory as a string or an array of
@@ -152,7 +152,7 @@ Metadata Cache (***RECOMMENDED***)
$config->getMetadataCache();
Gets or sets the cache adapter to use for caching metadata
information, that is, all the information you supply via
information, that is, all the information you supply via attributes,
annotations, xml or yaml, so that they do not need to be parsed and
loaded from scratch on every single request which is a waste of
resources. The cache implementation must implement the PSR-6
@@ -404,15 +404,15 @@ Multiple Metadata Sources
When using different components using Doctrine ORM you may end up
with them using two different metadata drivers, for example XML and
YAML. You can use the DriverChain Metadata implementations to
YAML. You can use the MappingDriverChain Metadata implementations to
aggregate these drivers based on namespaces:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\DriverChain;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
$chain = new DriverChain();
$chain = new MappingDriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');

View File

@@ -37,7 +37,26 @@ A many-to-one association is the most common association between objects. Exampl
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
#[ManyToOne(targetEntity: Address::class)]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
private Address|null $address = null;
}
#[Entity]
class Address
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -49,7 +68,7 @@ A many-to-one association is the most common association between objects. Exampl
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
private $address;
private Address|null $address = null;
}
/** @Entity */
@@ -82,9 +101,11 @@ A many-to-one association is the most common association between objects. Exampl
.. note::
The above ``@JoinColumn`` is optional as it would default
The above ``#[JoinColumn]`` is optional as it would default
to ``address_id`` and ``id`` anyways. You can omit it and let it
use the defaults.
Likewise, inside the ``#[ManyToOne]`` attribute you can omit the
``targetEntity`` argument and it will default to ``Address``.
Generated MySQL Schema:
@@ -111,7 +132,29 @@ references one ``Shipment`` entity.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class Product
{
// ...
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment|null $shipment = null;
// ...
}
#[Entity]
class Shipment
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -124,7 +167,7 @@ references one ``Shipment`` entity.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
private Shipment|null $shipment = null;
// ...
}
@@ -156,7 +199,7 @@ references one ``Shipment`` entity.
name: shipment_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
Note that the ``#[JoinColumn]`` is not really necessary in this example,
as the defaults would be the same.
Generated MySQL Schema:
@@ -188,7 +231,35 @@ object.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class Customer
{
// ...
/** One Customer has One Cart. */
#[OneToOne(targetEntity: Cart::class, mappedBy: 'customer')]
private Cart|null $cart = null;
// ...
}
#[Entity]
class Cart
{
// ...
/** One Cart has One Customer. */
#[OneToOne(targetEntity: Customer::class, inversedBy: 'cart')]
#[JoinColumn(name: 'customer_id', referencedColumnName: 'id')]
private Customer|null $customer = null;
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -200,7 +271,7 @@ object.
* One Customer has One Cart.
* @OneToOne(targetEntity="Cart", mappedBy="customer")
*/
private $cart;
private Cart|null $cart = null;
// ...
}
@@ -215,7 +286,7 @@ object.
* @OneToOne(targetEntity="Customer", inversedBy="cart")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
private Customer|null $customer = null;
// ...
}
@@ -281,17 +352,15 @@ below.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class Student
{
// ...
/**
* One Student has One Mentor.
* @OneToOne(targetEntity="Student")
* @JoinColumn(name="mentor_id", referencedColumnName="id")
*/
private $mentor;
/** One Student has One Mentor. */
#[OneToOne(targetEntity: Student::class)]
#[JoinColumn(name: 'mentor_id', referencedColumnName: 'id')]
private Student|null $mentor = null;
// ...
}
@@ -326,7 +395,40 @@ bidirectional many-to-one.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Product
{
// ...
/**
* One product has many features. This is the inverse side.
* @var Collection<int, Feature>
*/
#[OneToMany(targetEntity: Feature::class, mappedBy: 'product')]
private Collection $features;
// ...
public function __construct() {
$this->features = new ArrayCollection();
}
}
#[Entity]
class Feature
{
// ...
/** Many features have one product. This is the owning side. */
#[ManyToOne(targetEntity: Product::class, inversedBy: 'features')]
#[JoinColumn(name: 'product_id', referencedColumnName: 'id')]
private Product|null $product = null;
// ...
}
.. code-block:: annotation
<?php
use Doctrine\Common\Collections\ArrayCollection;
@@ -337,9 +439,10 @@ bidirectional many-to-one.
// ...
/**
* One product has many features. This is the inverse side.
* @var Collection<int, Feature>
* @OneToMany(targetEntity="Feature", mappedBy="product")
*/
private $features;
private Collection $features;
// ...
public function __construct() {
@@ -356,7 +459,7 @@ bidirectional many-to-one.
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
private Product|null $product = null;
// ...
}
@@ -421,7 +524,39 @@ The following example sets up such a unidirectional one-to-many association:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
/**
* Many Users have Many Phonenumbers.
* @var Collection<int, Phonenumber>
*/
#[JoinTable(name: 'users_phonenumbers')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'phonenumber_id', referencedColumnName: 'id', unique: true)]
#[ManyToMany(targetEntity: 'Phonenumber')]
private Collection $phonenumbers;
public function __construct()
{
$this->phonenumbers = new ArrayCollection();
}
// ...
}
#[Entity]
class Phonenumber
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -436,8 +571,9 @@ The following example sets up such a unidirectional one-to-many association:
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
* @var Collection<int, Phonenumber>
*/
private $phonenumbers;
private Collection $phonenumbers;
public function __construct()
{
@@ -523,7 +659,32 @@ database perspective is known as an adjacency list approach.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class Category
{
// ...
/**
* One Category has Many Categories.
* @var Collection<int, Category>
*/
#[OneToMany(targetEntity: Category::class, mappedBy: 'parent')]
private Collection $children;
/** Many Categories have One Category. */
#[ManyToOne(targetEntity: Category::class, inversedBy: 'children')]
#[JoinColumn(name: 'parent_id', referencedColumnName: 'id')]
private Category|null $parent = null;
// ...
public function __construct() {
$this->children = new ArrayCollection();
}
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -533,15 +694,16 @@ database perspective is known as an adjacency list approach.
/**
* One Category has Many Categories.
* @OneToMany(targetEntity="Category", mappedBy="parent")
* @var Collection<int, Category>
*/
private $children;
private Collection $children;
/**
* Many Categories have One Category.
* @ManyToOne(targetEntity="Category", inversedBy="children")
* @JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
private Category|null $parent = null;
// ...
public function __construct() {
@@ -594,7 +756,38 @@ entities:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
*/
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
public function __construct() {
$this->groups = new ArrayCollection();
}
}
#[Entity]
class Group
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -609,8 +802,9 @@ entities:
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
private $groups;
private Collection $groups;
// ...
@@ -695,7 +889,48 @@ one is bidirectional.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
*/
#[ManyToMany(targetEntity: Group::class, inversedBy: 'users')]
#[JoinTable(name: 'users_groups')]
private Collection $groups;
public function __construct() {
$this->groups = new ArrayCollection();
}
// ...
}
#[Entity]
class Group
{
// ...
/**
* Many Groups have Many Users.
* @var Collection<int, User>
*/
#[ManyToMany(targetEntity: User::class, mappedBy: 'groups')]
private Collection $users;
public function __construct() {
$this->users = new ArrayCollection();
}
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -707,8 +942,9 @@ one is bidirectional.
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups")
* @var Collection<int, Group>
*/
private $groups;
private Collection $groups;
public function __construct() {
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
@@ -724,8 +960,9 @@ one is bidirectional.
/**
* Many Groups have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="groups")
* @var Collection<int, User>
*/
private $users;
private Collection $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
@@ -806,9 +1043,9 @@ understandable:
<?php
class Article
{
private $tags;
private Collection $tags;
public function addTag(Tag $tag)
public function addTag(Tag $tag): void
{
$tag->addArticle($this); // synchronously updating inverse side
$this->tags[] = $tag;
@@ -817,9 +1054,9 @@ understandable:
class Tag
{
private $articles;
private Collection $articles;
public function addArticle(Article $article)
public function addArticle(Article $article): void
{
$this->articles[] = $article;
}
@@ -847,30 +1084,31 @@ field named ``$friendsWithMe`` and ``$myFriends``.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class User
{
// ...
/**
* Many Users have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
* @var Collection<int, User>
*/
private $friendsWithMe;
#[ManyToMany(targetEntity: User::class, mappedBy: 'myFriends')]
private Collection $friendsWithMe;
/**
* Many Users have many Users.
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* @JoinTable(name="friends",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
* @var Collection<int, User>
*/
private $myFriends;
#[JoinTable(name: 'friends')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'friend_user_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'User', inversedBy: 'friendsWithMe')]
private Collection $myFriends;
public function __construct() {
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
$this->friendsWithMe = new ArrayCollection();
$this->myFriends = new ArrayCollection();
}
// ...
@@ -910,11 +1148,17 @@ As an example, consider this mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[OneToOne(targetEntity: Shipment::class)]
private Shipment|null $shipment = null;
.. code-block:: annotation
<?php
/** @OneToOne(targetEntity="Shipment") */
private $shipment;
private Shipment|null $shipment = null;
.. code-block:: xml
@@ -937,7 +1181,15 @@ mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment|null $shipment = null;
.. code-block:: annotation
<?php
/**
@@ -945,7 +1197,7 @@ mapping:
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
private Shipment|null $shipment = null;
.. code-block:: xml
@@ -973,14 +1225,29 @@ similar defaults. As an example, consider this mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
/** @ManyToMany(targetEntity="Group") */
private $groups;
/** @var Collection<int, Group> */
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @var Collection<int, Group>
*/
private Collection $groups;
// ...
}
@@ -1004,7 +1271,25 @@ This is essentially the same as the following, more verbose, mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
*/
#[JoinTable(name: 'User_Group')]
#[JoinColumn(name: 'User_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'Group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
}
.. code-block:: annotation
<?php
class User
@@ -1017,8 +1302,9 @@ This is essentially the same as the following, more verbose, mapping:
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
private $groups;
private Collection $groups;
// ...
}
@@ -1069,7 +1355,13 @@ attribute on ``JoinColumn`` will be inherited from PHP type. So that:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[OneToOne]
private Shipment $shipment;
.. code-block:: annotation
<?php
/** @OneToOne */
@@ -1094,7 +1386,7 @@ Is essentially the same as following:
.. configuration-block::
.. code-block:: php
.. code-block:: annotation
<?php
/**
@@ -1104,6 +1396,14 @@ Is essentially the same as following:
*/
private Shipment $shipment;
.. code-block:: attribute
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
private Shipment $shipment;
.. code-block:: xml
<doctrine-mapping>
@@ -1163,22 +1463,19 @@ and ``@ManyToMany`` associations in the constructor of your entities:
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
#[Entity]
class User
{
/**
* Many Users have Many Groups.
* @var Collection
* @ManyToMany(targetEntity="Group")
*/
private $groups;
/** Many Users have Many Groups. */
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
public function __construct()
{
$this->groups = new ArrayCollection();
}
public function getGroups()
public function getGroups(): Collection
{
return $this->groups;
}

View File

@@ -10,8 +10,8 @@ annotation metadata supported since the first version 2.0.
Index
-----
- :ref:`#[AssociationOverride] <attrref_associationoverride]`
- :ref:`#[AttributeOverride] <attrref_attributeoverride]`
- :ref:`#[AssociationOverride] <attrref_associationoverride>`
- :ref:`#[AttributeOverride] <attrref_attributeoverride>`
- :ref:`#[Column] <attrref_column>`
- :ref:`#[Cache] <attrref_cache>`
- :ref:`#[ChangeTrackingPolicy <attrref_changetrackingpolicy>`
@@ -27,7 +27,6 @@ Index
- :ref:`#[Id] <attrref_id>`
- :ref:`#[InheritanceType] <attrref_inheritancetype>`
- :ref:`#[JoinColumn] <attrref_joincolumn>`
- :ref:`#[JoinColumns] <attrref_joincolumns>`
- :ref:`#[JoinTable] <attrref_jointable>`
- :ref:`#[ManyToOne] <attrref_manytoone>`
- :ref:`#[ManyToMany] <attrref_manytomany>`
@@ -179,7 +178,7 @@ Optional parameters:
If not specified, default value is ``false``.
- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
included when inserting a new row into the underlying entities table.
If not specified, default value is true.
- **updatable**: Boolean value to determine if the column should be

View File

@@ -50,8 +50,9 @@ mapping metadata:
- :doc:`PHP code <php-mapping>`
- :doc:`YAML <yaml-mapping>` (deprecated and will be removed in ``doctrine/orm`` 3.0.)
This manual will usually show mapping metadata via docblock annotations, though
many examples also show the equivalent configuration in YAML and XML.
This manual will usually show mapping metadata via attributes, though
many examples also show the equivalent configuration in annotations,
YAML and XML.
.. note::
@@ -157,10 +158,10 @@ Property Mapping
The next step is mapping its properties to columns in the table.
To configure a property use the ``Column`` docblock annotation. The ``type``
attribute specifies the :ref:`Doctrine Mapping Type <reference-mapping-types>`
to use for the field. If the type is not specified, ``string`` is used as the
default.
To configure a property use the ``Column`` attribute. The ``type``
argument specifies the :ref:`Doctrine Mapping Type
<reference-mapping-types>` to use for the field. If the type is not
specified, ``string`` is used as the default.
.. configuration-block::
@@ -360,12 +361,23 @@ Identifiers / Primary Keys
--------------------------
Every entity class must have an identifier/primary key. You can select
the field that serves as the identifier with the ``@Id``
annotation.
the field that serves as the identifier with the ``#[Id]`` attribute.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class Message
{
#[Id]
#[Column(type: 'integer')]
#[GeneratedValue]
private int|null $id = null;
// ...
}
.. code-block:: annotation
<?php
class Message
@@ -375,7 +387,7 @@ annotation.
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
private int|null $id = null;
// ...
}
@@ -402,7 +414,7 @@ annotation.
fields:
# fields here
In most cases using the automatic generator strategy (``@GeneratedValue``) is
In most cases using the automatic generator strategy (``#[GeneratedValue]``) is
what you want. It defaults to the identifier generation mechanism your current
database vendor prefers: AUTO_INCREMENT with MySQL, sequences with PostgreSQL
and Oracle and so on.
@@ -438,8 +450,8 @@ Here is the list of possible generation strategies:
- ``NONE``: Tells Doctrine that the identifiers are assigned (and
thus generated) by your code. The assignment must take place before
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`` annotation.
same as leaving off the ``#[GeneratedValue]`` entirely.
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
Sequence Generator
@@ -451,7 +463,19 @@ besides specifying the sequence's name:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class Message
{
#[Id]
#[GeneratedValue(strategy: 'SEQUENCE')]
#[SequenceGenerator(sequenceName: 'message_seq', initialValue: 1, allocationSize: 100)]
protected int|null $id = null;
// ...
}
.. code-block:: annotation
<?php
class Message
@@ -461,7 +485,7 @@ besides specifying the sequence's name:
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
protected int|null $id = null;
// ...
}
@@ -526,11 +550,12 @@ need to access the sequence once to generate the identifiers for
Composite Keys
~~~~~~~~~~~~~~
With Doctrine ORM you can use composite primary keys, using ``@Id`` on more then
one column. Some restrictions exist opposed to using a single identifier in
this case: The use of the ``@GeneratedValue`` annotation is not supported,
which means you can only use composite keys if you generate the primary key
values yourself before calling ``EntityManager#persist()`` on the entity.
With Doctrine ORM you can use composite primary keys, using ``#[Id]`` on
more than one column. Some restrictions exist opposed to using a single
identifier in this case: The use of the ``#[GeneratedValue]`` attribute
is not supported, which means you can only use composite keys if you
generate the primary key values yourself before calling
``EntityManager#persist()`` on the entity.
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
<../tutorials/composite-primary-keys>`.
@@ -546,7 +571,8 @@ needs to be done explicitly using ticks in the definition.
.. code-block:: php
<?php
/** @Column(name="`number`", type="integer") */
#[Column(name: '`number`', type: 'integer')]
private $number;
Doctrine will then quote this column name in all SQL statements

View File

@@ -74,11 +74,13 @@ collections in entities in the constructor. Example:
<?php
namespace MyProject\Model;
use Doctrine\Common\Collections\ArrayCollection;
class User {
private $addresses;
private $articles;
/** @var Collection<int, Address> */
private Collection $addresses;
/** @var Collection<int, Article> */
private Collection $articles;
public function __construct() {
$this->addresses = new ArrayCollection;
$this->articles = new ArrayCollection;

View File

@@ -109,8 +109,9 @@ Metadata Cache
~~~~~~~~~~~~~~
Your class metadata can be parsed from a few different sources like
YAML, XML, Annotations, etc. Instead of parsing this information on
each request we should cache it using one of the cache drivers.
YAML, XML, Attributes, Annotations etc. Instead of parsing this
information on each request we should cache it using one of the cache
drivers.
Just like the query and result cache we need to configure it
first.
@@ -199,5 +200,3 @@ not letting your users' requests populate the cache.
You can read more about cache slams
`in this blog post <http://notmysock.org/blog/php/user-cache-timebomb.html>`_.

View File

@@ -49,10 +49,9 @@ This policy can be configured as follows:
.. code-block:: php
<?php
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
#[Entity]
#[ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
class User
{
// ...
@@ -78,18 +77,16 @@ follows:
<?php
use Doctrine\Persistence\NotifyPropertyChanged,
Doctrine\Persistence\PropertyChangedListener;
/**
* @Entity
* @ChangeTrackingPolicy("NOTIFY")
*/
#[Entity]
#[ChangeTrackingPolicy('NOTIFY')]
class MyEntity implements NotifyPropertyChanged
{
// ...
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
private array $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener): void
{
$this->_listeners[] = $listener;
}
@@ -104,12 +101,12 @@ behaviour:
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue)
protected function _onPropertyChanged($propName, $oldValue, $newValue): void
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
@@ -117,8 +114,8 @@ behaviour:
}
}
}
public function setData($data)
public function setData($data): void
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
@@ -134,18 +131,18 @@ The check whether the new value is different from the old one is
not mandatory but recommended. That way you also have full control
over when you consider a property changed.
If your entity contains an embeddable, you will need to notify
separately for each property in the embeddable when it changes
If your entity contains an embeddable, you will need to notify
separately for each property in the embeddable when it changes
for example:
.. code-block:: php
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
public function setEmbeddable(MyValueObject $embeddable)
public function setEmbeddable(MyValueObject $embeddable): void
{
if (!$embeddable->equals($this->embeddable)) {
// notice the entityField.embeddableField notation for referencing the property
@@ -178,5 +175,3 @@ The positive point and main advantage of this policy is its
effectiveness. It has the best performance characteristics of the 3
policies with larger units of work and a flush() operation is very
cheap when nothing has changed.

View File

@@ -55,7 +55,7 @@ access point to ORM functionality provided by Doctrine.
'dbname' => 'foo',
);
$config = ORMSetup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
.. note::
@@ -83,9 +83,9 @@ Or if you prefer YAML:
.. note::
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
::
"symfony/yaml": "*"
Inside the ``ORMSetup`` methods several assumptions are made:

View File

@@ -672,18 +672,18 @@ The same restrictions apply for the reference of related entities.
DQL DELETE statements are ported directly into an SQL DELETE statement.
Therefore, some limitations apply:
- Lifecycle events for the affected entities are not executed.
- A cascading ``remove`` operation (as indicated e. g. by ``cascade={"remove"}``
or ``cascade={"all"}`` in the mapping configuration) is not being performed
- A cascading ``remove`` operation (as indicated e. g. by ``cascade: ['remove']``
or ``cascade: ['all']`` in the mapping configuration) is not being performed
for associated entities. You can rely on database level cascade operations by
configuring each join column with the ``onDelete`` option.
- Checks for the version column are bypassed if they are not explicitly added
to the WHERE clause of the query.
When you rely on one of these features, one option is to use the
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
It means collections and related entities are fetched into memory
When you rely on one of these features, one option is to use the
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
It means collections and related entities are fetched into memory
(even if they are marked as lazy). Pulling object graphs into memory on cascade
can cause considerable performance overhead, especially when the cascaded collections
are large. Make sure to weigh the benefits and downsides.
@@ -864,36 +864,26 @@ scenario it is a generic Person and Employee example:
<?php
namespace Entities;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])]
class Person
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
protected $id;
#[Id, Column(type: 'integer')]
#[GeneratedValue]
protected int|null $id = null;
/**
* @Column(type="string", length=50)
*/
protected $name;
#[Column(type: 'string', length: 50)]
protected string $name;
// ...
}
/**
* @Entity
*/
#[Entity]
class Employee extends Person
{
/**
* @Column(type="string", length=50)
*/
#[Column(type: 'string', length: 50)]
private $department;
// ...
@@ -959,12 +949,11 @@ table, you just need to change the inheritance type from
.. code-block:: php
<?php
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
#[Entity]
#[InheritanceType('JOINED')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])]
class Person
{
// ...
@@ -1841,5 +1830,3 @@ Functions
"LOWER" "(" StringPrimary ")" |
"UPPER" "(" StringPrimary ")" |
"IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"

View File

@@ -827,7 +827,41 @@ you need to map the listener method using the event type mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\Persistence\Event\LifecycleEventArgs;
class UserListener
{
#[PrePersist]
public function prePersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
#[PostPersist]
public function postPersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
#[PreUpdate]
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
#[PostUpdate]
public function postUpdateHandler(User $user, LifecycleEventArgs $event): void { // ... }
#[PostRemove]
public function postRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
#[PreRemove]
public function preRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
#[PreFlush]
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
#[PostLoad]
public function postLoadHandler(User $user, LifecycleEventArgs $event): void { // ... }
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Event\PreUpdateEventArgs;
@@ -837,29 +871,30 @@ you need to map the listener method using the event type mapping:
class UserListener
{
/** @PrePersist */
public function prePersistHandler(User $user, LifecycleEventArgs $event) { // ... }
public function prePersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
/** @PostPersist */
public function postPersistHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postPersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
/** @PreUpdate */
public function preUpdateHandler(User $user, PreUpdateEventArgs $event) { // ... }
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
/** @PostUpdate */
public function postUpdateHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postUpdateHandler(User $user, LifecycleEventArgs $event): void { // ... }
/** @PostRemove */
public function postRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
/** @PreRemove */
public function preRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
public function preRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
/** @PreFlush */
public function preFlushHandler(User $user, PreFlushEventArgs $event) { // ... }
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
/** @PostLoad */
public function postLoadHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postLoadHandler(User $user, LifecycleEventArgs $event): void { // ... }
}
.. code-block:: xml
<doctrine-mapping>
@@ -980,7 +1015,7 @@ Load ClassMetadata Event
``loadClassMetadata`` - The ``loadClassMetadata`` event occurs after the
mapping metadata for a class has been loaded from a mapping source
(annotations/xml/yaml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
(attributes/annotations/xml/yaml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
You can hook in to this process and manipulate the instance.
This event is not a lifecycle callback.

View File

@@ -13,10 +13,11 @@ Database Schema
How do I set the charset and collation for MySQL tables?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can't set these values inside the annotations, yml or xml mapping files. To make a database
work with the default charset and collation you should configure MySQL to use it as default charset,
or create the database with charset and collation details. This way they get inherited to all newly
created database tables and columns.
You can't set these values with attributes, annotations or inside yml or
xml mapping files. To make a database work with the default charset and
collation you should configure MySQL to use it as default charset, or
create the database with charset and collation details. This way they
get inherited to all newly created database tables and columns.
Entity Classes
--------------
@@ -32,11 +33,12 @@ upon insert:
class User
{
const STATUS_DISABLED = 0;
const STATUS_ENABLED = 1;
private const STATUS_DISABLED = 0;
private const STATUS_ENABLED = 1;
private $algorithm = "sha1";
private $status = self:STATUS_DISABLED;
private string $algorithm = "sha1";
/** @var self::STATUS_* */
private int $status = self:STATUS_DISABLED;
}
.

View File

@@ -42,7 +42,7 @@ proper quoting of parameters.
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
// Check if the entity implements the LocalAware interface
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {

View File

@@ -38,38 +38,36 @@ Example:
use Doctrine\ORM\Mapping\MappedSuperclass;
use Doctrine\ORM\Mapping\Entity;
/** @MappedSuperclass */
#[MappedSuperclass]
class Person
{
/** @Column(type="integer") */
protected $mapped1;
/** @Column(type="string") */
protected $mapped2;
/**
* @OneToOne(targetEntity="Toothbrush")
* @JoinColumn(name="toothbrush_id", referencedColumnName="id")
*/
protected $toothbrush;
#[Column(type: 'integer')]
protected int $mapped1;
#[Column(type: 'string')]
protected string $mapped2;
#[OneToOne(targetEntity: Toothbrush::class)]
#[JoinColumn(name: 'toothbrush_id', referencedColumnName: 'id')]
protected Toothbrush|null $toothbrush = null;
// ... more fields and methods
}
/** @Entity */
#[Entity]
class Employee extends Person
{
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
#[Id, Column(type: 'integer')]
private int|null $id = null;
#[Column(type: 'string')]
private string $name;
// ... more fields and methods
}
/** @Entity */
#[Entity]
class Toothbrush
{
/** @Id @Column(type="integer") */
private $id;
#[Id, Column(type: 'integer')]
private int|null $id = null;
// ... more fields and methods
}
@@ -99,11 +97,31 @@ Example:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace MyProject\Model;
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
class Person
{
// ...
}
#[Entity]
class Employee extends Person
{
// ...
}
.. code-block:: annotation
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
@@ -114,7 +132,7 @@ Example:
{
// ...
}
/**
* @Entity
*/
@@ -124,7 +142,7 @@ Example:
}
.. code-block:: yaml
MyProject\Model\Person:
type: entity
inheritanceType: SINGLE_TABLE
@@ -134,29 +152,30 @@ Example:
discriminatorMap:
person: Person
employee: Employee
MyProject\Model\Employee:
type: entity
Things to note:
- The @InheritanceType and @DiscriminatorColumn must be specified
on the topmost class that is part of the mapped entity hierarchy.
- The @DiscriminatorMap specifies which values of the
- The ``#[InheritanceType]`` and ``#[DiscriminatorColumn]`` must be
specified on the topmost class that is part of the mapped entity
hierarchy.
- The ``#[DiscriminatorMap]`` specifies which values of the
discriminator column identify a row as being of a certain type. In
the case above a value of "person" identifies a row as being of
type ``Person`` and "employee" identifies a row as being of type
``Employee``.
- All entity classes that is part of the mapped entity hierarchy
(including the topmost class) should be specified in the
@DiscriminatorMap. In the case above Person class included.
``#[DiscriminatorMap]``. In the case above Person class included.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
Design-time considerations
@@ -212,19 +231,17 @@ Example:
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
#[Entity]
#[InheritanceType('JOINED')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
class Person
{
// ...
}
/** @Entity */
#[Entity]
class Employee extends Person
{
// ...
@@ -233,10 +250,10 @@ Example:
Things to note:
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
must be specified on the topmost class that is part of the mapped
entity hierarchy.
- The @DiscriminatorMap specifies which values of the
- The ``#[InheritanceType]``, ``#[DiscriminatorColumn]`` and
``#[DiscriminatorMap]`` must be specified on the topmost class that is
part of the mapped entity hierarchy.
- The ``#[DiscriminatorMap]`` specifies which values of the
discriminator column identify a row as being of which type. In the
case above a value of "person" identifies a row as being of type
``Person`` and "employee" identifies a row as being of type
@@ -246,7 +263,7 @@ Things to note:
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
.. note::
@@ -290,7 +307,7 @@ be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
There is also another important performance consideration that it is *NOT POSSIBLE*
There is also another important performance consideration that it is *NOT POSSIBLE*
to query for the base entity without any LEFT JOINs to the sub-types.
SQL Schema considerations
@@ -330,7 +347,52 @@ Example:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// user mapping
namespace MyProject\Model;
#[MappedSuperclass]
class User
{
// other fields mapping
/** @var Collection<int, Group> */
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'Group', inversedBy: 'users')]
protected Collection $groups;
#[ManyToOne(targetEntity: 'Address')]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
protected Address|null $address = null;
}
// admin mapping
namespace MyProject\Model;
#[Entity]
#[AssociationOverrides([
new AssociationOverride(
name: 'groups',
joinTable: new JoinTable(
name: 'users_admingroups',
),
joinColumns: [new JoinColumn(name: 'adminuser_id')],
inverseJoinColumns: [new JoinColumn(name: 'admingroup_id')]
),
new AssociationOverride(
name: 'address',
joinColumns: [new JoinColumn(name: 'adminaddress_id', referencedColumnName: 'id')]
)
])]
class Admin extends User
{
}
.. code-block:: annotation
<?php
// user mapping
@@ -348,14 +410,15 @@ Example:
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
protected $groups;
protected Collection $groups;
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
protected $address;
protected Address|null $address = null;
}
// admin mapping
@@ -490,7 +553,51 @@ Could be used by an entity that extends a mapped superclass to override a field
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// user mapping
namespace MyProject\Model;
#[MappedSuperclass]
class User
{
#[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)]
protected int|null $id = null;
#[Column(name: 'user_name', nullable: true, unique: false, length: 250)]
protected string $name;
// other fields mapping
}
// guest mapping
namespace MyProject\Model;
#[Entity]
#[AttributeOverrides([
new AttributeOverride(
name: 'id',
column: new Column(
name: 'guest_id',
type: 'integer',
length: 140
)
),
new AttributeOverride(
name: 'name',
column: new Column(
name: 'guest_name',
nullable: false,
unique: true,
length: 240
)
)
])]
class Guest extends User
{
}
.. code-block:: annotation
<?php
// user mapping
@@ -501,10 +608,10 @@ Could be used by an entity that extends a mapped superclass to override a field
class User
{
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
protected $id;
protected int|null $id = null;
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
protected $name;
protected string $name;
// other fields mapping
}

View File

@@ -7,7 +7,7 @@ reduce the verbosity of the mapping document, eliminating repetitive noise (eg:
.. warning
The naming strategy is always overridden by entity mapping such as the `Table` annotation.
The naming strategy is always overridden by entity mapping such as the `Table` attribute.
Configuring a naming strategy
-----------------------------

View File

@@ -90,8 +90,8 @@ Static Function
In addition to the PHP files you can also specify your mapping
information inside of a static function defined on the entity class
itself. This is useful for cases where you want to keep your entity
and mapping information together but don't want to use annotations.
For this you just need to use the ``StaticPHPDriver``:
and mapping information together but don't want to use attributes or
annotations. For this you just need to use the ``StaticPHPDriver``:
.. code-block:: php
@@ -325,5 +325,3 @@ entities themselves.
- ``setIdentifierValues($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``

View File

@@ -434,6 +434,12 @@ complete list of supported helper methods available:
// Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
public function isNotNull($x); // Returns string
// Example - $qb->expr()->isMemberOf('?1', 'u.groups') => ?1 MEMBER OF u.groups
public function isMemberOf($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->isInstanceOf('u', Employee::class) => u INSTANCE OF Employee
public function isInstanceOf($x, $y); // Returns Expr\Comparison instance
/** Arithmetic objects **/

View File

@@ -277,26 +277,44 @@ level cache region.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
#[Cache(usage: 'READ_ONLY', region: 'my_entity_region')]
class Country
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
#[Column(unique: true)]
protected string $name;
// other properties and methods
}
.. code-block:: annotation
<?php
/**
* @Entity
* @Cache(usage="READ_ONLY", region="my_entity_region")
* @Cache("NONSTRICT_READ_WRITE")
*/
class Country
class State
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
protected int|null $id = null;
/**
* @Column(unique=true)
*/
protected $name;
protected string $name;
// other properties and methods
}
@@ -340,7 +358,35 @@ It caches the primary keys of association and cache each element will be cached
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
class State
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
#[Column(unique: true)]
protected string $name;
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[ManyToOne(targetEntity: Country::class)]
#[JoinColumn(name: 'country_id', referencedColumnName: 'id')]
protected Country|null $country = null;
/** @var Collection<int, City> */
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[OneToMany(targetEntity: City::class, mappedBy: 'state')]
protected Collection $cities;
// other properties and methods
}
.. code-block:: annotation
<?php
/**
@@ -354,25 +400,26 @@ It caches the primary keys of association and cache each element will be cached
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
protected int|null $id = null;
/**
* @Column(unique=true)
*/
protected $name;
protected string $name;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country_id", referencedColumnName="id")
*/
protected $country;
protected Country|null $country;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
* @var Collection<int, City>
*/
protected $cities;
protected Collection $cities;
// other properties and methods
}
@@ -673,23 +720,18 @@ For performance reasons the cache API does not extract from composite primary ke
.. code-block:: php
<?php
/**
* @Entity
*/
#[Entity]
class Reference
{
/**
* @Id
* @ManyToOne(targetEntity="Article", inversedBy="references")
* @JoinColumn(name="source_id", referencedColumnName="article_id")
*/
private $source;
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'source_id', referencedColumnName: 'article_id')]
private Article|null $source = null;
/**
* @Id
* @ManyToOne(targetEntity="Article")
* @JoinColumn(name="target_id", referencedColumnName="article_id")
*/
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'target_id', referencedColumnName: 'article_id')]
private $target;
}

View File

@@ -98,19 +98,20 @@ entity might look like this:
<?php
/**
* @Entity
*/
#[Entity]
class InsecureEntity
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column */
private $email;
/** @Column(type="boolean") */
private $isAdmin;
#[Id, Column, GeneratedValue]
private int|null $id = null;
public function fromArray(array $userInput)
#[Column]
private string $email;
#[Column]
private bool $isAdmin;
/** @param array<string, mixed> $userInput */
public function fromArray(array $userInput): void
{
foreach ($userInput as $key => $value) {
$this->$key = $value;
@@ -118,7 +119,7 @@ entity might look like this:
}
}
Now the possiblity of mass-asignment exists on this entity and can
Now the possiblity of mass-assignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:

View File

@@ -189,14 +189,25 @@ example we'll use an integer.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
#[Version, Column(type: 'integer')]
private int $version;
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
private int $version;
// ...
}
@@ -222,14 +233,25 @@ timestamp or datetime):
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
#[Version, Column(type: 'datetime')]
private DateTime $version;
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
private DateTime $version;
// ...
}
@@ -279,15 +301,15 @@ either when calling ``EntityManager#find()``:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
@@ -300,16 +322,16 @@ Or you can use ``EntityManager#lock()`` to find out:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
$entity = $em->find('User', $theEntityId);
try {
// assert version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
@@ -348,7 +370,7 @@ See the example code, The form (GET Request):
<?php
$post = $em->find('BlogPost', 123456);
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
@@ -359,7 +381,7 @@ And the change headline action (POST Request):
<?php
$postId = (int)$_GET['id'];
$postVersion = (int)$_GET['version'];
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
.. _transactions-and-concurrency_pessimistic-locking:
@@ -405,5 +427,3 @@ You can use pessimistic locks in three different scenarios:
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``

View File

@@ -17,7 +17,7 @@ ask for an entity with a specific ID twice, it will return the same instance:
.. code-block:: php
public function testIdentityMap()
public function testIdentityMap(): void
{
$objectA = $this->entityManager->find('EntityName', 1);
$objectB = $this->entityManager->find('EntityName', 1);
@@ -34,7 +34,7 @@ will still end up with the same reference:
.. code-block:: php
public function testIdentityMapReference()
public function testIdentityMapReference(): void
{
$objectA = $this->entityManager->getReference('EntityName', 1);
// check for proxyinterface
@@ -104,7 +104,7 @@ How Doctrine Detects Changes
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
This means you map php objects into a relational database that don't
necessarily know about the database at all. A natural question would now be,
"how does Doctrine even detect objects have changed?".
"how does Doctrine even detect objects have changed?".
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
an object from the database Doctrine will keep a copy of all the properties and
@@ -202,4 +202,3 @@ ClassMetadataFactory
~~~~~~~~~~~~~~~~~~~~
tbr

View File

@@ -32,62 +32,62 @@ information about its type and if it's the owning or inverse side.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class User
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
#[Id, GeneratedValue, Column]
private int|null $id = null;
/**
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
*
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
* @JoinTable(name="user_favorite_comments")
* @var Collection<int, Comment>
*/
private $favorites;
#[ManyToMany(targetEntity: Comment::class, inversedBy: 'userFavorites')]
#[JoinTable(name: 'user_favorite_comments')]
private Collection $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @ManyToMany(targetEntity="Comment")
* @JoinTable(name="user_read_comments")
* @var Collection<int, Comment>
*/
private $commentsRead;
#[ManyToMany(targetEntity: Comment::class)]
#[JoinTable(name: 'user_read_comments')]
private Collection $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author")
* @var Collection<int, Comment>
*/
private $commentsAuthored;
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author')]
private Collection $commentsAuthored;
/**
* Unidirectional - Many-To-One
*
* @ManyToOne(targetEntity="Comment")
*/
private $firstComment;
/** Unidirectional - Many-To-One */
#[ManyToOne(targetEntity: Comment::class)]
private Comment|null $firstComment = null;
}
/** @Entity */
#[Entity]
class Comment
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
#[Id, GeneratedValue, Column]
private string $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @ManyToMany(targetEntity="User", mappedBy="favorites")
* @var Collection<int, User>
*/
private $userFavorites;
#[ManyToMany(targetEntity: User::class, mappedBy: 'favorites')]
private Collection $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
* @ManyToOne(targetEntity="User", inversedBy="commentsAuthored")
*/
private $author;
#[ManyToOne(targetEntity: User::class, inversedBy: 'commentsAuthored')]
private User|null $author = null;
}
This two entities generate the following MySQL Schema (Foreign Key
@@ -132,11 +132,12 @@ relations of the ``User``:
class User
{
// ...
public function getReadComments() {
/** @return Collection<int, Comment> */
public function getReadComments(): Collection {
return $this->commentsRead;
}
public function setFirstComment(Comment $c) {
public function setFirstComment(Comment $c): void {
$this->firstComment = $c;
}
}
@@ -172,11 +173,13 @@ fields on both sides:
{
// ..
public function getAuthoredComments() {
/** @return Collection<int, Comment> */
public function getAuthoredComments(): Collection {
return $this->commentsAuthored;
}
public function getFavoriteComments() {
/** @return Collection<int, Comment> */
public function getFavoriteComments(): Collection {
return $this->favorites;
}
}
@@ -185,11 +188,12 @@ fields on both sides:
{
// ...
public function getUserFavorites() {
/** @return Collection<int, User> */
public function getUserFavorites(): Collection {
return $this->userFavorites;
}
public function setAuthor(User $author = null) {
public function setAuthor(User|null $author = null): void {
$this->author = $author;
}
}
@@ -292,12 +296,12 @@ example that encapsulate much of the association management code:
class User
{
// ...
public function markCommentRead(Comment $comment) {
public function markCommentRead(Comment $comment): void {
// Collections implement ArrayAccess
$this->commentsRead[] = $comment;
}
public function addComment(Comment $comment) {
public function addComment(Comment $comment): void {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
}
@@ -305,16 +309,16 @@ example that encapsulate much of the association management code:
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c) {
private function setFirstComment(Comment $c): void {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment) {
public function addFavorite(Comment $comment): void {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment) {
public function removeFavorite(Comment $comment): void {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
@@ -324,11 +328,11 @@ example that encapsulate much of the association management code:
{
// ..
public function addUserFavorite(User $user) {
public function addUserFavorite(User $user): void {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user) {
public function removeUserFavorite(User $user): void {
$this->userFavorites->removeElement($user);
}
}
@@ -356,7 +360,8 @@ the details inside the classes can be challenging.
<?php
class User {
public function getReadComments() {
/** @return array<int, Comment> */
public function getReadComments(): array {
return $this->commentsRead->toArray();
}
}
@@ -437,8 +442,10 @@ only accessing it through the User entity:
// User entity
class User
{
private $id;
private $comments;
private int $id;
/** @var Collection<int, Comment> */
private Collection $comments;
public function __construct()
{
@@ -464,11 +471,8 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
class User
{
// ...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
/** Bidirectional - One-To-Many (INVERSE SIDE) */
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author', cascade: ['persist', 'remove'])]
private $commentsAuthored;
// ...
}
@@ -577,31 +581,30 @@ and StandingData:
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
*/
#[Entity]
class Contact
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @OneToOne(targetEntity="StandingData", cascade={"persist"}, orphanRemoval=true) */
private $standingData;
#[OneToOne(targetEntity: StandingData::class, cascade: ['persist'], orphanRemoval: true)]
private StandingData|null $standingData = null;
/** @OneToMany(targetEntity="Address", mappedBy="contact", cascade={"persist"}, orphanRemoval=true) */
private $addresses;
/** @var Collection<int, Address> */
#[OneToMany(targetEntity: Address::class, mappedBy: 'contact', cascade: ['persist'], orphanRemoval: true)]
private Collection $addresses;
public function __construct()
{
$this->addresses = new ArrayCollection();
}
public function newStandingData(StandingData $sd)
public function newStandingData(StandingData $sd): void
{
$this->standingData = $sd;
}
public function removeAddress($pos)
public function removeAddress(int $pos): void
{
unset($this->addresses[$pos]);
}

View File

@@ -95,28 +95,29 @@ from newly opened EntityManager.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @Column(type="string") */
private $headline;
#[Column(type: 'string')]
private string $headline;
/** @ManyToOne(targetEntity="User") */
private $author;
#[ManyToOne(targetEntity: User::class)]
private User|null $author = null;
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments;
/** @var Collection<int, Comment> */
#[OneToMany(targetEntity: Comment::class, mappedBy: 'article')]
private Collection $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getAuthor() { return $this->author; }
public function getComments() { return $this->comments; }
public function getAuthor(): User|null { return $this->author; }
public function getComments(): Collection { return $this->comments; }
}
$article = $em->find('Article', 1);
@@ -170,12 +171,12 @@ methods along the lines of the ``getName()`` method shown below:
<?php
class UserProxy extends User implements Proxy
{
private function _load()
private function _load(): void
{
// lazy loading code
}
public function getName()
public function getName(): string
{
$this->_load();
return parent::getName();
@@ -304,7 +305,7 @@ as follows:
- A removed entity X will be removed from the database as a result
of the flush operation.
After an entity has been removed its in-memory state is the same as
After an entity has been removed, its in-memory state is the same as
before the removal, except for generated identifiers.
Removing an entity will also automatically delete any existing
@@ -339,8 +340,8 @@ in multiple ways with very different performance impacts.
.. note::
Calling ``remove`` on an entity will remove the object from the identity
map and therefore detach it. Querying the same entity again, for example
via a lazy loaded relation, will return a new object.
map and therefore detach it. Querying the same entity again, for example
via a lazy loaded relation, will return a new object.
Detaching entities
@@ -833,7 +834,7 @@ By default the EntityManager returns a default implementation of
``Doctrine\ORM\EntityRepository`` when you call
``EntityManager#getRepository($entityClass)``. You can overwrite
this behaviour by specifying the class name of your own Entity
Repository in the Annotation, XML or YAML metadata. In large
Repository in the Attribute, Annotation, XML or YAML metadata. In large
applications that require lots of specialized DQL queries using a
custom repository is one recommended way of grouping these queries
in a central location.
@@ -843,12 +844,11 @@ in a central location.
<?php
namespace MyDomain\Model;
use MyDomain\Model\UserRepository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
@@ -856,7 +856,8 @@ in a central location.
class UserRepository extends EntityRepository
{
public function getAllAdminUsers()
/** @return Collection<User> */
public function getAllAdminUsers(): Collection
{
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
@@ -871,5 +872,3 @@ You can access your repository now by calling:
// $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();

View File

@@ -23,7 +23,34 @@ and year of production as primary keys:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace VehicleCatalogue\Model;
#[Entity]
class Car
{
public function __construct(
#[Id, Column(type: 'string')]
private string $name,
#[Id, Column(type: 'integer')]
private int $year,
) {
}
public function getModelName(): string
{
return $this->name;
}
public function getYearOfProduction(): int
{
return $this->year;
}
}
.. code-block:: annotation
<?php
namespace VehicleCatalogue\Model;
@@ -34,9 +61,9 @@ and year of production as primary keys:
class Car
{
/** @Id @Column(type="string") */
private $name;
private string $name;
/** @Id @Column(type="integer") */
private $year;
private int $year;
public function __construct($name, $year)
{
@@ -44,17 +71,38 @@ and year of production as primary keys:
$this->year = $year;
}
public function getModelName()
public function getModelName(): string
{
return $this->name;
}
public function getYearOfProduction()
public function getYearOfProduction(): int
{
return $this->year;
}
}
.. code-block:: annotation
<?php
/**
* @Entity
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
private int|null $id = null;
}
/**
* @Entity
*/
class Address
{
/** @Id @OneToOne(targetEntity="User") */
private User|null $user = null;
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
@@ -131,7 +179,7 @@ of one or many parent entities.
The semantics of mapping identity through foreign entities are easy:
- Only allowed on Many-To-One or One-To-One associations.
- Plug an ``@Id`` annotation onto every association.
- Plug an ``#[Id]`` attribute onto every association.
- Set an attribute ``association-key`` with the field name of the association in XML.
- Set a key ``associationKey:`` with the field name of the association in YAML.
@@ -155,16 +203,17 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
private int|null $id = null;
/** @Column(type="string") */
private $title;
private string $title;
/**
* @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
* @var Collection<int, ArticleAttribute>
*/
private $attributes;
private Collection $attributes;
public function addAttribute($name, $value)
public function addAttribute($name, $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
@@ -176,13 +225,13 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
class ArticleAttribute
{
/** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
private $article;
private Article|null $article;
/** @Id @Column(type="string") */
private $attribute;
private string $attribute;
/** @Column(type="string") */
private $value;
private string $value;
public function __construct($name, $value, $article)
{
@@ -192,6 +241,51 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
}
}
.. code-block:: attribute
<?php
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Article
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
private string $title;
/** @var ArrayCollection<string, ArticleAttribute> */
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
public function addAttribute(string $name, ArticleAttribute $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
#[Entity]
class ArticleAttribute
{
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
private Article $article;
#[Id, Column(type: 'string')]
private string $attribute;
#[Column(type: 'string')]
private string $value;
public function __construct(string $name, string $value, Article $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: xml
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
@@ -202,7 +296,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
<entity name="Application\Model\ArticleAttribute">
<id name="article" association-key="true" />
<id name="attribute" type="string" />
<field name="value" type="string" />
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
@@ -237,25 +331,22 @@ One good example for this is a user-address relationship:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
/**
* @Entity
*/
#[Entity]
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
}
/**
* @Entity
*/
#[Entity]
class Address
{
/** @Id @OneToOne(targetEntity="User") */
private $user;
#[Id, OneToOne(targetEntity: User::class)]
private User|null $user = null;
}
.. code-block:: yaml
@@ -288,68 +379,70 @@ of products purchased and maybe even the current price.
.. code-block:: php
<?php
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
#[Entity]
class Order
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @ManyToOne(targetEntity="Customer") */
private $customer;
/** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
/** @var ArrayCollection<int, OrderItem> */
#[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')]
private Collection $items;
/** @Column(type="boolean") */
private $paid = false;
/** @Column(type="boolean") */
private $shipped = false;
/** @Column(type="datetime") */
private $created;
#[Column(type: 'boolean')]
private bool $paid = false;
#[Column(type: 'boolean')]
private bool $shipped = false;
#[Column(type: 'datetime')]
private DateTime $created;
public function __construct(Customer $customer)
{
$this->customer = $customer;
public function __construct(
#[ManyToOne(targetEntity: Customer::class)]
private Customer $customer,
) {
$this->items = new ArrayCollection();
$this->created = new \DateTime("now");
$this->created = new DateTime("now");
}
}
/** @Entity */
#[Entity]
class Product
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @Column(type="string") */
private $name;
#[Column(type: 'string')]
private string $name;
/** @Column(type="decimal") */
private $currentPrice;
#[Column(type: 'decimal')]
private float $currentPrice;
public function getCurrentPrice()
public function getCurrentPrice(): float
{
return $this->currentPrice;
}
}
/** @Entity */
#[Entity]
class OrderItem
{
/** @Id @ManyToOne(targetEntity="Order") */
private $order;
#[Id, ManyToOne(targetEntity: Order::class)]
private Order|null $order = null;
/** @Id @ManyToOne(targetEntity="Product") */
private $product;
#[Id, ManyToOne(targetEntity: Product::class)]
private Product|null $product = null;
/** @Column(type="integer") */
private $amount = 1;
#[Column(type: 'integer')]
private int $amount = 1;
/** @Column(type="decimal") */
private $offeredPrice;
#[Column(type: 'decimal')]
private float $offeredPrice;
public function __construct(Order $order, Product $product, $amount = 1)
public function __construct(Order $order, Product $product, int $amount = 1)
{
$this->order = $order;
$this->product = $product;

View File

@@ -1,10 +1,10 @@
Separating Concerns using Embeddables
-------------------------------------
=====================================
Embeddables are classes which are not entities themselves, but are embedded
in entities and can also be queried in DQL. You'll mostly want to use them
to reduce duplication or separating concerns. Value objects such as date range
or address are the primary use case for this feature.
or address are the primary use case for this feature.
.. note::
@@ -17,7 +17,34 @@ instead of simply adding the respective columns to the ``User`` class.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
#[Embedded(class: Address::class)]
private Address $address;
}
#[Embeddable]
class Address
{
#[Column(type: "string")]
private string $street;
#[Column(type: "string")]
private string $postalCode;
#[Column(type: "string")]
private string $city;
#[Column(type: "string")]
private string $country;
}
.. code-block:: annotation
<?php
@@ -25,23 +52,23 @@ instead of simply adding the respective columns to the ``User`` class.
class User
{
/** @Embedded(class = "Address") */
private $address;
private Address $address;
}
/** @Embeddable */
class Address
{
/** @Column(type = "string") */
private $street;
private string $street;
/** @Column(type = "string") */
private $postalCode;
private string $postalCode;
/** @Column(type = "string") */
private $city;
private string $city;
/** @Column(type = "string") */
private $country;
private string $country;
}
.. code-block:: xml
@@ -109,7 +136,18 @@ The following example shows you how to set your prefix to ``myPrefix_``:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
#[Embedded(class: Address::class, columnPrefix: "myPrefix_")]
private Address $address;
}
.. code-block:: annotation
<?php
@@ -140,7 +178,18 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
#[Embedded(class: Address::class, columnPrefix: false)]
private Address $address;
}
.. code-block:: annotation
<?php
@@ -148,9 +197,15 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
class User
{
/** @Embedded(class = "Address", columnPrefix = false) */
private $address;
private Address $address;
}
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" use-column-prefix="false" />
</entity>
.. code-block:: yaml
User:
@@ -160,12 +215,6 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
class: Address
columnPrefix: false
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" use-column-prefix="false" />
</entity>
DQL
---
@@ -176,4 +225,3 @@ as if they were declared in the ``User`` class:
.. code-block:: sql
SELECT u FROM User u WHERE u.address.city = :myCity

View File

@@ -52,7 +52,20 @@ switch to extra lazy as shown in these examples:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Doctrine\Tests\Models\CMS;
#[Entity]
class CmsGroup
{
/** @var Collection<int, CmsUser> */
#[ManyToMany(targetEntity: CmsUser::class, mappedBy: 'groups', fetch: 'EXTRA_LAZY')]
public Collection $users;
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\CMS;
@@ -64,8 +77,9 @@ switch to extra lazy as shown in these examples:
{
/**
* @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
* @var Collection<int, CmsUser>
*/
public $users;
public Collection $users;
}
.. code-block:: xml
@@ -92,4 +106,3 @@ switch to extra lazy as shown in these examples:
targetEntity: CmsUser
mappedBy: groups
fetch: EXTRA_LAZY

View File

@@ -83,7 +83,6 @@ that directory with the following contents:
"require": {
"doctrine/orm": "^2.11.0",
"doctrine/dbal": "^3.2",
"doctrine/annotations": "1.13.2",
"symfony/yaml": "^5.4",
"symfony/cache": "^5.4"
},
@@ -142,15 +141,24 @@ step:
require_once "vendor/autoload.php";
// Create a simple "default" Doctrine ORM configuration for Annotations
$isDevMode = true;
$proxyDir = null;
$cache = null;
$useSimpleAnnotationReader = false;
$config = ORMSetup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
// or if you prefer YAML or XML
// $config = ORMSetup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
// $config = ORMSetup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
// Create a simple "default" Doctrine ORM configuration for Attributes
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: array(__DIR__."/src"),
isDevMode: true,
);
// or if you prefer annotation, YAML or XML
// $config = ORMSetup::createAnnotationMetadataConfiguration(
// paths: array(__DIR__."/src"),
// isDevMode: true,
// );
// $config = ORMSetup::createXMLMetadataConfiguration(
// paths: array(__DIR__."/config/xml"),
// isDevMode: true,
//);
// $config = ORMSetup::createYAMLMetadataConfiguration(
// paths: array(__DIR__."/config/yaml"),
// isDevMode: true,
// );
// database configuration parameters
$conn = array(
@@ -165,10 +173,6 @@ step:
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
.. note::
It is recommended not to use the SimpleAnnotationReader because its
usage will be removed for version 3.0.
The ``require_once`` statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
@@ -256,14 +260,8 @@ entity definition:
// src/Product.php
class Product
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $name;
private int|null $id = null;
private string $name;
}
When creating entity classes, all of the fields should be ``private``.
@@ -500,14 +498,35 @@ the ``Product`` entity to Doctrine using a metadata language. The metadata
language describes how entities, their properties and references should be
persisted and what constraints should be applied to them.
Metadata for an Entity can be configured using DocBlock annotations directly
in the Entity class itself, or in an external XML or YAML file. This Getting
Started guide will demonstrate metadata mappings using all three methods,
but you only need to choose one.
Metadata for an Entity can be configured using attributes directly in
the Entity class itself, or in an external XML or YAML file. This
Getting Started guide will demonstrate metadata mappings using all three
methods, but you only need to choose one.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// src/Product.php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'products')]
class Product
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $name;
// .. (other code)
}
.. code-block:: annotation
<?php
// src/Product.php
@@ -525,11 +544,11 @@ but you only need to choose one.
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
private $id;
private int|null $id = null;
/**
* @ORM\Column(type="string")
*/
private $name;
private string $name;
// .. (other code)
}
@@ -708,49 +727,35 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="bugs")
*/
#[ORM\Entity]
#[ORM\Table(name: 'bugs')]
class Bug
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
* @var int
*/
private $id;
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int $id;
/**
* @ORM\Column(type="string")
* @var string
*/
private $description;
#[ORM\Column(type: 'string')]
private string $description;
/**
* @ORM\Column(type="datetime")
* @var DateTime
*/
private $created;
#[ORM\Column(type: 'datetime')]
private DateTime $created;
/**
* @ORM\Column(type="string")
* @var string
*/
private $status;
#[ORM\Column(type: 'string')]
private string $status;
public function getId()
public function getId(): int|null
{
return $this->id;
}
public function getDescription()
public function getDescription(): string
{
return $this->description;
}
public function setDescription($description)
public function setDescription(string $description): void
{
$this->description = $description;
}
@@ -760,17 +765,17 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
$this->created = $created;
}
public function getCreated()
public function getCreated(): DateTime
{
return $this->created;
}
public function setStatus($status)
public function setStatus($status): void
{
$this->status = $status;
}
public function getStatus()
public function getStatus():string
{
return $this->status;
}
@@ -783,37 +788,31 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="users")
*/
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @var int
*/
private $id;
/** @var int */
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int|null $id = null;
/**
* @ORM\Column(type="string")
* @var string
*/
private $name;
/** @var string */
#[ORM\Column(type: 'string')]
private string $name;
public function getId()
public function getId(): int|null
{
return $this->id;
}
public function getName()
public function getName(): string
{
return $this->name;
}
public function setName($name)
public function setName(string $name): void
{
$this->name = $name;
}
@@ -842,13 +841,16 @@ domain model to match the requirements:
<?php
// src/Bug.php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
class Bug
{
// ... (previous code)
private $products;
/** @var Collection<int, Product> */
private Collection $products;
public function __construct()
{
@@ -866,8 +868,10 @@ domain model to match the requirements:
{
// ... (previous code)
private $reportedBugs;
private $assignedBugs;
/** @var Collection<int, Bug> */
private Collection $reportedBugs;
/** @var Collection<int, Bug> */
private Collection $assignedBugs;
public function __construct()
{
@@ -942,27 +946,27 @@ the bi-directional reference:
{
// ... (previous code)
private $engineer;
private $reporter;
private User $engineer;
private User $reporter;
public function setEngineer(User $engineer)
public function setEngineer(User $engineer): void
{
$engineer->assignedToBug($this);
$this->engineer = $engineer;
}
public function setReporter(User $reporter)
public function setReporter(User $reporter): void
{
$reporter->addReportedBug($this);
$this->reporter = $reporter;
}
public function getEngineer()
public function getEngineer(): User
{
return $this->engineer;
}
public function getReporter()
public function getReporter(): User
{
return $this->reporter;
}
@@ -976,15 +980,17 @@ the bi-directional reference:
{
// ... (previous code)
private $reportedBugs;
private $assignedBugs;
/** @var Collection<int, Bug> */
private Collection $reportedBugs;
/** @var Collection<int, Bug> */
private Collection $assignedBugs;
public function addReportedBug(Bug $bug)
public function addReportedBug(Bug $bug): void
{
$this->reportedBugs[] = $bug;
}
public function assignedToBug(Bug $bug)
public function assignedToBug(Bug $bug): void
{
$this->assignedBugs[] = $bug;
}
@@ -1028,14 +1034,16 @@ the database that points from Bugs to Products.
{
// ... (previous code)
private $products;
/** @var Collection<int, Product> */
private Collection $products;
public function assignToProduct(Product $product)
public function assignToProduct(Product $product): void
{
$this->products[] = $product;
}
public function getProducts()
/** @return Collection<int, Product> */
public function getProducts(): Collection
{
return $this->products;
}
@@ -1046,7 +1054,46 @@ Lets add metadata mappings for the ``Bug`` entity, as we did for
the ``Product`` before:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// src/Bug.php
use DateTime;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'bugs')]
class Bug
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $description;
#[ORM\Column(type: 'datetime')]
private DateTime $created;
#[ORM\Column(type: 'string')]
private string $status;
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'assignedBugs')]
private User|null $engineer = null;
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'reportedBugs')]
private User|null $reporter;
/** @var Collection<int, Product> */
#[ORM\ManyToMany(targetEntity: Product::class)]
private Collection $products;
// ... (other code)
}
.. code-block:: annotation
<?php
// src/Bug.php
@@ -1064,37 +1111,37 @@ the ``Product`` before:
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
private $id;
private int|null $id = null;
/**
* @ORM\Column(type="string")
*/
private $description;
private string $description;
/**
* @ORM\Column(type="datetime")
*/
private $created;
private DateTime $created;
/**
* @ORM\Column(type="string")
*/
private $status;
private string $status;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
*/
private $engineer;
private User|null $engineer;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
*/
private $reporter;
private User|null $reporter;
/**
* @ORM\ManyToMany(targetEntity="Product")
*/
private $products;
private Collection $products;
// ... (other code)
}
@@ -1183,7 +1230,37 @@ Finally, we'll add metadata mappings for the ``User`` entity.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $name;
/** @var Collection<int, Bug> An ArrayCollection of Bug objects. */
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'reporter')]
private Collection $reportedBugs;
/** @var Collection<int,Bug> An ArrayCollection of Bug objects. */
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'engineer')]
private $assignedBugs;
// .. (other code)
}
.. code-block:: annotation
<?php
// src/User.php
@@ -1202,29 +1279,28 @@ Finally, we'll add metadata mappings for the ``User`` entity.
* @ORM\Column(type="integer")
* @var int
*/
private $id;
private int|null $id = null;
/**
* @ORM\Column(type="string")
* @var string
*/
private $name;
private string $name;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Bug[] An ArrayCollection of Bug objects.
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
*/
private $reportedBugs;
private Collection $reportedBugs;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Bug[] An ArrayCollection of Bug objects.
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
*/
private $assignedBugs;
private Collection $assignedBugs;
// .. (other code)
}
.. code-block:: xml
<!-- config/xml/User.dcm.xml -->
@@ -1754,7 +1830,20 @@ we have to adjust the metadata slightly.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: BugRepository::class)]
#[ORM\Table(name: 'bugs')]
class Bug
{
// ...
}
.. code-block:: annotation
<?php
@@ -1763,7 +1852,7 @@ we have to adjust the metadata slightly.
/**
* @ORM\Entity(repositoryClass="BugRepository")
* @ORM\Table(name="bugs")
**/
*/
class Bug
{
// ...

View File

@@ -5,28 +5,42 @@ There are use-cases when you'll want to sort collections when they are
retrieved from the database. In userland you do this as long as you
haven't initially saved an entity with its associations into the
database. To retrieve a sorted collection from the database you can
use the ``@OrderBy`` annotation with a collection that specifies
use the ``#[OrderBy]`` attribute with a collection that specifies
a DQL snippet that is appended to all queries with this
collection.
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
can specify the ``@OrderBy`` in the following way:
Additional to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute you
can specify the ``#[OrderBy]`` in the following way:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
#[ManyToMany(targetEntity: Group::class)]
#[OrderBy(["name" => "ASC"])]
private Collection $groups;
}
.. code-block:: annotation
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
**/
private $groups;
* @var Collection<int, Group>
*/
private Collection $groups;
}
.. code-block:: xml
@@ -62,7 +76,7 @@ The DQL Snippet in OrderBy is only allowed to consist of
unqualified, unquoted field names and of an optional ASC/DESC
positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
class of the ``#[ManyToMany]`` or ``#[OneToMany]`` attribute.
The semantics of this feature can be described as follows:
@@ -106,5 +120,3 @@ You can reverse the order with an explicit DQL ORDER BY:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC

View File

@@ -14,47 +14,43 @@ Suppose we have a class ExampleEntityWithOverride. This class uses trait Example
.. code-block:: php
<?php
/**
* @Entity
*
* @AttributeOverrides({
* @AttributeOverride(name="foo",
* column=@Column(
* name = "foo_overridden",
* type = "integer",
* length = 140,
* nullable = false,
* unique = false
* )
* )
* })
*
* @AssociationOverrides({
* @AssociationOverride(name="bar",
* joinColumns=@JoinColumn(
* name="example_entity_overridden_bar_id", referencedColumnName="id"
* )
* )
* })
*/
#[Entity]
#[AttributeOverrides([
new AttributeOverride('foo', [
'column' => new Column([
'name' => 'foo_overridden',
'type' => 'integer',
'length' => 140,
'nullable' => false,
'unique' => false,
]),
]),
])]
#[AssociationOverrides([
new AssociationOverride('bar', [
'joinColumns' => new JoinColumn([
'name' => 'example_entity_overridden_bar_id',
'referencedColumnName' => 'id',
]),
]),
])]
class ExampleEntityWithOverride
{
use ExampleTrait;
}
/**
* @Entity
*/
#[Entity]
class Bar
{
/** @Id @Column(type="string") */
#[Id, Column(type: 'string')]
private $id;
}
The docblock is showing metadata override of the attribute and association type. It
basically changes the names of the columns mapped for a property ``foo`` and for
the association ``bar`` which relates to Bar class shown above. Here is the trait
which has mapping metadata that is overridden by the annotation above:
which has mapping metadata that is overridden by the attribute above:
.. code-block:: php
@@ -64,19 +60,15 @@ which has mapping metadata that is overridden by the annotation above:
*/
trait ExampleTrait
{
/** @Id @Column(type="string") */
private $id;
#[Id, Column(type: 'integer')]
private int|null $id = null;
/**
* @Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true)
*/
protected $foo;
#[Column(name: 'trait_foo', type: 'integer', length: 100, nullable: true, unique: true)]
protected int $foo;
/**
* @OneToOne(targetEntity="Bar", cascade={"persist", "merge"})
* @JoinColumn(name="example_trait_bar_id", referencedColumnName="id")
*/
protected $bar;
#[OneToOne(targetEntity: Bar::class, cascade: ['persist', 'merge'])]
#[JoinColumn(name: 'example_trait_bar_id', referencedColumnName: 'id')]
protected Bar|null $bar = null;
}
The case for just extending a class would be just the same but:

View File

@@ -23,6 +23,7 @@ Mapping Indexed Associations
You can map indexed associations by adding:
* ``indexBy`` argument to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute.
* ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation.
* ``index-by`` attribute to any ``<one-to-many />`` or ``<many-to-many />`` xml element.
* ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files.
@@ -30,7 +31,66 @@ You can map indexed associations by adding:
The code and mappings for the Market entity looks like this:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Doctrine\Tests\Models\StockExchange;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[Entity]
#[Table(name: 'exchange_markets')]
class Market
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
private string $name;
/** @var Collection<string, Stock> */
#[OneToMany(targetEntity: Stock::class, mappedBy: 'market', indexBy: 'symbol')]
private Collection $stocks;
public function __construct(string $name)
{
$this->name = $name;
$this->stocks = new ArrayCollection();
}
public function getId(): int|null
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock(string $symbol): Stock
{
if (!isset($this->stocks[$symbol])) {
throw new \InvalidArgumentException("Symbol is not traded on this market.");
}
return $this->stocks[$symbol];
}
/** @return array<string, Stock> */
public function getStocks(): array
{
return $this->stocks->toArray();
}
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\StockExchange;
@@ -47,19 +107,19 @@ The code and mappings for the Market entity looks like this:
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
private $id;
private int|null $id = null;
/**
* @Column(type="string")
* @var string
*/
private $name;
private string $name;
/**
* @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol")
* @var Stock[]
* @var Collection<int, Stock>
*/
private $stocks;
private Collection $stocks;
public function __construct($name)
{
@@ -67,22 +127,22 @@ The code and mappings for the Market entity looks like this:
$this->stocks = new ArrayCollection();
}
public function getId()
public function getId(): int|null
{
return $this->id;
}
public function getName()
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock)
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock($symbol)
public function getStock($symbol): Stock
{
if (!isset($this->stocks[$symbol])) {
throw new \InvalidArgumentException("Symbol is not traded on this market.");
@@ -91,7 +151,8 @@ The code and mappings for the Market entity looks like this:
return $this->stocks[$symbol];
}
public function getStocks()
/** @return array<string, Stock> */
public function getStocks(): array
{
return $this->stocks->toArray();
}
@@ -142,7 +203,38 @@ The ``Stock`` entity doesn't contain any special instructions that are new, but
here are the code and mappings for it:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Doctrine\Tests\Models\StockExchange;
#[Entity]
#[Table(name: 'exchange_stocks')]
class Stock
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string', unique: true)]
private string $symbol;
#[ManyToOne(targetEntity: Market::class, inversedBy: 'stocks')]
private Market|null $market;
public function __construct(string $symbol, Market $market)
{
$this->symbol = $symbol;
$this->market = $market;
$market->addStock($this);
}
public function getSymbol(): string
{
return $this->symbol;
}
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\StockExchange;
@@ -157,18 +249,18 @@ here are the code and mappings for it:
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
private $id;
private int|null $id = null;
/**
* @Column(type="string", unique=true)
*/
private $symbol;
private string $symbol;
/**
* @ManyToOne(targetEntity="Market", inversedBy="stocks")
* @var Market
*/
private $market;
private Market|null $market = null;
public function __construct($symbol, Market $market)
{
@@ -177,7 +269,7 @@ here are the code and mappings for it:
$market->addStock($this);
}
public function getSymbol()
public function getSymbol(): string
{
return $this->symbol;
}
@@ -249,7 +341,7 @@ now query for the market:
// $em is the EntityManager
$marketId = 1;
$symbol = "AAPL";
$market = $em->find("Doctrine\Tests\Models\StockExchange\Market", $marketId);
// Access the stocks by symbol now:

View File

@@ -1077,7 +1077,8 @@ abstract class AbstractQuery
*
* @param ArrayCollection|mixed[]|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
* @psalm-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
*
* @return IterableResult
*/

View File

@@ -54,13 +54,13 @@ use function strpos;
* the static create() method. The quickest way to obtain a fully
* configured EntityManager is:
*
* use Doctrine\ORM\Tools\Setup;
* use Doctrine\ORM\Tools\ORMSetup;
* use Doctrine\ORM\EntityManager;
*
* $paths = array('/path/to/entity/mapping/files');
* $paths = ['/path/to/entity/mapping/files'];
*
* $config = Setup::createAnnotationMetadataConfiguration($paths);
* $dbParams = array('driver' => 'pdo_sqlite', 'memory' => true);
* $config = ORMSetup::createAttributeMetadataConfiguration($paths);
* $dbParams = ['driver' => 'pdo_sqlite', 'memory' => true];
* $entityManager = EntityManager::create($dbParams, $config);
*
* For more information see
@@ -767,6 +767,8 @@ class EntityManager implements EntityManagerInterface
/**
* {@inheritDoc}
*
* @psalm-return never
*/
public function copy($entity, $deep = false)
{

View File

@@ -42,7 +42,7 @@ class LifecycleEventArgs extends BaseLifecycleEventArgs
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/9875',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObjectManager() instead.',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObject() instead.',
__METHOD__
);

View File

@@ -82,7 +82,7 @@ final class Events
/**
* The loadClassMetadata event occurs after the mapping metadata for a class
* has been loaded from a mapping source (annotations/xml/yaml).
* has been loaded from a mapping source (attributes/xml/yaml).
*/
public const loadClassMetadata = 'loadClassMetadata';

View File

@@ -464,14 +464,14 @@ abstract class AbstractHydrator
break;
}
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
$value = $this->buildEnum($value, $cacheKeyInfo['enumType']);
}
$rowData['data'][$dqlAlias][$fieldName] = $type
? $type->convertToPHPValue($value, $this->_platform)
: $value;
if ($rowData['data'][$dqlAlias][$fieldName] !== null && isset($cacheKeyInfo['enumType'])) {
$rowData['data'][$dqlAlias][$fieldName] = $this->buildEnum($rowData['data'][$dqlAlias][$fieldName], $cacheKeyInfo['enumType']);
}
if ($cacheKeyInfo['isIdentifier'] && $value !== null) {
$id[$dqlAlias] .= '|' . $value;
$nonemptyComponents[$dqlAlias] = true;

View File

@@ -76,12 +76,11 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
}
/**
* {@inheritDoc}
*
* Do an optimized search of an element
*
* @param object $element
* @psalm-param TValue $element
*
* @return bool
* @template TMaybeContained
*/
public function contains($element)
{

View File

@@ -82,7 +82,7 @@ use const PHP_VERSION_ID;
* nullable?: bool,
* notInsertable?: bool,
* notUpdatable?: bool,
* generated?: string,
* generated?: int,
* enumType?: class-string<BackedEnum>,
* columnDefinition?: string,
* precision?: int,
@@ -142,6 +142,13 @@ use const PHP_VERSION_ID;
* type: int,
* unique?: bool,
* }
* @psalm-type DiscriminatorColumnMapping = array{
* name: string,
* fieldName: string,
* type: string,
* length?: int,
* columnDefinition?: string|null,
* }
*/
class ClassMetadataInfo implements ClassMetadata
{
@@ -574,7 +581,8 @@ class ClassMetadataInfo implements ClassMetadata
* READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
* inheritance mappings.
*
* @psalm-var array{name: string, fieldName: string, type: string, length?: int, columnDefinition?: string|null}|null
* @var array<string, mixed>
* @psalm-var DiscriminatorColumnMapping|null
*/
public $discriminatorColumn;
@@ -3248,7 +3256,10 @@ class ClassMetadataInfo implements ClassMetadata
}
}
/** @return array<string, mixed> */
/**
* @return array<string, mixed>
* @psalm-return DiscriminatorColumnMapping
*/
final public function getDiscriminatorColumn(): array
{
if ($this->discriminatorColumn === null) {

View File

@@ -19,6 +19,7 @@ class SimplifiedXmlDriver extends XmlDriver
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION)
{
$locator = new SymfonyFileLocator((array) $prefixes, $fileExtension);
parent::__construct($locator, $fileExtension);
}
}

View File

@@ -21,6 +21,7 @@ class SimplifiedYamlDriver extends YamlDriver
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION)
{
$locator = new SymfonyFileLocator((array) $prefixes, $fileExtension);
parent::__construct($locator, $fileExtension);
}
}

View File

@@ -824,7 +824,8 @@ class MappingException extends ORMException
return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $annotation . "'");
}
public static function invalidGeneratedMode(string $annotation): MappingException
/** @param int|string $annotation */
public static function invalidGeneratedMode($annotation): MappingException
{
return new self("Invalid generated mode '" . $annotation . "'");
}

View File

@@ -63,8 +63,8 @@ class ReflectionEnumProperty extends ReflectionProperty
}
/**
* @param object $object
* @param int|string|int[]|string[]|null $value
* @param object $object
* @param int|string|int[]|string[]|BackedEnum|BackedEnum[]|null $value
*/
public function setValue($object, $value = null): void
{
@@ -82,11 +82,15 @@ class ReflectionEnumProperty extends ReflectionProperty
}
/**
* @param object $object
* @param int|string $value
* @param object $object
* @param int|string|BackedEnum $value
*/
private function initializeEnumValue($object, $value): BackedEnum
{
if ($value instanceof BackedEnum) {
return $value;
}
$enumType = $this->enumType;
try {

View File

@@ -20,6 +20,7 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use function apcu_enabled;
use function class_exists;
use function extension_loaded;
use function md5;
@@ -181,7 +182,7 @@ final class ORMSetup
$namespace = 'dc2_' . md5($proxyDir);
if (extension_loaded('apcu')) {
if (extension_loaded('apcu') && apcu_enabled()) {
return new ApcuAdapter($namespace);
}

View File

@@ -23,6 +23,7 @@ class OptimisticLockException extends ORMException
public function __construct($msg, $entity)
{
parent::__construct($msg);
$this->entity = $entity;
}

View File

@@ -393,6 +393,8 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
/**
* {@inheritdoc}
*
* @template TMaybeContained
*/
public function contains($element): bool
{

View File

@@ -23,7 +23,7 @@ class SqlValueVisitor extends ExpressionVisitor
/**
* Converts a comparison expression into the target query language output.
*
* @return void
* {@inheritDoc}
*/
public function walkComparison(Comparison $comparison)
{
@@ -32,35 +32,39 @@ class SqlValueVisitor extends ExpressionVisitor
$operator = $comparison->getOperator();
if (($operator === Comparison::EQ || $operator === Comparison::IS) && $value === null) {
return;
return null;
} elseif ($operator === Comparison::NEQ && $value === null) {
return;
return null;
}
$this->values[] = $value;
$this->types[] = [$field, $value, $operator];
return null;
}
/**
* Converts a composite expression into the target query language output.
*
* @return void
* {@inheritDoc}
*/
public function walkCompositeExpression(CompositeExpression $expr)
{
foreach ($expr->getExpressionList() as $child) {
$this->dispatch($child);
}
return null;
}
/**
* Converts a value expression into the target query language part.
*
* @return void
* {@inheritDoc}
*/
public function walkValue(Value $value)
{
return;
return null;
}
/**

View File

@@ -56,6 +56,7 @@ class ProxyFactory extends AbstractProxyFactory
$proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs);
$proxyGenerator->setPlaceholder('baseProxyInterface', Proxy::class);
parent::__construct($proxyGenerator, $em->getMetadataFactory(), $autoGenerate);
$this->em = $em;

View File

@@ -49,19 +49,19 @@ abstract class Node
}
/**
* @param object $obj
* @param mixed $value
*
* @return string
*/
public function dump($obj)
public function dump($value)
{
static $ident = 0;
$str = '';
if ($obj instanceof Node) {
$str .= get_debug_type($obj) . '(' . PHP_EOL;
$props = get_object_vars($obj);
if ($value instanceof Node) {
$str .= get_debug_type($value) . '(' . PHP_EOL;
$props = get_object_vars($value);
foreach ($props as $name => $prop) {
$ident += 4;
@@ -71,12 +71,12 @@ abstract class Node
}
$str .= str_repeat(' ', $ident) . ')';
} elseif (is_array($obj)) {
} elseif (is_array($value)) {
$ident += 4;
$str .= 'array(';
$some = false;
foreach ($obj as $k => $v) {
foreach ($value as $k => $v) {
$str .= PHP_EOL . str_repeat(' ', $ident) . '"'
. $k . '" => ' . $this->dump($v) . ',';
$some = true;
@@ -84,10 +84,10 @@ abstract class Node
$ident -= 4;
$str .= ($some ? PHP_EOL . str_repeat(' ', $ident) : '') . ')';
} elseif (is_object($obj)) {
$str .= 'instanceof(' . get_debug_type($obj) . ')';
} elseif (is_object($value)) {
$str .= 'instanceof(' . get_debug_type($value) . ')';
} else {
$str .= var_export($obj, true);
$str .= var_export($value, true);
}
return $str;

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\Expr;
use InvalidArgumentException;
use Stringable;
use function count;
use function get_class;
@@ -30,10 +31,10 @@ abstract class Base
/** @var string */
protected $postSeparator = ')';
/** @psalm-var list<class-string> */
/** @var list<class-string> */
protected $allowedClasses = [];
/** @psalm-var list<string|object> */
/** @var list<string|Stringable> */
protected $parts = [];
/** @param mixed $args */

View File

@@ -551,7 +551,7 @@ class Parser
* @param bool $resetPeek Reset peek after finding the closing parenthesis.
*
* @return mixed[]
* @psalm-return array{value: string, type: int|null|string, position: int}|null
* @psalm-return Token|null
*/
private function peekBeyondClosingParenthesis(bool $resetPeek = true): ?array
{

View File

@@ -7,9 +7,12 @@ namespace Doctrine\ORM\Tools\Console\Command;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Console\EntityManagerProvider;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use function assert;
abstract class AbstractEntityManagerCommand extends Command
{
/** @var EntityManagerProvider|null */
@@ -33,7 +36,10 @@ abstract class AbstractEntityManagerCommand extends Command
$this->getName()
);
return $this->getHelper('em')->getEntityManager();
$helper = $this->getHelper('em');
assert($helper instanceof EntityManagerHelper);
return $helper->getEntityManager();
}
return $input->getOption('em') === null

View File

@@ -527,11 +527,12 @@ public function __construct(<params>)
* Sets the class fields visibility for the entity (can either be private or protected).
*
* @param string $visibility
* @psalm-param self::FIELD_VISIBLE_*
*
* @return void
*
* @throws InvalidArgumentException
*
* @psalm-assert self::FIELD_VISIBLE_* $visibility
*/
public function setFieldVisibility($visibility)
{
@@ -1054,7 +1055,7 @@ public function __construct(<params>)
}
if (isset($metadata->table['options']) && $metadata->table['options']) {
$table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
$table[] = 'options={' . $this->exportTableOptions($metadata->table['options']) . '}';
}
if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {

View File

@@ -44,9 +44,7 @@ class PhpExporter extends AbstractExporter
$lines[] = '$metadata->isMappedSuperclass = true;';
}
if ($metadata->inheritanceType) {
$lines[] = '$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_' . $this->_getInheritanceTypeString($metadata->inheritanceType) . ');';
}
$lines[] = '$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_' . $this->_getInheritanceTypeString($metadata->inheritanceType) . ');';
if ($metadata->customRepositoryClassName) {
$lines[] = "\$metadata->customRepositoryClassName = '" . $metadata->customRepositoryClassName . "';";

View File

@@ -57,7 +57,7 @@ class XmlExporter extends AbstractExporter
$root->addAttribute('schema', $metadata->table['schema']);
}
if ($metadata->inheritanceType && $metadata->inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
if ($metadata->inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
$root->addAttribute('inheritance-type', $this->_getInheritanceTypeString($metadata->inheritanceType));
}

View File

@@ -26,6 +26,7 @@ use function count;
* The paginator can handle various complex scenarios with DQL.
*
* @template T
* @implements IteratorAggregate<array-key, T>
*/
class Paginator implements Countable, IteratorAggregate
{
@@ -40,7 +41,7 @@ class Paginator implements Countable, IteratorAggregate
/** @var bool|null */
private $useOutputWalkers;
/** @var int */
/** @var int|null */
private $count;
/**

View File

@@ -27,6 +27,7 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use function apcu_enabled;
use function class_exists;
use function dirname;
use function extension_loaded;
@@ -236,7 +237,7 @@ class Setup
if ($isDevMode === true) {
$cache = class_exists(ArrayCache::class) ? new ArrayCache() : new ArrayAdapter();
} elseif (extension_loaded('apcu')) {
} elseif (extension_loaded('apcu') && apcu_enabled()) {
$cache = class_exists(ApcuCache::class) ? new ApcuCache() : new ApcuAdapter();
} elseif (extension_loaded('memcached') && (class_exists(MemcachedCache::class) || MemcachedAdapter::isSupported())) {
$memcached = new Memcached();

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use BackedEnum;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
@@ -743,6 +744,20 @@ class UnitOfWork implements PropertyChangedListener
$orgValue = $originalData[$propName];
if (! empty($class->fieldMappings[$propName]['enumType'])) {
if (is_array($orgValue)) {
foreach ($orgValue as $id => $val) {
if ($val instanceof BackedEnum) {
$orgValue[$id] = $val->value;
}
}
} else {
if ($orgValue instanceof BackedEnum) {
$orgValue = $orgValue->value;
}
}
}
// skip if value haven't changed
if ($orgValue === $actualValue) {
continue;
@@ -799,6 +814,7 @@ class UnitOfWork implements PropertyChangedListener
}
if ($orgValue !== null && $assoc['orphanRemoval']) {
assert(is_object($orgValue));
$this->scheduleOrphanRemoval($orgValue);
}
}
@@ -2807,6 +2823,10 @@ class UnitOfWork implements PropertyChangedListener
$joinColumnValue = $data[$srcColumn] ?? null;
if ($joinColumnValue !== null) {
if ($joinColumnValue instanceof BackedEnum) {
$joinColumnValue = $joinColumnValue->value;
}
if ($targetClass->containsForeignIdentifier) {
$associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
} else {

View File

@@ -175,6 +175,11 @@ parameters:
count: 2
path: lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
-
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\:\\:fullyQualifiedClassName\\(\\) should return class\\-string\\|null but returns string\\|null\\.$#"
count: 1
path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
-
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\NamingStrategy\\:\\:joinColumnName\\(\\) invoked with 2 parameters, 1 required\\.$#"
count: 2
@@ -420,16 +425,6 @@ parameters:
count: 1
path: lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php
-
message: "#^Call to function is_array\\(\\) with object will always evaluate to false\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Node.php
-
message: "#^Else branch is unreachable because previous condition is always true\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Node.php
-
message: "#^Call to an undefined method Doctrine\\\\ORM\\\\Query\\\\SqlWalker\\:\\:walkWhenClauseExpression\\(\\)\\.$#"
count: 1
@@ -501,7 +496,7 @@ parameters:
path: lib/Doctrine/ORM/Query/Parser.php
-
message: "#^Offset 'columns' on array\\{name\\: string, entities\\: array\\{\\}, columns\\: array\\}\\|array\\{name\\: string, entities\\: non\\-empty\\-array, columns\\: array\\} in isset\\(\\) always exists and is not nullable\\.$#"
message: "#^Offset 'columns' on array\\{name\\: string, entities\\: array, columns\\: array\\} in isset\\(\\) always exists and is not nullable\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php
@@ -515,6 +510,11 @@ parameters:
count: 1
path: lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php
-
message: "#^Access to an undefined property Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node\\:\\:\\$pathExpression\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/SqlWalker.php
-
message: "#^Call to function is_string\\(\\) with Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node will always evaluate to false\\.$#"
count: 1
@@ -657,13 +657,8 @@ parameters:
-
message: "#^If condition is always true\\.$#"
count: 2
path: lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php
-
message: "#^Left side of && is always true\\.$#"
count: 1
path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php
path: lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php
-
message: "#^Offset 'name' on array\\{name\\: string, schema\\?\\: string, indexes\\?\\: array, uniqueConstraints\\?\\: array, options\\?\\: array\\<string, mixed\\>, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#"

View File

@@ -61,8 +61,3 @@ parameters:
-
message: '#^Call to method injectObjectManager\(\) on an unknown class Doctrine\\Persistence\\ObjectManagerAware\.$#'
path: lib/Doctrine/ORM/UnitOfWork.php
# https://github.com/phpstan/phpstan/issues/7292
-
message: '#^Offset class\-string on non\-empty\-array\<class\-string, array\<string, mixed\>\> in isset\(\) always exists and is not nullable\.$#'
path: lib/Doctrine/ORM/UnitOfWork.php

View File

@@ -8,4 +8,4 @@ parameters:
earlyTerminatingMethodCalls:
Doctrine\ORM\Query\Parser:
- syntaxError
phpVersion: 80100
phpVersion: 80200

View File

@@ -8,9 +8,6 @@ parameters:
- '/^Call to an undefined method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getGuidExpression\(\).$/'
# Fallback logic for DBAL 2
-
message: '/HelperSet constructor expects/'
path: lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php
-
message: '/Application::add\(\) expects Symfony\\Component\\Console\\Command\\Command/'
path: lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php
@@ -39,8 +36,3 @@ parameters:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
# https://github.com/phpstan/phpstan/issues/7292
-
message: '#^Offset class\-string on non\-empty\-array\<class\-string, array\<string, mixed\>\> in isset\(\) always exists and is not nullable\.$#'
path: lib/Doctrine/ORM/UnitOfWork.php

View File

@@ -8,9 +8,6 @@ parameters:
- '/^Call to an undefined method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getGuidExpression\(\).$/'
# Fallback logic for DBAL 2
-
message: '/HelperSet constructor expects/'
path: lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php
-
message: '/Application::add\(\) expects Symfony\\Component\\Console\\Command\\Command/'
path: lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php
@@ -50,8 +47,3 @@ parameters:
-
message: '#^Call to method injectObjectManager\(\) on an unknown class Doctrine\\Persistence\\ObjectManagerAware\.$#'
path: lib/Doctrine/ORM/UnitOfWork.php
# https://github.com/phpstan/phpstan/issues/7292
-
message: '#^Offset class\-string on non\-empty\-array\<class\-string, array\<string, mixed\>\> in isset\(\) always exists and is not nullable\.$#'
path: lib/Doctrine/ORM/UnitOfWork.php

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
phpVersion="8.1"
phpVersion="8.2"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
@@ -68,8 +68,16 @@
<referencedMethod name="Doctrine\DBAL\Connection::getSchemaManager"/>
<referencedMethod name="Doctrine\DBAL\Platforms\AbstractPlatform::getGuidExpression"/>
<referencedMethod name="Doctrine\DBAL\Platforms\AbstractPlatform::supportsForeignKeyConstraints"/>
<!-- Remove on 2.14.x -->
<referencedMethod name="Doctrine\DBAL\Schema\AbstractSchemaManager::createSchema"/>
<referencedMethod name="Doctrine\DBAL\Schema\Index::isFullfilledBy"/>
<referencedMethod name="Doctrine\DBAL\Schema\Table::changeColumn"/>
<referencedMethod name="Doctrine\DBAL\Schema\Table::hasPrimaryKey"/>
<!-- Remove on 3.0.x -->
<referencedMethod name="Doctrine\DBAL\Connection::getEventManager"/>
<referencedMethod name="Doctrine\DBAL\Schema\Schema::visit"/>
<referencedMethod name="Doctrine\DBAL\Schema\SchemaDiff::toSaveSql"/>
<referencedMethod name="Doctrine\DBAL\Schema\SchemaDiff::toSql"/>
<referencedMethod name="Doctrine\ORM\Internal\Hydration\AbstractHydrator::hydrateRow"/>
<referencedMethod name="Doctrine\ORM\Configuration::ensureProductionSettings"/>
<referencedMethod name="Doctrine\ORM\Configuration::newDefaultAnnotationDriver"/>
@@ -92,6 +100,8 @@
<!-- DBAL 3.2 forward compatibility -->
<file name="lib/Doctrine/ORM/Tools/Pagination/CountOutputWalker.php"/>
<file name="lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php"/>
<!-- https://github.com/vimeo/psalm/issues/8520 -->
<file name="lib/Doctrine/ORM/PersistentCollection.php"/>
</errorLevel>
</DocblockTypeContradiction>
<InvalidArgument>
@@ -106,6 +116,12 @@
<referencedClass name="Doctrine\DBAL\Platforms\PostgreSQLPlatform" />
</errorLevel>
</InvalidClass>
<InvalidReturnType>
<errorLevel type="suppress">
<!-- https://github.com/vimeo/psalm/issues/8819 -->
<file name="lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php"/>
</errorLevel>
</InvalidReturnType>
<MethodSignatureMismatch>
<errorLevel type="suppress">
<!-- See https://github.com/vimeo/psalm/issues/7357 -->

View File

@@ -89,7 +89,9 @@ class CmsAddress
* @Id
* @GeneratedValue
*/
#[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public $id;
/**

View File

@@ -25,7 +25,9 @@ use Doctrine\ORM\Mapping\Table;
* @Entity
* @Table("cache_city")
*/
#[ORM\Entity, ORM\Table(name: 'cache_city'), ORM\Cache]
#[ORM\Entity]
#[ORM\Table(name: 'cache_city')]
#[ORM\Cache]
class City
{
/**
@@ -34,7 +36,9 @@ class City
* @GeneratedValue
* @Column(type="integer")
*/
#[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
@@ -51,7 +55,7 @@ class City
* @JoinColumn(name="state_id", referencedColumnName="id")
*/
#[ORM\Cache]
#[ORM\ManyToOne(targetEntity: 'State', inversedBy: 'citities')]
#[ORM\ManyToOne(targetEntity: 'State', inversedBy: 'cities')]
#[ORM\JoinColumn(name: 'state_id', referencedColumnName: 'id')]
protected $state;
@@ -68,7 +72,8 @@ class City
* @OrderBy({"name" = "ASC"})
* @OneToMany(targetEntity="Attraction", mappedBy="city")
*/
#[ORM\Cache, ORM\OrderBy(['name' => 'ASC'])]
#[ORM\Cache]
#[ORM\OrderBy(['name' => 'ASC'])]
#[ORM\OneToMany(targetEntity: 'Attraction', mappedBy: 'city')]
public $attractions;

View File

@@ -81,7 +81,8 @@ use Doctrine\ORM\Mapping\Table;
* ),
* })
*/
#[ORM\Entity, ORM\Table(name: 'company_contracts')]
#[ORM\Entity]
#[ORM\Table(name: 'company_contracts')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'discr', type: 'string')]
#[ORM\DiscriminatorMap(['fix' => 'CompanyFixContract', 'flexible' => 'CompanyFlexContract', 'flexultra' => 'CompanyFlexUltraContract'])]
@@ -94,7 +95,9 @@ abstract class CompanyContract
* @Column(type="integer")
* @GeneratedValue
*/
#[ORM\Id, ORM\Column(type: 'integer'), ORM\GeneratedValue]
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private $id;
/**

View File

@@ -21,7 +21,9 @@ class DDC1476EntityWithDefaultFieldType
* @Column()
* @GeneratedValue("NONE")
*/
#[ORM\Id, ORM\Column, ORM\GeneratedValue(strategy: 'NONE')]
#[ORM\Id]
#[ORM\Column]
#[ORM\GeneratedValue(strategy: 'NONE')]
protected $id;
/**

View File

@@ -15,7 +15,8 @@ use Doctrine\ORM\Mapping\Table;
* @Entity
* @Table(name="explicit_table", schema="explicit_schema")
*/
#[ORM\Entity, ORM\Table(name: 'explicit_table', schema: 'explicit_schema')]
#[ORM\Entity]
#[ORM\Table(name: 'explicit_table', schema: 'explicit_schema')]
class ExplicitSchemaAndTable
{
/**
@@ -24,6 +25,8 @@ class ExplicitSchemaAndTable
* @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
#[ORM\Id, ORM\Column(type: 'integer'), ORM\GeneratedValue(strategy: 'AUTO')]
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
public $id;
}

View File

@@ -18,7 +18,8 @@ use Doctrine\ORM\Mapping\Table;
* @Entity
* @Table(name="implicit_schema.implicit_table")
*/
#[ORM\Entity, ORM\Table(name: 'implicit_schema.implicit_table')]
#[ORM\Entity]
#[ORM\Table(name: 'implicit_schema.implicit_table')]
class SchemaAndTableInTableName
{
/**
@@ -27,6 +28,8 @@ class SchemaAndTableInTableName
* @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
#[ORM\Id, ORM\Column(type: 'integer'), ORM\GeneratedValue(strategy: 'AUTO')]
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
public $id;
}

View File

@@ -22,7 +22,9 @@ class DDC3579Group
* @Id
* @Column(type="integer")
*/
#[Id, GeneratedValue, Column(type: 'integer')]
#[Id]
#[GeneratedValue]
#[Column(type: 'integer')]
private $id;
/**

View File

@@ -22,7 +22,9 @@ class DDC3579User
* @GeneratedValue
* @Column(type="integer", name="user_id", length=150)
*/
#[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)]
#[Id]
#[GeneratedValue]
#[Column(type: 'integer', name: 'user_id', length: 150)]
protected $id;
/**

View File

@@ -24,6 +24,7 @@ class DDC3597Image extends DDC3597Media
public function __construct(string $distributionHash)
{
parent::__construct($distributionHash);
$this->dimension = new DDC3597Dimension();
}

View File

@@ -23,7 +23,9 @@ class DDC5934BaseContract
* @Column(name="id", type="integer")
* @GeneratedValue()
*/
#[Id, Column, GeneratedValue]
#[Id]
#[Column]
#[GeneratedValue]
public $id;
/**

View File

@@ -21,7 +21,9 @@ class DDC869Payment
* @Column(type="integer")
* @GeneratedValue
*/
#[ORM\Id, ORM\Column(type: 'integer'), ORM\GeneratedValue]
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
protected $id;
/**

View File

@@ -27,7 +27,9 @@ class DDC964User
* @GeneratedValue
* @Column(type="integer", name="user_id", length=150)
*/
#[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)]
#[Id]
#[GeneratedValue]
#[Column(type: 'integer', name: 'user_id', length: 150)]
protected $id;
/**

View File

@@ -18,7 +18,9 @@ class Card
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
#[Id, GeneratedValue, Column(type: 'integer')]
#[Id]
#[GeneratedValue]
#[Column(type: 'integer')]
public $id;
/**

View File

@@ -11,7 +11,8 @@ use Doctrine\ORM\Mapping\Id;
#[Entity]
class CardWithDefault
{
#[Id, Column]
#[Id]
#[Column]
public string $id;
#[Column(options: ['default' => Suit::Hearts])]

View File

@@ -18,7 +18,9 @@ class CardWithNullable
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
#[Id, GeneratedValue, Column(type: 'integer')]
#[Id]
#[GeneratedValue]
#[Column(type: 'integer')]
public $id;
/**

View File

@@ -13,7 +13,9 @@ use Doctrine\ORM\Mapping\Id;
#[Entity]
class Product
{
#[Id, GeneratedValue, Column(type: 'integer')]
#[Id]
#[GeneratedValue]
#[Column(type: 'integer')]
public int $id;
#[Embedded(class: Quantity::class)]

View File

@@ -18,7 +18,9 @@ class Scale
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
#[Id, GeneratedValue, Column(type: 'integer')]
#[Id]
#[GeneratedValue]
#[Column(type: 'integer')]
public $id;
/**

View File

@@ -12,7 +12,9 @@ use Doctrine\ORM\Mapping\Id;
#[Entity]
class TypedCard
{
#[Id, GeneratedValue, Column]
#[Id]
#[GeneratedValue]
#[Column]
public int $id;
#[Column]

View File

@@ -17,13 +17,15 @@ class TypedCardEnumCompositeId
* @ORM\Id()
* @ORM\Column(type="string", enumType=Suit::class)
*/
#[Id, Column(type: 'string', enumType: Suit::class)]
#[Id]
#[Column(type: 'string', enumType: Suit::class)]
public Suit $suit;
/**
* @ORM\Id()
* @ORM\Column(type="string", enumType=Unit::class)
*/
#[Id, Column(type: 'string', enumType: Unit::class)]
#[Id]
#[Column(type: 'string', enumType: Unit::class)]
public Unit $unit;
}

View File

@@ -17,6 +17,7 @@ class TypedCardEnumId
* @ORM\Id()
* @ORM\Column(type="string", enumType=Suit::class)
*/
#[Id, Column(type: 'string', enumType: Suit::class)]
#[Id]
#[Column(type: 'string', enumType: Suit::class)]
public Suit $suit;
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\GH10132;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\Tests\Models\Enums\Suit;
/** @Entity */
class Complex
{
/**
* @Id
* @Column(type = "string", enumType = Suit::class)
*/
protected Suit $type;
/** @OneToMany(targetEntity = ComplexChild::class, mappedBy = "complex", cascade = {"persist"}) */
protected Collection $complexChildren;
public function __construct()
{
$this->complexChildren = new ArrayCollection();
}
public function getType(): Suit
{
return $this->type;
}
public function setType(Suit $type): void
{
$this->type = $type;
}
public function getComplexChildren(): Collection
{
return $this->complexChildren;
}
public function addComplexChild(ComplexChild $complexChild): void
{
$this->complexChildren->add($complexChild);
}
}

Some files were not shown because too many files have changed in this diff Show More