Compare commits

...

79 Commits

Author SHA1 Message Date
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
Grégoire Paris
a8b02fd70f Merge pull request #10054 from greg0ire/sa-fixes 2022-09-22 15:36:43 +02:00
Alexander M. Turek
60adc6b7d9 Fix CS 2022-09-22 12:06:40 +02:00
Alexander M. Turek
c65ff6651c Merge branch '2.13.x-enum-arrayhydration' into 2.13.x
* 2.13.x-enum-arrayhydration:
  Fix ArrayHydration of enums
2022-09-22 12:04:22 +02:00
Alexander M. Turek
5f29fcdea2 Revert "Fix EnumType not being hydrated with HYDRATE_ARRAY (#9995)"
This reverts commit bb3ce7e802.
2022-09-22 12:04:09 +02:00
Grégoire Paris
2b8ac15813 Mark ClassMetadataInfo::sequenceGeneratorDefinition as nullable 2022-09-22 09:12:50 +02:00
Grégoire Paris
6dd07e4c76 Merge pull request #9983 from VincentLanglet/discriminatorColumn
Add phpdoc for ClassMetadataInfo::discriminatorColumn property
2022-09-21 23:17:54 +02:00
Alexander M. Turek
0d043059b9 PHPStan 1.8.5, Psalm 4.27.0 (#10033) 2022-09-18 15:06:51 +02:00
Ilya Shashilov
bb3ce7e802 Fix EnumType not being hydrated with HYDRATE_ARRAY (#9995)
Co-authored-by: Ilya Shashilov <kvushiha@gmail.com>
2022-09-14 14:33:42 +02:00
Tomas
bc7e252f00 Fix ArrayHydration of enums 2022-09-12 17:01:40 +02:00
Carlos Buenosvinos
498da2ff98 "Strange" return lines in documentation of inheritance-mapping.rst (#10027) 2022-09-04 18:16:02 +02:00
Carlos Buenosvinos
73e1e42ab5 More strange break lines in inheritance-mapping.rst (#10028) 2022-09-03 21:40:53 +02:00
Alexander M. Turek
5d11648767 Bump Ubuntu version and shared workflows (#10020) 2022-08-30 21:10:38 +02:00
Alexander M. Turek
0ecac1b255 Fail gracefully if EM could not be constructed in tests (#10008) 2022-08-30 13:40:54 +02:00
Grégoire Paris
b2a4fac40b Merge pull request #10009 from greg0ire/cs-update
Upgrade to doctrine/coding-standard 10.0.0
2022-08-27 20:53:59 +02:00
Grégoire Paris
beeba93a53 Upgrade to doctrine/coding-standard 10.0.0 2022-08-27 19:04:47 +02:00
Alexander M. Turek
4c253f2403 Merge pull request #10006 from derrabus/fix/build
Fix build
2022-08-26 13:07:34 +02:00
Alexander M. Turek
46ec86557e Bump coding standard to 9.0.2 2022-08-26 12:27:20 +02:00
Alexander M. Turek
f287b74470 Fix tests for doctrine/common 3.4 2022-08-26 00:34:50 +02:00
Alexander M. Turek
12d086551e Fix static analysis errors for Collections 1.7 2022-08-26 00:09:37 +02:00
Alberto Acha
5283e1441c Fix type in docs (#9994) 2022-08-16 20:40:01 +02:00
Michael Olšavský
18be6d2218 Improve orphan removal documentation - recommend using cascade=persist (#9848)
* Improve orphanRemoval documentation

* Wording improvement

* Update docs/en/reference/working-with-associations.rst

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2022-08-09 21:39:43 +02:00
Vincent Langlet
2fda625dba Add phpdoc for discriminatorColumn 2022-08-08 22:34:18 +02:00
710 changed files with 3879 additions and 6420 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,15 +0,0 @@
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
push:
branches:
- "*.x"
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@1.4.1"
with:
php-version: "8.1"

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@2.1.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
@@ -14,7 +28,7 @@ env:
jobs:
phpunit-smoke-check:
name: "PHPUnit with SQLite"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
matrix:
@@ -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,7 +64,7 @@ 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"
@@ -53,48 +73,48 @@ 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+"
- 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"
phpunit-postgres:
name: "PHPUnit with PostgreSQL"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
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
@@ -126,7 +146,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+"
@@ -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"
@@ -142,17 +162,18 @@ jobs:
phpunit-mariadb:
name: "PHPUnit with MariaDB"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
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
@@ -202,7 +215,7 @@ jobs:
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"
@@ -218,15 +231,16 @@ jobs:
phpunit-mysql:
name: "PHPUnit with MySQL"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
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
@@ -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"
@@ -301,7 +307,7 @@ jobs:
phpunit-lower-php-versions:
name: "PHPUnit with SQLite"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
matrix:
@@ -313,7 +319,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -324,17 +330,17 @@ jobs:
ini-values: "zend.assertions=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:
name: "Upload coverage to Codecov"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
needs:
- "phpunit-smoke-check"
- "phpunit-postgres"
@@ -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
@@ -15,7 +27,7 @@ env:
jobs:
phpbench:
name: "PHPBench"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
matrix:
@@ -24,7 +36,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -36,7 +48,7 @@ jobs:
ini-values: "zend.assertions=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@1.4.1"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@2.1.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}

View File

@@ -5,14 +5,28 @@ 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:
name: "Static Analysis with PHPStan"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
fail-fast: false
@@ -33,7 +47,7 @@ jobs:
steps:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -50,7 +64,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"
@@ -68,7 +82,7 @@ jobs:
static-analysis-psalm:
name: "Static Analysis with Psalm"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
fail-fast: false
@@ -78,7 +92,7 @@ jobs:
steps:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -87,7 +101,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"

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",
"doctrine/coding-standard": "^9.0.2 || ^10.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.8.2",
"phpstan/phpstan": "~1.4.10 || 1.9.2",
"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.26.0"
"vimeo/psalm": "4.30.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
@@ -178,9 +197,9 @@ relationships involving types that employ this mapping strategy are
very performing.
There is a general performance consideration with Single Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is an STI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Inheritance: If the target-entity of a many-to-one or one-to-one
association is an STI entity, it is preferable for performance reasons that it
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.
@@ -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::
@@ -284,13 +301,13 @@ themselves on access of any subtype fields, so accessing fields of
subtypes after such a query is not safe.
There is a general performance consideration with Class Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is a CTI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Inheritance: If the target-entity of a many-to-one or one-to-one
association is a CTI entity, it is preferable for performance reasons that it
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

@@ -268,7 +268,7 @@ Doctrine\DBAL\ParameterType::* or a DBAL Type name for conversion.
use Doctrine\DBAL\Types\Types;
// prevents attempt to load metadata for date time class, improving performance
$qb->setParameter('date', new \DateTimeImmutable(), Types::DATE_IMMUTABLE)
$qb->setParameter('date', new \DateTimeImmutable(), Types::DATETIME_IMMUTABLE)
If you've got several parameters to bind to your query, you can
also use setParameters() instead of setParameter() with the
@@ -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;
/**
* Unidirectional - Many-To-One
*
* @ManyToOne(targetEntity="Comment")
*/
private $firstComment;
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author')]
private Collection $commentsAuthored;
/** 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
@@ -100,19 +100,19 @@ definitions omitted):
firstComment_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Comment (
id VARCHAR(255) NOT NULL,
author_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE user_favorite_comments (
user_id VARCHAR(255) NOT NULL,
favorite_comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, favorite_comment_id)
) ENGINE = InnoDB;
CREATE TABLE user_read_comments (
user_id VARCHAR(255) NOT NULL,
comment_id VARCHAR(255) NOT NULL,
@@ -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;
}
}
@@ -148,17 +149,17 @@ The interaction code would then look like in the following snippet
<?php
$user = $em->find('User', $userId);
// unidirectional many to many
$comment = $em->find('Comment', $readCommentId);
$user->getReadComments()->add($comment);
$em->flush();
// unidirectional many to one
$myFirstComment = new Comment();
$user->setFirstComment($myFirstComment);
$em->persist($myFirstComment);
$em->flush();
@@ -171,40 +172,43 @@ fields on both sides:
class User
{
// ..
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;
}
}
class Comment
{
// ...
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;
}
}
// Many-to-Many
$user->getFavorites()->add($favoriteComment);
$favoriteComment->getUserFavorites()->add($user);
$em->flush();
// Many-To-One / One-To-Many Bidirectional
$newComment = new Comment();
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
$em->persist($newComment);
$em->flush();
@@ -225,10 +229,10 @@ element. Here are some examples:
// Remove by Elements
$user->getComments()->removeElement($comment);
$comment->setAuthor(null);
$user->getFavorites()->removeElement($comment);
$comment->getUserFavorites()->removeElement($user);
// Remove by Key
$user->getComments()->remove($ithComment);
$comment->setAuthor(null);
@@ -240,7 +244,7 @@ Notice how both sides of the bidirectional association are always
updated. Unidirectional associations are consequently simpler to
handle.
Also note that if you use type-hinting in your methods, you will
Also note that if you use type-hinting in your methods, you will
have to specify a nullable type, i.e. ``setAddress(?Address $address)``,
otherwise ``setAddress(null)`` will fail to remove the association.
Another way to deal with this is to provide a special method, like
@@ -271,8 +275,8 @@ entities that have been re-added to the collection.
Say you clear a collection of tags by calling
``$post->getTags()->clear();`` and then call
``$post->getTags()->add($tag)``. This will not recognize the tag having
already been added previously and will consequently issue two separate database
``$post->getTags()->add($tag)``. This will not recognize the tag having
already been added previously and will consequently issue two separate database
calls.
Association Management Methods
@@ -292,43 +296,43 @@ 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);
}
$this->comments[] = $comment;
$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);
}
}
class Comment
{
// ..
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();
}
}
@@ -373,7 +378,7 @@ as your preferences.
Synchronizing Bidirectional Collections
---------------------------------------
In the case of Many-To-Many associations you as the developer have the
In the case of Many-To-Many associations you as the developer have the
responsibility of keeping the collections on the owning and inverse side
in sync when you apply changes to them. Doctrine can only
guarantee a consistent state for the hydration, not for your client
@@ -387,7 +392,7 @@ can show the possible caveats you can encounter:
<?php
$user->getFavorites()->add($favoriteComment);
// not calling $favoriteComment->getUserFavorites()->add($user);
$user->getFavorites()->contains($favoriteComment); // TRUE
$favoriteComment->getUserFavorites()->contains($user); // FALSE
@@ -422,7 +427,7 @@ comment might look like in your controller (without ``cascade: persist``):
$user = new User();
$myFirstComment = new Comment();
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment); // required, if `cascade: persist` is not set
$em->flush();
@@ -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;
// ...
}
@@ -480,7 +484,7 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
<?php
$user = new User();
$user->comment('Lorem ipsum', new DateTime());
$em->persist($user);
$em->flush();
@@ -559,6 +563,13 @@ OrphanRemoval works with one-to-one, one-to-many and many-to-many associations.
If you neglect this assumption your entities will get deleted by Doctrine even if
you assigned the orphaned entity to another one.
.. note::
``orphanRemoval=true`` option should be used in combination with ``cascade=["persist"]`` option
as the child entity, that is manually persisted, will not be deleted automatically by Doctrine
when a collection is still an instance of ArrayCollection (before first flush / hydration).
This is a Doctrine limitation since ArrayCollection does not have access to a UnitOfWork.
As a better example consider an Addressbook application where you have Contacts, Addresses
and StandingData:
@@ -570,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", orphanRemoval=true) */
private $standingData;
#[OneToOne(targetEntity: StandingData::class, cascade: ['persist'], orphanRemoval: true)]
private StandingData|null $standingData = null;
/** @OneToMany(targetEntity="Address", mappedBy="contact", 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]);
}
@@ -612,10 +622,10 @@ Now two examples of what happens when you remove the references:
$em->flush();
In this case you have not only changed the ``Contact`` entity itself but
you have also removed the references for standing data and as well as one
address reference. When flush is called not only are the references removed
but both the old standing data and the one address entity are also deleted
In this case you have not only changed the ``Contact`` entity itself but
you have also removed the references for standing data and as well as one
address reference. When flush is called not only are the references removed
but both the old standing data and the one address entity are also deleted
from the database.
.. _filtering-collections:

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

@@ -198,9 +198,7 @@ abstract class AbstractQuery
return $this;
}
/**
* @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise.
*/
/** @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise. */
public function isCacheable()
{
return $this->cacheable;
@@ -228,17 +226,13 @@ abstract class AbstractQuery
return $this->cacheRegion;
}
/**
* @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise.
*/
/** @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise. */
protected function isCacheEnabled()
{
return $this->cacheable && $this->hasCache;
}
/**
* @return int
*/
/** @return int */
public function getLifetime()
{
return $this->lifetime;
@@ -582,9 +576,7 @@ abstract class AbstractQuery
return $this;
}
/**
* @return QueryCacheProfile|null
*/
/** @return QueryCacheProfile|null */
public function getHydrationCacheProfile()
{
return $this->_hydrationCacheProfile;
@@ -834,9 +826,7 @@ abstract class AbstractQuery
return $this->_expireResultCache;
}
/**
* @return QueryCacheProfile|null
*/
/** @return QueryCacheProfile|null */
public function getQueryCacheProfile()
{
return $this->_queryCacheProfile;
@@ -1087,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

@@ -23,41 +23,31 @@ class CacheConfiguration
/** @var QueryCacheValidator|null */
private $queryValidator;
/**
* @return CacheFactory|null
*/
/** @return CacheFactory|null */
public function getCacheFactory()
{
return $this->cacheFactory;
}
/**
* @return void
*/
/** @return void */
public function setCacheFactory(CacheFactory $factory)
{
$this->cacheFactory = $factory;
}
/**
* @return CacheLogger|null
*/
/** @return CacheLogger|null */
public function getCacheLogger()
{
return $this->cacheLogger;
}
/**
* @return void
*/
/** @return void */
public function setCacheLogger(CacheLogger $logger)
{
$this->cacheLogger = $logger;
}
/**
* @return RegionsConfiguration
*/
/** @return RegionsConfiguration */
public function getRegionsConfiguration()
{
if ($this->regionsConfig === null) {
@@ -67,17 +57,13 @@ class CacheConfiguration
return $this->regionsConfig;
}
/**
* @return void
*/
/** @return void */
public function setRegionsConfiguration(RegionsConfiguration $regionsConfig)
{
$this->regionsConfig = $regionsConfig;
}
/**
* @return QueryCacheValidator
*/
/** @return QueryCacheValidator */
public function getQueryValidator()
{
if ($this->queryValidator === null) {
@@ -89,9 +75,7 @@ class CacheConfiguration
return $this->queryValidator;
}
/**
* @return void
*/
/** @return void */
public function setQueryValidator(QueryCacheValidator $validator)
{
$this->queryValidator = $validator;

View File

@@ -17,9 +17,7 @@ class CollectionCacheEntry implements CacheEntry
*/
public $identifiers;
/**
* @param CacheKey[] $identifiers List of entity identifiers hold by the collection
*/
/** @param CacheKey[] $identifiers List of entity identifiers hold by the collection */
public function __construct(array $identifiers)
{
$this->identifiers = $identifiers;

View File

@@ -20,8 +20,6 @@ interface CollectionHydrator
*/
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection);
/**
* @return mixed[]|null
*/
/** @return mixed[]|null */
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection);
}

View File

@@ -262,9 +262,7 @@ class DefaultCache implements Cache
return $this->queryCaches[$regionName];
}
/**
* @param mixed $identifier The entity identifier.
*/
/** @param mixed $identifier The entity identifier. */
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier): EntityCacheKey
{
if (! is_array($identifier)) {
@@ -274,9 +272,7 @@ class DefaultCache implements Cache
return new EntityCacheKey($metadata->rootEntityName, $identifier);
}
/**
* @param mixed $ownerIdentifier The identifier of the owning entity.
*/
/** @param mixed $ownerIdentifier The identifier of the owning entity. */
private function buildCollectionCacheKey(
ClassMetadata $metadata,
string $association,

View File

@@ -49,9 +49,7 @@ class DefaultCacheFactory implements CacheFactory
/** @var string|null */
private $fileLockRegionDirectory;
/**
* @param CacheItemPoolInterface $cacheItemPool
*/
/** @param CacheItemPoolInterface $cacheItemPool */
public function __construct(RegionsConfiguration $cacheConfig, $cacheItemPool)
{
if ($cacheItemPool instanceof LegacyCache) {
@@ -89,25 +87,19 @@ class DefaultCacheFactory implements CacheFactory
$this->fileLockRegionDirectory = (string) $fileLockRegionDirectory;
}
/**
* @return string
*/
/** @return string */
public function getFileLockRegionDirectory()
{
return $this->fileLockRegionDirectory;
}
/**
* @return void
*/
/** @return void */
public function setRegion(Region $region)
{
$this->regions[$region->getName()] = $region;
}
/**
* @return void
*/
/** @return void */
public function setTimestampRegion(TimestampRegion $region)
{
$this->timestampRegion = $region;

View File

@@ -27,9 +27,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
/** @var array<string,mixed> */
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param EntityManagerInterface $em The entity manager.
*/
/** @param EntityManagerInterface $em The entity manager. */
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;

View File

@@ -38,9 +38,7 @@ class DefaultEntityHydrator implements EntityHydrator
/** @var array<string,mixed> */
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param EntityManagerInterface $em The entity manager.
*/
/** @param EntityManagerInterface $em The entity manager. */
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;

View File

@@ -4,9 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
/**
* @deprecated
*/
/** @deprecated */
final class InvalidResultCacheDriver extends CacheException
{
public static function create(): self

View File

@@ -21,9 +21,7 @@ class Lock
$this->time = $time ?: time();
}
/**
* @return Lock
*/
/** @return Lock */
public static function createLockRead()
{
return new self(uniqid((string) time(), true));

View File

@@ -33,9 +33,7 @@ class CacheLoggerChain implements CacheLogger
return $this->loggers[$name] ?? null;
}
/**
* @return array<string, CacheLogger>
*/
/** @return array<string, CacheLogger> */
public function getLoggers()
{
return $this->loggers;

View File

@@ -141,25 +141,19 @@ class StatisticsCacheLogger implements CacheLogger
return $this->cachePutCountMap[$regionName] ?? 0;
}
/**
* @return array<string, int>
*/
/** @return array<string, int> */
public function getRegionsMiss()
{
return $this->cacheMissCountMap;
}
/**
* @return array<string, int>
*/
/** @return array<string, int> */
public function getRegionsHit()
{
return $this->cacheHitCountMap;
}
/**
* @return array<string, int>
*/
/** @return array<string, int> */
public function getRegionsPut()
{
return $this->cachePutCountMap;

View File

@@ -16,14 +16,10 @@ use Doctrine\ORM\Persisters\Collection\CollectionPersister;
*/
interface CachedCollectionPersister extends CachedPersister, CollectionPersister
{
/**
* @return ClassMetadata
*/
/** @return ClassMetadata */
public function getSourceEntityMetadata();
/**
* @return ClassMetadata
*/
/** @return ClassMetadata */
public function getTargetEntityMetadata();
/**

View File

@@ -14,9 +14,7 @@ use function spl_object_id;
class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/**
* @param mixed[] $association The association mapping.
*/
/** @param mixed[] $association The association mapping. */
public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association)
{
parent::__construct($persister, $region, $em, $association);

View File

@@ -177,9 +177,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->region;
}
/**
* @return EntityHydrator
*/
/** @return EntityHydrator */
public function getEntityHydrator()
{
return $this->hydrator;
@@ -207,9 +205,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $cached;
}
/**
* @param object $entity
*/
/** @param object $entity */
private function storeJoinedAssociations($entity): void
{
if ($this->joinedAssociations === null) {

View File

@@ -14,9 +14,7 @@ use Doctrine\ORM\Persisters\Entity\EntityPersister;
*/
interface CachedEntityPersister extends CachedPersister, EntityPersister
{
/**
* @return EntityHydrator
*/
/** @return EntityHydrator */
public function getEntityHydrator();
/**

View File

@@ -82,9 +82,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache['update'][] = $entity;
}
/**
* @param object $entity
*/
/** @param object $entity */
private function updateCache($entity, bool $isChanged): bool
{
$class = $this->metadataFactory->getMetadataFor(get_class($entity));

View File

@@ -12,9 +12,7 @@ use Doctrine\ORM\Query\ResultSetMapping;
*/
interface QueryCache
{
/**
* @return bool
*/
/** @return bool */
public function clear();
/**
@@ -32,8 +30,6 @@ interface QueryCache
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []);
/**
* @return Region
*/
/** @return Region */
public function getRegion();
}

View File

@@ -34,9 +34,7 @@ class QueryCacheKey extends CacheKey
*/
public $timestampKey;
/**
* @psalm-param Cache::MODE_* $cacheMode
*/
/** @psalm-param Cache::MODE_* $cacheMode */
public function __construct(
string $cacheId,
int $lifetime = 0,

View File

@@ -31,9 +31,7 @@ use function strtr;
*/
class DefaultRegion implements Region
{
/**
* @internal since 2.11, this constant will be private in 3.0.
*/
/** @internal since 2.11, this constant will be private in 3.0. */
public const REGION_KEY_SEPARATOR = '_';
private const REGION_PREFIX = 'DC2_REGION_';
@@ -61,9 +59,7 @@ class DefaultRegion implements Region
/** @var CacheItemPoolInterface */
private $cacheItemPool;
/**
* @param CacheItemPoolInterface $cacheItemPool
*/
/** @param CacheItemPoolInterface $cacheItemPool */
public function __construct(string $name, $cacheItemPool, int $lifetime = 0)
{
if ($cacheItemPool instanceof LegacyCache) {

View File

@@ -103,17 +103,13 @@ class FileLockRegion implements ConcurrentRegion
return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
}
/**
* @return string|false
*/
/** @return string|false */
private function getLockContent(string $filename)
{
return @file_get_contents($filename);
}
/**
* @return int|false
*/
/** @return int|false */
private function getLockTime(string $filename)
{
return @fileatime($filename);

View File

@@ -31,9 +31,7 @@ class RegionsConfiguration
$this->defaultLockLifetime = (int) $defaultLockLifetime;
}
/**
* @return int
*/
/** @return int */
public function getDefaultLifetime()
{
return $this->defaultLifetime;
@@ -49,9 +47,7 @@ class RegionsConfiguration
$this->defaultLifetime = (int) $defaultLifetime;
}
/**
* @return int
*/
/** @return int */
public function getDefaultLockLifetime()
{
return $this->defaultLockLifetime;

View File

@@ -17,9 +17,7 @@ class TimestampCacheEntry implements CacheEntry
*/
public $time;
/**
* @param float|null $time
*/
/** @param float|null $time */
public function __construct($time = null)
{
$this->time = $time ? (float) $time : microtime(true);

View File

@@ -9,9 +9,7 @@ namespace Doctrine\ORM\Cache;
*/
class TimestampCacheKey extends CacheKey
{
/**
* @param string $space Result cache id
*/
/** @param string $space Result cache id */
public function __construct($space)
{
$this->hash = (string) $space;

View File

@@ -975,9 +975,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
return $this->_attributes['repositoryFactory'] ?? new DefaultRepositoryFactory();
}
/**
* @return bool
*/
/** @return bool */
public function isSecondLevelCacheEnabled()
{
return $this->_attributes['isSecondLevelCacheEnabled'] ?? false;
@@ -993,17 +991,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
$this->_attributes['isSecondLevelCacheEnabled'] = (bool) $flag;
}
/**
* @return void
*/
/** @return void */
public function setSecondLevelCacheConfiguration(CacheConfiguration $cacheConfig)
{
$this->_attributes['secondLevelCacheConfiguration'] = $cacheConfig;
}
/**
* @return CacheConfiguration|null
*/
/** @return CacheConfiguration|null */
public function getSecondLevelCacheConfiguration()
{
if (! isset($this->_attributes['secondLevelCacheConfiguration']) && $this->isSecondLevelCacheEnabled()) {

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

View File

@@ -65,9 +65,7 @@ class EntityRepository implements ObjectRepository, Selectable
/** @var Inflector|null */
private static $inflector;
/**
* @psalm-param ClassMetadata<T> $class
*/
/** @psalm-param ClassMetadata<T> $class */
public function __construct(EntityManagerInterface $em, ClassMetadata $class)
{
$this->_entityName = $class->name;
@@ -306,9 +304,7 @@ class EntityRepository implements ObjectRepository, Selectable
return $this->getEntityName();
}
/**
* @return EntityManagerInterface
*/
/** @return EntityManagerInterface */
protected function getEntityManager()
{
return $this->_em;

View File

@@ -16,9 +16,7 @@ use Doctrine\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs;
*/
class LifecycleEventArgs extends BaseLifecycleEventArgs
{
/**
* @param object $object
*/
/** @param object $object */
public function __construct($object, EntityManagerInterface $objectManager)
{
Deprecation::triggerIfCalledFromOutside(
@@ -44,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

@@ -39,9 +39,7 @@ class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs
parent::__construct($objectManager);
}
/**
* @return void
*/
/** @return void */
public function setFoundMetadata(?ClassMetadata $classMetadata = null)
{
if (func_num_args() < 1) {
@@ -56,9 +54,7 @@ class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs
$this->foundMetadata = $classMetadata;
}
/**
* @return ClassMetadata|null
*/
/** @return ClassMetadata|null */
public function getFoundMetadata()
{
return $this->foundMetadata;

View File

@@ -20,9 +20,7 @@ class OnClearEventArgs extends BaseOnClearEventArgs
/** @var string|null */
private $entityClass;
/**
* @param string|null $entityClass Optional entity class.
*/
/** @param string|null $entityClass Optional entity class. */
public function __construct(EntityManagerInterface $em, $entityClass = null)
{
parent::__construct($em);

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

@@ -8,9 +8,7 @@ use function get_debug_type;
final class EntityMissingAssignedId extends ORMException
{
/**
* @param object $entity
*/
/** @param object $entity */
public static function forField($entity, string $field): self
{
return new self('Entity of type ' . get_debug_type($entity) . " is missing an assigned ID for field '" . $field . "'. " .

View File

@@ -11,9 +11,7 @@ final class MultipleSelectorsFoundException extends ORMException
{
public const MULTIPLE_SELECTORS_FOUND_EXCEPTION = 'Multiple selectors found: %s. Please select only one.';
/**
* @param string[] $selectors
*/
/** @param string[] $selectors */
public static function create(array $selectors): self
{
return new self(

View File

@@ -6,9 +6,7 @@ namespace Doctrine\ORM\Exception;
use function sprintf;
/**
* @deprecated No replacement planned.
*/
/** @deprecated No replacement planned. */
final class UnknownEntityNamespace extends ORMException implements ConfigurationException
{
public static function fromNamespaceAlias(string $entityNamespaceAlias): self

View File

@@ -9,9 +9,7 @@ use function sprintf;
final class UnrecognizedIdentifierFields extends ORMException implements ManagerException
{
/**
* @param string[] $fieldNames
*/
/** @param string[] $fieldNames */
public static function fromClassAndFieldNames(string $className, array $fieldNames): self
{
return new self(sprintf(

View File

@@ -99,9 +99,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
return serialize($this->__serialize());
}
/**
* @return array<string, mixed>
*/
/** @return array<string, mixed> */
public function __serialize(): array
{
return [
@@ -122,9 +120,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
$this->__unserialize(unserialize($serialized));
}
/**
* @param array<string, mixed> $data
*/
/** @param array<string, mixed> $data */
public function __unserialize(array $data): void
{
$this->_sequenceName = $data['sequenceName'];

View File

@@ -468,6 +468,10 @@ abstract class AbstractHydrator
? $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;
@@ -547,6 +551,7 @@ abstract class AbstractHydrator
'fieldName' => $fieldName,
'type' => Type::getType($fieldMapping['type']),
'dqlAlias' => $ownerMap,
'enumType' => $this->_rsm->enumMappings[$key] ?? null,
];
// the current discriminator value must be saved in order to disambiguate fields hydration,

View File

@@ -11,9 +11,7 @@ use function sprintf;
class HydrationException extends ORMException
{
/**
* @return HydrationException
*/
/** @return HydrationException */
public static function nonUniqueResult()
{
return new self('The result returned by the query was not unique.');

View File

@@ -27,9 +27,7 @@ class IterableResult implements Iterator
/** @var mixed[]|null */
private $_current = null;
/**
* @param AbstractHydrator $hydrator
*/
/** @param AbstractHydrator $hydrator */
public function __construct($hydrator)
{
$this->_hydrator = $hydrator;
@@ -65,27 +63,21 @@ class IterableResult implements Iterator
return $this->_current;
}
/**
* @return mixed
*/
/** @return mixed */
#[ReturnTypeWillChange]
public function current()
{
return $this->_current;
}
/**
* @return int
*/
/** @return int */
#[ReturnTypeWillChange]
public function key()
{
return $this->_key;
}
/**
* @return bool
*/
/** @return bool */
#[ReturnTypeWillChange]
public function valid()
{

View File

@@ -13,9 +13,7 @@ use function method_exists;
use function strtolower;
use function strtoupper;
/**
* @internal
*/
/** @internal */
trait SQLResultCasing
{
private function getSQLResultCasing(AbstractPlatform $platform, string $column): string

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

@@ -25,9 +25,7 @@ final class AssociationOverrides implements Annotation
*/
public $overrides = [];
/**
* @param array<AssociationOverride>|AssociationOverride $overrides
*/
/** @param array<AssociationOverride>|AssociationOverride $overrides */
public function __construct($overrides)
{
if (! is_array($overrides)) {

View File

@@ -25,9 +25,7 @@ final class AttributeOverrides implements Annotation
*/
public $overrides = [];
/**
* @param array<AttributeOverride>|AttributeOverride $overrides
*/
/** @param array<AttributeOverride>|AttributeOverride $overrides */
public function __construct($overrides)
{
if (! is_array($overrides)) {

View File

@@ -56,9 +56,7 @@ class AssociationBuilder
return $this;
}
/**
* @return $this
*/
/** @return $this */
public function cascadeAll()
{
$this->mapping['cascade'] = ['ALL'];
@@ -66,9 +64,7 @@ class AssociationBuilder
return $this;
}
/**
* @return $this
*/
/** @return $this */
public function cascadePersist()
{
$this->mapping['cascade'][] = 'persist';
@@ -76,9 +72,7 @@ class AssociationBuilder
return $this;
}
/**
* @return $this
*/
/** @return $this */
public function cascadeRemove()
{
$this->mapping['cascade'][] = 'remove';
@@ -86,9 +80,7 @@ class AssociationBuilder
return $this;
}
/**
* @return $this
*/
/** @return $this */
public function cascadeMerge()
{
$this->mapping['cascade'][] = 'merge';
@@ -96,9 +88,7 @@ class AssociationBuilder
return $this;
}
/**
* @return $this
*/
/** @return $this */
public function cascadeDetach()
{
$this->mapping['cascade'][] = 'detach';
@@ -106,9 +96,7 @@ class AssociationBuilder
return $this;
}
/**
* @return $this
*/
/** @return $this */
public function cascadeRefresh()
{
$this->mapping['cascade'][] = 'refresh';
@@ -116,9 +104,7 @@ class AssociationBuilder
return $this;
}
/**
* @return $this
*/
/** @return $this */
public function fetchExtraLazy()
{
$this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY;
@@ -126,9 +112,7 @@ class AssociationBuilder
return $this;
}
/**
* @return $this
*/
/** @return $this */
public function fetchEager()
{
$this->mapping['fetch'] = ClassMetadata::FETCH_EAGER;
@@ -136,9 +120,7 @@ class AssociationBuilder
return $this;
}
/**
* @return $this
*/
/** @return $this */
public function fetchLazy()
{
$this->mapping['fetch'] = ClassMetadata::FETCH_LAZY;

View File

@@ -36,9 +36,7 @@ class ClassMetadataBuilder
$this->cm = $cm;
}
/**
* @return ClassMetadataInfo
*/
/** @return ClassMetadataInfo */
public function getClassMetadata()
{
return $this->cm;

View File

@@ -17,9 +17,7 @@ class EmbeddedBuilder
/** @var mixed[] */
private $mapping;
/**
* @param mixed[] $mapping
*/
/** @param mixed[] $mapping */
public function __construct(ClassMetadataBuilder $builder, array $mapping)
{
$this->builder = $builder;

View File

@@ -31,9 +31,7 @@ class FieldBuilder
/** @var string|null */
private $customIdGenerator;
/**
* @param mixed[] $mapping
*/
/** @param mixed[] $mapping */
public function __construct(ClassMetadataBuilder $builder, array $mapping)
{
$this->builder = $builder;

View File

@@ -55,9 +55,7 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
return $this;
}
/**
* @return ClassMetadataBuilder
*/
/** @return ClassMetadataBuilder */
public function build()
{
$mapping = $this->mapping;

View File

@@ -35,9 +35,7 @@ class OneToManyAssociationBuilder extends AssociationBuilder
return $this;
}
/**
* @return ClassMetadataBuilder
*/
/** @return ClassMetadataBuilder */
public function build()
{
$mapping = $this->mapping;

View File

@@ -65,9 +65,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/** @var mixed[] */
private $embeddablesActiveNesting = [];
/**
* @return void
*/
/** @return void */
public function setEntityManager(EntityManagerInterface $em)
{
$this->em = $em;
@@ -659,9 +657,7 @@ DEPRECATION
}
}
/**
* @psalm-return ClassMetadata::GENERATOR_TYPE_SEQUENCE|ClassMetadata::GENERATOR_TYPE_IDENTITY
*/
/** @psalm-return ClassMetadata::GENERATOR_TYPE_SEQUENCE|ClassMetadata::GENERATOR_TYPE_IDENTITY */
private function determineIdGeneratorStrategy(AbstractPlatform $platform): int
{
if (

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,
@@ -574,7 +574,7 @@ class ClassMetadataInfo implements ClassMetadata
* READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
* inheritance mappings.
*
* @psalm-var array<string, mixed>|null
* @psalm-var array{name: string, fieldName: string, type: string, length?: int, columnDefinition?: string|null}|null
*/
public $discriminatorColumn;
@@ -716,8 +716,8 @@ class ClassMetadataInfo implements ClassMetadata
* )
* </code>
*
* @var array<string, mixed>
* @psalm-var array{sequenceName: string, allocationSize: string, initialValue: string, quoted?: mixed}
* @var array<string, mixed>|null
* @psalm-var array{sequenceName: string, allocationSize: string, initialValue: string, quoted?: mixed}|null
* @todo Merge with tableGeneratorDefinition into generic generatorDefinition
*/
public $sequenceGeneratorDefinition;
@@ -2323,9 +2323,7 @@ class ClassMetadataInfo implements ClassMetadata
return $this->generatorType !== self::GENERATOR_TYPE_NONE;
}
/**
* @return bool
*/
/** @return bool */
public function isInheritanceTypeNone()
{
return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
@@ -2378,6 +2376,8 @@ class ClassMetadataInfo implements ClassMetadata
* Checks whether the class uses a sequence for id generation.
*
* @return bool TRUE if the class uses the SEQUENCE generator, FALSE otherwise.
*
* @psalm-assert-if-true !null $this->sequenceGeneratorDefinition
*/
public function isIdGeneratorSequence()
{
@@ -3215,7 +3215,7 @@ class ClassMetadataInfo implements ClassMetadata
* @see getDiscriminatorColumn()
*
* @param mixed[]|null $columnDef
* @psalm-param array<string, mixed>|null $columnDef
* @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null}|null $columnDef
*
* @return void
*
@@ -3248,9 +3248,7 @@ class ClassMetadataInfo implements ClassMetadata
}
}
/**
* @return array<string, mixed>
*/
/** @return array<string, mixed> */
final public function getDiscriminatorColumn(): array
{
if ($this->discriminatorColumn === null) {
@@ -3845,9 +3843,7 @@ class ClassMetadataInfo implements ClassMetadata
}
}
/**
* @throws MappingException
*/
/** @throws MappingException */
private function assertFieldNotMapped(string $fieldName): void
{
if (
@@ -3899,9 +3895,7 @@ class ClassMetadataInfo implements ClassMetadata
return $sequencePrefix;
}
/**
* @psalm-param array<string, mixed> $mapping
*/
/** @psalm-param array<string, mixed> $mapping */
private function assertMappingOrderBy(array $mapping): void
{
if (isset($mapping['orderBy']) && ! is_array($mapping['orderBy'])) {
@@ -3909,9 +3903,7 @@ class ClassMetadataInfo implements ClassMetadata
}
}
/**
* @psalm-param class-string $class
*/
/** @psalm-param class-string $class */
private function getAccessibleProperty(ReflectionService $reflService, string $class, string $field): ?ReflectionProperty
{
$reflectionProperty = $reflService->getAccessibleProperty($class, $field);

View File

@@ -18,9 +18,7 @@ final class DiscriminatorMap implements Annotation
/** @var array<int|string, string> */
public $value;
/**
* @param array<int|string, string> $value
*/
/** @param array<int|string, string> $value */
public function __construct(array $value)
{
$this->value = $value;

View File

@@ -46,9 +46,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
*/
protected $reader;
/**
* @param array<string> $paths
*/
/** @param array<string> $paths */
public function __construct(array $paths)
{
if (PHP_VERSION_ID < 80000) {
@@ -254,10 +252,10 @@ class AttributeDriver extends CompatibilityAnnotationDriver
$metadata->setDiscriminatorColumn(
[
'name' => $discrColumnAttribute->name,
'type' => $discrColumnAttribute->type ?: 'string',
'length' => $discrColumnAttribute->length ?: 255,
'columnDefinition' => $discrColumnAttribute->columnDefinition,
'name' => isset($discrColumnAttribute->name) ? (string) $discrColumnAttribute->name : null,
'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string',
'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255,
'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null,
]
);
} else {

View File

@@ -17,9 +17,7 @@ use function is_string;
use function is_subclass_of;
use function sprintf;
/**
* @internal
*/
/** @internal */
final class AttributeReader
{
/** @var array<class-string<Annotation>,bool> */
@@ -134,9 +132,7 @@ final class AttributeReader
return $instances;
}
/**
* @param class-string<Annotation> $attributeClassName
*/
/** @param class-string<Annotation> $attributeClassName */
private function isRepeatable(string $attributeClassName): bool
{
if (isset($this->isRepeatableAttribute[$attributeClassName])) {

View File

@@ -10,16 +10,12 @@ use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use function class_exists;
if (! class_exists(PersistenceAnnotationDriver::class)) {
/**
* @internal This class will be removed in ORM 3.0.
*/
/** @internal This class will be removed in ORM 3.0. */
abstract class CompatibilityAnnotationDriver implements MappingDriver
{
}
} else {
/**
* @internal This class will be removed in ORM 3.0.
*/
/** @internal This class will be removed in ORM 3.0. */
abstract class CompatibilityAnnotationDriver extends PersistenceAnnotationDriver
{
}

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