Compare commits

..

24 Commits
4.0.x ... 3.6.3

Author SHA1 Message Date
Grégoire Paris
e88cd591f0 Merge pull request #12423 from greg0ire/3.6.x
Merge 2.20.x up into 3.6.x
2026-04-02 08:53:27 +02:00
Grégoire Paris
88a8f75f62 Merge remote-tracking branch 'origin/2.20.x' into 3.6.x 2026-04-02 08:27:51 +02:00
Tom Roar Furunes
9fe8ce4bf7 Merge pull request #12373 from tomme87/12225-fix-hydration-issue
12225 Fix hydration issue when using indexBy, SQL filter, and inheritance mapping
2026-04-02 08:18:54 +02:00
Grégoire Paris
a46ff16339 Merge pull request #12414 from ahmed-bhs/docs/enum-type-mapping
Add documentation for enumType mapping with PHP backed enums
2026-04-01 00:35:46 +02:00
Grégoire Paris
94e60e4318 Merge pull request #12419 from greg0ire/backport-12222
Backport #12222
2026-04-01 00:35:01 +02:00
Grégoire Paris
f59cd4019a Add ORDER BY clause to SELECT query
The order of results is not guaranteed unless we do so, and the test can
fail in some cases:

	There was 1 failure:

	1) Doctrine\Tests\ORM\Functional\QueryTest::testToIterableWithMixedResultArbitraryJoinsScalars
	Failed asserting that two strings are equal.
	--- Expected
	+++ Actual
	@@ @@
	-'Doctrine 2'
	+'lala 2'

	/home/runner/work/orm/orm/tests/Tests/ORM/Functional/QueryTest.php:481
2026-03-29 10:33:06 +02:00
Grégoire Paris
81558a8b2a Merge pull request #12418 from doctrine/dependabot/github_actions/2.20.x/codecov/codecov-action-6
Bump codecov/codecov-action from 5 to 6
2026-03-29 10:10:19 +02:00
dependabot[bot]
e431ee113d Bump codecov/codecov-action from 5 to 6
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-29 06:12:22 +00:00
Ahmed EBEN HASSINE
de4ec208fd Mention PostgreSQL jsonb as a reason to favor json over simple_array 2026-03-26 08:52:24 +01:00
Ahmed EBEN HASSINE 脳の流れ
df014a74c9 Update docs/en/reference/basic-mapping.rst
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2026-03-26 08:52:24 +01:00
Ahmed EBEN HASSINE
2f46d95028 Add nullable enums, default values, query usage and XML mapping to enum docs
Cover additional enumType behaviors: nullable columns, database default
values with enum cases, usage in DQL/QueryBuilder/findBy, and XML
mapping syntax with enum-type attribute.
2026-03-26 08:52:24 +01:00
Ahmed EBEN HASSINE
3fda5629f6 Add documentation for enumType mapping with PHP backed enums
The enumType option on #[Column] was barely mentioned in the docs and
had no dedicated section. This adds a complete reference covering
single-value columns, collection types (json, simple_array), automatic
type inference, validation behavior, and platform compatibility.
2026-03-26 08:52:00 +01:00
Grégoire Paris
6b273234d6 Merge pull request #12407 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/composer-lint.yml-14.0.0
Bump doctrine/.github/.github/workflows/composer-lint.yml from 13.1.0 to 14.0.0
2026-03-26 08:08:34 +01:00
Grégoire Paris
580a95ce3f Merge pull request #12413 from greg0ire/group-doctrine-updates
Group Doctrine workflow updates together
2026-03-26 06:53:01 +01:00
Grégoire Paris
8b91e248eb Group Doctrine workflow updates together
We do very small releases, often touching only one workflow, which means
it is unlikely to have several issues when attempting to bump several
references to this repository.
2026-03-25 20:48:20 +01:00
Grégoire Paris
d2266c7d0c Merge pull request #12408 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/documentation.yml-14.0.0
Bump doctrine/.github/.github/workflows/documentation.yml from 13.1.0 to 14.0.0
2026-03-22 13:53:32 +00:00
Grégoire Paris
eb0485869a Merge pull request #12406 from doctrine/dependabot/github_actions/2.20.x/ramsey/composer-install-4
Bump ramsey/composer-install from 3 to 4
2026-03-22 13:52:17 +00:00
Grégoire Paris
8372d600c6 Merge pull request #12409 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/coding-standards.yml-14.0.0
Bump doctrine/.github/.github/workflows/coding-standards.yml from 13.1.0 to 14.0.0
2026-03-22 08:41:51 +00:00
Grégoire Paris
dde1d71b34 Merge pull request #12410 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/release-on-milestone-closed.yml-14.0.0
Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml from 13.1.0 to 14.0.0
2026-03-22 08:38:30 +00:00
dependabot[bot]
0d03255061 Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml
Bumps [doctrine/.github/.github/workflows/release-on-milestone-closed.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:28 +00:00
dependabot[bot]
75a18090d9 Bump doctrine/.github/.github/workflows/coding-standards.yml
Bumps [doctrine/.github/.github/workflows/coding-standards.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:27 +00:00
dependabot[bot]
77ffd2ab68 Bump doctrine/.github/.github/workflows/documentation.yml
Bumps [doctrine/.github/.github/workflows/documentation.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:25 +00:00
dependabot[bot]
401cc06d71 Bump doctrine/.github/.github/workflows/composer-lint.yml
Bumps [doctrine/.github/.github/workflows/composer-lint.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:22 +00:00
dependabot[bot]
5029b193ee Bump ramsey/composer-install from 3 to 4
Bumps [ramsey/composer-install](https://github.com/ramsey/composer-install) from 3 to 4.
- [Release notes](https://github.com/ramsey/composer-install/releases)
- [Commits](https://github.com/ramsey/composer-install/compare/v3...v4)

---
updated-dependencies:
- dependency-name: ramsey/composer-install
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:19 +00:00
393 changed files with 6029 additions and 1662 deletions

View File

@@ -7,3 +7,7 @@ updates:
labels:
- "CI"
target-branch: "2.20.x"
groups:
doctrine:
patterns:
- "doctrine/*"

View File

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

View File

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

View File

@@ -38,10 +38,14 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
extension:
- "sqlite3"
- "pdo_sqlite"
@@ -49,30 +53,37 @@ jobs:
- "highest"
stability:
- "stable"
native_lazy:
- "0"
include:
- php-version: "8.4"
- php-version: "8.2"
dbal-version: "4@dev"
extension: "pdo_sqlite"
stability: "stable"
- php-version: "8.4"
native_lazy: "0"
- php-version: "8.2"
dbal-version: "4@dev"
extension: "sqlite3"
stability: "stable"
- php-version: "8.4"
native_lazy: "0"
- php-version: "8.1"
dbal-version: "default"
deps: "lowest"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "0"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "1"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "sqlite3"
stability: "dev"
native_lazy: "1"
steps:
- name: "Checkout"
@@ -100,8 +111,12 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Downgrade VarExporter"
run: 'composer require --no-update "symfony/var-exporter:^6.4 || ^7.4"'
if: "${{ matrix.native_lazy == '0' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
dependency-versions: "${{ matrix.deps }}"
@@ -110,6 +125,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
- name: "Run PHPUnit with Second Level Cache and PHPUnit 10"
run: |
@@ -131,11 +147,12 @@ jobs:
if: "${{ matrix.php-version != '8.1' }}"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
- name: "Upload coverage file"
uses: "actions/upload-artifact@v7"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.stability }}-coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.stability }}-${{ matrix.native_lazy }}-coverage"
path: "coverage*.xml"
@@ -187,23 +204,26 @@ jobs:
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
postgres-version:
- "17"
extension:
- pdo_pgsql
- pgsql
include:
- php-version: "8.4"
- php-version: "8.2"
dbal-version: "4@dev"
postgres-version: "14"
extension: pdo_pgsql
- php-version: "8.4"
dbal-version: "default"
postgres-version: "10"
- php-version: "8.2"
dbal-version: "3.7"
postgres-version: "9.6"
extension: pdo_pgsql
services:
@@ -237,7 +257,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -265,10 +285,13 @@ jobs:
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
- "4@dev"
mariadb-version:
- "11.4"
@@ -308,7 +331,7 @@ jobs:
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -336,10 +359,13 @@ jobs:
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
mysql-version:
- "5.7"
- "8.0"
@@ -347,11 +373,11 @@ jobs:
- "mysqli"
- "pdo_mysql"
include:
- php-version: "8.4"
- php-version: "8.2"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "mysqli"
- php-version: "8.4"
- php-version: "8.2"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "pdo_mysql"
@@ -387,7 +413,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -444,7 +470,7 @@ jobs:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v5"
uses: "codecov/codecov-action@v6"
with:
directory: reports
env:

View File

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

View File

@@ -32,7 +32,7 @@ jobs:
strategy:
matrix:
php-version:
- "8.4"
- "8.1"
steps:
- name: "Checkout"
@@ -48,7 +48,7 @@ jobs:
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
- name: "Run PHPBench"
run: "vendor/bin/phpbench run --report=default"

View File

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

View File

@@ -25,6 +25,14 @@ jobs:
name: Static Analysis with PHPStan
runs-on: ubuntu-22.04
strategy:
matrix:
include:
- dbal-version: default
config: phpstan.neon
- dbal-version: 3.8.2
config: phpstan-dbal3.neon
steps:
- name: "Checkout code"
uses: "actions/checkout@v6"
@@ -36,8 +44,13 @@ jobs:
php-version: "8.4"
tools: cs2pr
- name: Require specific DBAL version
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: Install dependencies with Composer
uses: ramsey/composer-install@v2
uses: ramsey/composer-install@v4
- name: Run static analysis with phpstan/phpstan
run: "vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr"
run: "vendor/bin/phpstan analyse -c ${{ matrix.config }} --error-format=checkstyle | cs2pr"

View File

@@ -3,7 +3,7 @@
| [![Build status][4.0 image]][4.0 workflow] | [![Build status][3.7 image]][3.7 workflow] | [![Build status][3.6 image]][3.6 workflow] | [![Build status][2.21 image]][2.21 workflow] | [![Build status][2.20 image]][2.20 workflow] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.7 coverage image]][3.7 coverage] | [![Coverage Status][3.6 coverage image]][3.6 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
Doctrine ORM is an object-relational mapper for PHP 8.4+ that provides transparent persistence
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility

View File

@@ -6,145 +6,6 @@ awareness about deprecated code.
- Use of our low-overhead runtime deprecation API, details:
https://github.com/doctrine/deprecations/
# Upgrade to 4.0
## BC BREAK: EventManager to EventManagerInterface migration
The following methods used to return an instance of `Doctrine\Common\EventManager`,
and now return an instance of `Doctrine\Common\EventManagerInterface`:
- `Doctrine\ORM\EntityManager::getEventManager()`
- `Doctrine\ORM\EntityManagerDecorator::getEventManager()`
- `Doctrine\ORM\EntityManagerInterface::getEventManager()`
## BC BREAK: Remove `FieldMapping::$default`
The `default` property of `Doctrine\ORM\Mapping\FieldMapping` has been removed.
Use `FieldMapping::$options['default']` instead.
## BC BREAK: throw on `nullable` on columns that end up being used in a primary key
Specifying `nullable` on join columns that are part of a primary key is
an error and will cause an exception to be thrown.
## BC BREAK: `Doctrine\ORM\Mapping\LegacyReflectionFields` is removed
The `Doctrine\ORM\Mapping\LegacyReflectionFields` class has been removed.
Also, `Doctrine\ORM\Mapping\ClassMetadata::$reflFields` has been removed, as
well as methods depending on it.
## BC BREAK: Userland lazy objects are no longer supported
Userland lazy objects are no longer supported.
[Native lazy objects](https://www.php.net/manual/en/language.oop5.lazy-objects.php)
are used instead.
## BC BREAK: type declarations on constants
All constants in the ORM now have type declarations and are final. You may no
longer override them in extending types.
## Remove methods for configuring no longer configurable features
Since 3.0, lazy ghosts are enabled unconditionally, and so is rejecting ID
collisions in the identity map.
As a consequence, the following methods are removed:
* `Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()`
* `Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()`
* `Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()`
* `Doctrine\ORM\Configuration::isRejectIdCollisionInIdentityMapEnabled()`
## BC BREAK: New argument to `NewObjectExpression::dispatch()`
```diff
<?php
class NewObjectExpression extends Node
{
// …
- public function dispatch(SqlWalker $walker): string
+ public function dispatch(SqlWalker $walker, string|null $parentAlias = null): string
{
// …
}
}
```
## BC BREAK: New argument to `AbstractEntityPersister::buildCollectionCacheKey()`
```diff
<?php
abstract class AbstractEntityPersister implements CachedEntityPersister
{
// …
protected function buildCollectionCacheKey(
AssociationMapping $association,
array $ownerId,
+ string $filterHash
): CollectionCacheKey {
// …
}
}
```
## Require implementation of `OutputWalker`, remove `SqlWalker::getExecutor()`
The `SqlWalker::getExecutor()` method is removed. Output walkers should
implement the `\Doctrine\ORM\Query\OutputWalker` interface and create
`Doctrine\ORM\Query\Exec\SqlFinalizer` instances.
## Remove `DatabaseDriver`
The class `Doctrine\ORM\Mapping\Driver\DatabaseDriver` is removed.
## Remove the `NotSupported` exception
The class `Doctrine\ORM\Exception\NotSupported` has been removed without replacement.
## Remove remaining `Serializable` implementation
`SequenceGenerator` does not implement the `Serializable` interface anymore.
The following methods have been removed:
* `SequenceGenerator::serialize()`
* `SequenceGenerator::unserialize()`
## Remove `orm:schema-tool:update` option `--complete`
That option was a no-op.
## Remove `Doctrine\ORM\Mapping\ReflectionEnumProperty`
This class has been removed.
Instead, use `Doctrine\Persistence\Reflection\EnumReflectionProperty` from
`doctrine/persistence`.
## Forbid passing null to `ClassMetadata::fullyQualifiedClassName()`
Passing `null` to `Doctrine\ORM\ClassMetadata::fullyQualifiedClassName()` is
no longer possible.
## Remove array access
Using array access on instances of the following classes is no longer possible:
- `Doctrine\ORM\Mapping\DiscriminatorColumnMapping`
- `Doctrine\ORM\Mapping\EmbedClassMapping`
- `Doctrine\ORM\Mapping\FieldMapping`
- `Doctrine\ORM\Mapping\JoinColumnMapping`
- `Doctrine\ORM\Mapping\JoinTableMapping`
## Remove properties `$indexes` and `$uniqueConstraints` from `Doctrine\ORM\Mapping\Table`
The properties `$indexes` and `$uniqueConstraints` have been removed since they had no effect at all.
The preferred way of defining indices and unique constraints is by
using the `\Doctrine\ORM\Mapping\UniqueConstraint` and `\Doctrine\ORM\Mapping\Index` attributes.
# Upgrade to 3.x General Notes
We recommend you upgrade to DBAL 3 first before upgrading to ORM 3. See
@@ -165,44 +26,6 @@ to run with ORM 3.
At this point, we recommend upgrading to PHP 8.4 first and then directly from
ORM 2.19 to 3.5 and up so that you can skip the lazy ghost proxy generation
and directly start using native lazy objects.
>>>>>>> origin/3.6.x
# Upgrade to 3.7
## Conditional breaking changes
3.7 adds support for `doctrine/collections` 3. If you upgrade to that version
of `doctrine/collections`, there are breaking changes in `doctrine/orm` as well,
because of cross-package inheritance and type declarations.
Most notably, `Doctrine\ORM\PersistentCollection::add` no longer returns a boolean:
```diff
- public function add(mixed $value): bool
+ public function add(mixed $value): void
```
That method always returned `true`, so you can safely stop using the return
value before upgrading.
Also, if you extend `Doctrine\ORM\Persisters\SqlValueVisitor`, you need to
ensure the following methods have a return type in your subclasses:
- `walkComparison()`
- `walkCompositeExpression()`
- `walkValue()`
## Deprecate `EventManager` return type in `EntityManager` methods
The return type of the following methods has been changed from
`Doctrine\Common\EventManager` to `Doctrine\Common\EventManagerInterface`:
- `Doctrine\ORM\Decorator\EntityManagerDecorator::getEventManager()`
- `Doctrine\ORM\EntityManager::getEventManager()`
- `Doctrine\ORM\EntityManagerInterface::getEventManager()`
All three methods continue to return an instance of `EventManager`, however
relying on that is deprecated and will no longer be the guaranteed in 4.0.
# Upgrade to 3.6

View File

@@ -31,19 +31,20 @@
],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"require": {
"php": "^8.4",
"php": "^8.1",
"ext-ctype": "*",
"composer-runtime-api": "^2",
"doctrine/collections": "^3",
"doctrine/dbal": "^4.2.1",
"doctrine/deprecations": "^1.1",
"doctrine/event-manager": "^2.1.1",
"doctrine/collections": "^2.2",
"doctrine/dbal": "^3.8.2 || ^4",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3 || ^2",
"doctrine/lexer": "^3",
"doctrine/persistence": "^4",
"doctrine/persistence": "^3.3.1 || ^4",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0"
"symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^14.0",
@@ -51,10 +52,9 @@
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "2.1.23",
"phpstan/phpstan-deprecation-rules": "^2",
"phpunit/phpunit": "^11.5.42",
"phpunit/phpunit": "^10.5.0 || ^11.5",
"psr/log": "^1 || ^2 || ^3",
"symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0"
"symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0"
},
"suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files",

View File

@@ -33,7 +33,18 @@ steps of configuration.
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
$config->enableNativeLazyObjects(true);
if (PHP_VERSION_ID > 80400) {
$config->enableNativeLazyObjects(true);
} else {
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
$config->setProxyNamespace('MyProject\Proxies');
if ($applicationMode === "development") {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
}
}
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
@@ -65,6 +76,55 @@ Configuration Options
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
.. _reference-native-lazy-objects:
Native Lazy Objects (**OPTIONAL**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
With PHP 8.4 we recommend that you use native lazy objects instead of
the code generation approach using the ``symfony/var-exporter`` Ghost trait.
With Doctrine 4, the minimal requirement will become PHP 8.4 and native lazy objects
will become the only approach to lazy loading.
.. code-block:: php
<?php
$config->enableNativeLazyObjects(true);
Proxy Directory
~~~~~~~~~~~~~~~
Required except if you use native lazy objects with PHP 8.4.
This setting will be removed in the future.
.. code-block:: php
<?php
$config->setProxyDir($dir);
$config->getProxyDir();
Gets or sets the directory where Doctrine generates any proxy
classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Proxy Namespace
~~~~~~~~~~~~~~~
Required except if you use native lazy objects with PHP 8.4.
This setting will be removed in the future.
.. code-block:: php
<?php
$config->setProxyNamespace($namespace);
$config->getProxyNamespace();
Gets or sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
Metadata Driver (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -208,6 +268,63 @@ Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
Auto-generating Proxy Classes (**OPTIONAL**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This setting is not required if you use native lazy objects with PHP 8.4
and will be removed in the future.
Proxy classes can either be generated manually through the Doctrine
Console or automatically at runtime by Doctrine. The configuration
option that controls this behavior is:
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($mode);
Possible values for ``$mode`` are:
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
Never autogenerate a proxy. You will need to generate the proxies
manually, for this use the Doctrine Console like so:
.. code-block:: php
$ ./doctrine orm:generate-proxies
When you do this in a development environment,
be aware that you may get class/file not found errors if certain proxies
are not yet generated. You may also get failing lazy-loads if new
methods were added to the entity class that are not yet in the proxy class.
In such a case, simply use the Doctrine Console to (re)generate the
proxy classes.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
Always generates a new proxy in every request and writes it to disk.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Generate the proxy class when the proxy file does not exist.
This strategy causes a file exists call whenever any proxy is
used the first time in a request.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via eval(),
avoiding writing the proxies to disk.
This strategy is only sane for development.
In a production environment, it is highly recommended to use
AUTOGENERATE_NEVER to allow for optimal performances. The other
options are interesting in development environment.
``setAutoGenerateProxyClasses`` can accept a boolean
value. This is still possible, ``FALSE`` being equivalent to
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
Development vs Production Configuration
---------------------------------------
@@ -323,6 +440,55 @@ transparently initialize itself on first access.
This will override the 'fetch' option specified in the mapping for
that association, but only for that query.
Generating Proxy classes
~~~~~~~~~~~~~~~~~~~~~~~~
In a production environment, it is highly recommended to use
``AUTOGENERATE_NEVER`` to allow for optimal performances.
However you will be required to generate the proxies manually
using the Doctrine Console:
.. code-block:: php
$ ./doctrine orm:generate-proxies
The other options are interesting in development environment:
- ``AUTOGENERATE_ALWAYS`` will require you to create and configure
a proxy directory. Proxies will be generated and written to file
on each request, so any modification to your code will be acknowledged.
- ``AUTOGENERATE_FILE_NOT_EXISTS`` will not overwrite an existing
proxy file. If your code changes, you will need to regenerate the
proxies manually.
- ``AUTOGENERATE_EVAL`` will regenerate each proxy on each request,
but without writing them to disk.
Autoloading Proxies
-------------------
When you deserialize proxy objects from the session or any other storage
it is necessary to have an autoloading mechanism in place for these classes.
For implementation reasons Proxy class names are not PSR-0 compliant. This
means that you have to register a special autoloader for these classes:
.. code-block:: php
<?php
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";
Autoloader::register($proxyDir, $proxyNamespace);
If you want to execute additional logic to intercept the proxy file not found
state you can pass a closure as the third argument. It will be called with
the arguments proxydir, namespace and className when the proxy file could not
be found.
Multiple Metadata Sources
-------------------------

View File

@@ -18,7 +18,7 @@ well.
Requirements
------------
Doctrine ORM requires a minimum of PHP 8.4. For greatly improved
Doctrine ORM requires a minimum of PHP 8.1. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine ORM Packages
@@ -79,8 +79,9 @@ Entities
An entity is a lightweight, persistent domain object. An entity can
be any regular PHP class observing the following restrictions:
- An entity class can be final or read-only. It may contain final
methods or read-only properties too.
- An entity class can be final or read-only when
you use :ref:`native lazy objects <reference-native-lazy-objects>`.
It may contain final methods or read-only properties too.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B

View File

@@ -168,7 +168,7 @@ Here is a complete list of ``Column``s attributes (all optional):
- ``insertable`` (default: ``true``): Whether the column should be inserted.
- ``updatable`` (default: ``true``): Whether the column should be updated.
- ``generated`` (default: ``null``): Whether the generated strategy should be ``'NEVER'``, ``'INSERT'`` and ``ALWAYS``.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into. See :ref:`reference-enum-mapping`.
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
(applies only for decimal column),
which is the maximum number of digits that are stored for the values.
@@ -321,6 +321,160 @@ that value and raw value are different, you have to use the raw value representa
$messageRepository = $entityManager->getRepository(Message::class);
$deMessages = $messageRepository->findBy(['language' => 'de']); // Use lower case here for raw value representation
.. _reference-enum-mapping:
Mapping PHP Enums
-----------------
.. versionadded:: 2.11
Doctrine natively supports mapping PHP backed enums to database columns.
A backed enum is a PHP enum that the same scalar type (``string`` or ``int``)
assigned to each case. Doctrine stores the scalar value in the database and
converts it back to the enum instance when hydrating the entity.
Using ``enumType`` provides three main benefits:
- **Automatic conversion**: Doctrine handles the conversion in both directions
transparently. When loading an entity, scalar values from the database are
converted into enum instances. When persisting, enum instances are reduced
to their scalar ``->value`` before being sent to the database.
- **Type-safety**: Entity properties contain enum instances directly. Your
getters return ``Suit`` instead of ``string``, removing the need to call
``Suit::from()`` manually.
- **Validation**: When a database value does not match any enum case, Doctrine
throws a ``MappingException`` during hydration instead of silently returning
an invalid value.
This feature works with all database platforms supported by Doctrine (MySQL,
PostgreSQL, SQLite, etc.) as it relies on standard column types (``string``,
``integer``, ``json``, ``simple_array``) rather than any vendor-specific enum
type.
.. note::
This is unrelated to the MySQL-specific ``ENUM`` column type covered in
:doc:`the MySQL Enums cookbook entry </cookbook/mysql-enums>`.
Defining an Enum
~~~~~~~~~~~~~~~~
.. literalinclude:: basic-mapping/Suit.php
:language: php
Only backed enums (``string`` or ``int``) are supported. Unit enums (without
a scalar value) cannot be mapped.
Single-Value Columns
~~~~~~~~~~~~~~~~~~~~
Use the ``enumType`` option on ``#[Column]`` to map a property to a backed enum.
The underlying database column stores the enum's scalar value (``string`` or ``int``).
.. literalinclude:: basic-mapping/EnumMapping.php
:language: php
When the PHP property is typed with the enum class, Doctrine automatically
infers the appropriate column type (``string`` for string-backed enums,
``integer`` for int-backed enums) and sets ``enumType``. You can also specify
the column ``type`` explicitly.
Storing Collections of Enums
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can store multiple enum values in a single column by combining ``enumType``
with a collection column type: ``json`` or ``simple_array``.
.. note::
Automatic type inference does not apply to collection columns. When the
PHP property is typed as ``array``, Doctrine cannot detect the enum class.
You must specify both ``type`` and ``enumType`` explicitly.
.. literalinclude:: basic-mapping/EnumCollectionMapping.php
:language: php
With ``json``, the values are stored as a JSON array (e.g. ``["hearts","spades"]``).
With ``simple_array``, the values are stored as a comma-separated string
(e.g. ``hearts,spades``).
In both cases, Doctrine converts each element to and from the enum
automatically during hydration and persistence.
.. tip::
Use ``json`` when enum values may contain commas, when you need to store
int-backed enums (as it preserves value types), when the column also
stores complex/nested data structures, or when you want to query individual
values using database-native JSON operators (e.g. PostgreSQL ``jsonb``).
Prefer ``simple_array`` for a compact, human-readable storage of
string-backed enums whose values do not contain commas.
+-------------------+-----------------------------+-------------------------------+
| Column type | Database storage | PHP type |
+===================+=============================+===============================+
| ``string`` | ``hearts`` | ``Suit`` |
+-------------------+-----------------------------+-------------------------------+
| ``integer`` | ``1`` | ``Priority`` |
+-------------------+-----------------------------+-------------------------------+
| ``json`` | ``["hearts","spades"]`` | ``array<Suit>`` |
+-------------------+-----------------------------+-------------------------------+
| ``simple_array`` | ``hearts,spades`` | ``array<Suit>`` |
+-------------------+-----------------------------+-------------------------------+
Nullable Enums
~~~~~~~~~~~~~~
Enum columns can be nullable. When the database value is ``NULL``, Doctrine
preserves it as ``null`` without triggering any validation error.
.. code-block:: php
<?php
#[ORM\Column(type: 'string', nullable: true, enumType: Suit::class)]
private Suit|null $suit = null;
Default Values
~~~~~~~~~~~~~~
You can specify a database-level default using an enum case directly in the
column options:
.. code-block:: php
<?php
#[ORM\Column(options: ['default' => Suit::Hearts])]
public Suit $suit;
Using Enums in Queries
~~~~~~~~~~~~~~~~~~~~~~
Enum instances can be used directly as parameters in DQL, QueryBuilder, and
repository methods. Doctrine converts them to their scalar value automatically.
.. code-block:: php
<?php
// QueryBuilder
$qb = $em->createQueryBuilder();
$qb->select('c')
->from(Card::class, 'c')
->where('c.suit = :suit')
->setParameter('suit', Suit::Clubs);
// Repository
$cards = $em->getRepository(Card::class)->findBy(['suit' => Suit::Clubs]);
XML Mapping
~~~~~~~~~~~
When using XML mapping, the ``enum-type`` attribute is used on ``<field>``
elements:
.. code-block:: xml
<field name="suit" type="string" enum-type="App\Entity\Suit" />
.. _reference-mapping-types:
Doctrine Mapping Types

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Player
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;
/** @var list<Suit> */
#[ORM\Column(type: 'json', enumType: Suit::class)]
private array $favouriteSuits = [];
/** @var list<Suit> */
#[ORM\Column(type: 'simple_array', enumType: Suit::class)]
private array $allowedSuits = [];
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Card
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;
#[ORM\Column(enumType: Suit::class)]
private Suit $suit;
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Entity;
enum Suit: string
{
case Hearts = 'hearts';
case Diamonds = 'diamonds';
case Clubs = 'clubs';
case Spades = 'spades';
}

View File

@@ -83,6 +83,8 @@ The following Commands are currently available:
cache drivers.
- ``orm:clear-cache:result`` Clear result cache of the various
cache drivers.
- ``orm:generate-proxies`` Generates proxy classes for entity
classes. Deprecated in favor of using native lazy objects.
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
line.
- ``orm:schema-tool:create`` Processes the schema and either

View File

@@ -49,7 +49,9 @@ An entity contains persistable properties. A persistable property
is an instance variable of the entity that is saved into and retrieved from the database
by Doctrine's data mapping capabilities.
An entity class can be final or read-only. It may contain final methods or read-only properties too.
An entity class can be final or read-only when you use
:ref:`native lazy objects <reference-native-lazy-objects>`.
It may contain final methods or read-only properties too.
An Example Model: Bug Tracker
-----------------------------

View File

@@ -11,7 +11,7 @@
<!-- Ignore warnings, show progress of the run and show sniff names -->
<arg value="nps"/>
<config name="php_version" value="80400"/>
<config name="php_version" value="80100"/>
<file>src</file>
<file>tests</file>
@@ -50,6 +50,8 @@
</rule>
<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
<exclude-pattern>src/Mapping/Driver/LoadMappingFileImplementation.php</exclude-pattern>
<exclude-pattern>src/Mapping/GetReflectionClassImplementation.php</exclude-pattern>
<exclude-pattern>tests/*</exclude-pattern>
</rule>
@@ -208,6 +210,12 @@
<exclude-pattern>tests/Tests/Models/DDC1590/DDC1590User.php</exclude-pattern>
</rule>
<rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
<!-- we need to test what happens with an stdClass proxy -->
<exclude-pattern>tests/Tests/Proxy/DefaultProxyClassNameResolverTest.php</exclude-pattern>
</rule>
<rule ref="Squiz.Commenting.FunctionComment.WrongStyle">
<!-- https://github.com/squizlabs/PHP_CodeSniffer/issues/1961 -->
<exclude-pattern>tests/Tests/Mocks/DatabasePlatformMock.php</exclude-pattern>

View File

@@ -942,6 +942,12 @@ parameters:
count: 4
path: src/Mapping/ClassMetadata.php
-
message: '#^If condition is always true\.$#'
identifier: if.alwaysTrue
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadata\:\:inlineEmbeddable\(\) has parameter \$embeddable with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
@@ -1302,12 +1308,6 @@ parameters:
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Expression on left side of \?\? is not nullable\.$#'
identifier: nullCoalesce.expr
count: 1
path: src/Mapping/Driver/AttributeDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\AttributeDriver\:\:isRepeatedPropertyDeclaration\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
@@ -1326,6 +1326,96 @@ parameters:
count: 1
path: src/Mapping/Driver/AttributeDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedColumn…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedTableN…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencingColum…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Index'' and ''getIndexedColumns'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Table'' and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Index and ''getType'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Table and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 2
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Instanceof between Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\> and Doctrine\\ORM\\Mapping\\ClassMetadata will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:__construct\(\) has parameter \$sm with generic class Doctrine\\DBAL\\Schema\\AbstractSchemaManager but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:buildFieldMappings\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:buildIndexes\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:buildToOneAssociationMappings\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) has parameter \$asset with generic class Doctrine\\DBAL\\Schema\\AbstractAsset but does not specify its types\: N$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getClassNameForTable\(\) should return class\-string but returns string\.$#'
identifier: return.type
count: 2
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#2 \$columnName of method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getFieldNameForColumn\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 4
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\SimplifiedXmlDriver\:\:__construct\(\) has parameter \$fileExtension with no type specified\.$#'
identifier: missingType.parameter
@@ -1428,6 +1518,18 @@ parameters:
count: 1
path: src/Mapping/JoinTableMapping.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\LegacyReflectionFields\:\:__construct\(\) has parameter \$classMetadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/LegacyReflectionFields.php
-
message: '#^Parameter \#1 \$class of method Doctrine\\Persistence\\Mapping\\ReflectionService\:\:getAccessibleProperty\(\) expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/LegacyReflectionFields.php
-
message: '#^Strict comparison using \!\=\= between array\<string, string\> and null will always evaluate to true\.$#'
identifier: notIdentical.alwaysTrue
@@ -1500,6 +1602,12 @@ parameters:
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ReflectionEnumProperty\:\:getValue\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Mapping/ReflectionEnumProperty.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\:\:fromMappingArray\(\) should return static\(Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\) but returns Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\.$#'
identifier: return.type
@@ -1573,7 +1681,7 @@ parameters:
path: src/PersistentCollection.php
-
message: '#^Method Doctrine\\ORM\\PersistentCollection\:\:matching\(\) should return Doctrine\\Common\\Collections\\Collection\<TKey of \(int\|string\), T\> but returns Doctrine\\Common\\Collections\\ReadableCollection\<TKey of \(int\|string\), T\>\.$#'
message: '#^Method Doctrine\\ORM\\PersistentCollection\:\:matching\(\) should return Doctrine\\Common\\Collections\\Collection\<TKey of \(int\|string\), T\> but returns Doctrine\\Common\\Collections\\ReadableCollection\<TKey of \(int\|string\), T\>&Doctrine\\Common\\Collections\\Selectable\<TKey of \(int\|string\), T\>\.$#'
identifier: return.type
count: 1
path: src/PersistentCollection.php
@@ -1585,7 +1693,7 @@ parameters:
path: src/PersistentCollection.php
-
message: '#^Parameter \#2 \$callback of function array_walk expects callable\(object, int\)\: mixed, array\{Doctrine\\Common\\Collections\\Collection\<TKey of \(int\|string\), T\>, ''add''\} given\.$#'
message: '#^Parameter \#2 \$callback of function array_walk expects callable\(object, int\)\: mixed, array\{Doctrine\\Common\\Collections\\Collection\<TKey of \(int\|string\), T\>&Doctrine\\Common\\Collections\\Selectable\<TKey of \(int\|string\), T\>, ''add''\} given\.$#'
identifier: argument.type
count: 1
path: src/PersistentCollection.php
@@ -1710,12 +1818,6 @@ parameters:
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:count\(\) should return int\<0, max\> but returns int\.$#'
identifier: return.type
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:delete\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
@@ -2004,6 +2106,12 @@ parameters:
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Parameter \#2 \$lockMode of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:appendLockHint\(\) expects Doctrine\\DBAL\\LockMode, Doctrine\\DBAL\\LockMode\:\:NONE\|Doctrine\\DBAL\\LockMode\:\:OPTIMISTIC\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_READ\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_WRITE\|int given\.$#'
identifier: argument.type
count: 2
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Parameter \#3 \$hints of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:hydrateAll\(\) expects array\<string, string\>, array\<string, Doctrine\\ORM\\PersistentCollection\|true\> given\.$#'
identifier: argument.type
@@ -2076,6 +2184,12 @@ parameters:
count: 1
path: src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '#^Parameter \#2 \$lockMode of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:appendLockHint\(\) expects Doctrine\\DBAL\\LockMode, Doctrine\\DBAL\\LockMode\:\:NONE\|Doctrine\\DBAL\\LockMode\:\:OPTIMISTIC\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_READ\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_WRITE\|int given\.$#'
identifier: argument.type
count: 1
path: src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
@@ -2100,6 +2214,156 @@ parameters:
count: 1
path: src/Persisters/SqlValueVisitor.php
-
message: '#^Parameter \#3 \$className of static method Doctrine\\ORM\\Proxy\\Autoloader\:\:resolveFile\(\) expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/Autoloader.php
-
message: '#^Parameter \#3 of closure expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/Autoloader.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\DefaultProxyClassNameResolver\:\:resolveClassName\(\) should return class\-string\<T of object\> but returns class\-string\<Doctrine\\Persistence\\Proxy\<T of object\>\>\|class\-string\<T of object\>\.$#'
identifier: return.type
count: 1
path: src/Proxy/DefaultProxyClassNameResolver.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\DefaultProxyClassNameResolver\:\:resolveClassName\(\) should return class\-string\<T of object\> but returns string\.$#'
identifier: return.type
count: 1
path: src/Proxy/DefaultProxyClassNameResolver.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$isEmbeddedClass\.$#'
identifier: property.notFound
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$isMappedSuperclass\.$#'
identifier: property.notFound
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Call to an undefined static method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyGhost\(\)\.$#'
identifier: staticMethod.notFound
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Call to function is_bool\(\) with bool will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Comparison operation "\<" between 0\|1\|2\|3\|4 and 0 is always false\.$#'
identifier: smaller.alwaysFalse
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Comparison operation "\>" between 0\|1\|2\|3\|4 and 4 is always false\.$#'
identifier: greater.alwaysFalse
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) has Doctrine\\ORM\\EntityNotFoundException in PHPDoc @throws tag but it''s not thrown\.$#'
identifier: throws.unusedType
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) has parameter \$classMetadata with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) return type with generic interface Doctrine\\ORM\\Proxy\\InternalProxy does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateProxyClass\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateProxyClasses\(\) has parameter \$classes with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateSerializeImpl\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateUseLazyGhostTrait\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:loadProxyClass\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:skipClass\(\) has parameter \$metadata with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#1 \$class of method Doctrine\\ORM\\Utility\\IdentifierFlattener\:\:flattenIdentifier\(\) expects Doctrine\\ORM\\Mapping\\ClassMetadata, Doctrine\\Persistence\\Mapping\\ClassMetadata given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#1 \$filename of function filemtime expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#3 \$length of function substr expects int\|null, int\|false given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#3 \$newScope of static method Closure\:\:bind\(\) expects ''static''\|class\-string\|object\|null, string given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Result of \|\| is always false\.$#'
identifier: booleanOr.alwaysFalse
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Query\:\:processParameterMappings\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
@@ -2220,6 +2484,12 @@ parameters:
count: 1
path: src/Query/AST/Functions/SizeFunction.php
-
message: '#^Parameter \#2 \$mode of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getTrimExpression\(\) expects Doctrine\\DBAL\\Platforms\\TrimMode, Doctrine\\DBAL\\Platforms\\TrimMode\:\:BOTH\|Doctrine\\DBAL\\Platforms\\TrimMode\:\:LEADING\|Doctrine\\DBAL\\Platforms\\TrimMode\:\:TRAILING\|Doctrine\\DBAL\\Platforms\\TrimMode\:\:UNSPECIFIED\|int given\.$#'
identifier: argument.type
count: 2
path: src/Query/AST/Functions/TrimFunction.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Query\\SqlWalker\:\:walkJoinPathExpression\(\)\.$#'
identifier: method.notFound
@@ -2286,6 +2556,12 @@ parameters:
count: 1
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '#^Parameter \#3 \$types of method Doctrine\\DBAL\\Connection\:\:executeStatement\(\) expects array\<int\<0, max\>\|string, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|string\>, list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|Doctrine\\DBAL\\Types\\Type\|int\|string\> given\.$#'
identifier: argument.type
count: 1
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '#^Parameter \#1 \$sql of method Doctrine\\DBAL\\Connection\:\:executeQuery\(\) expects string, list\<string\>\|string given\.$#'
identifier: argument.type
@@ -2310,6 +2586,12 @@ parameters:
count: 1
path: src/Query/Expr/Func.php
-
message: '#^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns int so it can be removed from the return type\.$#'
identifier: return.unusedType
count: 1
path: src/Query/ParameterTypeInferer.php
-
message: '#^@readonly property cannot have a default value\.$#'
identifier: property.readOnlyByPhpDocDefaultValue
@@ -2544,12 +2826,6 @@ parameters:
count: 2
path: src/QueryBuilder.php
-
message: '#^Parameter \#3 \$type of method Doctrine\\ORM\\QueryBuilder\:\:setParameter\(\) expects Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|string\|null, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|int\|string\|null given\.$#'
identifier: argument.type
count: 1
path: src/QueryBuilder.php
-
message: '#^Method Doctrine\\ORM\\Repository\\DefaultRepositoryFactory\:\:createRepository\(\) return type with generic class Doctrine\\ORM\\EntityRepository does not specify its types\: T$#'
identifier: missingType.generics
@@ -2562,6 +2838,24 @@ parameters:
count: 1
path: src/Repository/DefaultRepositoryFactory.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Parameter \#1 \$filename of function file_exists expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Parameter \#1 \$filename of function is_writable expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\Command\\MappingDescribeCommand\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
@@ -3135,6 +3429,12 @@ parameters:
count: 2
path: src/UnitOfWork.php
-
message: '#^Parameter \#3 \$collection of class Doctrine\\ORM\\PersistentCollection constructor expects Doctrine\\Common\\Collections\\Collection\<\(int\|string\), mixed\>&Doctrine\\Common\\Collections\\Selectable\<\(int\|string\), mixed\>, Doctrine\\Common\\Collections\\Collection given\.$#'
identifier: argument.type
count: 1
path: src/UnitOfWork.php
-
message: '#^Parameter \#5 \$invoke of method Doctrine\\ORM\\Event\\ListenersInvoker\:\:invoke\(\) expects int\<0, 7\>, int\<min, \-1\>\|int\<1, max\> given\.$#'
identifier: argument.type
@@ -3153,6 +3453,12 @@ parameters:
count: 1
path: src/UnitOfWork.php
-
message: '#^Unable to resolve the template type T in call to method static method Symfony\\Component\\VarExporter\\Hydrator\:\:hydrate\(\)$#'
identifier: argument.templateType
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
@@ -3242,3 +3548,9 @@ parameters:
identifier: missingType.generics
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:inferParameterTypes\(\) should return list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|string\> but returns list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|int\>\.$#'
identifier: return.type
count: 1
path: src/Utility/PersisterHelper.php

162
phpstan-dbal3.neon Normal file
View File

@@ -0,0 +1,162 @@
includes:
- phpstan-baseline.neon
- phpstan-params.neon
parameters:
reportUnmatchedIgnoredErrors: false # Some errors in the baseline only apply to DBAL 4
ignoreErrors:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
# We can be certain that those values are not matched.
-
message: '~^Match expression does not handle remaining values:~'
path: src/Utility/PersisterHelper.php
# DBAL 4 compatibility
-
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~.*getTrimExpression.*expects int.*~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~^Call to static method unquoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Instantiated class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName not found\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table::addPrimaryKeyConstraint\(\)\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to static method quoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:getObjectName\(\)\.$~'
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint::get.*\.$~'
identifier: method.notFound
-
message: '~createComparator~'
identifier: arguments.count
-
message: '~UnqualifiedName~'
identifier: class.notFound
-
message: '~IndexedColumn~'
identifier: class.notFound
-
message: '~PrimaryKeyConstraint~'
identifier: class.notFound
-
message: '~IndexType~'
identifier: class.notFound
-
message: '~dropForeignKey~'
identifier: method.notFound
-
message: '~getIndexedColumns~'
identifier: method.notFound
-
message: '~getPrimaryKeyConstraint~'
identifier: method.notFound
-
message: '~PrimaryKeyConstraint~'
identifier: class.notFound
path: src/Tools/SchemaTool.php
-
message: '~^Call to method toString.*UnqualifiedName\.$~'
path: src/Tools/SchemaTool.php
- '~^Call to method getObjectName\(\) on an unknown class Doctrine\\DBAL\\Schema\\NamedObject\.$~'
- '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~'
- '~^Class Doctrine\\DBAL\\Schema\\NamedObject not found\.$~'
-
message: '~sort~'
identifier: argument.unresolvableType
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#1 \$asset of static method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) expects Doctrine\\DBAL\\Schema\\AbstractAsset, Doctrine\\DBAL\\Schema\\Column\|false given\.$#'
identifier: argument.type
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Instantiated class Doctrine\\DBAL\\Schema\\DefaultExpression\\\w+ not found\.$#'
identifier: class.notFound
path: src/Tools/SchemaTool.php
# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
paths:
- src/Mapping/Driver/AttributeDriver.php
-
message: '~^Call to deprecated method getEventManager\(\) of class Doctrine\\DBAL\\Connection\.$~'
path: src/EntityManager.php
-
message: '~deprecated class Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand\:~'
path: src/Tools/Console/ConsoleRunner.php
# Compatibility with Persistence 3
-
message: '#Expression on left side of \?\? is not nullable.#'
path: src/Mapping/Driver/AttributeDriver.php
-
message: '~^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Utility/PersisterHelper.php
-
message: '~inferParameterTypes.*should return~'
path: src/Utility/PersisterHelper.php
-
message: '~.*appendLockHint.*expects.*LockMode given~'
paths:
- src/Persisters/Entity/BasicEntityPersister.php
- src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '~.*executeStatement.*expects~'
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '~method_exists.*getEventManager~'
path: src/EntityManager.php
-
message: '~method_exists.*getIdentitySequence~'
path: src/Mapping/ClassMetadataFactory.php
-
message: '~expand(Criteria)?Parameters.*should return array~'
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '~inferType.*never returns~'
path: src/Query/ParameterTypeInferer.php

View File

@@ -3,8 +3,6 @@ includes:
- phpstan-params.neon
parameters:
checkMissingOverrideMethodAttribute: true
ignoreErrors:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
@@ -13,3 +11,46 @@ parameters:
-
message: '~^Match expression does not handle remaining values:~'
path: src/Utility/PersisterHelper.php
# The return type is already narrow enough.
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns ''[a-z_]+'' so it can be removed from the return type\.$~'
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns Doctrine\\DBAL\\(?:Array)?ParameterType\:\:[A-Z_]+ so it can be removed from the return type\.$~'
# DBAL 4 compatibility
-
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Utility/PersisterHelper.php
# Compatibility with DBAL 3
# See https://github.com/doctrine/dbal/pull/3480
-
message: '~^Result of method Doctrine\\DBAL\\Connection::commit\(\) \(void\) is used\.$~'
path: src/UnitOfWork.php
-
message: '~^Strict comparison using === between null and false will always evaluate to false\.$~'
path: src/UnitOfWork.php
-
message: '~^Variable \$e on left side of \?\? always exists and is not nullable\.$~'
path: src/UnitOfWork.php
-
message: '~^Parameter #2 \$command of static method Doctrine\\ORM\\Tools\\Console\\ConsoleRunner::addCommandToApplication\(\) expects Symfony\\Component\\Console\\Command\\Command, Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand given\.$~'
path: src/Tools/Console/ConsoleRunner.php
-
message: '~Strict comparison using \=\=\= between callable\(\)\: mixed and null will always evaluate to false\.~'
path: src/Tools/SchemaTool.php
# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
paths:
- src/Mapping/Driver/AttributeDriver.php
# Compatibility with Persistence 3
-
message: '#Expression on left side of \?\? is not nullable.#'
path: src/Mapping/Driver/AttributeDriver.php

View File

@@ -16,6 +16,7 @@ use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\Mapping\MappingException;
@@ -50,32 +51,32 @@ abstract class AbstractQuery
/**
* Hydrates an object graph. This is the default behavior.
*/
final public const int HYDRATE_OBJECT = 1;
public const HYDRATE_OBJECT = 1;
/**
* Hydrates an array graph.
*/
final public const int HYDRATE_ARRAY = 2;
public const HYDRATE_ARRAY = 2;
/**
* Hydrates a flat, rectangular result set with scalar values.
*/
final public const int HYDRATE_SCALAR = 3;
public const HYDRATE_SCALAR = 3;
/**
* Hydrates a single scalar value.
*/
final public const int HYDRATE_SINGLE_SCALAR = 4;
public const HYDRATE_SINGLE_SCALAR = 4;
/**
* Very simple object hydrator (optimized for performance).
*/
final public const int HYDRATE_SIMPLEOBJECT = 5;
public const HYDRATE_SIMPLEOBJECT = 5;
/**
* Hydrates scalar column value.
*/
final public const int HYDRATE_SCALAR_COLUMN = 6;
public const HYDRATE_SCALAR_COLUMN = 6;
/**
* The parameter map of this query.
@@ -319,16 +320,16 @@ abstract class AbstractQuery
/**
* Sets a query parameter.
*
* @param string|int $key The parameter position or name.
* @param mixed $value The parameter value.
* @param ParameterType|ArrayParameterType|string|null $type The parameter type. If specified, the given value
* will be run through the type conversion of this
* type. This is usually not needed for strings and
* numeric types.
* @param string|int $key The parameter position or name.
* @param mixed $value The parameter value.
* @param ParameterType|ArrayParameterType|string|int|null $type The parameter type. If specified, the given value
* will be run through the type conversion of this
* type. This is usually not needed for strings and
* numeric types.
*
* @return $this
*/
public function setParameter(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|null $type = null): static
public function setParameter(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|int|null $type = null): static
{
$existingParameter = $this->getParameter($key);
@@ -377,7 +378,7 @@ abstract class AbstractQuery
}
try {
$class = $value::class;
$class = DefaultProxyClassNameResolver::getClass($value);
$value = $this->em->getUnitOfWork()->getSingleIdentifierValue($value);
if ($value === null) {

View File

@@ -12,31 +12,31 @@ use Doctrine\ORM\Cache\Region;
*/
interface Cache
{
final public const string DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
public const DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
final public const string DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
public const DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
/**
* May read items from the cache, but will not add items.
*/
final public const int MODE_GET = 1;
public const MODE_GET = 1;
/**
* Will never read items from the cache,
* but will add items to the cache as it reads them from the database.
*/
final public const int MODE_PUT = 2;
public const MODE_PUT = 2;
/**
* May read items from the cache, and add items to the cache.
*/
final public const int MODE_NORMAL = 3;
public const MODE_NORMAL = 3;
/**
* The query will never read items from the cache,
* but will refresh items to the cache as it reads them from the database.
*/
final public const int MODE_REFRESH = 4;
public const MODE_REFRESH = 4;
public function getEntityCacheRegion(string $className): Region|null;

View File

@@ -9,8 +9,8 @@ use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\ORMInvalidArgumentException;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\UnitOfWork;
use Override;
use function is_array;
use function is_object;
@@ -40,7 +40,6 @@ class DefaultCache implements Cache
->getCacheFactory();
}
#[Override]
public function getEntityCacheRegion(string $className): Region|null
{
$metadata = $this->em->getClassMetadata($className);
@@ -53,7 +52,6 @@ class DefaultCache implements Cache
return $persister->getCacheRegion();
}
#[Override]
public function getCollectionCacheRegion(string $className, string $association): Region|null
{
$metadata = $this->em->getClassMetadata($className);
@@ -66,7 +64,6 @@ class DefaultCache implements Cache
return $persister->getCacheRegion();
}
#[Override]
public function containsEntity(string $className, mixed $identifier): bool
{
$metadata = $this->em->getClassMetadata($className);
@@ -79,7 +76,6 @@ class DefaultCache implements Cache
return $persister->getCacheRegion()->contains($this->buildEntityCacheKey($metadata, $identifier));
}
#[Override]
public function evictEntity(string $className, mixed $identifier): void
{
$metadata = $this->em->getClassMetadata($className);
@@ -92,7 +88,6 @@ class DefaultCache implements Cache
$persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier));
}
#[Override]
public function evictEntityRegion(string $className): void
{
$metadata = $this->em->getClassMetadata($className);
@@ -105,7 +100,6 @@ class DefaultCache implements Cache
$persister->getCacheRegion()->evictAll();
}
#[Override]
public function evictEntityRegions(): void
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
@@ -121,7 +115,6 @@ class DefaultCache implements Cache
}
}
#[Override]
public function containsCollection(string $className, string $association, mixed $ownerIdentifier): bool
{
$metadata = $this->em->getClassMetadata($className);
@@ -134,7 +127,6 @@ class DefaultCache implements Cache
return $persister->getCacheRegion()->contains($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
}
#[Override]
public function evictCollection(string $className, string $association, mixed $ownerIdentifier): void
{
$metadata = $this->em->getClassMetadata($className);
@@ -147,7 +139,6 @@ class DefaultCache implements Cache
$persister->getCacheRegion()->evict($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
}
#[Override]
public function evictCollectionRegion(string $className, string $association): void
{
$metadata = $this->em->getClassMetadata($className);
@@ -160,7 +151,6 @@ class DefaultCache implements Cache
$persister->getCacheRegion()->evictAll();
}
#[Override]
public function evictCollectionRegions(): void
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
@@ -182,13 +172,11 @@ class DefaultCache implements Cache
}
}
#[Override]
public function containsQuery(string $regionName): bool
{
return isset($this->queryCaches[$regionName]);
}
#[Override]
public function evictQueryRegion(string|null $regionName = null): void
{
if ($regionName === null && $this->defaultQueryCache !== null) {
@@ -202,7 +190,6 @@ class DefaultCache implements Cache
}
}
#[Override]
public function evictQueryRegions(): void
{
$this->getQueryCache()->clear();
@@ -212,7 +199,6 @@ class DefaultCache implements Cache
}
}
#[Override]
public function getQueryCache(string|null $regionName = null): QueryCache
{
if ($regionName === null) {
@@ -247,7 +233,7 @@ class DefaultCache implements Cache
private function toIdentifierArray(ClassMetadata $metadata, mixed $identifier): array
{
if (is_object($identifier)) {
$class = $identifier::class;
$class = DefaultProxyClassNameResolver::getClass($identifier);
if ($this->em->getMetadataFactory()->hasMetadataFor($class)) {
$identifier = $this->uow->getSingleIdentifierValue($identifier)
?? throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);

View File

@@ -23,7 +23,6 @@ use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use InvalidArgumentException;
use LogicException;
use Override;
use Psr\Cache\CacheItemPoolInterface;
use function assert;
@@ -64,7 +63,6 @@ class DefaultCacheFactory implements CacheFactory
$this->timestampRegion = $region;
}
#[Override]
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata): CachedEntityPersister
{
assert($metadata->cache !== null);
@@ -90,7 +88,6 @@ class DefaultCacheFactory implements CacheFactory
throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage));
}
#[Override]
public function buildCachedCollectionPersister(
EntityManagerInterface $em,
CollectionPersister $persister,
@@ -119,7 +116,6 @@ class DefaultCacheFactory implements CacheFactory
throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage));
}
#[Override]
public function buildQueryCache(EntityManagerInterface $em, string|null $regionName = null): QueryCache
{
return new DefaultQueryCache(
@@ -133,13 +129,11 @@ class DefaultCacheFactory implements CacheFactory
);
}
#[Override]
public function buildCollectionHydrator(EntityManagerInterface $em, AssociationMapping $mapping): CollectionHydrator
{
return new DefaultCollectionHydrator($em);
}
#[Override]
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata): EntityHydrator
{
return new DefaultEntityHydrator($em);
@@ -148,7 +142,6 @@ class DefaultCacheFactory implements CacheFactory
/**
* {@inheritDoc}
*/
#[Override]
public function getRegion(array $cache): Region
{
if (isset($this->regions[$cache['region']])) {
@@ -177,7 +170,6 @@ class DefaultCacheFactory implements CacheFactory
return $this->regions[$cache['region']] = $region;
}
#[Override]
public function getTimestampRegion(): TimestampRegion
{
if ($this->timestampRegion === null) {
@@ -190,7 +182,6 @@ class DefaultCacheFactory implements CacheFactory
return $this->timestampRegion;
}
#[Override]
public function createCache(EntityManagerInterface $entityManager): Cache
{
return new DefaultCache($entityManager);

View File

@@ -11,7 +11,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Override;
use function assert;
@@ -31,7 +30,6 @@ class DefaultCollectionHydrator implements CollectionHydrator
$this->uow = $em->getUnitOfWork();
}
#[Override]
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, array|Collection $collection): CollectionCacheEntry
{
$data = [];
@@ -43,7 +41,6 @@ class DefaultCollectionHydrator implements CollectionHydrator
return new CollectionCacheEntry($data);
}
#[Override]
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection): array|null
{
$assoc = $metadata->associationMappings[$key->association];

View File

@@ -6,10 +6,10 @@ namespace Doctrine\ORM\Cache;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Doctrine\ORM\Utility\IdentifierFlattener;
use Override;
use function assert;
use function is_array;
@@ -34,7 +34,6 @@ class DefaultEntityHydrator implements EntityHydrator
$this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory());
}
#[Override]
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, object $entity): EntityCacheEntry
{
$data = $this->uow->getOriginalEntityData($entity);
@@ -98,7 +97,7 @@ class DefaultEntityHydrator implements EntityHydrator
}
if (! isset($assoc->id)) {
$targetClass = $data[$name]::class;
$targetClass = DefaultProxyClassNameResolver::getClass($data[$name]);
$targetId = $this->uow->getEntityIdentifier($data[$name]);
$data[$name] = new AssociationCacheEntry($targetClass, $targetId);
@@ -126,7 +125,6 @@ class DefaultEntityHydrator implements EntityHydrator
return new EntityCacheEntry($metadata->name, $data);
}
#[Override]
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, object|null $entity = null): object|null
{
$data = $entry->data;

View File

@@ -18,7 +18,6 @@ use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\UnitOfWork;
use Override;
use function array_map;
use function array_shift;
@@ -55,7 +54,6 @@ class DefaultQueryCache implements QueryCache
/**
* {@inheritDoc}
*/
#[Override]
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []): array|null
{
if (! ($key->cacheMode & Cache::MODE_GET)) {
@@ -199,7 +197,6 @@ class DefaultQueryCache implements QueryCache
/**
* {@inheritDoc}
*/
#[Override]
public function put(QueryCacheKey $key, ResultSetMapping $rsm, mixed $result, array $hints = []): bool
{
if ($rsm->scalarMappings) {
@@ -409,13 +406,11 @@ class DefaultQueryCache implements QueryCache
return $values;
}
#[Override]
public function clear(): bool
{
return $this->region->evictAll();
}
#[Override]
public function getRegion(): Region
{
return $this->region;

View File

@@ -7,7 +7,6 @@ namespace Doctrine\ORM\Cache\Logging;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Override;
class CacheLoggerChain implements CacheLogger
{
@@ -30,7 +29,6 @@ class CacheLoggerChain implements CacheLogger
return $this->loggers;
}
#[Override]
public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -38,7 +36,6 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -46,7 +43,6 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function collectionCachePut(string $regionName, CollectionCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -54,7 +50,6 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function entityCacheHit(string $regionName, EntityCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -62,7 +57,6 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function entityCacheMiss(string $regionName, EntityCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -70,7 +64,6 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function entityCachePut(string $regionName, EntityCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -78,7 +71,6 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function queryCacheHit(string $regionName, QueryCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -86,7 +78,6 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function queryCacheMiss(string $regionName, QueryCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -94,7 +85,6 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function queryCachePut(string $regionName, QueryCacheKey $key): void
{
foreach ($this->loggers as $logger) {

View File

@@ -7,7 +7,6 @@ namespace Doctrine\ORM\Cache\Logging;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Override;
use function array_sum;
@@ -25,63 +24,54 @@ class StatisticsCacheLogger implements CacheLogger
/** @var array<string, int> */
private array $cachePutCountMap = [];
#[Override]
public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void
{
$this->cacheMissCountMap[$regionName]
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void
{
$this->cacheHitCountMap[$regionName]
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function collectionCachePut(string $regionName, CollectionCacheKey $key): void
{
$this->cachePutCountMap[$regionName]
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function entityCacheMiss(string $regionName, EntityCacheKey $key): void
{
$this->cacheMissCountMap[$regionName]
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function entityCacheHit(string $regionName, EntityCacheKey $key): void
{
$this->cacheHitCountMap[$regionName]
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function entityCachePut(string $regionName, EntityCacheKey $key): void
{
$this->cachePutCountMap[$regionName]
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function queryCacheHit(string $regionName, QueryCacheKey $key): void
{
$this->cacheHitCountMap[$regionName]
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function queryCacheMiss(string $regionName, QueryCacheKey $key): void
{
$this->cacheMissCountMap[$regionName]
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function queryCachePut(string $regionName, QueryCacheKey $key): void
{
$this->cachePutCountMap[$regionName]

View File

@@ -17,9 +17,9 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\UnitOfWork;
use Override;
use function array_values;
use function assert;
@@ -63,25 +63,21 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
$this->targetEntity = $em->getClassMetadata($association->targetEntity);
}
#[Override]
public function getCacheRegion(): Region
{
return $this->region;
}
#[Override]
public function getSourceEntityMetadata(): ClassMetadata
{
return $this->sourceEntity;
}
#[Override]
public function getTargetEntityMetadata(): ClassMetadata
{
return $this->targetEntity;
}
#[Override]
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key): array|null
{
$cache = $this->region->get($key);
@@ -93,7 +89,6 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
return $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection);
}
#[Override]
public function storeCollectionCache(CollectionCacheKey $key, Collection|array $elements): void
{
$associationMapping = $this->sourceEntity->associationMappings[$key->association];
@@ -116,7 +111,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
$class = $this->targetEntity;
$className = $elements[$index]::class;
$className = DefaultProxyClassNameResolver::getClass($elements[$index]);
if ($className !== $this->targetEntity->name) {
$class = $this->metadataFactory->getMetadataFor($className);
@@ -133,19 +128,16 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
}
#[Override]
public function contains(PersistentCollection $collection, object $element): bool
{
return $this->persister->contains($collection, $element);
}
#[Override]
public function containsKey(PersistentCollection $collection, mixed $key): bool
{
return $this->persister->containsKey($collection, $key);
}
#[Override]
public function count(PersistentCollection $collection): int
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
@@ -159,7 +151,6 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
return $this->persister->count($collection);
}
#[Override]
public function get(PersistentCollection $collection, mixed $index): mixed
{
return $this->persister->get($collection, $index);
@@ -168,7 +159,6 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
/**
* {@inheritDoc}
*/
#[Override]
public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array
{
return $this->persister->slice($collection, $offset, $length);
@@ -177,7 +167,6 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array
{
return $this->persister->loadCriteria($collection, $criteria);

View File

@@ -6,13 +6,11 @@ namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\PersistentCollection;
use Override;
use function spl_object_id;
class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
#[Override]
public function afterTransactionComplete(): void
{
if (isset($this->queuedCache['update'])) {
@@ -30,13 +28,11 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
$this->queuedCache = [];
}
#[Override]
public function afterTransactionRolledBack(): void
{
$this->queuedCache = [];
}
#[Override]
public function delete(PersistentCollection $collection): void
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
@@ -47,7 +43,6 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
$this->queuedCache['delete'][spl_object_id($collection)] = $key;
}
#[Override]
public function update(PersistentCollection $collection): void
{
$isInitialized = $collection->isInitialized();

View File

@@ -6,16 +6,15 @@ namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyCollection;
use Doctrine\ORM\PersistentCollection;
use Override;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister
{
#[Override]
public function update(PersistentCollection $collection): void
{
if ($collection->isDirty() && $collection->getSnapshot()) {
throw CannotUpdateReadOnlyCollection::fromEntityAndField(
$collection->getOwner()::class,
DefaultProxyClassNameResolver::getClass($collection->getOwner()),
$this->association->fieldName,
);
}

View File

@@ -10,7 +10,6 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Override;
use function spl_object_id;
@@ -25,7 +24,6 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
parent::__construct($persister, $region, $em, $association);
}
#[Override]
public function afterTransactionComplete(): void
{
if (isset($this->queuedCache['update'])) {
@@ -43,7 +41,6 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
$this->queuedCache = [];
}
#[Override]
public function afterTransactionRolledBack(): void
{
if (isset($this->queuedCache['update'])) {
@@ -61,7 +58,6 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
$this->queuedCache = [];
}
#[Override]
public function delete(PersistentCollection $collection): void
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
@@ -80,7 +76,6 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
];
}
#[Override]
public function update(PersistentCollection $collection): void
{
$isInitialized = $collection->isInitialized();

View File

@@ -23,12 +23,13 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use Override;
use function array_merge;
use function func_get_args;
use function serialize;
use function sha1;
@@ -76,7 +77,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
}
#[Override]
public function addInsert(object $entity): void
{
$this->persister->addInsert($entity);
@@ -85,17 +85,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function getInserts(): array
{
return $this->persister->getInserts();
}
#[Override]
public function getSelectSQL(
array|Criteria $criteria,
AssociationMapping|null $assoc = null,
LockMode|null $lockMode = null,
LockMode|int|null $lockMode = null,
int|null $limit = null,
int|null $offset = null,
array|null $orderBy = null,
@@ -103,25 +101,21 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy);
}
#[Override]
public function getCountSQL(array|Criteria $criteria = []): string
{
return $this->persister->getCountSQL($criteria);
}
#[Override]
public function getInsertSQL(): string
{
return $this->persister->getInsertSQL();
}
#[Override]
public function getResultSetMapping(): ResultSetMapping
{
return $this->persister->getResultSetMapping();
}
#[Override]
public function getSelectConditionStatementSQL(
string $field,
mixed $value,
@@ -131,7 +125,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison);
}
#[Override]
public function exists(object $entity, Criteria|null $extraConditions = null): bool
{
if ($extraConditions === null) {
@@ -145,23 +138,20 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->persister->exists($entity, $extraConditions);
}
#[Override]
public function getCacheRegion(): Region
{
return $this->region;
}
#[Override]
public function getEntityHydrator(): EntityHydrator
{
return $this->hydrator;
}
#[Override]
public function storeEntityCache(object $entity, EntityCacheKey $key): bool
{
$class = $this->class;
$className = $entity::class;
$className = DefaultProxyClassNameResolver::getClass($entity);
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
@@ -235,7 +225,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function expandParameters(array $criteria): array
{
return $this->persister->expandParameters($criteria);
@@ -244,13 +233,11 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function expandCriteriaParameters(Criteria $criteria): array
{
return $this->persister->expandCriteriaParameters($criteria);
}
#[Override]
public function getClassMetadata(): ClassMetadata
{
return $this->persister->getClassMetadata();
@@ -259,7 +246,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function getManyToManyCollection(
AssociationMapping $assoc,
object $sourceEntity,
@@ -272,7 +258,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function getOneToManyCollection(
AssociationMapping $assoc,
object $sourceEntity,
@@ -282,13 +267,11 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit);
}
#[Override]
public function getOwningTable(string $fieldName): string
{
return $this->persister->getOwningTable($fieldName);
}
#[Override]
public function executeInserts(): void
{
// The commit order/foreign key relationships may make it necessary that multiple calls to executeInsert()
@@ -305,13 +288,12 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function load(
array $criteria,
object|null $entity = null,
AssociationMapping|null $assoc = null,
array $hints = [],
LockMode|null $lockMode = null,
LockMode|int|null $lockMode = null,
int|null $limit = null,
array|null $orderBy = null,
): object|null {
@@ -353,7 +335,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadAll(
array $criteria = [],
array|null $orderBy = null,
@@ -390,7 +371,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadById(array $identifier, object|null $entity = null): object|null
{
$cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier);
@@ -418,7 +398,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
$class = $this->class;
$className = $entity::class;
$className = DefaultProxyClassNameResolver::getClass($entity);
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
@@ -440,7 +420,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $entity;
}
#[Override]
public function count(array|Criteria $criteria = []): int
{
return $this->persister->count($criteria);
@@ -449,7 +428,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadCriteria(Criteria $criteria): array
{
$orderBy = $criteria->orderings();
@@ -485,7 +463,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadManyToManyCollection(
AssociationMapping $assoc,
object $sourceEntity,
@@ -517,7 +494,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $list;
}
#[Override]
public function loadOneToManyCollection(
AssociationMapping $assoc,
object $sourceEntity,
@@ -552,7 +528,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null
{
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
@@ -561,8 +536,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function lock(array $criteria, LockMode $lockMode): void
public function lock(array $criteria, LockMode|int $lockMode): void
{
$this->persister->lock($criteria, $lockMode);
}
@@ -570,15 +544,16 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function refresh(array $id, object $entity, LockMode|null $lockMode = null): void
public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void
{
$this->persister->refresh($id, $entity, $lockMode);
}
/** @param array<string, mixed> $ownerId */
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId, string $filterHash): CollectionCacheKey
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId, /* string $filterHash */): CollectionCacheKey
{
$filterHash = (string) (func_get_args()[2] ?? ''); // todo: move to argument in next major release
return new CollectionCacheKey(
$this->metadataFactory->getMetadataFor($association->sourceEntity)->rootEntityName,
$association->fieldName,

View File

@@ -5,14 +5,12 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\EntityCacheKey;
use Override;
/**
* Specific non-strict read/write cached entity persister
*/
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
{
#[Override]
public function afterTransactionComplete(): void
{
$isChanged = false;
@@ -44,13 +42,11 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache = [];
}
#[Override]
public function afterTransactionRolledBack(): void
{
$this->queuedCache = [];
}
#[Override]
public function delete(object $entity): bool
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
@@ -65,7 +61,6 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
return $deleted;
}
#[Override]
public function update(object $entity): void
{
$this->persister->update($entity);

View File

@@ -5,16 +5,15 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity;
use Override;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
/**
* Specific read-only region entity persister
*/
class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister
{
#[Override]
public function update(object $entity): void
{
throw CannotUpdateReadOnlyEntity::fromEntity($entity::class);
throw CannotUpdateReadOnlyEntity::fromEntity(DefaultProxyClassNameResolver::getClass($entity));
}
}

View File

@@ -9,7 +9,6 @@ use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Override;
/**
* Specific read-write entity persister
@@ -21,7 +20,6 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
parent::__construct($persister, $region, $em, $class);
}
#[Override]
public function afterTransactionComplete(): void
{
$isChanged = true;
@@ -49,7 +47,6 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache = [];
}
#[Override]
public function afterTransactionRolledBack(): void
{
if (isset($this->queuedCache['update'])) {
@@ -67,7 +64,6 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache = [];
}
#[Override]
public function delete(object $entity): bool
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
@@ -90,7 +86,6 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
return $deleted;
}
#[Override]
public function update(object $entity): void
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));

View File

@@ -9,7 +9,6 @@ use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\CollectionCacheEntry;
use Doctrine\ORM\Cache\Lock;
use Doctrine\ORM\Cache\Region;
use Override;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Traversable;
@@ -23,8 +22,8 @@ use function strtr;
*/
class DefaultRegion implements Region
{
private const string REGION_KEY_SEPARATOR = '_';
private const string REGION_PREFIX = 'DC2_REGION_';
private const REGION_KEY_SEPARATOR = '_';
private const REGION_PREFIX = 'DC2_REGION_';
public function __construct(
private readonly string $name,
@@ -33,19 +32,16 @@ class DefaultRegion implements Region
) {
}
#[Override]
public function getName(): string
{
return $this->name;
}
#[Override]
public function contains(CacheKey $key): bool
{
return $this->cacheItemPool->hasItem($this->getCacheEntryKey($key));
}
#[Override]
public function get(CacheKey $key): CacheEntry|null
{
$item = $this->cacheItemPool->getItem($this->getCacheEntryKey($key));
@@ -58,7 +54,6 @@ class DefaultRegion implements Region
return $entry;
}
#[Override]
public function getMultiple(CollectionCacheEntry $collection): array|null
{
$keys = array_map(
@@ -88,7 +83,6 @@ class DefaultRegion implements Region
return $result;
}
#[Override]
public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool
{
$item = $this->cacheItemPool
@@ -102,13 +96,11 @@ class DefaultRegion implements Region
return $this->cacheItemPool->save($item);
}
#[Override]
public function evict(CacheKey $key): bool
{
return $this->cacheItemPool->deleteItem($this->getCacheEntryKey($key));
}
#[Override]
public function evictAll(): bool
{
return $this->cacheItemPool->clear(self::REGION_PREFIX . $this->name);

View File

@@ -11,7 +11,6 @@ use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\Cache\Lock;
use Doctrine\ORM\Cache\Region;
use InvalidArgumentException;
use Override;
use function array_filter;
use function array_map;
@@ -36,7 +35,7 @@ use const LOCK_EX;
*/
class FileLockRegion implements ConcurrentRegion
{
final public const string LOCK_EXTENSION = 'lock';
final public const LOCK_EXTENSION = 'lock';
/**
* @param numeric-string|int $lockLifetime
@@ -103,13 +102,11 @@ class FileLockRegion implements ConcurrentRegion
return @fileatime($filename);
}
#[Override]
public function getName(): string
{
return $this->region->getName();
}
#[Override]
public function contains(CacheKey $key): bool
{
if ($this->isLocked($key)) {
@@ -119,7 +116,6 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->contains($key);
}
#[Override]
public function get(CacheKey $key): CacheEntry|null
{
if ($this->isLocked($key)) {
@@ -129,7 +125,6 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->get($key);
}
#[Override]
public function getMultiple(CollectionCacheEntry $collection): array|null
{
if (array_filter(array_map($this->isLocked(...), $collection->identifiers))) {
@@ -139,7 +134,6 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->getMultiple($collection);
}
#[Override]
public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool
{
if ($this->isLocked($key, $lock)) {
@@ -149,7 +143,6 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->put($key, $entry);
}
#[Override]
public function evict(CacheKey $key): bool
{
if ($this->isLocked($key)) {
@@ -159,7 +152,6 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->evict($key);
}
#[Override]
public function evictAll(): bool
{
// The check below is necessary because on some platforms glob returns false
@@ -173,7 +165,6 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->evictAll();
}
#[Override]
public function lock(CacheKey $key): Lock|null
{
if ($this->isLocked($key)) {
@@ -192,7 +183,6 @@ class FileLockRegion implements ConcurrentRegion
return $lock;
}
#[Override]
public function unlock(CacheKey $key, Lock $lock): bool
{
if ($this->isLocked($key, $lock)) {

View File

@@ -7,14 +7,12 @@ namespace Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\TimestampCacheEntry;
use Doctrine\ORM\Cache\TimestampRegion;
use Override;
/**
* Tracks the timestamps of the most recent updates to particular keys.
*/
class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
{
#[Override]
public function update(CacheKey $key): void
{
$this->put($key, new TimestampCacheEntry());

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache;
use Override;
use function microtime;
class TimestampQueryCacheValidator implements QueryCacheValidator
@@ -14,7 +12,6 @@ class TimestampQueryCacheValidator implements QueryCacheValidator
{
}
#[Override]
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry): bool
{
if ($this->regionUpdated($key, $entry)) {

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Exception\InvalidEntityRepository;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
@@ -17,17 +18,21 @@ use Doctrine\ORM\Mapping\EntityListenerResolver;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Mapping\TypedFieldMapper;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use LogicException;
use Psr\Cache\CacheItemPoolInterface;
use function class_exists;
use function is_a;
use function strtolower;
use const PHP_VERSION_ID;
/**
* Configuration container for all configuration options of Doctrine.
* It combines all configuration options from DBAL & ORM.
@@ -54,6 +59,112 @@ class Configuration extends \Doctrine\DBAL\Configuration
return $this->identityGenerationPreferences;
}
/**
* Sets the directory where Doctrine generates any necessary proxy class files.
*/
public function setProxyDir(string $dir): void
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
$this->attributes['proxyDir'] = $dir;
}
/**
* Gets the directory where Doctrine generates any necessary proxy class files.
*/
public function getProxyDir(): string|null
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
return $this->attributes['proxyDir'] ?? null;
}
/**
* Gets the strategy for automatically generating proxy classes.
*
* @return ProxyFactory::AUTOGENERATE_*
*/
public function getAutoGenerateProxyClasses(): int
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
return $this->attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS;
}
/**
* Sets the strategy for automatically generating proxy classes.
*
* @param bool|ProxyFactory::AUTOGENERATE_* $autoGenerate True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
*/
public function setAutoGenerateProxyClasses(bool|int $autoGenerate): void
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
$this->attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
}
/**
* Gets the namespace where proxy classes reside.
*/
public function getProxyNamespace(): string|null
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
return $this->attributes['proxyNamespace'] ?? null;
}
/**
* Sets the namespace where proxy classes reside.
*/
public function setProxyNamespace(string $ns): void
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
$this->attributes['proxyNamespace'] = $ns;
}
/**
* Sets the cache driver implementation that is used for metadata caching.
*
@@ -539,6 +650,70 @@ class Configuration extends \Doctrine\DBAL\Configuration
$this->attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses;
}
public function isNativeLazyObjectsEnabled(): bool
{
return $this->attributes['nativeLazyObjects'] ?? false;
}
public function enableNativeLazyObjects(bool $nativeLazyObjects): void
{
if (PHP_VERSION_ID >= 80400 && ! $nativeLazyObjects) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Disabling native lazy objects is deprecated and will be impossible in Doctrine ORM 4.0.',
);
}
if (PHP_VERSION_ID < 80400 && $nativeLazyObjects) {
throw new LogicException('Lazy loading proxies require PHP 8.4 or higher.');
}
$this->attributes['nativeLazyObjects'] = $nativeLazyObjects;
}
/**
* @deprecated lazy ghost objects are always enabled
*
* @return true
*/
public function isLazyGhostObjectEnabled(): bool
{
return true;
}
/** @deprecated lazy ghost objects cannot be disabled */
public function setLazyGhostObjectEnabled(bool $flag): void
{
if (! $flag) {
throw new LogicException(<<<'EXCEPTION'
The lazy ghost object feature cannot be disabled anymore.
Please remove the call to setLazyGhostObjectEnabled(false).
EXCEPTION);
}
}
/** @deprecated rejecting ID collisions in the identity map cannot be disabled */
public function setRejectIdCollisionInIdentityMap(bool $flag): void
{
if (! $flag) {
throw new LogicException(<<<'EXCEPTION'
Rejecting ID collisions in the identity map cannot be disabled anymore.
Please remove the call to setRejectIdCollisionInIdentityMap(false).
EXCEPTION);
}
}
/**
* @deprecated rejecting ID collisions in the identity map is always enabled
*
* @return true
*/
public function isRejectIdCollisionInIdentityMapEnabled(): bool
{
return true;
}
public function setEagerFetchBatchSize(int $batchSize = 100): void
{
$this->attributes['fetchModeSubselectBatchSize'] = $batchSize;

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Decorator;
use DateTimeInterface;
use Doctrine\Common\EventManagerInterface;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Cache;
@@ -24,7 +24,6 @@ use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\ObjectManagerDecorator;
use Override;
/**
* Base class for EntityManager decorators
@@ -38,163 +37,136 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
$this->wrapped = $wrapped;
}
#[Override]
public function getRepository(string $className): EntityRepository
{
return $this->wrapped->getRepository($className);
}
#[Override]
public function getMetadataFactory(): ClassMetadataFactory
{
return $this->wrapped->getMetadataFactory();
}
#[Override]
public function getClassMetadata(string $className): ClassMetadata
{
return $this->wrapped->getClassMetadata($className);
}
#[Override]
public function getConnection(): Connection
{
return $this->wrapped->getConnection();
}
#[Override]
public function getExpressionBuilder(): Expr
{
return $this->wrapped->getExpressionBuilder();
}
#[Override]
public function beginTransaction(): void
{
$this->wrapped->beginTransaction();
}
#[Override]
public function wrapInTransaction(callable $func): mixed
{
return $this->wrapped->wrapInTransaction($func);
}
#[Override]
public function commit(): void
{
$this->wrapped->commit();
}
#[Override]
public function rollback(): void
{
$this->wrapped->rollback();
}
#[Override]
public function createQuery(string $dql = ''): Query
{
return $this->wrapped->createQuery($dql);
}
#[Override]
public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery
{
return $this->wrapped->createNativeQuery($sql, $rsm);
}
#[Override]
public function createQueryBuilder(): QueryBuilder
{
return $this->wrapped->createQueryBuilder();
}
#[Override]
public function getReference(string $entityName, mixed $id): object|null
{
return $this->wrapped->getReference($entityName, $id);
}
#[Override]
public function close(): void
{
$this->wrapped->close();
}
#[Override]
public function lock(object $entity, LockMode $lockMode, DateTimeInterface|int|null $lockVersion = null): void
public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void
{
$this->wrapped->lock($entity, $lockMode, $lockVersion);
}
#[Override]
public function find(string $className, mixed $id, LockMode|null $lockMode = null, int|null $lockVersion = null): object|null
public function find(string $className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null
{
return $this->wrapped->find($className, $id, $lockMode, $lockVersion);
}
#[Override]
public function refresh(object $object, LockMode|null $lockMode = null): void
public function refresh(object $object, LockMode|int|null $lockMode = null): void
{
$this->wrapped->refresh($object, $lockMode);
}
#[Override]
public function getEventManager(): EventManagerInterface
public function getEventManager(): EventManager
{
return $this->wrapped->getEventManager();
}
#[Override]
public function getConfiguration(): Configuration
{
return $this->wrapped->getConfiguration();
}
#[Override]
public function isOpen(): bool
{
return $this->wrapped->isOpen();
}
#[Override]
public function getUnitOfWork(): UnitOfWork
{
return $this->wrapped->getUnitOfWork();
}
#[Override]
public function newHydrator(string|int $hydrationMode): AbstractHydrator
{
return $this->wrapped->newHydrator($hydrationMode);
}
#[Override]
public function getProxyFactory(): ProxyFactory
{
return $this->wrapped->getProxyFactory();
}
#[Override]
public function getFilters(): FilterCollection
{
return $this->wrapped->getFilters();
}
#[Override]
public function isFiltersStateClean(): bool
{
return $this->wrapped->isFiltersStateClean();
}
#[Override]
public function hasFilters(): bool
{
return $this->wrapped->hasFilters();
}
#[Override]
public function getCache(): Cache|null
{
return $this->wrapped->getCache();

View File

@@ -7,7 +7,6 @@ namespace Doctrine\ORM;
use BackedEnum;
use DateTimeInterface;
use Doctrine\Common\EventManager;
use Doctrine\Common\EventManagerInterface;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Exception\EntityManagerClosed;
@@ -19,17 +18,18 @@ use Doctrine\ORM\Exception\UnrecognizedIdentifierFields;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Repository\RepositoryFactory;
use Override;
use function array_keys;
use function is_array;
use function is_object;
use function ltrim;
use function method_exists;
/**
* The EntityManager is the central access point to ORM functionality.
@@ -72,7 +72,7 @@ class EntityManager implements EntityManagerInterface
/**
* The event manager that is the central point of the event system.
*/
private EventManagerInterface $eventManager;
private EventManager $eventManager;
/**
* The proxy factory used to create dynamic proxies.
@@ -113,13 +113,17 @@ class EntityManager implements EntityManagerInterface
public function __construct(
private Connection $conn,
private Configuration $config,
EventManagerInterface|null $eventManager = null,
EventManager|null $eventManager = null,
) {
if (! $config->getMetadataDriverImpl()) {
throw MissingMappingDriverImplementation::create();
}
$this->eventManager = $eventManager ?? new EventManager();
$this->eventManager = $eventManager
?? (method_exists($conn, 'getEventManager')
? $conn->getEventManager()
: new EventManager()
);
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
@@ -130,7 +134,16 @@ class EntityManager implements EntityManagerInterface
$this->repositoryFactory = $config->getRepositoryFactory();
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory($this);
if ($config->isNativeLazyObjectsEnabled()) {
$this->proxyFactory = new ProxyFactory($this);
} else {
$this->proxyFactory = new ProxyFactory(
$this,
$config->getProxyDir(),
$config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses(),
);
}
if ($config->isSecondLevelCacheEnabled()) {
$cacheConfig = $config->getSecondLevelCacheConfiguration();
@@ -139,37 +152,31 @@ class EntityManager implements EntityManagerInterface
}
}
#[Override]
public function getConnection(): Connection
{
return $this->conn;
}
#[Override]
public function getMetadataFactory(): ClassMetadataFactory
{
return $this->metadataFactory;
}
#[Override]
public function getExpressionBuilder(): Expr
{
return $this->expressionBuilder ??= new Expr();
}
#[Override]
public function beginTransaction(): void
{
$this->conn->beginTransaction();
}
#[Override]
public function getCache(): Cache|null
{
return $this->cache;
}
#[Override]
public function wrapInTransaction(callable $func): mixed
{
$this->conn->beginTransaction();
@@ -195,13 +202,11 @@ class EntityManager implements EntityManagerInterface
}
}
#[Override]
public function commit(): void
{
$this->conn->commit();
}
#[Override]
public function rollback(): void
{
$this->conn->rollBack();
@@ -214,13 +219,11 @@ class EntityManager implements EntityManagerInterface
*
* {@inheritDoc}
*/
#[Override]
public function getClassMetadata(string $className): Mapping\ClassMetadata
{
return $this->metadataFactory->getMetadataFor($className);
}
#[Override]
public function createQuery(string $dql = ''): Query
{
$query = new Query($this);
@@ -232,7 +235,6 @@ class EntityManager implements EntityManagerInterface
return $query;
}
#[Override]
public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery
{
$query = new NativeQuery($this);
@@ -243,7 +245,6 @@ class EntityManager implements EntityManagerInterface
return $query;
}
#[Override]
public function createQueryBuilder(): QueryBuilder
{
return new QueryBuilder($this);
@@ -261,7 +262,6 @@ class EntityManager implements EntityManagerInterface
* makes use of optimistic locking fails.
* @throws ORMException
*/
#[Override]
public function flush(): void
{
$this->errorIfClosed();
@@ -271,8 +271,7 @@ class EntityManager implements EntityManagerInterface
/**
* {@inheritDoc}
*/
#[Override]
public function find($className, mixed $id, LockMode|null $lockMode = null, int|null $lockVersion = null): object|null
public function find($className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null
{
$class = $this->metadataFactory->getMetadataFor(ltrim($className, '\\'));
@@ -290,7 +289,7 @@ class EntityManager implements EntityManagerInterface
foreach ($id as $i => $value) {
if (is_object($value)) {
$className = $value::class;
$className = DefaultProxyClassNameResolver::getClass($value);
if ($this->metadataFactory->hasMetadataFor($className)) {
$id[$i] = $this->unitOfWork->getSingleIdentifierValue($value);
@@ -368,7 +367,6 @@ class EntityManager implements EntityManagerInterface
}
}
#[Override]
public function getReference(string $entityName, mixed $id): object|null
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
@@ -414,13 +412,11 @@ class EntityManager implements EntityManagerInterface
* Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached.
*/
#[Override]
public function clear(): void
{
$this->unitOfWork->clear();
}
#[Override]
public function close(): void
{
$this->clear();
@@ -440,7 +436,6 @@ class EntityManager implements EntityManagerInterface
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
#[Override]
public function persist(object $object): void
{
$this->errorIfClosed();
@@ -457,7 +452,6 @@ class EntityManager implements EntityManagerInterface
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
#[Override]
public function remove(object $object): void
{
$this->errorIfClosed();
@@ -465,8 +459,7 @@ class EntityManager implements EntityManagerInterface
$this->unitOfWork->remove($object);
}
#[Override]
public function refresh(object $object, LockMode|null $lockMode = null): void
public function refresh(object $object, LockMode|int|null $lockMode = null): void
{
$this->errorIfClosed();
@@ -482,14 +475,12 @@ class EntityManager implements EntityManagerInterface
*
* @throws ORMInvalidArgumentException
*/
#[Override]
public function detach(object $object): void
{
$this->unitOfWork->detach($object);
}
#[Override]
public function lock(object $entity, LockMode $lockMode, DateTimeInterface|int|null $lockVersion = null): void
public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void
{
$this->unitOfWork->lock($entity, $lockMode, $lockVersion);
}
@@ -503,7 +494,6 @@ class EntityManager implements EntityManagerInterface
*
* @template T of object
*/
#[Override]
public function getRepository(string $className): EntityRepository
{
return $this->repositoryFactory->getRepository($this, $className);
@@ -514,7 +504,6 @@ class EntityManager implements EntityManagerInterface
*
* @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
*/
#[Override]
public function contains(object $object): bool
{
return $this->unitOfWork->isScheduledForInsert($object)
@@ -522,13 +511,11 @@ class EntityManager implements EntityManagerInterface
&& ! $this->unitOfWork->isScheduledForDelete($object);
}
#[Override]
public function getEventManager(): EventManagerInterface
public function getEventManager(): EventManager
{
return $this->eventManager;
}
#[Override]
public function getConfiguration(): Configuration
{
return $this->config;
@@ -546,19 +533,16 @@ class EntityManager implements EntityManagerInterface
}
}
#[Override]
public function isOpen(): bool
{
return ! $this->closed;
}
#[Override]
public function getUnitOfWork(): UnitOfWork
{
return $this->unitOfWork;
}
#[Override]
public function newHydrator(string|int $hydrationMode): AbstractHydrator
{
return match ($hydrationMode) {
@@ -572,13 +556,11 @@ class EntityManager implements EntityManagerInterface
};
}
#[Override]
public function getProxyFactory(): ProxyFactory
{
return $this->proxyFactory;
}
#[Override]
public function initializeObject(object $obj): void
{
$this->unitOfWork->initializeObject($obj);
@@ -587,35 +569,33 @@ class EntityManager implements EntityManagerInterface
/**
* {@inheritDoc}
*/
#[Override]
public function isUninitializedObject($value): bool
{
return $this->unitOfWork->isUninitializedObject($value);
}
#[Override]
public function getFilters(): FilterCollection
{
return $this->filterCollection ??= new FilterCollection($this);
}
#[Override]
public function isFiltersStateClean(): bool
{
return $this->filterCollection === null || $this->filterCollection->isClean();
}
#[Override]
public function hasFilters(): bool
{
return $this->filterCollection !== null;
}
/**
* @phpstan-param LockMode::* $lockMode
*
* @throws OptimisticLockException
* @throws TransactionRequiredException
*/
private function checkLockRequirements(LockMode $lockMode, ClassMetadata $class): void
private function checkLockRequirements(LockMode|int $lockMode, ClassMetadata $class): void
{
switch ($lockMode) {
case LockMode::OPTIMISTIC:

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use DateTimeInterface;
use Doctrine\Common\EventManagerInterface;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Exception\ORMException;
@@ -16,7 +16,6 @@ use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\ObjectManager;
use Override;
interface EntityManagerInterface extends ObjectManager
{
@@ -29,7 +28,6 @@ interface EntityManagerInterface extends ObjectManager
*
* @template T of object
*/
#[Override]
public function getRepository(string $className): EntityRepository;
/**
@@ -42,7 +40,6 @@ interface EntityManagerInterface extends ObjectManager
*/
public function getConnection(): Connection;
#[Override]
public function getMetadataFactory(): ClassMetadataFactory;
/**
@@ -113,13 +110,15 @@ interface EntityManagerInterface extends ObjectManager
/**
* Finds an Entity by its identifier.
*
* @param string $className The class name of the entity to find.
* @param mixed $id The identity of the entity to find.
* @param LockMode|null $lockMode The lock mode or NULL if no specific lock mode
* should be used during the search.
* @param int|null $lockVersion The version of the entity to find when using
* optimistic locking.
* @param string $className The class name of the entity to find.
* @param mixed $id The identity of the entity to find.
* @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
* or NULL if no specific lock mode should be used
* during the search.
* @param int|null $lockVersion The version of the entity to find when using
* optimistic locking.
* @phpstan-param class-string<T> $className
* @phpstan-param LockMode::*|null $lockMode
*
* @return object|null The entity instance or NULL if the entity can not be found.
* @phpstan-return T|null
@@ -131,22 +130,22 @@ interface EntityManagerInterface extends ObjectManager
*
* @template T of object
*/
#[Override]
public function find(string $className, mixed $id, LockMode|null $lockMode = null, int|null $lockVersion = null): object|null;
public function find(string $className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null;
/**
* Refreshes the persistent state of an object from the database,
* overriding any local changes that have not yet been persisted.
*
* @param LockMode|null $lockMode The lock mode or NULL if no specific lock mode
* should be used during the search.
* @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
* or NULL if no specific lock mode should be used
* during the search.
* @phpstan-param LockMode::*|null $lockMode
*
* @throws ORMInvalidArgumentException
* @throws ORMException
* @throws TransactionRequiredException
*/
#[Override]
public function refresh(object $object, LockMode|null $lockMode = null): void;
public function refresh(object $object, LockMode|int|null $lockMode = null): void;
/**
* Gets a reference to the entity identified by the given type and identifier
@@ -173,15 +172,17 @@ interface EntityManagerInterface extends ObjectManager
/**
* Acquire a lock on the given entity.
*
* @phpstan-param LockMode::* $lockMode
*
* @throws OptimisticLockException
* @throws PessimisticLockException
*/
public function lock(object $entity, LockMode $lockMode, DateTimeInterface|int|null $lockVersion = null): void;
public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void;
/**
* Gets the EventManagerInterface used by the EntityManager.
* Gets the EventManager used by the EntityManager.
*/
public function getEventManager(): EventManagerInterface;
public function getEventManager(): EventManager;
/**
* Gets the Configuration used by the EntityManager.
@@ -236,6 +237,5 @@ interface EntityManagerInterface extends ObjectManager
*
* @phpstan-template T of object
*/
#[Override]
public function getClassMetadata(string $className): Mapping\ClassMetadata;
}

View File

@@ -15,7 +15,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall;
use Doctrine\Persistence\ObjectRepository;
use Override;
use function array_slice;
use function lcfirst;
@@ -74,14 +73,15 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds an entity by its primary key / identifier.
*
* @param LockMode|null $lockMode The lock mode or NULL if no specific lock mode
* should be used during the search.
* @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
* or NULL if no specific lock mode should be used
* during the search.
* @phpstan-param LockMode::*|null $lockMode
*
* @return object|null The entity instance or NULL if the entity can not be found.
* @phpstan-return ?T
*/
#[Override]
public function find(mixed $id, LockMode|null $lockMode = null, int|null $lockVersion = null): object|null
public function find(mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null
{
return $this->em->find($this->entityName, $id, $lockMode, $lockVersion);
}
@@ -91,7 +91,6 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @phpstan-return list<T> The entities.
*/
#[Override]
public function findAll(): array
{
return $this->findBy([]);
@@ -104,7 +103,6 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @phpstan-return list<T>
*/
#[Override]
public function findBy(array $criteria, array|null $orderBy = null, int|null $limit = null, int|null $offset = null): array
{
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->entityName);
@@ -120,7 +118,6 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @phpstan-return T|null
*/
#[Override]
public function findOneBy(array $criteria, array|null $orderBy = null): object|null
{
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->entityName);
@@ -178,7 +175,6 @@ class EntityRepository implements ObjectRepository, Selectable
return $this->entityName;
}
#[Override]
public function getClassName(): string
{
return $this->getEntityName();
@@ -201,7 +197,6 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @phpstan-return AbstractLazyCollection<int, T>&Selectable<int, T>
*/
#[Override]
public function matching(Criteria $criteria): AbstractLazyCollection&Selectable
{
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->entityName);

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\Common\EventDispatcher;
use Doctrine\Common\EventManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\EntityListenerResolver;
@@ -15,21 +15,21 @@ use Doctrine\ORM\Mapping\EntityListenerResolver;
*/
class ListenersInvoker
{
final public const int INVOKE_NONE = 0;
final public const int INVOKE_LISTENERS = 1;
final public const int INVOKE_CALLBACKS = 2;
final public const int INVOKE_MANAGER = 4;
final public const INVOKE_NONE = 0;
final public const INVOKE_LISTENERS = 1;
final public const INVOKE_CALLBACKS = 2;
final public const INVOKE_MANAGER = 4;
/** The Entity listener resolver. */
private readonly EntityListenerResolver $resolver;
/** The EventDispatcher used for dispatching events. */
private readonly EventDispatcher $eventDispatcher;
/** The EventManager used for dispatching events. */
private readonly EventManager $eventManager;
public function __construct(EntityManagerInterface $em)
{
$this->eventDispatcher = $em->getEventManager();
$this->resolver = $em->getConfiguration()->getEntityListenerResolver();
$this->eventManager = $em->getEventManager();
$this->resolver = $em->getConfiguration()->getEntityListenerResolver();
}
/**
@@ -52,7 +52,7 @@ class ListenersInvoker
$invoke |= self::INVOKE_LISTENERS;
}
if ($this->eventDispatcher->hasListeners($eventName)) {
if ($this->eventManager->hasListeners($eventName)) {
$invoke |= self::INVOKE_MANAGER;
}
@@ -92,7 +92,7 @@ class ListenersInvoker
}
if ($invoke & self::INVOKE_MANAGER) {
$this->eventDispatcher->dispatchEvent($eventName, $event);
$this->eventManager->dispatchEvent($eventName, $event);
}
}
}

View File

@@ -24,7 +24,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
final public const string preRemove = 'preRemove';
public const preRemove = 'preRemove';
/**
* The postRemove event occurs for an entity after the entity has
@@ -32,7 +32,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
final public const string postRemove = 'postRemove';
public const postRemove = 'postRemove';
/**
* The prePersist event occurs for a given entity before the respective
@@ -40,7 +40,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
final public const string prePersist = 'prePersist';
public const prePersist = 'prePersist';
/**
* The postPersist event occurs for an entity after the entity has
@@ -49,7 +49,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
final public const string postPersist = 'postPersist';
public const postPersist = 'postPersist';
/**
* The preUpdate event occurs before the database update operations to
@@ -57,7 +57,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
final public const string preUpdate = 'preUpdate';
public const preUpdate = 'preUpdate';
/**
* The postUpdate event occurs after the database update operations to
@@ -65,7 +65,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
final public const string postUpdate = 'postUpdate';
public const postUpdate = 'postUpdate';
/**
* The postLoad event occurs for an entity after the entity has been loaded
@@ -78,26 +78,26 @@ final class Events
*
* This is an entity lifecycle event.
*/
final public const string postLoad = 'postLoad';
public const postLoad = 'postLoad';
/**
* The loadClassMetadata event occurs after the mapping metadata for a class
* has been loaded from a mapping source (attributes/xml).
*/
final public const string loadClassMetadata = 'loadClassMetadata';
public const loadClassMetadata = 'loadClassMetadata';
/**
* The onClassMetadataNotFound event occurs whenever loading metadata for a class
* failed.
*/
final public const string onClassMetadataNotFound = 'onClassMetadataNotFound';
public const onClassMetadataNotFound = 'onClassMetadataNotFound';
/**
* The preFlush event occurs when the EntityManager#flush() operation is invoked,
* but before any changes to managed entities have been calculated. This event is
* always raised right after EntityManager#flush() call.
*/
final public const string preFlush = 'preFlush';
public const preFlush = 'preFlush';
/**
* The onFlush event occurs when the EntityManager#flush() operation is invoked,
@@ -105,7 +105,7 @@ final class Events
* actual database operations are executed. The event is only raised if there is
* actually something to do for the underlying UnitOfWork.
*/
final public const string onFlush = 'onFlush';
public const onFlush = 'onFlush';
/**
* The postFlush event occurs when the EntityManager#flush() operation is invoked and
@@ -113,11 +113,11 @@ final class Events
* actually something to do for the underlying UnitOfWork. The event won't be raised if an error occurs during the
* flush operation.
*/
final public const string postFlush = 'postFlush';
public const postFlush = 'postFlush';
/**
* The onClear event occurs when the EntityManager#clear() operation is invoked,
* after all references to entities have been removed from the unit of work.
*/
final public const string onClear = 'onClear';
public const onClear = 'onClear';
}

View File

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

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function sprintf;
/** @deprecated */
final class NotSupported extends LogicException implements ORMException
{
public static function create(): self
{
return new self('This behaviour is (currently) not supported by Doctrine 2');
}
public static function createForDbal3(string $context): self
{
return new self(sprintf(
<<<'EXCEPTION'
Context: %s
Problem: Feature was deprecated in doctrine/dbal 2.x and is not supported by installed doctrine/dbal:3.x
Solution: See the doctrine/deprecations logs for new alternative approaches.
EXCEPTION
,
$context,
));
}
public static function createForPersistence3(string $context): self
{
return new self(sprintf(
<<<'EXCEPTION'
Context: %s
Problem: Feature was deprecated in doctrine/persistence 2.x and is not supported by installed doctrine/persistence:3.x
Solution: See the doctrine/deprecations logs for new alternative approaches.
EXCEPTION
,
$context,
));
}
}

View File

@@ -6,7 +6,6 @@ namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Exception\EntityMissingAssignedId;
use Override;
/**
* Special generator for application-assigned identifiers (doesn't really generate anything).
@@ -20,7 +19,6 @@ class AssignedGenerator extends AbstractIdGenerator
*
* @throws EntityMissingAssignedId
*/
#[Override]
public function generateId(EntityManagerInterface $em, object|null $entity): array
{
$class = $em->getClassMetadata($entity::class);

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManagerInterface;
use Override;
/**
* Id generator that obtains IDs from special "identity" columns. These are columns
@@ -14,13 +13,11 @@ use Override;
*/
class BigIntegerIdentityGenerator extends AbstractIdGenerator
{
#[Override]
public function generateId(EntityManagerInterface $em, object|null $entity): string
{
return (string) $em->getConnection()->lastInsertId();
}
#[Override]
public function isPostInsertGenerator(): bool
{
return true;

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManagerInterface;
use Override;
/**
* Id generator that obtains IDs from special "identity" columns. These are columns
@@ -14,13 +13,11 @@ use Override;
*/
class IdentityGenerator extends AbstractIdGenerator
{
#[Override]
public function generateId(EntityManagerInterface $em, object|null $entity): int
{
return (int) $em->getConnection()->lastInsertId();
}
#[Override]
public function isPostInsertGenerator(): bool
{
return true;

View File

@@ -5,13 +5,17 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Override;
use Serializable;
use function serialize;
use function unserialize;
/**
* Represents an ID generator that uses a database sequence.
*/
class SequenceGenerator extends AbstractIdGenerator
class SequenceGenerator extends AbstractIdGenerator implements Serializable
{
private int $nextValue = 0;
private int|null $maxValue = null;
@@ -28,7 +32,6 @@ class SequenceGenerator extends AbstractIdGenerator
) {
}
#[Override]
public function generateId(EntityManagerInterface $em, object|null $entity): int
{
if ($this->maxValue === null || $this->nextValue === $this->maxValue) {
@@ -63,6 +66,20 @@ class SequenceGenerator extends AbstractIdGenerator
return $this->nextValue;
}
/** @deprecated without replacement. */
final public function serialize(): string
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11468',
'%s() is deprecated, use __serialize() instead. %s won\'t implement the Serializable interface anymore in ORM 4.',
__METHOD__,
self::class,
);
return serialize($this->__serialize());
}
/** @return array<string, mixed> */
public function __serialize(): array
{
@@ -72,6 +89,20 @@ class SequenceGenerator extends AbstractIdGenerator
];
}
/** @deprecated without replacement. */
final public function unserialize(string $serialized): void
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11468',
'%s() is deprecated, use __unserialize() instead. %s won\'t implement the Serializable interface anymore in ORM 4.',
__METHOD__,
self::class,
);
$this->__unserialize(unserialize($serialized));
}
/** @param array<string, mixed> $data */
public function __unserialize(array $data): void
{

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal\Hydration;
use Override;
use function array_key_last;
use function count;
use function is_array;
@@ -34,7 +32,6 @@ class ArrayHydrator extends AbstractHydrator
private int $resultCounter = 0;
#[Override]
protected function prepare(): void
{
$this->isSimpleQuery = count($this->resultSetMapping()->aliasMap) <= 1;
@@ -49,7 +46,6 @@ class ArrayHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateAllData(): array
{
$result = [];
@@ -64,7 +60,6 @@ class ArrayHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateRowData(array $row, array &$result): void
{
// 1) Initialize

View File

@@ -10,7 +10,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Override;
use function array_fill_keys;
use function array_keys;
@@ -52,7 +51,6 @@ class ObjectHydrator extends AbstractHydrator
/** @var mixed[] */
private array $existingCollections = [];
#[Override]
protected function prepare(): void
{
if (! isset($this->hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
@@ -110,7 +108,6 @@ class ObjectHydrator extends AbstractHydrator
}
}
#[Override]
protected function cleanup(): void
{
$eagerLoad = isset($this->hints[UnitOfWork::HINT_DEFEREAGERLOAD]) && $this->hints[UnitOfWork::HINT_DEFEREAGERLOAD] === true;
@@ -130,7 +127,6 @@ class ObjectHydrator extends AbstractHydrator
$this->uow->hydrationComplete();
}
#[Override]
protected function cleanupAfterRowIteration(): void
{
$this->identifierMap =
@@ -143,7 +139,6 @@ class ObjectHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateAllData(): array
{
$result = [];
@@ -323,7 +318,6 @@ class ObjectHydrator extends AbstractHydrator
* @param mixed[] $row The data of the row to process.
* @param mixed[] $result The result array to fill.
*/
#[Override]
protected function hydrateRowData(array $row, array &$result): void
{
// Initialize
@@ -580,7 +574,6 @@ class ObjectHydrator extends AbstractHydrator
}
/** @param mixed[] $data pre-hydrated SQL Result Row. */
#[Override]
protected function hydrateNestedEntity(array $data, string $dqlAlias): mixed
{
if (isset($this->resultSetMapping()->nestedEntities[$dqlAlias])) {
@@ -594,7 +587,6 @@ class ObjectHydrator extends AbstractHydrator
* When executed in a hydrate() loop we may have to clear internal state to
* decrease memory consumption.
*/
#[Override]
public function onClear(mixed $eventArgs): void
{
parent::onClear($eventArgs);

View File

@@ -6,7 +6,6 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\ORM\Exception\MultipleSelectorsFoundException;
use Override;
use function count;
@@ -21,7 +20,6 @@ final class ScalarColumnHydrator extends AbstractHydrator
* @throws MultipleSelectorsFoundException
* @throws Exception
*/
#[Override]
protected function hydrateAllData(): array
{
if (count($this->resultSetMapping()->fieldMappings) > 1) {

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal\Hydration;
use Override;
/**
* Hydrator that produces flat, rectangular results of scalar data.
* The created result is almost the same as a regular SQL result set, except
@@ -16,7 +14,6 @@ class ScalarHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateAllData(): array
{
$result = [];
@@ -31,7 +28,6 @@ class ScalarHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateRowData(array $row, array &$result): void
{
$result[] = $this->gatherScalarRowData($row);

View File

@@ -9,7 +9,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Query;
use Exception;
use Override;
use RuntimeException;
use ValueError;
@@ -29,7 +28,6 @@ class SimpleObjectHydrator extends AbstractHydrator
private ClassMetadata|null $class = null;
#[Override]
protected function prepare(): void
{
if (count($this->resultSetMapping()->aliasMap) !== 1) {
@@ -43,7 +41,6 @@ class SimpleObjectHydrator extends AbstractHydrator
$this->class = $this->getClassMetadata(reset($this->resultSetMapping()->aliasMap));
}
#[Override]
protected function cleanup(): void
{
parent::cleanup();
@@ -55,7 +52,6 @@ class SimpleObjectHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateAllData(): array
{
$result = [];
@@ -72,7 +68,6 @@ class SimpleObjectHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateRowData(array $row, array &$result): void
{
assert($this->class !== null);

View File

@@ -6,7 +6,6 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Override;
use function array_shift;
use function count;
@@ -17,7 +16,6 @@ use function key;
*/
class SingleScalarHydrator extends AbstractHydrator
{
#[Override]
protected function hydrateAllData(): mixed
{
$data = $this->statement()->fetchAllAssociative();

View File

@@ -25,9 +25,9 @@ use function spl_object_id;
*/
final class StronglyConnectedComponents
{
private const int NOT_VISITED = 1;
private const int IN_PROGRESS = 2;
private const int VISITED = 3;
private const NOT_VISITED = 1;
private const IN_PROGRESS = 2;
private const VISITED = 3;
/**
* Array of all nodes, indexed by object ids.

View File

@@ -20,9 +20,9 @@ use function spl_object_id;
*/
final class TopologicalSort
{
private const int NOT_VISITED = 1;
private const int IN_PROGRESS = 2;
private const int VISITED = 3;
private const NOT_VISITED = 1;
private const IN_PROGRESS = 2;
private const VISITED = 3;
/**
* Array of all nodes, indexed by object ids.

View File

@@ -10,7 +10,8 @@ use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Override;
use function assert;
/**
* A lazy collection that allows a fast count when using criteria object
@@ -25,7 +26,6 @@ use Override;
*/
class LazyCriteriaCollection extends AbstractLazyCollection implements Selectable
{
/** @var non-negative-int|null */
private int|null $count = null;
public function __construct(
@@ -37,7 +37,6 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
/**
* Do an efficient count on the collection
*/
#[Override]
public function count(): int
{
if ($this->isInitialized()) {
@@ -55,7 +54,6 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
/**
* check if collection is empty without loading it
*/
#[Override]
public function isEmpty(): bool
{
if ($this->isInitialized()) {
@@ -72,7 +70,6 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
*
* @return bool TRUE if the collection contains $element, FALSE otherwise.
*/
#[Override]
public function contains(mixed $element): bool
{
if ($this->isInitialized()) {
@@ -83,15 +80,14 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
}
/** @return ReadableCollection<TKey, TValue>&Selectable<TKey, TValue> */
#[Override]
public function matching(Criteria $criteria): ReadableCollection&Selectable
{
$this->initialize();
assert($this->collection instanceof Selectable);
return $this->collection->matching($criteria);
}
#[Override]
protected function doInitialize(): void
{
$elements = $this->entityPersister->loadCriteria($this->criteria);

View File

@@ -6,7 +6,6 @@ namespace Doctrine\ORM\Mapping;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\ORM\Internal\SQLResultCasing;
use Override;
/**
* ANSI compliant quote strategy, this strategy does not apply any quote.
@@ -16,7 +15,6 @@ class AnsiQuoteStrategy implements QuoteStrategy
{
use SQLResultCasing;
#[Override]
public function getColumnName(
string $fieldName,
ClassMetadata $class,
@@ -25,7 +23,6 @@ class AnsiQuoteStrategy implements QuoteStrategy
return $class->fieldMappings[$fieldName]->columnName;
}
#[Override]
public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string
{
return $class->table['name'];
@@ -34,19 +31,16 @@ class AnsiQuoteStrategy implements QuoteStrategy
/**
* {@inheritDoc}
*/
#[Override]
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string
{
return $definition['sequenceName'];
}
#[Override]
public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string
{
return $joinColumn->name;
}
#[Override]
public function getReferencedJoinColumnName(
JoinColumnMapping $joinColumn,
ClassMetadata $class,
@@ -55,7 +49,6 @@ class AnsiQuoteStrategy implements QuoteStrategy
return $joinColumn->referencedColumnName;
}
#[Override]
public function getJoinTableName(
ManyToManyOwningSideMapping $association,
ClassMetadata $class,
@@ -67,13 +60,11 @@ class AnsiQuoteStrategy implements QuoteStrategy
/**
* {@inheritDoc}
*/
#[Override]
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array
{
return $class->identifier;
}
#[Override]
public function getColumnAlias(
string $columnName,
int $counter,

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Deprecations\Deprecation;
use InvalidArgumentException;
use function property_exists;
/** @internal */
trait ArrayAccessImplementation
{
/** @param string $offset */
public function offsetExists(mixed $offset): bool
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
return isset($this->$offset);
}
/** @param string $offset */
public function offsetGet(mixed $offset): mixed
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
if (! property_exists($this, $offset)) {
throw new InvalidArgumentException('Undefined property: ' . $offset);
}
return $this->$offset;
}
/** @param string $offset */
public function offsetSet(mixed $offset, mixed $value): void
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
$this->$offset = $value;
}
/** @param string $offset */
public function offsetUnset(mixed $offset): void
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
$this->$offset = null;
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use Exception;
use OutOfRangeException;
@@ -13,7 +14,8 @@ use function in_array;
use function property_exists;
use function sprintf;
abstract class AssociationMapping
/** @template-implements ArrayAccess<string, mixed> */
abstract class AssociationMapping implements ArrayAccess
{
/**
* The names of persistence operations to cascade on the association.

View File

@@ -17,7 +17,7 @@ use function get_class_methods;
class EntityListenerBuilder
{
/** Hash-map to handle event names. */
private const array EVENTS = [
private const EVENTS = [
Events::preRemove => true,
Events::postRemove => true,
Events::prePersist => true,

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Builder;
use Override;
/**
* ManyToMany Association Builder
*
@@ -31,7 +29,6 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
*
* @return $this
*/
#[Override]
public function addJoinColumn(
string $columnName,
string $referencedColumnName,
@@ -75,7 +72,6 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
return $this;
}
#[Override]
public function build(): ClassMetadataBuilder
{
$mapping = $this->mapping;

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Builder;
use Override;
/**
* OneToMany Association Builder
*
@@ -33,7 +31,6 @@ class OneToManyAssociationBuilder extends AssociationBuilder
return $this;
}
#[Override]
public function build(): ClassMetadataBuilder
{
$mapping = $this->mapping;

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
use Override;
use ReflectionProperty;
final class ChainTypedFieldMapper implements TypedFieldMapper
@@ -25,7 +24,6 @@ final class ChainTypedFieldMapper implements TypedFieldMapper
/**
* {@inheritDoc}
*/
#[Override]
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
foreach ($this->typedFieldMappers as $typedFieldMapper) {

View File

@@ -22,9 +22,9 @@ use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\ReflectionService;
use InvalidArgumentException;
use LogicException;
use Override;
use ReflectionClass;
use ReflectionNamedType;
use ReflectionProperty;
use Stringable;
use function array_column;
@@ -80,38 +80,40 @@ use function trim;
*/
class ClassMetadata implements PersistenceClassMetadata, Stringable
{
use GetReflectionClassImplementation;
/* The inheritance mapping types */
/**
* NONE means the class does not participate in an inheritance hierarchy
* and therefore does not need an inheritance mapping type.
*/
final public const int INHERITANCE_TYPE_NONE = 1;
public const INHERITANCE_TYPE_NONE = 1;
/**
* JOINED means the class will be persisted according to the rules of
* <tt>Class Table Inheritance</tt>.
*/
final public const int INHERITANCE_TYPE_JOINED = 2;
public const INHERITANCE_TYPE_JOINED = 2;
/**
* SINGLE_TABLE means the class will be persisted according to the rules of
* <tt>Single Table Inheritance</tt>.
*/
final public const int INHERITANCE_TYPE_SINGLE_TABLE = 3;
public const INHERITANCE_TYPE_SINGLE_TABLE = 3;
/* The Id generator types. */
/**
* AUTO means the generator type will depend on what the used platform prefers.
* Offers full portability.
*/
final public const int GENERATOR_TYPE_AUTO = 1;
public const GENERATOR_TYPE_AUTO = 1;
/**
* SEQUENCE means a separate sequence object will be used. Platforms that do
* not have native sequence support may emulate it. Full portability is currently
* not guaranteed.
*/
final public const int GENERATOR_TYPE_SEQUENCE = 2;
public const GENERATOR_TYPE_SEQUENCE = 2;
/**
* IDENTITY means an identity column is used for id generation. The database
@@ -119,18 +121,18 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
* native identity columns may emulate them. Full portability is currently
* not guaranteed.
*/
final public const int GENERATOR_TYPE_IDENTITY = 4;
public const GENERATOR_TYPE_IDENTITY = 4;
/**
* NONE means the class does not have a generated id. That means the class
* must have a natural, manually assigned id.
*/
final public const int GENERATOR_TYPE_NONE = 5;
public const GENERATOR_TYPE_NONE = 5;
/**
* CUSTOM means that customer will use own ID generator that supposedly work
*/
final public const int GENERATOR_TYPE_CUSTOM = 7;
public const GENERATOR_TYPE_CUSTOM = 7;
/**
* DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
@@ -139,92 +141,92 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* This is the default change tracking policy.
*/
final public const int CHANGETRACKING_DEFERRED_IMPLICIT = 1;
public const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
/**
* DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
* by doing a property-by-property comparison with the original data. This will
* be done only for entities that were explicitly saved (through persist() or a cascade).
*/
final public const int CHANGETRACKING_DEFERRED_EXPLICIT = 2;
public const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
/**
* Specifies that an association is to be fetched when it is first accessed.
*/
final public const int FETCH_LAZY = 2;
public const FETCH_LAZY = 2;
/**
* Specifies that an association is to be fetched when the owner of the
* association is fetched.
*/
final public const int FETCH_EAGER = 3;
public const FETCH_EAGER = 3;
/**
* Specifies that an association is to be fetched lazy (on first access) and that
* commands such as Collection#count, Collection#slice are issued directly against
* the database if the collection is not yet initialized.
*/
final public const int FETCH_EXTRA_LAZY = 4;
public const FETCH_EXTRA_LAZY = 4;
/**
* Identifies a one-to-one association.
*/
final public const int ONE_TO_ONE = 1;
public const ONE_TO_ONE = 1;
/**
* Identifies a many-to-one association.
*/
final public const int MANY_TO_ONE = 2;
public const MANY_TO_ONE = 2;
/**
* Identifies a one-to-many association.
*/
final public const int ONE_TO_MANY = 4;
public const ONE_TO_MANY = 4;
/**
* Identifies a many-to-many association.
*/
final public const int MANY_TO_MANY = 8;
public const MANY_TO_MANY = 8;
/**
* Combined bitmask for to-one (single-valued) associations.
*/
final public const int TO_ONE = 3;
public const TO_ONE = 3;
/**
* Combined bitmask for to-many (collection-valued) associations.
*/
final public const int TO_MANY = 12;
public const TO_MANY = 12;
/**
* ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks,
*/
final public const int CACHE_USAGE_READ_ONLY = 1;
public const CACHE_USAGE_READ_ONLY = 1;
/**
* Nonstrict Read Write Cache doesnt employ any locks but can do inserts, update and deletes.
*/
final public const int CACHE_USAGE_NONSTRICT_READ_WRITE = 2;
public const CACHE_USAGE_NONSTRICT_READ_WRITE = 2;
/**
* Read Write Attempts to lock the entity before update/delete.
*/
final public const int CACHE_USAGE_READ_WRITE = 3;
public const CACHE_USAGE_READ_WRITE = 3;
/**
* The value of this column is never generated by the database.
*/
final public const int GENERATED_NEVER = 0;
public const GENERATED_NEVER = 0;
/**
* The value of this column is generated by the database on INSERT, but not on UPDATE.
*/
final public const int GENERATED_INSERT = 1;
public const GENERATED_INSERT = 1;
/**
* The value of this column is generated by the database on both INSERT and UDPATE statements.
*/
final public const int GENERATED_ALWAYS = 2;
public const GENERATED_ALWAYS = 2;
/**
* READ-ONLY: The namespace the entity class is contained in.
@@ -517,9 +519,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* The ReflectionClass instance of the mapped class.
*
* @var ReflectionClass<T>
* @var ReflectionClass<T>|null
*/
public ReflectionClass $reflClass;
public ReflectionClass|null $reflClass = null;
/**
* Is this entity marked as "read-only"?
@@ -535,6 +537,15 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*/
protected NamingStrategy $namingStrategy;
/**
* The ReflectionProperty instances of the mapped class.
*
* @deprecated Use $propertyAccessors instead.
*
* @var LegacyReflectionFields|array<string, ReflectionProperty>
*/
public LegacyReflectionFields|array $reflFields = [];
/** @var array<string, PropertyAccessor> */
public array $propertyAccessors = [];
@@ -557,6 +568,19 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
$this->typedFieldMapper = $typedFieldMapper ?? new DefaultTypedFieldMapper();
}
/**
* Gets the ReflectionProperties of the mapped class.
*
* @deprecated Use getPropertyAccessors() instead.
*
* @return LegacyReflectionFields|ReflectionProperty[] An array of ReflectionProperty instances.
* @phpstan-return LegacyReflectionFields|array<string, ReflectionProperty>
*/
public function getReflectionProperties(): array|LegacyReflectionFields
{
return $this->reflFields;
}
/**
* Gets the ReflectionProperties of the mapped class.
*
@@ -567,11 +591,35 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
return $this->propertyAccessors;
}
/**
* Gets a ReflectionProperty for a specific field of the mapped class.
*
* @deprecated Use getPropertyAccessor() instead.
*/
public function getReflectionProperty(string $name): ReflectionProperty|null
{
return $this->reflFields[$name];
}
public function getPropertyAccessor(string $name): PropertyAccessor|null
{
return $this->propertyAccessors[$name] ?? null;
}
/**
* @deprecated Use getPropertyAccessor() instead.
*
* @throws BadMethodCallException If the class has a composite identifier.
*/
public function getSingleIdReflectionProperty(): ReflectionProperty|null
{
if ($this->isIdentifierComposite) {
throw new BadMethodCallException('Class ' . $this->name . ' has a composite identifier.');
}
return $this->reflFields[$this->identifier[0]];
}
/** @throws BadMethodCallException If the class has a composite identifier. */
public function getSingleIdPropertyAccessor(): PropertyAccessor|null
{
@@ -590,7 +638,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* @return array<string, mixed>
*/
#[Override]
public function getIdentifierValues(object $entity): array
{
if ($this->isIdentifierComposite) {
@@ -654,7 +701,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* @todo Construct meaningful string representation.
*/
#[Override]
public function __toString(): string
{
return self::class . '@' . spl_object_id($this);
@@ -667,8 +713,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
* That means any metadata properties that are not set or empty or simply have
* their default value are NOT serialized.
*
* Parts that are also NOT serialized because they can not be properly
* unserialized, e.g. reflClass (ReflectionClass)
* Parts that are also NOT serialized because they can not be properly unserialized:
* - reflClass (ReflectionClass)
* - reflFields (ReflectionProperty array)
*
* @return string[] The names of all the fields that should be serialized.
*/
@@ -777,7 +824,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
public function wakeupReflection(ReflectionService $reflService): void
{
// Restore ReflectionClass and properties
$this->reflClass = $reflService->getClass($this->name);
$this->reflClass = $reflService->getClass($this->name);
/** @phpstan-ignore property.deprecated */
$this->reflFields = new LegacyReflectionFields($this, $reflService);
$this->instantiator = $this->instantiator ?: new Instantiator();
$parentAccessors = [];
@@ -858,7 +907,10 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
{
$this->reflClass = $reflService->getClass($this->name);
$this->namespace = $reflService->getClassNamespace($this->name);
$this->name = $this->rootEntityName = $this->reflClass->name;
if ($this->reflClass) {
$this->name = $this->rootEntityName = $this->reflClass->name;
}
$this->table['name'] = $this->namingStrategy->classToTableName($this->name);
}
@@ -918,12 +970,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
}
}
#[Override]
public function getReflectionClass(): ReflectionClass
{
return $this->reflClass;
}
/** @phpstan-param array{usage?: mixed, region?: mixed} $cache */
public function enableCache(array $cache): void
{
@@ -990,7 +1036,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* Checks whether a field is part of the identifier/primary key field(s).
*/
#[Override]
public function isIdentifier(string $fieldName): bool
{
if (! $this->identifier) {
@@ -1096,7 +1141,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*/
private function isTypedProperty(string $name): bool
{
return $this->reflClass->hasProperty($name)
return isset($this->reflClass)
&& $this->reflClass->hasProperty($name)
&& $this->reflClass->getProperty($name)->hasType();
}
@@ -1223,7 +1269,11 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
$this->containsEnumIdentifier = true;
}
if ($mapping->type === Types::ENUM && ! isset($mapping->options['values'])) {
if (
defined('Doctrine\DBAL\Types\Types::ENUM')
&& $mapping->type === Types::ENUM
&& ! isset($mapping->options['values'])
) {
$mapping->options['values'] = array_column($mapping->enumType::cases(), 'value');
}
}
@@ -1410,7 +1460,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getIdentifierFieldNames(): array
{
return $this->identifier;
@@ -1462,13 +1511,11 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getIdentifier(): array
{
return $this->identifier;
}
#[Override]
public function hasField(string $fieldName): bool
{
return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
@@ -1595,7 +1642,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* @todo 3.0 Remove this. PersisterHelper should fix it somehow
*/
#[Override]
public function getTypeOfField(string $fieldName): string|null
{
return isset($this->fieldMappings[$fieldName])
@@ -2269,20 +2315,17 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
}
}
#[Override]
public function hasAssociation(string $fieldName): bool
{
return isset($this->associationMappings[$fieldName]);
}
#[Override]
public function isSingleValuedAssociation(string $fieldName): bool
{
return isset($this->associationMappings[$fieldName])
&& ($this->associationMappings[$fieldName]->isToOne());
}
#[Override]
public function isCollectionValuedAssociation(string $fieldName): bool
{
return isset($this->associationMappings[$fieldName])
@@ -2478,7 +2521,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getFieldNames(): array
{
return array_keys($this->fieldMappings);
@@ -2487,7 +2529,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getAssociationNames(): array
{
return array_keys($this->associationMappings);
@@ -2500,27 +2541,23 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* @throws InvalidArgumentException
*/
#[Override]
public function getAssociationTargetClass(string $assocName): string
{
return $this->associationMappings[$assocName]->targetEntity
?? throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
}
#[Override]
public function getName(): string
{
return $this->name;
}
#[Override]
public function isAssociationInverseSide(string $assocName): bool
{
return isset($this->associationMappings[$assocName])
&& ! $this->associationMappings[$assocName]->isOwningSide();
}
#[Override]
public function getAssociationMappedByTargetField(string $assocName): string
{
$assoc = $this->getAssociationMapping($assocName);
@@ -2544,12 +2581,24 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* @param C $className
*
* @phpstan-return (C is class-string ? class-string : string)
* @return string|null null if and only if the input value is null
* @phpstan-return (C is class-string ? class-string : (C is string ? string : null))
*
* @template C of string
* @template C of string|null
*/
public function fullyQualifiedClassName(string $className): string
public function fullyQualifiedClassName(string|null $className): string|null
{
if ($className === null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11294',
'Passing null to %s is deprecated and will not be supported in Doctrine ORM 4.0',
__METHOD__,
);
return null;
}
if (! str_contains($className, '\\') && $this->namespace) {
return $this->namespace . '\\' . $className;
}
@@ -2615,6 +2664,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
if (! empty($this->embeddedClasses[$property]->columnPrefix)) {
$fieldMapping['columnName'] = $this->embeddedClasses[$property]->columnPrefix . $fieldMapping['columnName'];
} elseif ($this->embeddedClasses[$property]->columnPrefix !== false) {
assert($this->reflClass !== null);
assert($embeddable->reflClass !== null);
$fieldMapping['columnName'] = $this->namingStrategy
->embeddedFieldToColumnName(
$property,

View File

@@ -4,9 +4,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Common\EventDispatcher;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Platforms;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
@@ -18,11 +19,11 @@ use Doctrine\ORM\Id\IdentityGenerator;
use Doctrine\ORM\Id\SequenceGenerator;
use Doctrine\ORM\Mapping\Exception\InvalidCustomGenerator;
use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\Mapping\ReflectionService;
use Override;
use ReflectionClass;
use ReflectionException;
@@ -34,11 +35,14 @@ use function explode;
use function in_array;
use function is_a;
use function is_subclass_of;
use function method_exists;
use function str_contains;
use function strlen;
use function strtolower;
use function substr;
use const PHP_VERSION_ID;
/**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
* metadata mapping information of a class which describes how a class should be mapped
@@ -51,17 +55,21 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
private EntityManagerInterface|null $em = null;
private AbstractPlatform|null $targetPlatform = null;
private MappingDriver|null $driver = null;
private EventDispatcher|null $eventDispatcher = null;
private EventManager|null $evm = null;
/** @var mixed[] */
private array $embeddablesActiveNesting = [];
private const array NON_IDENTITY_DEFAULT_STRATEGY = [
private const NON_IDENTITY_DEFAULT_STRATEGY = [
Platforms\OraclePlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
];
public function setEntityManager(EntityManagerInterface $em): void
{
if (! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
}
$this->em = $em;
}
@@ -99,20 +107,22 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
return $owningSide;
}
#[Override]
protected function initialize(): void
{
$this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
$this->eventDispatcher = $this->em->getEventManager();
$this->initialized = true;
$this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
$this->evm = $this->em->getEventManager();
$this->initialized = true;
}
#[Override]
protected function onNotFoundMetadata(string $className): ClassMetadata|null
{
if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
return null;
}
$eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em);
$this->eventDispatcher->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
$this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
$classMetadata = $eventArgs->getFoundMetadata();
assert($classMetadata instanceof ClassMetadata || $classMetadata === null);
@@ -122,7 +132,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* {@inheritDoc}
*/
#[Override]
protected function doLoadMetadata(
ClassMetadataInterface $class,
ClassMetadataInterface|null $parent,
@@ -236,10 +245,10 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
// During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402.
// So, we must not discover the missing subclasses before that.
$this->eventDispatcher->dispatchEvent(
Events::loadClassMetadata,
new LoadClassMetadataEventArgs($class, $this->em),
);
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
$this->findAbstractEntityClassesNotListedInDiscriminatorMap($class);
@@ -253,6 +262,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
*/
protected function validateRuntimeMetadata(ClassMetadata $class, ClassMetadataInterface|null $parent): void
{
if (! $class->reflClass) {
// only validate if there is a reflection class instance
return;
}
$class->validateIdentifier();
$class->validateAssociations();
$class->validateLifecycleCallbacks($this->getReflectionService());
@@ -288,7 +302,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
#[Override]
protected function newClassMetadataInstance(string $className): ClassMetadata
{
return new ClassMetadata(
@@ -613,8 +626,39 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
foreach (self::NON_IDENTITY_DEFAULT_STRATEGY as $platformFamily => $strategy) {
$nonIdentityDefaultStrategy = self::NON_IDENTITY_DEFAULT_STRATEGY;
// DBAL 3
if (method_exists($platform, 'getIdentitySequenceName')) {
$nonIdentityDefaultStrategy[Platforms\PostgreSQLPlatform::class] = ClassMetadata::GENERATOR_TYPE_SEQUENCE;
}
foreach ($nonIdentityDefaultStrategy as $platformFamily => $strategy) {
if (is_a($platform, $platformFamily)) {
if ($platform instanceof Platforms\PostgreSQLPlatform) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8893',
<<<'DEPRECATION'
Relying on non-optimal defaults for ID generation is deprecated, and IDENTITY
results in SERIAL, which is not recommended.
Instead, configure identifier generation strategies explicitly through
configuration.
We currently recommend "SEQUENCE" for "%s", when using DBAL 3,
and "IDENTITY" when using DBAL 4,
so you should probably use the following configuration before upgrading to DBAL 4,
and remove it after deploying that upgrade:
$configuration->setIdentityGenerationPreferences([
"%s" => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
]);
DEPRECATION,
$platformFamily,
$platformFamily,
);
}
return $strategy;
}
}
@@ -656,11 +700,14 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
#[Override]
protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService): void
{
$class->wakeupReflection($reflService);
if (PHP_VERSION_ID < 80400) {
return;
}
foreach ($class->propertyAccessors as $propertyAccessor) {
$property = $propertyAccessor->getUnderlyingReflector();
@@ -670,13 +717,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
#[Override]
protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService): void
{
$class->initializeReflection($reflService);
}
#[Override]
protected function getDriver(): MappingDriver
{
assert($this->driver !== null);
@@ -684,7 +729,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
return $this->driver;
}
#[Override]
protected function isEntity(ClassMetadataInterface $class): bool
{
return ! $class->isMappedSuperclass;

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
use function trim;
/**
@@ -16,7 +14,6 @@ class DefaultEntityListenerResolver implements EntityListenerResolver
/** @var array<class-string, object> Map to store entity listener instances. */
private array $instances = [];
#[Override]
public function clear(string|null $className = null): void
{
if ($className === null) {
@@ -29,13 +26,11 @@ class DefaultEntityListenerResolver implements EntityListenerResolver
unset($this->instances[$className]);
}
#[Override]
public function register(object $object): void
{
$this->instances[$object::class] = $object;
}
#[Override]
public function resolve(string $className): object
{
$className = trim($className, '\\');

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
use function str_contains;
use function strrpos;
use function strtolower;
@@ -18,7 +16,6 @@ use function substr;
*/
class DefaultNamingStrategy implements NamingStrategy
{
#[Override]
public function classToTableName(string $className): string
{
if (str_contains($className, '\\')) {
@@ -28,13 +25,11 @@ class DefaultNamingStrategy implements NamingStrategy
return $className;
}
#[Override]
public function propertyToColumnName(string $propertyName, string $className): string
{
return $propertyName;
}
#[Override]
public function embeddedFieldToColumnName(
string $propertyName,
string $embeddedColumnName,
@@ -44,19 +39,16 @@ class DefaultNamingStrategy implements NamingStrategy
return $propertyName . '_' . $embeddedColumnName;
}
#[Override]
public function referenceColumnName(): string
{
return 'id';
}
#[Override]
public function joinColumnName(string $propertyName, string $className): string
{
return $propertyName . '_' . $this->referenceColumnName();
}
#[Override]
public function joinTableName(
string $sourceEntity,
string $targetEntity,
@@ -66,7 +58,6 @@ class DefaultNamingStrategy implements NamingStrategy
$this->classToTableName($targetEntity));
}
#[Override]
public function joinKeyColumnName(
string $entityName,
string|null $referencedColumnName,

View File

@@ -6,7 +6,6 @@ namespace Doctrine\ORM\Mapping;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\ORM\Internal\SQLResultCasing;
use Override;
use function array_map;
use function array_merge;
@@ -25,7 +24,6 @@ class DefaultQuoteStrategy implements QuoteStrategy
{
use SQLResultCasing;
#[Override]
public function getColumnName(string $fieldName, ClassMetadata $class, AbstractPlatform $platform): string
{
return isset($class->fieldMappings[$fieldName]->quoted)
@@ -38,7 +36,6 @@ class DefaultQuoteStrategy implements QuoteStrategy
*
* @todo Table names should be computed in DBAL depending on the platform
*/
#[Override]
public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string
{
$tableName = $class->table['name'];
@@ -61,7 +58,6 @@ class DefaultQuoteStrategy implements QuoteStrategy
/**
* {@inheritDoc}
*/
#[Override]
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string
{
return isset($definition['quoted'])
@@ -72,7 +68,6 @@ class DefaultQuoteStrategy implements QuoteStrategy
: $definition['sequenceName'];
}
#[Override]
public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string
{
return isset($joinColumn->quoted)
@@ -80,7 +75,6 @@ class DefaultQuoteStrategy implements QuoteStrategy
: $joinColumn->name;
}
#[Override]
public function getReferencedJoinColumnName(
JoinColumnMapping $joinColumn,
ClassMetadata $class,
@@ -91,7 +85,6 @@ class DefaultQuoteStrategy implements QuoteStrategy
: $joinColumn->referencedColumnName;
}
#[Override]
public function getJoinTableName(
ManyToManyOwningSideMapping $association,
ClassMetadata $class,
@@ -115,7 +108,6 @@ class DefaultQuoteStrategy implements QuoteStrategy
/**
* {@inheritDoc}
*/
#[Override]
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array
{
$quotedColumnNames = [];
@@ -144,7 +136,6 @@ class DefaultQuoteStrategy implements QuoteStrategy
return $quotedColumnNames;
}
#[Override]
public function getColumnAlias(
string $columnName,
int $counter,

View File

@@ -11,7 +11,6 @@ use DateTime;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Override;
use ReflectionEnum;
use ReflectionNamedType;
use ReflectionProperty;
@@ -28,7 +27,7 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
/** @var array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
private array $typedFieldMappings;
private const array DEFAULT_TYPED_FIELD_MAPPINGS = [
private const DEFAULT_TYPED_FIELD_MAPPINGS = [
DateInterval::class => Types::DATEINTERVAL,
DateTime::class => Types::DATETIME_MUTABLE,
DateTimeImmutable::class => Types::DATETIME_IMMUTABLE,
@@ -53,7 +52,6 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
/**
* {@inheritDoc}
*/
#[Override]
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
$type = $field->getType();
@@ -65,7 +63,10 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
if (
! $type->isBuiltin()
&& enum_exists($type->getName())
&& (! isset($mapping['type']) || $mapping['type'] === Types::ENUM)
&& (! isset($mapping['type']) || (
defined('Doctrine\DBAL\Types\Types::ENUM')
&& $mapping['type'] === Types::ENUM
))
) {
$reflection = new ReflectionEnum($type->getName());
if (! $reflection->isBacked()) {

View File

@@ -4,14 +4,18 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use BackedEnum;
use Exception;
use function in_array;
use function property_exists;
final class DiscriminatorColumnMapping
/** @template-implements ArrayAccess<string, mixed> */
final class DiscriminatorColumnMapping implements ArrayAccess
{
use ArrayAccessImplementation;
/** The database length of the column. Optional. Default value taken from the type. */
public int|null $length = null;

View File

@@ -13,7 +13,7 @@ use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Override;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionMethod;
@@ -21,22 +21,33 @@ use function assert;
use function class_exists;
use function constant;
use function defined;
use function sprintf;
class AttributeDriver implements MappingDriver
{
use ColocatedMappingDriver;
use ReflectionBasedDriver;
private const array ENTITY_ATTRIBUTE_CLASSES = [
private const ENTITY_ATTRIBUTE_CLASSES = [
Mapping\Entity::class => 1,
Mapping\MappedSuperclass::class => 2,
];
private readonly AttributeReader $reader;
/** @param string[]|ClassLocator $paths a ClassLocator, or an array of directories. */
public function __construct(array|ClassLocator $paths)
/**
* @param string[]|ClassLocator $paths a ClassLocator, or an array of directories.
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
*/
public function __construct(array|ClassLocator $paths, bool $reportFieldsWhereDeclared = true)
{
if (! $reportFieldsWhereDeclared) {
throw new InvalidArgumentException(sprintf(
'The $reportFieldsWhereDeclared argument is no longer supported, make sure to omit it when calling %s.',
__METHOD__,
));
}
$this->reader = new AttributeReader();
if ($paths instanceof ClassLocator) {
@@ -46,7 +57,6 @@ class AttributeDriver implements MappingDriver
}
}
#[Override]
public function isTransient(string $className): bool
{
$classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className));
@@ -69,7 +79,6 @@ class AttributeDriver implements MappingDriver
*
* @template T of object
*/
#[Override]
public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void
{
$reflectionClass = $metadata->getReflectionClass()

View File

@@ -0,0 +1,639 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Index\IndexedColumn;
use Doctrine\DBAL\Schema\Index\IndexType;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\NamedObject;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use InvalidArgumentException;
use TypeError;
use function array_diff;
use function array_keys;
use function array_map;
use function array_merge;
use function assert;
use function count;
use function current;
use function enum_exists;
use function get_debug_type;
use function in_array;
use function method_exists;
use function preg_replace;
use function sort;
use function sprintf;
use function strtolower;
/**
* The DatabaseDriver reverse engineers the mapping metadata from a database.
*
* @deprecated No replacement planned
*
* @link www.doctrine-project.org
*/
class DatabaseDriver implements MappingDriver
{
/**
* Replacement for {@see Types::ARRAY}.
*
* To be removed as soon as support for DBAL 3 is dropped.
*/
private const ARRAY = 'array';
/**
* Replacement for {@see Types::OBJECT}.
*
* To be removed as soon as support for DBAL 3 is dropped.
*/
private const OBJECT = 'object';
/** @var array<string,Table>|null */
private array|null $tables = null;
/** @var array<class-string, string> */
private array $classToTableNames = [];
/** @phpstan-var array<string, Table> */
private array $manyToManyTables = [];
/** @var mixed[] */
private array $classNamesForTables = [];
/** @var mixed[] */
private array $fieldNamesForColumns = [];
/**
* The namespace for the generated entities.
*/
private string|null $namespace = null;
private Inflector $inflector;
public function __construct(private readonly AbstractSchemaManager $sm)
{
$this->inflector = InflectorFactory::create()->build();
}
/**
* Set the namespace for the generated entities.
*/
public function setNamespace(string $namespace): void
{
$this->namespace = $namespace;
}
public function isTransient(string $className): bool
{
return true;
}
/**
* {@inheritDoc}
*/
public function getAllClassNames(): array
{
$this->reverseEngineerMappingFromDatabase();
return array_keys($this->classToTableNames);
}
/**
* Sets class name for a table.
*/
public function setClassNameForTable(string $tableName, string $className): void
{
$this->classNamesForTables[$tableName] = $className;
}
/**
* Sets field name for a column on a specific table.
*/
public function setFieldNameForColumn(string $tableName, string $columnName, string $fieldName): void
{
$this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
}
/**
* Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager.
*
* @param Table[] $entityTables
* @param Table[] $manyToManyTables
* @phpstan-param list<Table> $entityTables
* @phpstan-param list<Table> $manyToManyTables
*/
public function setTables(array $entityTables, array $manyToManyTables): void
{
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
foreach ($entityTables as $table) {
$className = $this->getClassNameForTable(self::getAssetName($table));
$this->classToTableNames[$className] = self::getAssetName($table);
$this->tables[self::getAssetName($table)] = $table;
}
foreach ($manyToManyTables as $table) {
$this->manyToManyTables[self::getAssetName($table)] = $table;
}
}
public function setInflector(Inflector $inflector): void
{
$this->inflector = $inflector;
}
/**
* {@inheritDoc}
*
* @param class-string<T> $className
* @param ClassMetadata<T> $metadata
*
* @template T of object
*/
public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void
{
if (! $metadata instanceof ClassMetadata) {
throw new TypeError(sprintf(
'Argument #2 passed to %s() must be an instance of %s, %s given.',
__METHOD__,
ClassMetadata::class,
get_debug_type($metadata),
));
}
$this->reverseEngineerMappingFromDatabase();
if (! isset($this->classToTableNames[$className])) {
throw new InvalidArgumentException('Unknown class ' . $className);
}
$tableName = $this->classToTableNames[$className];
$metadata->name = $className;
$metadata->table['name'] = $tableName;
$this->buildIndexes($metadata);
$this->buildFieldMappings($metadata);
$this->buildToOneAssociationMappings($metadata);
foreach ($this->manyToManyTables as $manyTable) {
foreach ($manyTable->getForeignKeys() as $foreignKey) {
// foreign key maps to the table of the current entity, many to many association probably exists
if (! (strtolower($tableName) === strtolower(self::getReferencedTableName($foreignKey)))) {
continue;
}
$myFk = $foreignKey;
$otherFk = null;
foreach ($manyTable->getForeignKeys() as $foreignKey) {
if ($foreignKey !== $myFk) {
$otherFk = $foreignKey;
break;
}
}
if (! $otherFk) {
// the definition of this many to many table does not contain
// enough foreign key information to continue reverse engineering.
continue;
}
$localColumn = current(self::getReferencingColumnNames($myFk));
$associationMapping = [];
$associationMapping['fieldName'] = $this->getFieldNameForColumn(self::getAssetName($manyTable), current(self::getReferencingColumnNames($otherFk)), true);
$associationMapping['targetEntity'] = $this->getClassNameForTable(self::getReferencedTableName($otherFk));
if (self::getAssetName(current($manyTable->getColumns())) === $localColumn) {
$associationMapping['inversedBy'] = $this->getFieldNameForColumn(self::getAssetName($manyTable), current(self::getReferencingColumnNames($myFk)), true);
$associationMapping['joinTable'] = [
'name' => strtolower(self::getAssetName($manyTable)),
'joinColumns' => [],
'inverseJoinColumns' => [],
];
$fkCols = self::getReferencedColumnNames($myFk);
$cols = self::getReferencingColumnNames($myFk);
for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
$associationMapping['joinTable']['joinColumns'][] = [
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
];
}
$fkCols = self::getReferencedColumnNames($otherFk);
$cols = self::getReferencingColumnNames($otherFk);
for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
$associationMapping['joinTable']['inverseJoinColumns'][] = [
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
];
}
} else {
$associationMapping['mappedBy'] = $this->getFieldNameForColumn(
// @phpstan-ignore function.alreadyNarrowedType (DBAL 3 compatibility)
method_exists(Table::class, 'getObjectName')
? $manyTable->getObjectName()->toString()
: $manyTable->getName(), // DBAL < 4.4
current(self::getReferencingColumnNames($myFk)),
true,
);
}
$metadata->mapManyToMany($associationMapping);
break;
}
}
}
/** @throws MappingException */
private function reverseEngineerMappingFromDatabase(): void
{
if ($this->tables !== null) {
return;
}
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
foreach ($this->sm->listTables() as $table) {
$tableName = self::getAssetName($table);
$foreignKeys = $table->getForeignKeys();
$allForeignKeyColumns = [];
foreach ($foreignKeys as $foreignKey) {
$allForeignKeyColumns = array_merge($allForeignKeyColumns, self::getReferencingColumnNames($foreignKey));
}
if (method_exists($table, 'getPrimaryKeyConstraint')) {
$primaryKey = $table->getPrimaryKeyConstraint();
} else {
$primaryKey = $table->getPrimaryKey();
}
if ($primaryKey === null) {
throw new MappingException(
'Table ' . $tableName . ' has no primary key. Doctrine does not ' .
"support reverse engineering from tables that don't have a primary key.",
);
}
if ($primaryKey instanceof PrimaryKeyConstraint) {
$pkColumns = array_map(static fn (UnqualifiedName $name) => $name->toString(), $primaryKey->getColumnNames());
} else {
$pkColumns = self::getIndexedColumns($primaryKey);
}
sort($pkColumns);
sort($allForeignKeyColumns);
if ($pkColumns === $allForeignKeyColumns && count($foreignKeys) === 2) {
$this->manyToManyTables[$tableName] = $table;
} else {
// lower-casing is necessary because of Oracle Uppercase Tablenames,
// assumption is lower-case + underscore separated.
$className = $this->getClassNameForTable($tableName);
$this->tables[$tableName] = $table;
$this->classToTableNames[$className] = $tableName;
}
}
}
/**
* Build indexes from a class metadata.
*/
private function buildIndexes(ClassMetadata $metadata): void
{
$tableName = $metadata->table['name'];
$table = $this->tables[$tableName];
$primaryKey = self::getPrimaryKey($table);
$indexes = $table->getIndexes();
foreach ($indexes as $index) {
if ($index === $primaryKey) {
continue;
}
if (enum_exists(IndexType::class) && method_exists($index, 'getType')) {
$isUnique = $index->getType() === IndexType::UNIQUE;
} else {
$isUnique = $index->isUnique();
}
$indexName = self::getAssetName($index);
$indexColumns = self::getIndexedColumns($index);
$constraintType = $isUnique
? 'uniqueConstraints'
: 'indexes';
$metadata->table[$constraintType][$indexName]['columns'] = $indexColumns;
}
}
/**
* Build field mapping from class metadata.
*/
private function buildFieldMappings(ClassMetadata $metadata): void
{
$tableName = $metadata->table['name'];
$columns = $this->tables[$tableName]->getColumns();
$primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
$allForeignKeys = [];
foreach ($foreignKeys as $foreignKey) {
$allForeignKeys = array_merge($allForeignKeys, self::getReferencingColumnNames($foreignKey));
}
$ids = [];
$fieldMappings = [];
foreach ($columns as $column) {
if (in_array(self::getAssetName($column), $allForeignKeys, true)) {
continue;
}
$fieldMapping = $this->buildFieldMapping($tableName, $column);
if ($primaryKeys && in_array(self::getAssetName($column), $primaryKeys, true)) {
$fieldMapping['id'] = true;
$ids[] = $fieldMapping;
}
$fieldMappings[] = $fieldMapping;
}
// We need to check for the columns here, because we might have associations as id as well.
if ($ids && count($primaryKeys) === 1) {
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO);
}
foreach ($fieldMappings as $fieldMapping) {
$metadata->mapField($fieldMapping);
}
}
/**
* Build field mapping from a schema column definition
*
* @return mixed[]
* @phpstan-return array{
* fieldName: string,
* columnName: string,
* type: string,
* nullable: bool,
* options: array{
* unsigned?: bool,
* fixed?: bool,
* comment: string|null,
* default?: mixed
* },
* precision?: int,
* scale?: int,
* length?: int|null
* }
*/
private function buildFieldMapping(string $tableName, Column $column): array
{
$fieldMapping = [
'fieldName' => $this->getFieldNameForColumn($tableName, self::getAssetName($column), false),
'columnName' => self::getAssetName($column),
'type' => Type::getTypeRegistry()->lookupName($column->getType()),
'nullable' => ! $column->getNotnull(),
'options' => [
'comment' => $column->getComment(),
],
];
// Type specific elements
switch ($fieldMapping['type']) {
case self::ARRAY:
case Types::BLOB:
case Types::GUID:
case self::OBJECT:
case Types::SIMPLE_ARRAY:
case Types::STRING:
case Types::TEXT:
$fieldMapping['length'] = $column->getLength();
$fieldMapping['options']['fixed'] = $column->getFixed();
break;
case Types::DECIMAL:
case Types::FLOAT:
$fieldMapping['precision'] = $column->getPrecision();
$fieldMapping['scale'] = $column->getScale();
break;
case Types::INTEGER:
case Types::BIGINT:
case Types::SMALLINT:
$fieldMapping['options']['unsigned'] = $column->getUnsigned();
break;
}
// Default
$default = $column->getDefault();
if ($default !== null) {
$fieldMapping['options']['default'] = $default;
}
return $fieldMapping;
}
/**
* Build to one (one to one, many to one) association mapping from class metadata.
*/
private function buildToOneAssociationMappings(ClassMetadata $metadata): void
{
assert($this->tables !== null);
$tableName = $metadata->table['name'];
$primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
foreach ($foreignKeys as $foreignKey) {
$foreignTableName = self::getReferencedTableName($foreignKey);
$fkColumns = self::getReferencingColumnNames($foreignKey);
$fkForeignColumns = self::getReferencedColumnNames($foreignKey);
$localColumn = current($fkColumns);
$associationMapping = [
'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true),
'targetEntity' => $this->getClassNameForTable($foreignTableName),
];
if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) {
$associationMapping['fieldName'] .= '2'; // "foo" => "foo2"
}
if ($primaryKeys && in_array($localColumn, $primaryKeys, true)) {
$associationMapping['id'] = true;
}
for ($i = 0, $fkColumnsCount = count($fkColumns); $i < $fkColumnsCount; $i++) {
$associationMapping['joinColumns'][] = [
'name' => $fkColumns[$i],
'referencedColumnName' => $fkForeignColumns[$i],
];
}
// Here we need to check if $fkColumns are the same as $primaryKeys
if (! array_diff($fkColumns, $primaryKeys)) {
$metadata->mapOneToOne($associationMapping);
} else {
$metadata->mapManyToOne($associationMapping);
}
}
}
/**
* Retrieve schema table definition primary keys.
*
* @return string[]
*/
private function getTablePrimaryKeys(Table $table): array
{
try {
if (method_exists($table, 'getPrimaryKeyConstraint')) {
return array_map(static fn (UnqualifiedName $name) => $name->toString(), $table->getPrimaryKeyConstraint()->getColumnNames());
}
return self::getIndexedColumns($table->getPrimaryKey());
} catch (SchemaException) {
// Do nothing
}
return [];
}
/**
* Returns the mapped class name for a table if it exists. Otherwise return "classified" version.
*
* @return class-string
*/
private function getClassNameForTable(string $tableName): string
{
if (isset($this->classNamesForTables[$tableName])) {
return $this->namespace . $this->classNamesForTables[$tableName];
}
return $this->namespace . $this->inflector->classify(strtolower($tableName));
}
/**
* Return the mapped field name for a column, if it exists. Otherwise return camelized version.
*
* @param bool $fk Whether the column is a foreignkey or not.
*/
private function getFieldNameForColumn(
string $tableName,
string $columnName,
bool $fk = false,
): string {
if (isset($this->fieldNamesForColumns[$tableName], $this->fieldNamesForColumns[$tableName][$columnName])) {
return $this->fieldNamesForColumns[$tableName][$columnName];
}
$columnName = strtolower($columnName);
// Replace _id if it is a foreignkey column
if ($fk) {
$columnName = preg_replace('/_id$/', '', $columnName);
}
return $this->inflector->camelize($columnName);
}
private static function getReferencedTableName(ForeignKeyConstraint $foreignKey): string
{
if (method_exists(ForeignKeyConstraint::class, 'getReferencedTableName')) {
return $foreignKey->getReferencedTableName()->toString();
}
return $foreignKey->getForeignTableName();
}
/** @return string[] */
private static function getReferencingColumnNames(ForeignKeyConstraint $foreignKey): array
{
if (method_exists(ForeignKeyConstraint::class, 'getReferencingColumnNames')) {
return array_map(static fn (UnqualifiedName $name) => $name->toString(), $foreignKey->getReferencingColumnNames());
}
return $foreignKey->getLocalColumns();
}
/** @return string[] */
private static function getReferencedColumnNames(ForeignKeyConstraint $foreignKey): array
{
if (method_exists(ForeignKeyConstraint::class, 'getReferencedColumnNames')) {
return array_map(static fn (UnqualifiedName $name) => $name->toString(), $foreignKey->getReferencedColumnNames());
}
return $foreignKey->getForeignColumns();
}
/** @return string[] */
private static function getIndexedColumns(Index $index): array
{
if (method_exists(Index::class, 'getIndexedColumns')) {
return array_map(static fn (IndexedColumn $indexedColumn) => $indexedColumn->getColumnName()->toString(), $index->getIndexedColumns());
}
return $index->getColumns();
}
private static function getPrimaryKey(Table $table): Index|null
{
$primaryKeyConstraint = null;
if (method_exists(Table::class, 'getPrimaryKeyConstraint')) {
$primaryKeyConstraint = $table->getPrimaryKeyConstraint();
}
foreach ($table->getIndexes() as $index) {
if ($primaryKeyConstraint !== null) {
$primaryKeyConstraintColumns = array_map(static fn (UnqualifiedName $name) => $name->toString(), $primaryKeyConstraint->getColumnNames());
if ($primaryKeyConstraintColumns === self::getIndexedColumns($index)) {
return $index;
}
} elseif ($index->isPrimary()) {
return $index;
}
}
return null;
}
private static function getAssetName(AbstractAsset $asset): string
{
return $asset instanceof NamedObject
? $asset->getObjectName()->toString()
// DBAL < 4.4
: $asset->getName();
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Persistence\Mapping\StaticReflectionService;
use function class_exists;
if (! class_exists(StaticReflectionService::class)) {
/** @internal */
trait LoadMappingFileImplementation
{
/**
* {@inheritDoc}
*/
protected function loadMappingFile($file): array
{
return $this->doLoadMappingFile($file);
}
}
} else {
/** @internal */
trait LoadMappingFileImplementation
{
/**
* {@inheritDoc}
*/
protected function loadMappingFile($file)
{
return $this->doLoadMappingFile($file);
}
}
}

View File

@@ -11,7 +11,7 @@ use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator;
*/
class SimplifiedXmlDriver extends XmlDriver
{
final public const string DEFAULT_FILE_EXTENSION = '.orm.xml';
public const DEFAULT_FILE_EXTENSION = '.orm.xml';
/**
* {@inheritDoc}

View File

@@ -13,7 +13,6 @@ use Doctrine\Persistence\Mapping\Driver\FileLocator;
use DOMDocument;
use InvalidArgumentException;
use LogicException;
use Override;
use SimpleXMLElement;
use function assert;
@@ -42,7 +41,9 @@ use function strtoupper;
*/
class XmlDriver extends FileDriver
{
public const string DEFAULT_FILE_EXTENSION = '.dcm.xml';
use LoadMappingFileImplementation;
public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
/**
* {@inheritDoc}
@@ -76,7 +77,6 @@ class XmlDriver extends FileDriver
*
* @template T of object
*/
#[Override]
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
{
$xmlRoot = $this->getElement($className);
@@ -916,13 +916,8 @@ class XmlDriver extends FileDriver
return $cascades;
}
/**
* {@inheritDoc}
*
* @return array<class-string, SimpleXMLElement>
*/
#[Override]
protected function loadMappingFile(string $file): array
/** @return array<class-string, SimpleXMLElement> */
private function doLoadMappingFile(string $file): array
{
$this->validateMapping($file);
$result = [];

View File

@@ -4,10 +4,15 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use function property_exists;
final class EmbeddedClassMapping
/** @template-implements ArrayAccess<string, mixed> */
final class EmbeddedClassMapping implements ArrayAccess
{
use ArrayAccessImplementation;
public string|false|null $columnPrefix = null;
public string|null $declaredField = null;
public string|null $originalField = null;

View File

@@ -4,13 +4,17 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use BackedEnum;
use function in_array;
use function property_exists;
final class FieldMapping
/** @template-implements ArrayAccess<string, mixed> */
final class FieldMapping implements ArrayAccess
{
use ArrayAccessImplementation;
/** The database length of the column. Optional. Default value taken from the type. */
public int|null $length = null;
/**
@@ -68,6 +72,9 @@ final class FieldMapping
public array|null $options = null;
public bool|null $version = null;
/** @deprecated Use options with 'default' key instead */
public string|int|null $default = null;
/**
* @param string $type The type name of the mapped field. Can be one of
* Doctrine's mapping types or a custom mapping type.
@@ -155,6 +162,7 @@ final class FieldMapping
'declared',
'declaredField',
'options',
'default',
] as $key
) {
if ($this->$key !== null) {

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Persistence\Mapping\StaticReflectionService;
use ReflectionClass;
use function class_exists;
if (! class_exists(StaticReflectionService::class)) {
trait GetReflectionClassImplementation
{
public function getReflectionClass(): ReflectionClass
{
return $this->reflClass;
}
}
} else {
trait GetReflectionClassImplementation
{
/**
* {@inheritDoc}
*
* Can return null when using static reflection, in violation of the LSP
*/
public function getReflectionClass(): ReflectionClass|null
{
return $this->reflClass;
}
}
}

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
abstract class InverseSideMapping extends AssociationMapping
{
/**
@@ -22,7 +20,6 @@ abstract class InverseSideMapping extends AssociationMapping
}
/** @return list<string> */
#[Override]
public function __sleep(): array
{
return [

View File

@@ -4,10 +4,15 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use function property_exists;
final class JoinColumnMapping
/** @template-implements ArrayAccess<string, mixed> */
final class JoinColumnMapping implements ArrayAccess
{
use ArrayAccessImplementation;
public bool|null $deferrable = null;
public bool|null $unique = null;
public bool|null $quoted = null;

View File

@@ -4,11 +4,16 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use function array_map;
use function in_array;
final class JoinTableMapping
/** @template-implements ArrayAccess<string, mixed> */
final class JoinTableMapping implements ArrayAccess
{
use ArrayAccessImplementation;
public bool|null $quoted = null;
/** @var list<JoinColumnMapping> */

View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Persistence\Mapping\ReflectionService;
use Doctrine\Persistence\Reflection\EnumReflectionProperty;
use Generator;
use IteratorAggregate;
use OutOfBoundsException;
use ReflectionProperty;
use Traversable;
use function array_keys;
use function assert;
use function is_string;
use function str_contains;
use function str_replace;
/**
* @template-implements ArrayAccess<string, ReflectionProperty|null>
* @template-implements IteratorAggregate<string, ReflectionProperty|null>
*/
class LegacyReflectionFields implements ArrayAccess, IteratorAggregate
{
/** @var array<string, ReflectionProperty|null> */
private array $reflFields = [];
public function __construct(private ClassMetadata $classMetadata, private ReflectionService $reflectionService)
{
}
/** @param string $offset */
public function offsetExists($offset): bool // phpcs:ignore
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11659',
'Access to ClassMetadata::$reflFields is deprecated and will be removed in Doctrine ORM 4.0.',
);
return isset($this->classMetadata->propertyAccessors[$offset]);
}
/**
* @param string $field
*
* @psalm-suppress LessSpecificImplementedReturnType
*/
public function offsetGet($field): mixed // phpcs:ignore
{
if (isset($this->reflFields[$field])) {
return $this->reflFields[$field];
}
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11659',
'Access to ClassMetadata::$reflFields is deprecated and will be removed in Doctrine ORM 4.0.',
);
if (isset($this->classMetadata->propertyAccessors[$field])) {
$fieldName = str_contains($field, '.') ? $this->classMetadata->fieldMappings[$field]->originalField : $field;
$className = $this->classMetadata->name;
assert(is_string($fieldName));
if (isset($this->classMetadata->fieldMappings[$field]) && $this->classMetadata->fieldMappings[$field]->originalClass !== null) {
$className = $this->classMetadata->fieldMappings[$field]->originalClass;
} elseif (isset($this->classMetadata->fieldMappings[$field]) && $this->classMetadata->fieldMappings[$field]->declared !== null) {
$className = $this->classMetadata->fieldMappings[$field]->declared;
} elseif (isset($this->classMetadata->associationMappings[$field]) && $this->classMetadata->associationMappings[$field]->declared !== null) {
$className = $this->classMetadata->associationMappings[$field]->declared;
} elseif (isset($this->classMetadata->embeddedClasses[$field]) && $this->classMetadata->embeddedClasses[$field]->declared !== null) {
$className = $this->classMetadata->embeddedClasses[$field]->declared;
}
/** @psalm-suppress ArgumentTypeCoercion */
$this->reflFields[$field] = $this->getAccessibleProperty($className, $fieldName);
if (isset($this->classMetadata->fieldMappings[$field])) {
if ($this->classMetadata->fieldMappings[$field]->enumType !== null) {
$this->reflFields[$field] = new EnumReflectionProperty(
$this->reflFields[$field],
$this->classMetadata->fieldMappings[$field]->enumType,
);
}
if ($this->classMetadata->fieldMappings[$field]->originalField !== null) {
$parentField = str_replace('.' . $fieldName, '', $field);
$originalClass = $this->classMetadata->fieldMappings[$field]->originalClass;
if (! str_contains($parentField, '.')) {
$parentClass = $this->classMetadata->name;
} else {
$parentClass = $this->classMetadata->fieldMappings[$parentField]->originalClass;
}
/** @psalm-var class-string $parentClass */
/** @psalm-var class-string $originalClass */
$this->reflFields[$field] = new ReflectionEmbeddedProperty(
$this->getAccessibleProperty($parentClass, $parentField),
$this->reflFields[$field],
$originalClass,
);
}
}
return $this->reflFields[$field];
}
throw new OutOfBoundsException('Unknown field: ' . $this->classMetadata->name . ' ::$' . $field);
}
/**
* @param string $offset
* @param ReflectionProperty $value
*/
public function offsetSet($offset, $value): void // phpcs:ignore
{
$this->reflFields[$offset] = $value;
}
/** @param string $offset */
public function offsetUnset($offset): void // phpcs:ignore
{
unset($this->reflFields[$offset]);
}
/** @psalm-param class-string $class */
private function getAccessibleProperty(string $class, string $field): ReflectionProperty
{
$reflectionProperty = $this->reflectionService->getAccessibleProperty($class, $field);
assert($reflectionProperty !== null);
if ($reflectionProperty->isReadOnly()) {
$declaringClass = $reflectionProperty->class;
if ($declaringClass !== $class) {
$reflectionProperty = $this->reflectionService->getAccessibleProperty($declaringClass, $field);
assert($reflectionProperty !== null);
}
$reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty);
}
return $reflectionProperty;
}
/** @return Generator<string, ReflectionProperty> */
public function getIterator(): Traversable
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11659',
'Access to ClassMetadata::$reflFields is deprecated and will be removed in Doctrine ORM 4.0.',
);
$keys = array_keys($this->classMetadata->propertyAccessors);
foreach ($keys as $key) {
yield $key => $this->offsetGet($key);
}
}
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
use Doctrine\Deprecations\Deprecation;
use function strtolower;
use function trim;
@@ -28,7 +28,6 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
public array $relationToTargetKeyColumns = [];
/** @return array<string, mixed> */
#[Override]
public function toArray(): array
{
$array = parent::toArray();
@@ -131,7 +130,14 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
if ($joinColumn->nullable !== null) {
throw MappingException::cannotSetNullableOnManyToManyJoinColumns(
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12126',
<<<'DEPRECATION'
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
The ORM will always set it to false.
Doing so is deprecated and will be an error in 4.0.
DEPRECATION,
$mapping->sourceEntity,
$mapping->fieldName,
);
@@ -163,7 +169,14 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) {
if ($inverseJoinColumn->nullable !== null) {
throw MappingException::cannotSetNullableOnManyToManyJoinColumns(
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12126',
<<<'DEPRECATION'
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
The ORM will always set it to false.
Doing so is deprecated and will be an error in 4.0.
DEPRECATION,
$mapping->targetEntity,
$mapping->fieldName,
);
@@ -197,7 +210,6 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
}
/** @return list<string> */
#[Override]
public function __sleep(): array
{
$serialized = parent::__sleep();

View File

@@ -8,7 +8,6 @@ use BackedEnum;
use Doctrine\ORM\Exception\ORMException;
use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException;
use LibXMLError;
use Override;
use ReflectionException;
use ValueError;
@@ -88,7 +87,6 @@ class MappingException extends PersistenceMappingException implements ORMExcepti
return new self(sprintf("The embed mapping '%s' misses the 'class' attribute.", $fieldName));
}
#[Override]
public static function mappingFileNotFound(string $entityName, string $fileName): self
{
return new self(sprintf("No mapping file found named '%s' for class '%s'.", $fileName, $entityName));
@@ -319,7 +317,6 @@ class MappingException extends PersistenceMappingException implements ORMExcepti
));
}
#[Override]
public static function fileMappingDriversRequireConfiguredDirectoryPath(string|null $path = null): self
{
if (! empty($path)) {
@@ -427,28 +424,6 @@ class MappingException extends PersistenceMappingException implements ORMExcepti
return new self(sprintf("Discriminator column name on entity class '%s' is not defined.", $className));
}
public static function cannotSetNullableOnToOneIdentifierJoinColumns(
string $className,
string $fieldName,
): self {
return new self(sprintf(
'Cannot specify the "nullable" attribute for join columns in to-one associations (%s::$%s) that are part of the identifier',
$className,
$fieldName,
));
}
public static function cannotSetNullableOnManyToManyJoinColumns(
string $className,
string $fieldName,
): self {
return new self(sprintf(
'Cannot specify the "nullable" attribute for join columns in many-to-many associations (%s::$%s)',
$className,
$fieldName,
));
}
public static function cannotVersionIdField(string $className, string $fieldName): self
{
return new self(sprintf(

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
final class OneToManyAssociationMapping extends ToManyInverseSideMapping
{
/**
@@ -30,7 +28,6 @@ final class OneToManyAssociationMapping extends ToManyInverseSideMapping
* isOwningSide: bool,
* } $mappingArray
*/
#[Override]
public static function fromMappingArray(array $mappingArray): static
{
$mapping = parent::fromMappingArray($mappingArray);

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
abstract class OwningSideMapping extends AssociationMapping
{
/**
@@ -17,7 +15,6 @@ abstract class OwningSideMapping extends AssociationMapping
public string|null $inversedBy = null;
/** @return list<string> */
#[Override]
public function __sleep(): array
{
$serialized = parent::__sleep();

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\PropertyAccessors;
use Doctrine\Instantiator\Instantiator;
use Override;
use ReflectionProperty;
/** @internal */
@@ -21,7 +20,6 @@ class EmbeddablePropertyAccessor implements PropertyAccessor
) {
}
#[Override]
public function setValue(object $object, mixed $value): void
{
$embeddedObject = $this->parent->getValue($object);
@@ -37,7 +35,6 @@ class EmbeddablePropertyAccessor implements PropertyAccessor
$this->child->setValue($embeddedObject, $value);
}
#[Override]
public function getValue(object $object): mixed
{
$embeddedObject = $this->parent->getValue($object);
@@ -49,7 +46,6 @@ class EmbeddablePropertyAccessor implements PropertyAccessor
return $this->child->getValue($embeddedObject);
}
#[Override]
public function getUnderlyingReflector(): ReflectionProperty
{
return $this->child->getUnderlyingReflector();

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\PropertyAccessors;
use BackedEnum;
use Override;
use ReflectionProperty;
use function array_map;
@@ -20,7 +19,6 @@ class EnumPropertyAccessor implements PropertyAccessor
{
}
#[Override]
public function setValue(object $object, mixed $value): void
{
if ($value !== null) {
@@ -30,7 +28,6 @@ class EnumPropertyAccessor implements PropertyAccessor
$this->parent->setValue($object, $value);
}
#[Override]
public function getValue(object $object): mixed
{
$enum = $this->parent->getValue($object);
@@ -81,7 +78,6 @@ class EnumPropertyAccessor implements PropertyAccessor
return $this->enumType::from($value);
}
#[Override]
public function getUnderlyingReflector(): ReflectionProperty
{
return $this->parent->getUnderlyingReflector();

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\PropertyAccessors;
use Doctrine\ORM\Proxy\InternalProxy;
use ReflectionProperty;
use function ltrim;
/** @internal */
class ObjectCastPropertyAccessor implements PropertyAccessor
{
/** @param class-string $class */
public static function fromNames(string $class, string $name): self
{
$reflectionProperty = new ReflectionProperty($class, $name);
$key = $reflectionProperty->isPrivate() ? "\0" . ltrim($class, '\\') . "\0" . $name : ($reflectionProperty->isProtected() ? "\0*\0" . $name : $name);
return new self($reflectionProperty, $key);
}
public static function fromReflectionProperty(ReflectionProperty $reflectionProperty): self
{
$name = $reflectionProperty->getName();
$key = $reflectionProperty->isPrivate() ? "\0" . ltrim($reflectionProperty->getDeclaringClass()->getName(), '\\') . "\0" . $name : ($reflectionProperty->isProtected() ? "\0*\0" . $name : $name);
return new self($reflectionProperty, $key);
}
private function __construct(private ReflectionProperty $reflectionProperty, private string $key)
{
}
public function setValue(object $object, mixed $value): void
{
if (! ($object instanceof InternalProxy && ! $object->__isInitialized())) {
$this->reflectionProperty->setValue($object, $value);
return;
}
$object->__setInitialized(true);
$this->reflectionProperty->setValue($object, $value);
$object->__setInitialized(false);
}
public function getValue(object $object): mixed
{
return ((array) $object)[$this->key] ?? null;
}
public function getUnderlyingReflector(): ReflectionProperty
{
return $this->reflectionProperty;
}
}

View File

@@ -6,6 +6,8 @@ namespace Doctrine\ORM\Mapping\PropertyAccessors;
use ReflectionProperty;
use const PHP_VERSION_ID;
class PropertyAccessorFactory
{
/** @phpstan-param class-string $className */
@@ -13,7 +15,9 @@ class PropertyAccessorFactory
{
$reflectionProperty = new ReflectionProperty($className, $propertyName);
$accessor = RawValuePropertyAccessor::fromReflectionProperty($reflectionProperty);
$accessor = PHP_VERSION_ID >= 80400
? RawValuePropertyAccessor::fromReflectionProperty($reflectionProperty)
: ObjectCastPropertyAccessor::fromReflectionProperty($reflectionProperty);
if ($reflectionProperty->hasType() && ! $reflectionProperty->getType()->allowsNull()) {
$accessor = new TypedNoDefaultPropertyAccessor($accessor, $reflectionProperty);

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