mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5fb1a4a8f | ||
|
|
8cb7a5e212 | ||
|
|
da9b9de590 | ||
|
|
9ec697db7d | ||
|
|
a50a611bee | ||
|
|
abb30093ed | ||
|
|
df559d8ac0 | ||
|
|
a6de4b9663 | ||
|
|
6db296cd5c | ||
|
|
7c2adde6f2 | ||
|
|
69543ba1bf | ||
|
|
a33885575f | ||
|
|
e28dee0742 | ||
|
|
a28e2d8277 | ||
|
|
4759a1bf75 | ||
|
|
e0ad7ac506 | ||
|
|
9485d4d835 | ||
|
|
9a6e1b3505 | ||
|
|
c286742814 | ||
|
|
052887765b | ||
|
|
5464e21022 | ||
|
|
c26c55926f | ||
|
|
5369e4f425 | ||
|
|
29bc6cc955 | ||
|
|
31ff969628 | ||
|
|
01f139d76c | ||
|
|
660197ea71 | ||
|
|
ab06e07b53 | ||
|
|
022b945ed5 | ||
|
|
7203d05539 | ||
|
|
0bd5fbf215 | ||
|
|
6a713dd39e | ||
|
|
5f169d9325 | ||
|
|
cf4680d0e6 | ||
|
|
cc5775c3f4 | ||
|
|
c5cf6a046b | ||
|
|
77df5db3b9 | ||
|
|
ee8269ea55 | ||
|
|
d038f23570 | ||
|
|
3843d7e0cc | ||
|
|
d50ba2e248 | ||
|
|
8debb92d78 | ||
|
|
8ff7938e31 | ||
|
|
d6c0031d44 | ||
|
|
c78f933e57 | ||
|
|
80eb85beaa | ||
|
|
ed56f42cd5 | ||
|
|
8b28543939 | ||
|
|
eec3c42494 | ||
|
|
bc394877bc | ||
|
|
1753d03500 | ||
|
|
7ce6d8d427 | ||
|
|
a48d95c71d | ||
|
|
aee1d33042 | ||
|
|
8da741ad75 | ||
|
|
ba7387fd8c | ||
|
|
a83e4f7978 | ||
|
|
4f335ab565 | ||
|
|
f88b0032ad | ||
|
|
69c7791ba2 | ||
|
|
1090dbe9be | ||
|
|
c46b604ed7 | ||
|
|
8d9ebeded8 | ||
|
|
55f9178e84 | ||
|
|
60955755e0 | ||
|
|
a8e979819a | ||
|
|
4e8e3ef30b | ||
|
|
de7eee5ed7 | ||
|
|
4051937fda | ||
|
|
b2e42dc92d | ||
|
|
f219b87870 | ||
|
|
87fefbac34 | ||
|
|
853e80ca98 | ||
|
|
d68baef880 | ||
|
|
fdccfbd120 | ||
|
|
180afa8c8f | ||
|
|
dbbf5c7279 | ||
|
|
5d9b8f0ea8 | ||
|
|
174947155d | ||
|
|
2138cc9383 | ||
|
|
39a434914d | ||
|
|
227f60c832 | ||
|
|
f72f6b199b | ||
|
|
92e63ca4f9 | ||
|
|
3c98973ab3 | ||
|
|
3b8692fa4a | ||
|
|
c9efc1cdee | ||
|
|
c5e4e41e05 | ||
|
|
9431b2ea45 | ||
|
|
036ea713a5 | ||
|
|
0852847659 | ||
|
|
1e2625a82f | ||
|
|
3010fd1680 | ||
|
|
85ac2769a9 | ||
|
|
28e98b3475 | ||
|
|
99a37d864e | ||
|
|
27df173971 | ||
|
|
0aa45dd607 | ||
|
|
f8bf84d1aa | ||
|
|
c825e34f8d | ||
|
|
ff6bad486b | ||
|
|
30a2680bfd | ||
|
|
a460a4d054 | ||
|
|
f82485e651 | ||
|
|
c4835cca0d | ||
|
|
82a406332e | ||
|
|
8093c2eef6 | ||
|
|
f94cb9a5e6 | ||
|
|
6255461b84 | ||
|
|
0aa5946286 | ||
|
|
eda7558674 | ||
|
|
13bab31da6 | ||
|
|
f960bc2c11 | ||
|
|
15058ca83e | ||
|
|
9b14786738 | ||
|
|
497ee166bd | ||
|
|
db18161a1a | ||
|
|
284e81403b | ||
|
|
3ea8550ca6 | ||
|
|
aa4b62ce78 | ||
|
|
e5e674c686 | ||
|
|
9d5ab4ce76 | ||
|
|
a2b5bae923 | ||
|
|
5a8541b450 | ||
|
|
fa18e130cb | ||
|
|
24bf06725b | ||
|
|
d5ef6be4cc | ||
|
|
54336840e6 | ||
|
|
74986f1d53 | ||
|
|
16cd49ba89 | ||
|
|
f9d5a89a39 | ||
|
|
db7333cc84 | ||
|
|
47b4ccc4e6 | ||
|
|
5a7fce12b8 | ||
|
|
cc9e456ed8 | ||
|
|
da356316c1 | ||
|
|
1ce806fcb7 | ||
|
|
958d0b6193 | ||
|
|
843f3c3b23 | ||
|
|
82e4c644f9 | ||
|
|
9399f1f3a8 | ||
|
|
ad69810775 | ||
|
|
6c7a5e6faa | ||
|
|
dcc1c26826 | ||
|
|
8afb644a18 | ||
|
|
953e42d059 | ||
|
|
318af0a666 | ||
|
|
90ececcc85 | ||
|
|
88b36e07e1 | ||
|
|
a37c2cc05f | ||
|
|
40b34b03c1 | ||
|
|
8d9ab72613 | ||
|
|
12f0674b1a | ||
|
|
069206ba14 | ||
|
|
e8a4d2e91b | ||
|
|
284baf890e | ||
|
|
a1f9b28cdc | ||
|
|
2b7485af97 | ||
|
|
edb6332359 | ||
|
|
48c1eef1b8 | ||
|
|
5d88dc9be4 | ||
|
|
c97f0a1078 | ||
|
|
474f76fc8b | ||
|
|
25ce9b9101 | ||
|
|
75340b68b2 | ||
|
|
543be3fe35 | ||
|
|
1edfa91714 | ||
|
|
c828a3814b | ||
|
|
cf91ce63d3 | ||
|
|
f3f453286f | ||
|
|
7cb96fcf0e | ||
|
|
ac94d826dc | ||
|
|
f33919d7d6 | ||
|
|
f256d996cc | ||
|
|
d617323a48 | ||
|
|
6b61e26238 | ||
|
|
edad800711 | ||
|
|
fba05675b6 | ||
|
|
8160e89c5a | ||
|
|
a76b776802 | ||
|
|
ad1d1ca942 | ||
|
|
be835bb8e2 | ||
|
|
bac784c9ba | ||
|
|
1ad936a448 | ||
|
|
7e75807918 | ||
|
|
90f82202a8 | ||
|
|
97aa5b37e6 | ||
|
|
1a9f40c785 | ||
|
|
7ce9a6fe5c | ||
|
|
8e062955d5 | ||
|
|
0e65b0c3dc | ||
|
|
e14e9bebcc | ||
|
|
9422260efd | ||
|
|
d46512332c | ||
|
|
0d56ffc261 | ||
|
|
95c818928e | ||
|
|
f27034880a | ||
|
|
3735822662 | ||
|
|
52f7ddc680 | ||
|
|
de32d8239f | ||
|
|
397751ee65 | ||
|
|
b0038eeea6 | ||
|
|
f06f9f843e | ||
|
|
5d3fc62023 | ||
|
|
154e7130f5 | ||
|
|
99f044cbc7 | ||
|
|
e8ca7b4abf | ||
|
|
b05fb96ad3 | ||
|
|
bb020320ca | ||
|
|
1e0ac8899c | ||
|
|
4a04c8c2db | ||
|
|
470e2ddd05 | ||
|
|
6ec5ab9145 | ||
|
|
86000d9c70 | ||
|
|
328bf707cc | ||
|
|
7a9037e9d7 | ||
|
|
8d03f8f089 | ||
|
|
8dfe8b8782 |
@@ -12,21 +12,27 @@
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.14",
|
||||
"branchName": "2.14.x",
|
||||
"slug": "2.14",
|
||||
"name": "2.15",
|
||||
"branchName": "2.15.x",
|
||||
"slug": "2.15",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.13",
|
||||
"branchName": "2.13.x",
|
||||
"slug": "2.13",
|
||||
"name": "2.14",
|
||||
"branchName": "2.14.x",
|
||||
"slug": "2.14",
|
||||
"current": true,
|
||||
"aliases": [
|
||||
"current",
|
||||
"stable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "2.13",
|
||||
"branchName": "2.13.x",
|
||||
"slug": "2.13",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.12",
|
||||
"branchName": "2.12.x",
|
||||
|
||||
14
.github/workflows/continuous-integration.yml
vendored
14
.github/workflows/continuous-integration.yml
vendored
@@ -43,6 +43,8 @@ jobs:
|
||||
- "default"
|
||||
extension:
|
||||
- "pdo_sqlite"
|
||||
proxy:
|
||||
- "common"
|
||||
include:
|
||||
- php-version: "8.0"
|
||||
dbal-version: "2.13"
|
||||
@@ -53,6 +55,10 @@ jobs:
|
||||
- php-version: "8.2"
|
||||
dbal-version: "default"
|
||||
extension: "sqlite3"
|
||||
- php-version: "8.1"
|
||||
dbal-version: "default"
|
||||
proxy: "lazy-ghost"
|
||||
extension: "pdo_sqlite"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
@@ -81,11 +87,13 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 0
|
||||
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
|
||||
|
||||
- name: "Run PHPUnit with Second Level Cache"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
@@ -108,13 +116,18 @@ jobs:
|
||||
- "3@dev"
|
||||
postgres-version:
|
||||
- "15"
|
||||
extension:
|
||||
- pdo_pgsql
|
||||
- pgsql
|
||||
include:
|
||||
- php-version: "8.0"
|
||||
dbal-version: "2.13"
|
||||
postgres-version: "14"
|
||||
extension: pdo_pgsql
|
||||
- php-version: "8.2"
|
||||
dbal-version: "default"
|
||||
postgres-version: "9.6"
|
||||
extension: pdo_pgsql
|
||||
|
||||
services:
|
||||
postgres:
|
||||
@@ -138,6 +151,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
extensions: "pgsql pdo_pgsql"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
|
||||
6
.github/workflows/static-analysis.yml
vendored
6
.github/workflows/static-analysis.yml
vendored
@@ -55,8 +55,7 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Require specific persistence version"
|
||||
run: "composer require doctrine/persistence ^${{ matrix.persistence-version }} --no-update"
|
||||
if: "${{ matrix.persistence-version != 'default' }}"
|
||||
run: "composer require doctrine/persistence ^$([ ${{ matrix.persistence-version }} = default ] && echo '3.1' || echo ${{ matrix.persistence-version }}) --no-update"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
@@ -92,6 +91,9 @@ jobs:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
|
||||
- name: "Require specific persistence version"
|
||||
run: "composer require doctrine/persistence ^3.1 --no-update"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,5 +15,6 @@ vendor/
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
composer.lock
|
||||
.phpunit.cache
|
||||
.phpunit.result.cache
|
||||
/*.phpunit.xml
|
||||
|
||||
101
UPGRADE.md
101
UPGRADE.md
@@ -1,5 +1,84 @@
|
||||
# Upgrade to 2.14
|
||||
|
||||
## Deprecated `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byName($field)` method.
|
||||
|
||||
Use `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byFullyQualifiedName($className, $field)` instead.
|
||||
|
||||
## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator`
|
||||
|
||||
The following public constants have been deprecated:
|
||||
|
||||
* `CommitOrderCalculator::NOT_VISITED`
|
||||
* `CommitOrderCalculator::IN_PROGRESS`
|
||||
* `CommitOrderCalculator::VISITED`
|
||||
|
||||
These constants were used for internal purposes. Relying on them is discouraged.
|
||||
|
||||
## Deprecated `Doctrine\ORM\Query\AST\InExpression`
|
||||
|
||||
The AST parser will create a `InListExpression` or a `InSubselectExpression` when
|
||||
encountering an `IN ()` DQL expression instead of a generic `InExpression`.
|
||||
|
||||
As a consequence, `SqlWalker::walkInExpression()` has been deprecated in favor of
|
||||
`SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`.
|
||||
|
||||
## Deprecated constructing a `CacheKey` without `$hash`
|
||||
|
||||
The `Doctrine\ORM\Cache\CacheKey` class has an explicit constructor now with
|
||||
an optional parameter `$hash`. That parameter will become mandatory in 3.0.
|
||||
|
||||
## Deprecated `AttributeDriver::$entityAnnotationClasses`
|
||||
|
||||
If you need to change the behavior of `AttributeDriver::isTransient()`,
|
||||
override that method instead.
|
||||
|
||||
## Deprecated incomplete schema updates
|
||||
|
||||
Using `orm:schema-tool:update` without passing the `--complete` flag is
|
||||
deprecated. Use schema asset filtering if you need to preserve assets not
|
||||
managed by DBAL.
|
||||
|
||||
Likewise, calling `SchemaTool::updateSchema()` or
|
||||
`SchemaTool::getUpdateSchemaSql()` with a second argument is deprecated.
|
||||
|
||||
## Deprecated annotation mapping driver.
|
||||
|
||||
Please switch to one of the other mapping drivers. Native attributes which PHP
|
||||
supports since version 8.0 are probably your best option.
|
||||
|
||||
As a consequence, the following methods are deprecated:
|
||||
- `ORMSetup::createAnnotationMetadataConfiguration`
|
||||
- `ORMSetup::createDefaultAnnotationDriver`
|
||||
|
||||
The marker interface `Doctrine\ORM\Mapping\Annotation` is deprecated as well.
|
||||
All annotation/attribute classes implement
|
||||
`Doctrine\ORM\Mapping\MappingAttribute` now.
|
||||
|
||||
## Deprecated `Doctrine\ORM\Proxy\Proxy` interface.
|
||||
|
||||
Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized.
|
||||
|
||||
## Deprecated `Doctrine\ORM\Event\LifecycleEventArgs` class.
|
||||
|
||||
It will be removed in 3.0. Use one of the dedicated event classes instead:
|
||||
|
||||
* `Doctrine\ORM\Event\PrePersistEventArgs`
|
||||
* `Doctrine\ORM\Event\PreUpdateEventArgs`
|
||||
* `Doctrine\ORM\Event\PreRemoveEventArgs`
|
||||
* `Doctrine\ORM\Event\PostPersistEventArgs`
|
||||
* `Doctrine\ORM\Event\PostUpdateEventArgs`
|
||||
* `Doctrine\ORM\Event\PostRemoveEventArgs`
|
||||
* `Doctrine\ORM\Event\PostLoadEventArgs`
|
||||
|
||||
# Upgrade to 2.13
|
||||
|
||||
## Deprecated `EntityManager::create()`
|
||||
|
||||
The constructor of `EntityManager` is now public and should be used instead of the `create()` method.
|
||||
However, the constructor expects a `Connection` while `create()` accepted an array with connection parameters.
|
||||
You can pass that array to DBAL's `Doctrine\DBAL\DriverManager::getConnection()` method to bootstrap the
|
||||
connection.
|
||||
|
||||
## Deprecated `QueryBuilder` methods and constants.
|
||||
|
||||
1. The `QueryBuilder::getState()` method has been deprecated as the builder state is an internal concern.
|
||||
@@ -394,7 +473,7 @@ function foo(EntityManagerInterface $entityManager, callable $func) {
|
||||
if (method_exists($entityManager, 'wrapInTransaction')) {
|
||||
return $entityManager->wrapInTransaction($func);
|
||||
}
|
||||
|
||||
|
||||
return $entityManager->transactional($func);
|
||||
}
|
||||
```
|
||||
@@ -460,7 +539,7 @@ implementation. To work around this:
|
||||
* As a quick workaround, you can lock the doctrine/cache dependency to work around this: `composer require doctrine/cache ^1.11`.
|
||||
Note that this is only recommended as a bandaid fix, as future versions of ORM will no longer work with doctrine/cache
|
||||
1.11.
|
||||
|
||||
|
||||
## Deprecated: doctrine/cache for metadata caching
|
||||
|
||||
The `Doctrine\ORM\Configuration#setMetadataCacheImpl()` method is deprecated and should no longer be used. Please use
|
||||
@@ -485,12 +564,12 @@ Note that `toIterable()` yields results of the query, unlike `iterate()` which y
|
||||
|
||||
# Upgrade to 2.7
|
||||
|
||||
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
|
||||
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
|
||||
|
||||
Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for both enabling and disabling the cache
|
||||
(depending on passed flag) was split into two.
|
||||
(depending on passed flag) was split into two.
|
||||
|
||||
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
|
||||
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
|
||||
|
||||
To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to
|
||||
perform the pagination with join collections when max results isn't set in the query.
|
||||
@@ -509,7 +588,7 @@ In the last patch of the `v2.6.x` series, we fixed a bug that was not converting
|
||||
In order to not break BC we've introduced a way to enable the fixed behavior using a boolean constructor argument. This
|
||||
argument will be removed in 3.0 and the default behavior will be the fixed one.
|
||||
|
||||
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
|
||||
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
|
||||
|
||||
Method `Doctrine\ORM\AbstractQuery#useResultCache()` is deprecated because it is split into `enableResultCache()`
|
||||
and `disableResultCache()`. It will be removed in 3.0.
|
||||
@@ -539,7 +618,7 @@ These related classes have been deprecated:
|
||||
|
||||
* `Doctrine\ORM\Proxy\ProxyFactory`
|
||||
* `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead
|
||||
|
||||
|
||||
These methods have been deprecated:
|
||||
|
||||
* `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()`
|
||||
@@ -588,7 +667,7 @@ If your code relies on single entity flushing optimisations via
|
||||
|
||||
Said API was affected by multiple data integrity bugs due to the fact
|
||||
that change tracking was being restricted upon a subset of the managed
|
||||
entities. The ORM cannot support committing subsets of the managed
|
||||
entities. The ORM cannot support committing subsets of the managed
|
||||
entities while also guaranteeing data integrity, therefore this
|
||||
utility was removed.
|
||||
|
||||
@@ -689,8 +768,8 @@ either:
|
||||
- map those classes as `MappedSuperclass`
|
||||
|
||||
## Minor BC BREAK: ``EntityManagerInterface`` instead of ``EntityManager`` in type-hints
|
||||
|
||||
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
|
||||
|
||||
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
|
||||
an ``EntityManagerInterface`` instead.
|
||||
If you are extending any of the following classes, then you need to check following
|
||||
signatures:
|
||||
@@ -783,7 +862,7 @@ the `Doctrine\ORM\Repository\DefaultRepositoryFactory`.
|
||||
When executing DQL queries with new object expressions, instead of returning DTOs numerically indexes, it will now respect user provided aliases. Consider the following query:
|
||||
|
||||
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
|
||||
|
||||
|
||||
Previously, your result would be similar to this:
|
||||
|
||||
array(
|
||||
|
||||
40
ci/github/phpunit/pgsql.xml
Normal file
40
ci/github/phpunit/pgsql.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
colors="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTodoAnnotatedTests="true"
|
||||
failOnRisky="true"
|
||||
convertDeprecationsToExceptions="true"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<var name="db_driver" value="pgsql"/>
|
||||
<var name="db_host" value="localhost" />
|
||||
<var name="db_user" value="postgres" />
|
||||
<var name="db_password" value="postgres" />
|
||||
<var name="db_dbname" value="doctrine_tests" />
|
||||
|
||||
<!-- necessary change for some CLI/console output test assertions -->
|
||||
<env name="COLUMNS" value="120"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Doctrine DBAL Test Suite">
|
||||
<directory>../../../tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">../../../lib/Doctrine</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
</phpunit>
|
||||
@@ -24,34 +24,35 @@
|
||||
"composer-runtime-api": "^2",
|
||||
"ext-ctype": "*",
|
||||
"doctrine/cache": "^1.12.1 || ^2.1.1",
|
||||
"doctrine/collections": "^1.5",
|
||||
"doctrine/collections": "^1.5 || ^2.0",
|
||||
"doctrine/common": "^3.0.3",
|
||||
"doctrine/dbal": "^2.13.1 || ^3.2",
|
||||
"doctrine/deprecations": "^0.5.3 || ^1",
|
||||
"doctrine/event-manager": "^1.1",
|
||||
"doctrine/event-manager": "^1.2 || ^2",
|
||||
"doctrine/inflector": "^1.4 || ^2.0",
|
||||
"doctrine/instantiator": "^1.3",
|
||||
"doctrine/lexer": "^1.2.3",
|
||||
"doctrine/lexer": "^1.2.3 || ^2",
|
||||
"doctrine/persistence": "^2.4 || ^3",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/console": "^4.2 || ^5.0 || ^6.0",
|
||||
"symfony/polyfill-php72": "^1.23",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.13",
|
||||
"doctrine/annotations": "^1.13 || ^2",
|
||||
"doctrine/coding-standard": "^9.0.2 || ^11.0",
|
||||
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.9.4",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.10.6",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"squizlabs/php_codesniffer": "3.7.1",
|
||||
"squizlabs/php_codesniffer": "3.7.2",
|
||||
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
|
||||
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2",
|
||||
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
|
||||
"vimeo/psalm": "4.30.0 || 5.3.0"
|
||||
"vimeo/psalm": "4.30.0 || 5.9.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >= 2.0"
|
||||
"doctrine/annotations": "<1.13 || >= 3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "Provides support for XSD validation for XML mapping files",
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
Accessing private/protected properties/methods of the same class from different instance
|
||||
========================================================================================
|
||||
|
||||
.. sectionauthor:: Michael Olsavsky (olsavmic)
|
||||
|
||||
As explained in the :doc:`restrictions for entity classes in the manual <../reference/architecture>`,
|
||||
it is dangerous to access private/protected properties of different entity instance of the same class because of lazy loading.
|
||||
|
||||
The proxy instance that's injected instead of the real entity may not be initialized yet
|
||||
and therefore not contain expected data which may result in unexpected behavior.
|
||||
That's a limitation of current proxy implementation - only public methods automatically initialize proxies.
|
||||
|
||||
It is usually preferable to use a public interface to manipulate the object from outside the `$this`
|
||||
context but it may not be convenient in some cases. The following example shows how to do it safely.
|
||||
|
||||
Safely accessing private properties from different instance of the same class
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
To safely access private property of different instance of the same class, make sure to initialise
|
||||
the proxy before use manually as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Entity
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Entity")
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
*/
|
||||
private self $parent;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", nullable=false)
|
||||
*/
|
||||
private string $name;
|
||||
|
||||
// ...
|
||||
|
||||
public function doSomethingWithParent()
|
||||
{
|
||||
// Always initializing the proxy before use
|
||||
if ($this->parent instanceof Proxy) {
|
||||
$this->parent->__load();
|
||||
}
|
||||
|
||||
// Accessing the `$this->parent->name` property without loading the proxy first
|
||||
// may throw error in case the Proxy has not been initialized yet.
|
||||
$this->parent->name;
|
||||
}
|
||||
|
||||
public function doSomethingWithAnotherInstance(self $instance)
|
||||
{
|
||||
// Always initializing the proxy before use
|
||||
if ($instance instanceof Proxy) {
|
||||
$instance->__load();
|
||||
}
|
||||
|
||||
// Accessing the `$instance->name` property without loading the proxy first
|
||||
// may throw error in case the Proxy has not been initialized yet.
|
||||
$instance->name;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -196,7 +196,7 @@ Example usage
|
||||
<?php
|
||||
|
||||
// Bootstrapping stuff...
|
||||
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
|
||||
// $em = new \Doctrine\ORM\EntityManager($connection, $config);
|
||||
|
||||
// Setup custom mapping type
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
@@ -46,7 +46,7 @@ configuration:
|
||||
$config->addCustomNumericFunction($name, $class);
|
||||
$config->addCustomDatetimeFunction($name, $class);
|
||||
|
||||
$em = EntityManager::create($dbParams, $config);
|
||||
$em = new EntityManager($connection, $config);
|
||||
|
||||
The ``$name`` is the name the function will be referred to in the
|
||||
DQL query. ``$class`` is a string of a class-name which has to
|
||||
@@ -247,5 +247,3 @@ vendor sql functions and extend the DQL languages scope.
|
||||
Code for this Extension to DQL and other Doctrine Extensions can be
|
||||
found
|
||||
`in the GitHub DoctrineExtensions repository <https://github.com/beberlei/DoctrineExtensions>`_.
|
||||
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
Implementing Wakeup or Clone
|
||||
============================
|
||||
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
As explained in the :ref:`restrictions for entity classes in the manual
|
||||
<terminology_entities>`,
|
||||
it is usually not allowed for an entity to implement ``__wakeup``
|
||||
or ``__clone``, because Doctrine makes special use of them.
|
||||
However, it is quite easy to make use of these methods in a safe
|
||||
way by guarding the custom wakeup or clone code with an entity
|
||||
identity check, as demonstrated in the following sections.
|
||||
|
||||
Safely implementing __wakeup
|
||||
----------------------------
|
||||
|
||||
To safely implement ``__wakeup``, simply enclose your
|
||||
implementation code in an identity check as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyEntity
|
||||
{
|
||||
private $id; // This is the identifier of the entity.
|
||||
// ...
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
// If the entity has an identity, proceed as normal.
|
||||
if ($this->id) {
|
||||
// ... Your code here as normal ...
|
||||
}
|
||||
// otherwise do nothing, do NOT throw an exception!
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
Safely implementing __clone
|
||||
---------------------------
|
||||
|
||||
Safely implementing ``__clone`` is pretty much the same:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyEntity
|
||||
{
|
||||
private $id; // This is the identifier of the entity.
|
||||
// ...
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
// If the entity has an identity, proceed as normal.
|
||||
if ($this->id) {
|
||||
// ... Your code here as normal ...
|
||||
}
|
||||
// otherwise do nothing, do NOT throw an exception!
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
As you have seen, it is quite easy to safely make use of
|
||||
``__wakeup`` and ``__clone`` in your entities without adding any
|
||||
really Doctrine-specific or Doctrine-dependant code.
|
||||
|
||||
These implementations are possible and safe because when Doctrine
|
||||
invokes these methods, the entities never have an identity (yet).
|
||||
Furthermore, it is possibly a good idea to check for the identity
|
||||
in your code anyway, since it's rarely the case that you want to
|
||||
unserialize or clone an entity with no identity.
|
||||
|
||||
|
||||
@@ -127,7 +127,8 @@ the targetEntity resolution will occur reliably:
|
||||
// Add the ResolveTargetEntityListener
|
||||
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
|
||||
|
||||
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||
$connection = \Doctrine\DBAL\DriverManager::createConnection($connectionOptions, $config, $evm);
|
||||
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
|
||||
|
||||
Final Thoughts
|
||||
--------------
|
||||
@@ -136,5 +137,3 @@ With the ``ResolveTargetEntityListener``, we are able to decouple our
|
||||
bundles, keeping them usable by themselves, but still being able to
|
||||
define relationships between different objects. By using this method,
|
||||
I've found my bundles end up being easier to maintain independently.
|
||||
|
||||
|
||||
|
||||
@@ -81,6 +81,4 @@ before the prefix has been set.
|
||||
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
|
||||
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
|
||||
|
||||
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||
|
||||
|
||||
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
|
||||
|
||||
@@ -21,7 +21,7 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
#[Column(type='datetime')]
|
||||
#[Column(type: 'datetime')]
|
||||
private DateTime $updated;
|
||||
|
||||
public function setUpdated(): void
|
||||
|
||||
@@ -72,6 +72,7 @@ Advanced Topics
|
||||
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
|
||||
* :doc:`Filters <reference/filters>`
|
||||
* :doc:`NamingStrategy <reference/namingstrategy>`
|
||||
* :doc:`TypedFieldMapper <reference/typedfieldmapper>`
|
||||
* :doc:`Improving Performance <reference/improving-performance>`
|
||||
* :doc:`Caching <reference/caching>`
|
||||
* :doc:`Partial Objects <reference/partial-objects>`
|
||||
@@ -112,7 +113,6 @@ Cookbook
|
||||
* **Implementation**:
|
||||
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
|
||||
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
|
||||
:doc:`Using Wakeup Or Clone <cookbook/implementing-wakeup-or-clone>` |
|
||||
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
|
||||
:doc:`Validation <cookbook/validation-of-entities>` |
|
||||
:doc:`Entities in the Session <cookbook/entities-in-session>` |
|
||||
|
||||
@@ -41,12 +41,12 @@ steps of configuration.
|
||||
$config->setAutoGenerateProxyClasses(false);
|
||||
}
|
||||
|
||||
$connectionOptions = array(
|
||||
$connection = DriverManager::getConnection([
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => 'database.sqlite'
|
||||
);
|
||||
'path' => 'database.sqlite',
|
||||
], $config);
|
||||
|
||||
$em = EntityManager::create($connectionOptions, $config);
|
||||
$em = new EntityManager($connection, $config);
|
||||
|
||||
Doctrine and Caching
|
||||
--------------------
|
||||
@@ -114,11 +114,13 @@ classes.
|
||||
There are currently 5 available implementations:
|
||||
|
||||
|
||||
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\AttributeDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
|
||||
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver`` (deprecated and will
|
||||
be removed in ``doctrine/orm`` 3.0)
|
||||
- ``Doctrine\ORM\Mapping\Driver\YamlDriver`` (deprecated and will be
|
||||
removed in ``doctrine/orm`` 3.0)
|
||||
|
||||
Throughout the most part of this manual the AttributeDriver is
|
||||
used in the examples. For information on the usage of the
|
||||
@@ -214,7 +216,7 @@ option that controls this behavior is:
|
||||
|
||||
Possible values for ``$mode`` are:
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_NEVER``
|
||||
- ``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:
|
||||
@@ -230,17 +232,17 @@ 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\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_ALWAYS``
|
||||
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
|
||||
|
||||
Always generates a new proxy in every request and writes it to disk.
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
|
||||
- ``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\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_EVAL``
|
||||
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
|
||||
|
||||
Generate the proxy classes and evaluate them on the fly via eval(),
|
||||
avoiding writing the proxies to disk.
|
||||
@@ -274,15 +276,13 @@ proxy sets an exclusive file lock which can cause serious
|
||||
performance bottlenecks in systems with regular concurrent
|
||||
requests.
|
||||
|
||||
Connection Options
|
||||
------------------
|
||||
Connection
|
||||
----------
|
||||
|
||||
The ``$connectionOptions`` passed as the first argument to
|
||||
``EntityManager::create()`` has to be either an array or an
|
||||
instance of ``Doctrine\DBAL\Connection``. If an array is passed it
|
||||
is directly passed along to the DBAL Factory
|
||||
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
|
||||
configuration is explained in the
|
||||
The ``$connection`` passed as the first argument to he constructor of
|
||||
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
|
||||
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
|
||||
to create such a connection. The DBAL configuration is explained in the
|
||||
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
|
||||
|
||||
Proxy Objects
|
||||
@@ -325,8 +325,9 @@ identifier. You could simply do this:
|
||||
$cart->addItem($item);
|
||||
|
||||
Here, we added an Item to a Cart without loading the Item from the
|
||||
database. If you invoke any method on the Item instance, it would
|
||||
fully initialize its state transparently from the database. Here
|
||||
database. If you access any state that isn't yet available in the
|
||||
Item instance, the proxying mechanism would fully initialize the
|
||||
object's state transparently from the database. Here
|
||||
$item is actually an instance of the proxy class that was generated
|
||||
for the Item class but your code does not need to care. In fact it
|
||||
**should not care**. Proxy objects should be transparent to your
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
Annotations Reference
|
||||
=====================
|
||||
|
||||
.. warning::
|
||||
The annotation driver is deprecated and will be removed in version
|
||||
3.0. It is strongly recommended to switch to one of the other
|
||||
mapping drivers.
|
||||
|
||||
.. note::
|
||||
|
||||
To be able to use annotations, you will have to install an extra
|
||||
@@ -124,7 +129,7 @@ Optional attributes:
|
||||
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
|
||||
|
||||
- **insertable**: Boolean value to determine if the column should be
|
||||
included when inserting a new row into the underlying entities table.
|
||||
included when inserting a new row into the underlying entities table.
|
||||
If not specified, default value is true.
|
||||
|
||||
- **updatable**: Boolean value to determine if the column should be
|
||||
@@ -1382,4 +1387,3 @@ Example:
|
||||
* @Version
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
|
||||
@@ -74,32 +74,13 @@ Entities
|
||||
An entity is a lightweight, persistent domain object. An entity can
|
||||
be any regular PHP class observing the following restrictions:
|
||||
|
||||
|
||||
- An entity class must not be final or contain final methods.
|
||||
- All persistent properties/field of any entity class should
|
||||
always be private or protected, otherwise lazy-loading might not
|
||||
work as expected. In case you serialize entities (for example Session)
|
||||
properties should be protected (See Serialize section below).
|
||||
- An entity class must not implement ``__clone`` or
|
||||
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
- An entity class must not implement ``__wakeup`` or
|
||||
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
You can also consider implementing
|
||||
`Serializable <https://php.net/manual/en/class.serializable.php>`_,
|
||||
but be aware that it is deprecated since PHP 8.1. We do not recommend its usage.
|
||||
- PHP 7.4 introduces :doc:`the new magic method <https://php.net/manual/en/language.oop5.magic.php#object.unserialize>`
|
||||
``__unserialize``, which changes the execution priority between
|
||||
``__wakeup`` and itself when used. This can cause unexpected behaviour in
|
||||
an Entity.
|
||||
- An entity class must not be final nor read-only but
|
||||
it may contain final methods or read-only properties.
|
||||
- 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
|
||||
must not have a mapped field with the same name as an already
|
||||
mapped field that is inherited from A.
|
||||
- An entity cannot make use of func_get_args() to implement variable parameters.
|
||||
Generated proxies do not support this for performance reasons and your code might
|
||||
actually fail to work when violating this restriction.
|
||||
- Entity cannot access private/protected properties/methods of another entity of the same class or :doc:`do so safely <../cookbook/accessing-private-properties-of-the-same-class-from-different-instance>`.
|
||||
|
||||
Entities support inheritance, polymorphic associations, and
|
||||
polymorphic queries. Both abstract and concrete classes can be
|
||||
@@ -113,6 +94,25 @@ classes, and non-entity classes may extend entity classes.
|
||||
never calls entity constructors, thus you are free to use them as
|
||||
you wish and even have it require arguments of any type.
|
||||
|
||||
Mapped Superclasses
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A mapped superclass is an abstract or concrete class that provides
|
||||
persistent entity state and mapping information for its subclasses,
|
||||
but which is not itself an entity.
|
||||
|
||||
Mapped superclasses are explained in greater detail in the chapter
|
||||
on :doc:`inheritance mapping <reference/inheritance-mapping>`.
|
||||
|
||||
Transient Classes
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The term "transient class" appears in some places in the mapping
|
||||
drivers as well as the code dealing with metadata handling.
|
||||
|
||||
A transient class is a class that is neither an entity nor a mapped
|
||||
superclass. From the ORM's point of view, these classes can be
|
||||
completely ignored, and no class metadata is loaded for them at all.
|
||||
|
||||
Entity states
|
||||
~~~~~~~~~~~~~
|
||||
@@ -159,17 +159,13 @@ Serializing entities
|
||||
|
||||
Serializing entities can be problematic and is not really
|
||||
recommended, at least not as long as an entity instance still holds
|
||||
references to proxy objects or is still managed by an
|
||||
EntityManager. If you intend to serialize (and unserialize) entity
|
||||
instances that still hold references to proxy objects you may run
|
||||
into problems with private properties because of technical
|
||||
limitations. Proxy objects implement ``__sleep`` and it is not
|
||||
possible for ``__sleep`` to return names of private properties in
|
||||
parent classes. On the other hand it is not a solution for proxy
|
||||
objects to implement ``Serializable`` because Serializable does not
|
||||
work well with any potential cyclic object references (at least we
|
||||
did not find a way yet, if you did, please contact us). The
|
||||
``Serializable`` interface is also deprecated beginning with PHP 8.1.
|
||||
references to proxy objects or is still managed by an EntityManager.
|
||||
By default, serializing proxy objects does not initialize them. On
|
||||
unserialization, resulting objects are detached from the entity
|
||||
manager and cannot be initialiazed anymore. You can implement the
|
||||
``__serialize()`` method if you want to change that behavior, but
|
||||
then you need to ensure that you won't generate large serialized
|
||||
object graphs and take care of circular associations.
|
||||
|
||||
The EntityManager
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1386,6 +1386,14 @@ Is essentially the same as following:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
/** One Product has One Shipment. */
|
||||
#[OneToOne(targetEntity: Shipment::class)]
|
||||
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
@@ -1396,14 +1404,6 @@ Is essentially the same as following:
|
||||
*/
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
/** One Product has One Shipment. */
|
||||
#[OneToOne(targetEntity: Shipment::class)]
|
||||
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
|
||||
@@ -366,6 +366,8 @@ Optional parameters:
|
||||
|
||||
- **type**: By default this is string.
|
||||
- **length**: By default this is 255.
|
||||
- **columnDefinition**: By default this is null the definition according to the type will be used. This option allows to override it.
|
||||
- **enumType**: By default this is `null`. Allows to map discriminatorColumn value to PHP enum
|
||||
|
||||
.. _attrref_discriminatormap:
|
||||
|
||||
|
||||
@@ -45,9 +45,10 @@ Doctrine provides several different ways to specify object-relational
|
||||
mapping metadata:
|
||||
|
||||
- :doc:`Attributes <attributes-reference>`
|
||||
- :doc:`Docblock Annotations <annotations-reference>`
|
||||
- :doc:`XML <xml-mapping>`
|
||||
- :doc:`PHP code <php-mapping>`
|
||||
- :doc:`Docblock Annotations <annotations-reference>` (deprecated and
|
||||
will be removed in ``doctrine/orm`` 3.0)
|
||||
- :doc:`YAML <yaml-mapping>` (deprecated and will be removed in ``doctrine/orm`` 3.0.)
|
||||
|
||||
This manual will usually show mapping metadata via attributes, though
|
||||
@@ -288,6 +289,13 @@ These are the "automatic" mapping rules:
|
||||
As of version 2.11 Doctrine can also automatically map typed properties using a
|
||||
PHP 8.1 enum to set the right ``type`` and ``enumType``.
|
||||
|
||||
.. versionadded:: 2.14
|
||||
|
||||
Since version 2.14 you can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
|
||||
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
|
||||
|
||||
:doc:`Read more about TypedFieldMapper <typedfieldmapper>`.
|
||||
|
||||
.. _reference-mapping-types:
|
||||
|
||||
Doctrine Mapping Types
|
||||
@@ -526,8 +534,6 @@ the above example with ``allocationSize=100`` Doctrine ORM would only
|
||||
need to access the sequence once to generate the identifiers for
|
||||
100 new entities.
|
||||
|
||||
*The default allocationSize for a @SequenceGenerator is currently 10.*
|
||||
|
||||
.. caution::
|
||||
|
||||
The allocationSize is detected by SchemaTool and
|
||||
|
||||
@@ -19,11 +19,13 @@ especially what the strategies presented here provide help with.
|
||||
.. note::
|
||||
|
||||
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
|
||||
To avoid that you should disable it in the DBAL configuration:
|
||||
To avoid that you should remove the corresponding middleware.
|
||||
To remove all middlewares, you can use this line:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null);
|
||||
$em->getConnection()->getConfiguration()->setMiddlewares([]); // DBAL 3
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null); // DBAL 2
|
||||
|
||||
Bulk Inserts
|
||||
------------
|
||||
|
||||
@@ -41,22 +41,24 @@ access point to ORM functionality provided by Doctrine.
|
||||
// bootstrap.php
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
|
||||
$paths = array("/path/to/entity-files");
|
||||
$paths = ['/path/to/entity-files'];
|
||||
$isDevMode = false;
|
||||
|
||||
// the connection configuration
|
||||
$dbParams = array(
|
||||
$dbParams = [
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => 'root',
|
||||
'password' => '',
|
||||
'dbname' => 'foo',
|
||||
);
|
||||
];
|
||||
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -68,18 +70,20 @@ Or if you prefer XML:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$paths = array("/path/to/xml-mappings");
|
||||
$paths = ['/path/to/xml-mappings'];
|
||||
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
Or if you prefer YAML:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$paths = array("/path/to/yml-mappings");
|
||||
$paths = ['/path/to/yml-mappings'];
|
||||
$config = ORMSetup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
.. note::
|
||||
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
|
||||
|
||||
@@ -180,10 +180,10 @@ not need to lazy load the association with another query.
|
||||
|
||||
Doctrine allows you to walk all the associations between
|
||||
all the objects in your domain model. Objects that were not already
|
||||
loaded from the database are replaced with lazy load proxy
|
||||
instances. Non-loaded Collections are also replaced by lazy-load
|
||||
loaded from the database are replaced with lazy-loading proxy
|
||||
instances. Non-loaded Collections are also replaced by lazy-loading
|
||||
instances that fetch all the contained objects upon first access.
|
||||
However relying on the lazy-load mechanism leads to many small
|
||||
However relying on the lazy-loading mechanism leads to many small
|
||||
queries executed against the database, which can significantly
|
||||
affect the performance of your application. **Fetch Joins** are the
|
||||
solution to hydrate most or all of the entities that you need in a
|
||||
@@ -319,11 +319,11 @@ With Nested Conditions in WHERE Clause:
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
|
||||
$query->setParameters(array(
|
||||
$query->setParameters([
|
||||
'name' => 'Bob',
|
||||
'name2' => 'Alice',
|
||||
'id' => 321,
|
||||
));
|
||||
]);
|
||||
$users = $query->getResult(); // array of ForumUser objects
|
||||
|
||||
With COUNT DISTINCT:
|
||||
@@ -794,7 +794,7 @@ You can register custom DQL functions in your ORM Configuration:
|
||||
$config->addCustomNumericFunction($name, $class);
|
||||
$config->addCustomDatetimeFunction($name, $class);
|
||||
|
||||
$em = EntityManager::create($dbParams, $config);
|
||||
$em = new EntityManager($connection, $config);
|
||||
|
||||
The functions have to return either a string, numeric or datetime
|
||||
value depending on the registered function type. As an example we
|
||||
@@ -806,8 +806,8 @@ classes have to implement the base class :
|
||||
<?php
|
||||
namespace MyProject\Query\AST;
|
||||
|
||||
use \Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use \Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
|
||||
class MysqlFloor extends FunctionNode
|
||||
{
|
||||
@@ -1057,7 +1057,7 @@ the Query class. Here they are:
|
||||
|
||||
Instead of using these methods, you can alternatively use the
|
||||
general-purpose method
|
||||
``Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)``.
|
||||
``Query#execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``.
|
||||
Using this method you can directly supply the hydration mode as the
|
||||
second parameter via one of the Query constants. In fact, the
|
||||
methods mentioned earlier are just convenient shortcuts for the
|
||||
|
||||
@@ -144,20 +144,20 @@ Events Overview
|
||||
| Event | Dispatched by | Lifecycle | Passed |
|
||||
| | | Callback | Argument |
|
||||
+=================================================================+=======================+===========+=====================================+
|
||||
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `LifecycleEventArgs`_ |
|
||||
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
|
||||
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `LifecycleEventArgs`_ |
|
||||
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
|
||||
| | on *initial* persist | | |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
|
||||
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preUpdate<reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
|
||||
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `LifecycleEventArgs`_ |
|
||||
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
|
||||
| | metadata | | |
|
||||
@@ -214,7 +214,7 @@ specific to a particular entity class's lifecycle.
|
||||
|
||||
<?php
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\PrePersistEventArgs;
|
||||
|
||||
#[Entity]
|
||||
#[HasLifecycleCallbacks]
|
||||
@@ -226,7 +226,7 @@ specific to a particular entity class's lifecycle.
|
||||
public $value;
|
||||
|
||||
#[PrePersist]
|
||||
public function doStuffOnPrePersist(LifecycleEventArgs $eventArgs)
|
||||
public function doStuffOnPrePersist(PrePersistEventArgs $eventArgs)
|
||||
{
|
||||
$this->createdAt = date('Y-m-d H:i:s');
|
||||
}
|
||||
@@ -246,7 +246,7 @@ specific to a particular entity class's lifecycle.
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\PrePersistEventArgs;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
@@ -260,7 +260,7 @@ specific to a particular entity class's lifecycle.
|
||||
public $value;
|
||||
|
||||
/** @PrePersist */
|
||||
public function doStuffOnPrePersist(LifecycleEventArgs $eventArgs)
|
||||
public function doStuffOnPrePersist(PrePersistEventArgs $eventArgs)
|
||||
{
|
||||
$this->createdAt = date('Y-m-d H:i:s');
|
||||
}
|
||||
@@ -353,11 +353,11 @@ A lifecycle event listener looks like the following:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
class MyEventListener
|
||||
{
|
||||
public function preUpdate(LifecycleEventArgs $args)
|
||||
public function preUpdate(PreUpdateEventArgs $args)
|
||||
{
|
||||
$entity = $args->getObject();
|
||||
$entityManager = $args->getObjectManager();
|
||||
@@ -374,9 +374,9 @@ A lifecycle event subscriber may look like this:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\EventSubscriber;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
class MyEventSubscriber implements EventSubscriber
|
||||
{
|
||||
@@ -387,7 +387,7 @@ A lifecycle event subscriber may look like this:
|
||||
);
|
||||
}
|
||||
|
||||
public function postUpdate(LifecycleEventArgs $args)
|
||||
public function postUpdate(PostUpdateEventArgs $args)
|
||||
{
|
||||
$entity = $args->getObject();
|
||||
$entityManager = $args->getObjectManager();
|
||||
@@ -416,7 +416,7 @@ EventManager that is passed to the EntityManager factory:
|
||||
$eventManager->addEventListener([Events::preUpdate], new MyEventListener());
|
||||
$eventManager->addEventSubscriber(new MyEventSubscriber());
|
||||
|
||||
$entityManager = EntityManager::create($dbOpts, $config, $eventManager);
|
||||
$entityManager = new EntityManager($connection, $config, $eventManager);
|
||||
|
||||
You can also retrieve the event manager instance after the
|
||||
EntityManager was created:
|
||||
@@ -461,7 +461,7 @@ this association is marked as :ref:`cascade: persist<transitive-persistence>`. A
|
||||
during this operation is also persisted and ``prePersist`` called
|
||||
on it. This is called :ref:`persistence by reachability<persistence-by-reachability>`.
|
||||
|
||||
In both cases you get passed a ``LifecycleEventArgs`` instance
|
||||
In both cases you get passed a ``PrePersistEventArgs`` instance
|
||||
which has access to the entity and the entity manager.
|
||||
|
||||
This event is only triggered on *initial* persist of an entity
|
||||
@@ -830,69 +830,79 @@ you need to map the listener method using the event type mapping:
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
use Doctrine\ORM\Event\PostLoadEventArgs;
|
||||
use Doctrine\ORM\Event\PostPersistEventArgs;
|
||||
use Doctrine\ORM\Event\PostRemoveEventArgs;
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\PrePersistEventArgs;
|
||||
use Doctrine\ORM\Event\PreRemoveEventArgs;
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
class UserListener
|
||||
{
|
||||
#[PrePersist]
|
||||
public function prePersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function prePersistHandler(User $user, PrePersistEventArgs $event): void { // ... }
|
||||
|
||||
#[PostPersist]
|
||||
public function postPersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
|
||||
|
||||
#[PreUpdate]
|
||||
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
|
||||
|
||||
#[PostUpdate]
|
||||
public function postUpdateHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
|
||||
|
||||
#[PostRemove]
|
||||
public function postRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
|
||||
|
||||
#[PreRemove]
|
||||
public function preRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
|
||||
|
||||
#[PreFlush]
|
||||
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
|
||||
|
||||
#[PostLoad]
|
||||
public function postLoadHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
use Doctrine\ORM\Event\PostLoadEventArgs;
|
||||
use Doctrine\ORM\Event\PostPersistEventArgs;
|
||||
use Doctrine\ORM\Event\PostRemoveEventArgs;
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\PrePersistEventArgs;
|
||||
use Doctrine\ORM\Event\PreRemoveEventArgs;
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
class UserListener
|
||||
{
|
||||
/** @PrePersist */
|
||||
public function prePersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function prePersistHandler(User $user, PrePersistEventArgs $event): void { // ... }
|
||||
|
||||
/** @PostPersist */
|
||||
public function postPersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
|
||||
|
||||
/** @PreUpdate */
|
||||
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
|
||||
|
||||
/** @PostUpdate */
|
||||
public function postUpdateHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
|
||||
|
||||
/** @PostRemove */
|
||||
public function postRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
|
||||
|
||||
/** @PreRemove */
|
||||
public function preRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
|
||||
|
||||
/** @PreFlush */
|
||||
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
|
||||
|
||||
/** @PostLoad */
|
||||
public function postLoadHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -1006,7 +1016,7 @@ Implementing your own resolver:
|
||||
|
||||
// Configure the listener resolver only before instantiating the EntityManager
|
||||
$configurations->setEntityListenerResolver(new MyEntityListenerResolver);
|
||||
EntityManager::create(.., $configurations, ..);
|
||||
$entityManager = new EntityManager(.., $configurations, ..);
|
||||
|
||||
.. _reference-events-load-class-metadata:
|
||||
|
||||
@@ -1106,8 +1116,13 @@ and the EntityManager.
|
||||
}
|
||||
}
|
||||
|
||||
.. _LifecycleEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/LifecycleEventArgs.php
|
||||
.. _PrePersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PrePersistEventArgs.php
|
||||
.. _PreRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreRemoveEventArgs.php
|
||||
.. _PreUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php
|
||||
.. _PostPersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostPersistEventArgs.php
|
||||
.. _PostRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostRemoveEventArgs.php
|
||||
.. _PostUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostUpdateEventArgs.php
|
||||
.. _PostLoadEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostLoadEventArgs.php
|
||||
.. _PreFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreFlushEventArgs.php
|
||||
.. _PostFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostFlushEventArgs.php
|
||||
.. _OnFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/OnFlushEventArgs.php
|
||||
|
||||
@@ -38,7 +38,7 @@ upon insert:
|
||||
|
||||
private string $algorithm = "sha1";
|
||||
/** @var self::STATUS_* */
|
||||
private int $status = self:STATUS_DISABLED;
|
||||
private int $status = self::STATUS_DISABLED;
|
||||
}
|
||||
|
||||
.
|
||||
|
||||
@@ -28,10 +28,11 @@ table alias of the SQL table of the entity.
|
||||
In the case of joined or single table inheritance, you always get passed the ClassMetadata of the
|
||||
inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters.
|
||||
|
||||
Parameters for the query should be set on the filter object by
|
||||
``SQLFilter#setParameter()``. Only parameters set via this function can be used
|
||||
in filters. The ``SQLFilter#getParameter()`` function takes care of the
|
||||
proper quoting of parameters.
|
||||
For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
|
||||
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
|
||||
2. The filter must be deterministic. Don't change the values base on external inputs.
|
||||
|
||||
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
|
||||
@@ -45,8 +45,7 @@ in scenarios where data is loaded for read-only purposes.
|
||||
Read-Only Entities
|
||||
------------------
|
||||
|
||||
You can mark entities as read only (See metadata mapping
|
||||
references for details).
|
||||
You can mark entities as read only. For details, see :ref:`attrref_entity`
|
||||
|
||||
This means that the entity marked as read only is never considered for updates.
|
||||
During flush on the EntityManager these entities are skipped even if properties
|
||||
@@ -55,8 +54,6 @@ changed.
|
||||
Read-Only allows to persist new entities of a kind and remove existing ones,
|
||||
they are just not considered for updates.
|
||||
|
||||
See :ref:`annref_entity`
|
||||
|
||||
You can also explicitly mark individual entities read only directly on the
|
||||
UnitOfWork via a call to ``markReadOnly()``:
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
Inheritance Mapping
|
||||
===================
|
||||
|
||||
This chapter explains the available options for mapping class
|
||||
hierarchies.
|
||||
|
||||
Mapped Superclasses
|
||||
-------------------
|
||||
|
||||
@@ -14,6 +17,10 @@ Mapped superclasses, just as regular, non-mapped classes, can
|
||||
appear in the middle of an otherwise mapped inheritance hierarchy
|
||||
(through Single Table Inheritance or Class Table Inheritance).
|
||||
|
||||
No database table will be created for a mapped superclass itself,
|
||||
only for entity classes inheriting from it. Also, a mapped superclass
|
||||
need not have an ``#[Id]`` property.
|
||||
|
||||
.. note::
|
||||
|
||||
A mapped superclass cannot be an entity, it is not query-able and
|
||||
@@ -25,6 +32,25 @@ appear in the middle of an otherwise mapped inheritance hierarchy
|
||||
For further support of inheritance, the single or
|
||||
joined table inheritance features have to be used.
|
||||
|
||||
.. warning::
|
||||
|
||||
At least when using attributes or annotations to specify your mapping,
|
||||
it _seems_ as if you could inherit from a base class that is neither
|
||||
an entity nor a mapped superclass, but has properties with mapping configuration
|
||||
on them that would also be used in the inheriting class.
|
||||
|
||||
This, however, is due to how the corresponding mapping
|
||||
drivers work and what the PHP reflection API reports for inherited fields.
|
||||
|
||||
Such a configuration is explicitly not supported. To give just one example,
|
||||
it will break for ``private`` properties.
|
||||
|
||||
.. note::
|
||||
|
||||
You may be tempted to use traits to mix mapped fields or relationships
|
||||
into your entity classes to circumvent some of the limitations of
|
||||
mapped superclasses. Before doing that, please read the section on traits
|
||||
in the :doc:`Limitations and Known Issues <reference/limitations-and-known-issues>` chapter.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -77,21 +103,76 @@ like this (this is for SQLite):
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
|
||||
CREATE TABLE Employee (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, toothbrush_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
|
||||
|
||||
As you can see from this DDL snippet, there is only a single table
|
||||
for the entity subclass. All the mappings from the mapped
|
||||
superclass were inherited to the subclass as if they had been
|
||||
defined on that class directly.
|
||||
|
||||
Entity Inheritance
|
||||
------------------
|
||||
|
||||
As soon as one entity class inherits from another entity class, either
|
||||
directly, with a mapped superclass or other unmapped (also called
|
||||
"transient") classes in between, these entities form an inheritance
|
||||
hierarchy. The topmost entity class in this hierarchy is called the
|
||||
root entity, and the hierarchy includes all entities that are
|
||||
descendants of this root entity.
|
||||
|
||||
On the root entity class, ``#[InheritanceType]``,
|
||||
``#[DiscriminatorColumn]`` and ``#[DiscriminatorMap]`` must be specified.
|
||||
|
||||
``#[InheritanceType]`` specifies one of the two available inheritance
|
||||
mapping strategies that are explained in the following sections.
|
||||
|
||||
``#[DiscriminatorColumn]`` designates the so-called discriminator column.
|
||||
This is an extra column in the table that keeps information about which
|
||||
type from the hierarchy applies for a particular database row.
|
||||
|
||||
``#[DiscriminatorMap]`` declares the possible values for the discriminator
|
||||
column and maps them to class names in the hierarchy. This discriminator map
|
||||
has to declare all non-abstract entity classes that exist in that particular
|
||||
inheritance hierarchy. That includes the root as well as any intermediate
|
||||
entity classes, given they are not abstract.
|
||||
|
||||
The names of the classes in the discriminator map do not need to be fully
|
||||
qualified if the classes are contained in the same namespace as the entity
|
||||
class on which the discriminator map is applied.
|
||||
|
||||
If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map contains the
|
||||
lowercase short name of each class as key.
|
||||
|
||||
.. note::
|
||||
|
||||
Automatically generating the discriminator map is very expensive
|
||||
computation-wise. The mapping driver has to provide all classes
|
||||
for which mapping configuration exists, and those have to be
|
||||
loaded and checked against the current inheritance hierarchy
|
||||
to see if they are part of it. The resulting map, however, can be kept
|
||||
in the metadata cache.
|
||||
|
||||
Performance impact on to-one associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There is a general performance consideration when using entity inheritance:
|
||||
If the target-entity of a many-to-one or one-to-one association is part of
|
||||
an inheritance hierarchy, it is preferable for performance reasons that it
|
||||
be a leaf entity (ie. have no subclasses).
|
||||
|
||||
Otherwise, the ORM is currently unable to tell beforehand which entity class
|
||||
will have to be used, and so no appropriate proxy instance can be created.
|
||||
That means the referred-to entities will *always* be loaded eagerly, which
|
||||
might even propagate to relationships of these entities (in the case of
|
||||
self-referencing associations).
|
||||
|
||||
Single Table Inheritance
|
||||
------------------------
|
||||
|
||||
`Single Table Inheritance <https://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
|
||||
is an inheritance mapping strategy where all classes of a hierarchy
|
||||
are mapped to a single database table. In order to distinguish
|
||||
which row represents which type in the hierarchy a so-called
|
||||
discriminator column is used.
|
||||
is an inheritance mapping strategy where all classes of a hierarchy are
|
||||
mapped to a single database table.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -156,27 +237,9 @@ Example:
|
||||
MyProject\Model\Employee:
|
||||
type: entity
|
||||
|
||||
Things to note:
|
||||
|
||||
|
||||
- The ``#[InheritanceType]`` and ``#[DiscriminatorColumn]`` must be
|
||||
specified on the topmost class that is part of the mapped entity
|
||||
hierarchy.
|
||||
- The ``#[DiscriminatorMap]`` specifies which values of the
|
||||
discriminator column identify a row as being of a certain type. In
|
||||
the case above a value of "person" identifies a row as being of
|
||||
type ``Person`` and "employee" identifies a row as being of type
|
||||
``Employee``.
|
||||
- All entity classes that is part of the mapped entity hierarchy
|
||||
(including the topmost class) should be specified in the
|
||||
``#[DiscriminatorMap]``. In the case above Person class included.
|
||||
- The names of the classes in the discriminator map do not need to
|
||||
be fully qualified if the classes are contained in the same
|
||||
namespace as the entity class on which the discriminator map is
|
||||
applied.
|
||||
- If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map
|
||||
contains the lowercase short name of each class as key.
|
||||
In this example, the ``#[DiscriminatorMap]`` specifies that in the
|
||||
discriminator column, a value of "person" identifies a row as being of type
|
||||
``Person`` and employee" identifies a row as being of type ``Employee``.
|
||||
|
||||
Design-time considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -192,17 +255,10 @@ Performance impact
|
||||
|
||||
This strategy is very efficient for querying across all types in
|
||||
the hierarchy or for specific types. No table joins are required,
|
||||
only a WHERE clause listing the type identifiers. In particular,
|
||||
only a ``WHERE`` clause listing the type identifiers. In particular,
|
||||
relationships involving types that employ this mapping strategy are
|
||||
very performing.
|
||||
|
||||
There is a general performance consideration with Single Table
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is an STI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -210,7 +266,7 @@ For Single-Table-Inheritance to work in scenarios where you are
|
||||
using either a legacy database schema or a self-written database
|
||||
schema you have to make sure that all columns that are not in the
|
||||
root entity but in any of the different sub-entities has to allow
|
||||
null values. Columns that have NOT NULL constraints have to be on
|
||||
null values. Columns that have ``NOT NULL`` constraints have to be on
|
||||
the root entity of the single-table inheritance hierarchy.
|
||||
|
||||
Class Table Inheritance
|
||||
@@ -220,10 +276,11 @@ Class Table Inheritance
|
||||
is an inheritance mapping strategy where each class in a hierarchy
|
||||
is mapped to several tables: its own table and the tables of all
|
||||
parent classes. The table of a child class is linked to the table
|
||||
of a parent class through a foreign key constraint. Doctrine ORM
|
||||
implements this strategy through the use of a discriminator column
|
||||
in the topmost table of the hierarchy because this is the easiest
|
||||
way to achieve polymorphic queries with Class Table Inheritance.
|
||||
of a parent class through a foreign key constraint.
|
||||
|
||||
The discriminator column is placed in the topmost table of the hierarchy,
|
||||
because this is the easiest way to achieve polymorphic queries with Class
|
||||
Table Inheritance.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -247,24 +304,9 @@ Example:
|
||||
// ...
|
||||
}
|
||||
|
||||
Things to note:
|
||||
|
||||
|
||||
- The ``#[InheritanceType]``, ``#[DiscriminatorColumn]`` and
|
||||
``#[DiscriminatorMap]`` must be specified on the topmost class that is
|
||||
part of the mapped entity hierarchy.
|
||||
- The ``#[DiscriminatorMap]`` specifies which values of the
|
||||
discriminator column identify a row as being of which type. In the
|
||||
case above a value of "person" identifies a row as being of type
|
||||
``Person`` and "employee" identifies a row as being of type
|
||||
``Employee``.
|
||||
- The names of the classes in the discriminator map do not need to
|
||||
be fully qualified if the classes are contained in the same
|
||||
namespace as the entity class on which the discriminator map is
|
||||
applied.
|
||||
- If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map
|
||||
contains the lowercase short name of each class as key.
|
||||
As before, the ``#[DiscriminatorMap]`` specifies that in the
|
||||
discriminator column, a value of "person" identifies a row as being of type
|
||||
``Person`` and "employee" identifies a row as being of type ``Employee``.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -295,20 +337,13 @@ perform just about any query which can have a negative impact on
|
||||
performance, especially with large tables and/or large hierarchies.
|
||||
When partial objects are allowed, either globally or on the
|
||||
specific query, then querying for any type will not cause the
|
||||
tables of subtypes to be OUTER JOINed which can increase
|
||||
tables of subtypes to be ``OUTER JOIN``ed which can increase
|
||||
performance but the resulting partial objects will not fully load
|
||||
themselves on access of any subtype fields, so accessing fields of
|
||||
subtypes after such a query is not safe.
|
||||
|
||||
There is a general performance consideration with Class Table
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is a CTI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
There is also another important performance consideration that it is *NOT POSSIBLE*
|
||||
to query for the base entity without any LEFT JOINs to the sub-types.
|
||||
There is also another important performance consideration that it is *not possible*
|
||||
to query for the base entity without any ``LEFT JOIN``s to the sub-types.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -326,14 +361,16 @@ column and cascading on delete.
|
||||
Overrides
|
||||
---------
|
||||
|
||||
Used to override a mapping for an entity field or relationship. Can only be
|
||||
applied to an entity that extends a mapped superclass or uses a trait to
|
||||
override a relationship or field mapping defined by the mapped superclass or
|
||||
trait.
|
||||
Overrides can only be applied to entities that extend a mapped superclass or
|
||||
use traits. They are used to override a mapping for an entity field or
|
||||
relationship defined in that mapped superclass or trait.
|
||||
|
||||
It is not possible to override attributes or associations in entity to entity
|
||||
inheritance scenarios, because this can cause unforseen edge case behavior and
|
||||
increases complexity in ORM internal classes.
|
||||
It is not supported to use overrides in entity inheritance scenarios.
|
||||
|
||||
.. note::
|
||||
|
||||
When using traits, make sure not to miss the warnings given in the
|
||||
:doc:`Limitations and Known Issues<reference/limitations-and-known-issues>` chapter.
|
||||
|
||||
|
||||
Association Override
|
||||
@@ -538,10 +575,11 @@ Example:
|
||||
|
||||
Things to note:
|
||||
|
||||
- The "association override" specifies the overrides base on the property name.
|
||||
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
|
||||
- The association type *CANNOT* be changed.
|
||||
- The override could redefine the joinTables or joinColumns depending on the association type.
|
||||
- The "association override" specifies the overrides based on the property
|
||||
name.
|
||||
- This feature is available for all kind of associations (OneToOne, OneToMany, ManyToOne, ManyToMany).
|
||||
- The association type *cannot* be changed.
|
||||
- The override could redefine the ``joinTables`` or ``joinColumns`` depending on the association type.
|
||||
- The override could redefine ``inversedBy`` to reference more than one extended entity.
|
||||
- The override could redefine fetch to modify the fetch strategy of the extended entity.
|
||||
|
||||
@@ -714,8 +752,8 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
|
||||
Things to note:
|
||||
|
||||
- The "attribute override" specifies the overrides base on the property name.
|
||||
- The column type *CANNOT* be changed. If the column type is not equal you get a ``MappingException``
|
||||
- The "attribute override" specifies the overrides based on the property name.
|
||||
- The column type *cannot* be changed. If the column type is not equal, you get a ``MappingException``.
|
||||
- The override can redefine all the attributes except the type.
|
||||
|
||||
Query the Type
|
||||
|
||||
@@ -130,10 +130,51 @@ included in the core of Doctrine ORM. However there are already two
|
||||
extensions out there that offer support for Nested Set with
|
||||
ORM:
|
||||
|
||||
|
||||
- `Doctrine2 Hierarchical-Structural Behavior <https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
|
||||
- `Doctrine2 NestedSet <https://github.com/blt04/doctrine2-nestedset>`_
|
||||
|
||||
Using Traits in Entity Classes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The use of traits in entity or mapped superclasses, at least when they
|
||||
include mapping configuration or mapped fields, is currently not
|
||||
endorsed by the Doctrine project. The reasons for this are as follows.
|
||||
|
||||
Traits were added in PHP 5.4 more than 10 years ago, but at the same time
|
||||
more than two years after the initial Doctrine 2 release and the time where
|
||||
core components were designed.
|
||||
|
||||
In fact, this documentation mentions traits only in the context of
|
||||
:doc:`overriding field association mappings in subclasses <tutorials/override-field-association-mappings-in-subclasses>`.
|
||||
Coverage of traits in test cases is practically nonexistent.
|
||||
|
||||
Thus, you should at least be aware that when using traits in your entity and
|
||||
mapped superclasses, you will be in uncharted terrain.
|
||||
|
||||
.. warning::
|
||||
|
||||
There be dragons.
|
||||
|
||||
From a more technical point of view, traits basically work at the language level
|
||||
as if the code contained in them had been copied into the class where the trait
|
||||
is used, and even private fields are accessible by the using class. In addition to
|
||||
that, some precedence and conflict resolution rules apply.
|
||||
|
||||
When it comes to loading mapping configuration, the annotation and attribute drivers
|
||||
rely on PHP reflection to inspect class properties including their docblocks.
|
||||
As long as the results are consistent with what a solution _without_ traits would
|
||||
have produced, this is probably fine.
|
||||
|
||||
However, to mention known limitations, it is currently not possible to use "class"
|
||||
level `annotations <https://github.com/doctrine/orm/pull/1517>` or
|
||||
`attributes <https://github.com/doctrine/orm/issues/8868>` on traits, and attempts to
|
||||
improve parser support for traits as `here <https://github.com/doctrine/annotations/pull/102>`
|
||||
or `there <https://github.com/doctrine/annotations/pull/63>` have been abandoned
|
||||
due to complexity.
|
||||
|
||||
XML mapping configuration probably needs to completely re-configure or otherwise
|
||||
copy-and-paste configuration for fields used from traits.
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
@@ -177,27 +218,3 @@ MySQL with MyISAM tables
|
||||
Doctrine cannot provide atomic operations when calling ``EntityManager#flush()`` if one
|
||||
of the tables involved uses the storage engine MyISAM. You must use InnoDB or
|
||||
other storage engines that support transactions if you need integrity.
|
||||
|
||||
Entities, Proxies and Reflection
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using methods for Reflection on entities can be prone to error, when the entity
|
||||
is actually a proxy the following methods will not work correctly:
|
||||
|
||||
- ``new ReflectionClass``
|
||||
- ``new ReflectionObject``
|
||||
- ``get_class()``
|
||||
- ``get_parent_class()``
|
||||
|
||||
This is why ``Doctrine\Common\Util\ClassUtils`` class exists that has similar
|
||||
methods, which resolve the proxy problem beforehand.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
$bookProxy = $entityManager->getReference('Acme\Book');
|
||||
|
||||
$reflection = ClassUtils::newReflectionClass($bookProxy);
|
||||
$class = ClassUtils::getClass($bookProxy)¸
|
||||
|
||||
@@ -13,11 +13,16 @@ metadata:
|
||||
|
||||
|
||||
- **XML files** (XmlDriver)
|
||||
- **Class DocBlock Annotations** (AnnotationDriver)
|
||||
- **Attributes** (AttributeDriver)
|
||||
- **YAML files** (YamlDriver)
|
||||
- **PHP Code in files or static functions** (PhpDriver)
|
||||
|
||||
There are also two deprecated ways to do this:
|
||||
|
||||
- **Class DocBlock Annotations** (AnnotationDriver)
|
||||
- **YAML files** (YamlDriver)
|
||||
|
||||
They will be removed in 3.0, make sure to avoid them.
|
||||
|
||||
Something important to note about the above drivers is they are all
|
||||
an intermediate step to the same end result. The mapping
|
||||
information is populated to ``Doctrine\ORM\Mapping\ClassMetadata``
|
||||
@@ -40,8 +45,9 @@ an entity.
|
||||
|
||||
|
||||
If you want to use one of the included core metadata drivers you need to
|
||||
configure it. If you pick the annotation driver, you will additionally
|
||||
need to install ``doctrine/annotations``. All the drivers are in the
|
||||
configure it. If you pick the annotation driver despite it being
|
||||
deprecated, you will additionally need to install
|
||||
``doctrine/annotations``. All the drivers are in the
|
||||
``Doctrine\ORM\Mapping\Driver`` namespace:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -120,17 +126,17 @@ the ``FileDriver`` implementation for you to extend from:
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $_fileExtension = '.dcm.ext';
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadata $metadata)
|
||||
{
|
||||
$data = $this->_loadMappingFile($file);
|
||||
|
||||
|
||||
// populate ClassMetadata instance from $data
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -198,5 +204,3 @@ iterate over them:
|
||||
foreach ($class->fieldMappings as $fieldMapping) {
|
||||
echo $fieldMapping['fieldName'] . "\n";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ The API of the ClassMetadataBuilder has the following methods with a fluent inte
|
||||
- ``addNamedQuery($name, $dqlQuery)``
|
||||
- ``setJoinedTableInheritance()``
|
||||
- ``setSingleTableInheritance()``
|
||||
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
|
||||
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255, $columnDefinition = null, $enumType = null)``
|
||||
- ``addDiscriminatorMapClass($name, $class)``
|
||||
- ``setChangeTrackingPolicyDeferredExplicit()``
|
||||
- ``setChangeTrackingPolicyNotify()``
|
||||
|
||||
@@ -114,8 +114,8 @@ functionally equivalent to the previously shown code looks as follows:
|
||||
The difference between ``Connection#transactional($func)`` and
|
||||
``EntityManager#transactional($func)`` is that the latter
|
||||
abstraction flushes the ``EntityManager`` prior to transaction
|
||||
commit and rolls back the transaction when an
|
||||
exception occurs.
|
||||
commit and in case of an exception the ``EntityManager`` gets closed
|
||||
in addition to the transaction rollback.
|
||||
|
||||
.. _transactions-and-concurrency_exception-handling:
|
||||
|
||||
@@ -412,7 +412,7 @@ Doctrine ORM currently supports two pessimistic lock modes:
|
||||
locks other concurrent requests that attempt to update or lock rows
|
||||
in write mode.
|
||||
|
||||
You can use pessimistic locks in three different scenarios:
|
||||
You can use pessimistic locks in four different scenarios:
|
||||
|
||||
|
||||
1. Using
|
||||
@@ -424,6 +424,10 @@ You can use pessimistic locks in three different scenarios:
|
||||
or
|
||||
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
3. Using
|
||||
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
|
||||
or
|
||||
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
4. Using
|
||||
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
|
||||
or
|
||||
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
|
||||
179
docs/en/reference/typedfieldmapper.rst
Normal file
179
docs/en/reference/typedfieldmapper.rst
Normal file
@@ -0,0 +1,179 @@
|
||||
Implementing a TypedFieldMapper
|
||||
===============================
|
||||
|
||||
.. versionadded:: 2.14
|
||||
|
||||
You can specify custom typed field mapping between PHP type and DBAL type using ``Doctrine\ORM\Configuration``
|
||||
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$configuration->setTypedFieldMapper(new CustomTypedFieldMapper());
|
||||
|
||||
|
||||
DefaultTypedFieldMapper
|
||||
-----------------------
|
||||
|
||||
By default the ``Doctrine\ORM\Mapping\DefaultTypedFieldMapper`` is used, and you can pass an array of
|
||||
PHP type => DBAL type mappings into its constructor to override the default behavior or add new mappings.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use App\CustomIds\CustomIdObject;
|
||||
use App\DBAL\Type\CustomIdObjectType;
|
||||
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
|
||||
|
||||
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
|
||||
CustomIdObject::class => CustomIdObjectType::class,
|
||||
]));
|
||||
|
||||
Then, an entity using the ``CustomIdObject`` typed field will be correctly assigned its DBAL type
|
||||
(``CustomIdObjectType``) without the need of explicit declaration.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'cms_users_typed_with_custom_typed_field')]
|
||||
class UserTypedWithCustomTypedField
|
||||
{
|
||||
#[ORM\Column]
|
||||
public CustomIdObject $customId;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="cms_users_typed_with_custom_typed_field")
|
||||
*/
|
||||
class UserTypedWithCustomTypedField
|
||||
{
|
||||
/** @Column */
|
||||
public CustomIdObject $customId;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="UserTypedWithCustomTypedField">
|
||||
<field name="customId"/>
|
||||
<!-- -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
UserTypedWithCustomTypedField:
|
||||
type: entity
|
||||
fields:
|
||||
customId: ~
|
||||
|
||||
It is perfectly valid to override even the "automatic" mapping rules mentioned above:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use App\DBAL\Type\CustomIntType;
|
||||
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
|
||||
|
||||
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
|
||||
'int' => CustomIntType::class,
|
||||
]));
|
||||
|
||||
.. note::
|
||||
|
||||
If chained, once the first ``TypedFieldMapper`` assigns a type to a field, the ``DefaultTypedFieldMapper`` will
|
||||
ignore its mapping and not override it anymore (if it is later in the chain). See below for chaining type mappers.
|
||||
|
||||
|
||||
TypedFieldMapper interface
|
||||
-------------------------
|
||||
The interface ``Doctrine\ORM\Mapping\TypedFieldMapper`` allows you to implement your own
|
||||
typed field mapping logic. It consists of just one function
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Validates & completes the given field mapping based on typed property.
|
||||
*
|
||||
* @param array{fieldName: string, enumType?: string, type?: mixed} $mapping The field mapping to validate & complete.
|
||||
* @param \ReflectionProperty $field
|
||||
*
|
||||
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
|
||||
*/
|
||||
public function validateAndComplete(array $mapping, ReflectionProperty $field): array;
|
||||
|
||||
|
||||
ChainTypedFieldMapper
|
||||
---------------------
|
||||
|
||||
The class ``Doctrine\ORM\Mapping\ChainTypedFieldMapper`` allows you to chain multiple ``TypedFieldMapper`` instances.
|
||||
When being evaluated, the ``TypedFieldMapper::validateAndComplete`` is called in the order in which
|
||||
the instances were supplied to the ``ChainTypedFieldMapper`` constructor.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use App\DBAL\Type\CustomIntType;
|
||||
use Doctrine\ORM\Mapping\ChainTypedFieldMapper;
|
||||
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
|
||||
|
||||
$configuration->setTypedFieldMapper(
|
||||
new ChainTypedFieldMapper(
|
||||
new DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
|
||||
new CustomTypedFieldMapper()
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
Implementing a TypedFieldMapper
|
||||
-------------------------------
|
||||
|
||||
If you want to assign all ``BackedEnum`` fields to your custom ``BackedEnumDBALType`` or you want to use different
|
||||
DBAL types based on whether the entity field is nullable or not, you can achieve this by implementing your own
|
||||
typed field mapper.
|
||||
|
||||
You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMapper``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
final class CustomEnumTypedFieldMapper implements TypedFieldMapper
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
|
||||
{
|
||||
$type = $field->getType();
|
||||
|
||||
if (
|
||||
! isset($mapping['type'])
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
) {
|
||||
if (! $type->isBuiltin() && enum_exists($type->getName())) {
|
||||
$mapping['type'] = BackedEnumDBALType::class;
|
||||
}
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
Note that this case checks whether the mapping is already assigned, and if yes, it skips it. This is up to your
|
||||
implementation. You can make a "greedy" mapper which will always override the mapping with its own type, or one
|
||||
that behaves like the ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
|
||||
@@ -38,7 +38,7 @@ will still end up with the same reference:
|
||||
{
|
||||
$objectA = $this->entityManager->getReference('EntityName', 1);
|
||||
// check for proxyinterface
|
||||
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
|
||||
$this->assertInstanceOf('Doctrine\Persistence\Proxy', $objectA);
|
||||
|
||||
$objectB = $this->entityManager->find('EntityName', 1);
|
||||
|
||||
|
||||
@@ -162,28 +162,6 @@ your code. See the following code:
|
||||
echo "This will always be true!";
|
||||
}
|
||||
|
||||
A slice of the generated proxy classes code looks like the
|
||||
following piece of code. A real proxy class override ALL public
|
||||
methods along the lines of the ``getName()`` method shown below:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class UserProxy extends User implements Proxy
|
||||
{
|
||||
private function _load(): void
|
||||
{
|
||||
// lazy loading code
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
$this->_load();
|
||||
return parent::getName();
|
||||
}
|
||||
// .. other public methods of User
|
||||
}
|
||||
|
||||
.. warning::
|
||||
|
||||
Traversing the object graph for parts that are lazy-loaded will
|
||||
@@ -414,14 +392,6 @@ Example:
|
||||
// $entity now refers to the fully managed copy returned by the merge operation.
|
||||
// The EntityManager $em now manages the persistence of $entity as usual.
|
||||
|
||||
.. note::
|
||||
|
||||
When you want to serialize/unserialize entities you
|
||||
have to make all entity properties protected, never private. The
|
||||
reason for this is, if you serialize a class that was a proxy
|
||||
instance before, the private variables won't be serialized and a
|
||||
PHP Notice is thrown.
|
||||
|
||||
|
||||
The semantics of the merge operation, applied to an entity X, are
|
||||
as follows:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
YAML Mapping
|
||||
============
|
||||
|
||||
.. note::
|
||||
.. warning::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/implementing-wakeup-or-clone
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
|
||||
@@ -75,7 +75,6 @@ Cookbook
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/implementing-wakeup-or-clone
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
|
||||
@@ -32,9 +32,9 @@ and year of production as primary keys:
|
||||
class Car
|
||||
{
|
||||
public function __construct(
|
||||
#[Id, Column(type: 'string')]
|
||||
#[Id, Column]
|
||||
private string $name,
|
||||
#[Id, Column(type: 'integer')]
|
||||
#[Id, Column]
|
||||
private int $year,
|
||||
) {
|
||||
}
|
||||
@@ -82,27 +82,6 @@ and year of production as primary keys:
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private int|null $id = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
/** @Id @OneToOne(targetEntity="User") */
|
||||
private User|null $user = null;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -152,7 +131,7 @@ And for querying you can use arrays to both DQL and EntityRepositories:
|
||||
|
||||
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1";
|
||||
$audi = $em->createQuery($dql)
|
||||
->setParameter(1, array("name" => "Audi A8", "year" => 2010))
|
||||
->setParameter(1, ["name" => "Audi A8", "year" => 2010])
|
||||
->getSingleResult();
|
||||
|
||||
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
|
||||
@@ -190,7 +169,52 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
namespace Application\Model;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
#[Id, Column, GeneratedValue]
|
||||
private int|null $id = null;
|
||||
#[Column]
|
||||
private string $title;
|
||||
|
||||
/** @var ArrayCollection<string, ArticleAttribute> */
|
||||
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
|
||||
private Collection $attributes;
|
||||
|
||||
public function addAttribute(string $name, ArticleAttribute $value): void
|
||||
{
|
||||
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class ArticleAttribute
|
||||
{
|
||||
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
|
||||
private Article $article;
|
||||
|
||||
#[Id, Column]
|
||||
private string $attribute;
|
||||
|
||||
#[Column]
|
||||
private string $value;
|
||||
|
||||
public function __construct(string $name, string $value, Article $article)
|
||||
{
|
||||
$this->attribute = $name;
|
||||
$this->value = $value;
|
||||
$this->article = $article;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
namespace Application\Model;
|
||||
@@ -241,51 +265,6 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
namespace Application\Model;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
#[Column(type: 'string')]
|
||||
private string $title;
|
||||
|
||||
/** @var ArrayCollection<string, ArticleAttribute> */
|
||||
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
|
||||
private Collection $attributes;
|
||||
|
||||
public function addAttribute(string $name, ArticleAttribute $value): void
|
||||
{
|
||||
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class ArticleAttribute
|
||||
{
|
||||
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
|
||||
private Article $article;
|
||||
|
||||
#[Id, Column(type: 'string')]
|
||||
private string $attribute;
|
||||
|
||||
#[Column(type: 'string')]
|
||||
private string $value;
|
||||
|
||||
public function __construct(string $name, string $value, Article $article)
|
||||
{
|
||||
$this->attribute = $name;
|
||||
$this->value = $value;
|
||||
$this->article = $article;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
@@ -338,7 +317,7 @@ One good example for this is a user-address relationship:
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
#[Id, Column, GeneratedValue]
|
||||
private int|null $id = null;
|
||||
}
|
||||
|
||||
@@ -386,18 +365,18 @@ of products purchased and maybe even the current price.
|
||||
#[Entity]
|
||||
class Order
|
||||
{
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
#[Id, Column, GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
/** @var ArrayCollection<int, OrderItem> */
|
||||
#[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')]
|
||||
private Collection $items;
|
||||
|
||||
#[Column(type: 'boolean')]
|
||||
#[Column]
|
||||
private bool $paid = false;
|
||||
#[Column(type: 'boolean')]
|
||||
#[Column]
|
||||
private bool $shipped = false;
|
||||
#[Column(type: 'datetime')]
|
||||
#[Column]
|
||||
private DateTime $created;
|
||||
|
||||
public function __construct(
|
||||
@@ -412,16 +391,16 @@ of products purchased and maybe even the current price.
|
||||
#[Entity]
|
||||
class Product
|
||||
{
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
#[Id, Column, GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
#[Column(type: 'string')]
|
||||
#[Column]
|
||||
private string $name;
|
||||
|
||||
#[Column(type: 'decimal')]
|
||||
private float $currentPrice;
|
||||
#[Column]
|
||||
private int $currentPrice;
|
||||
|
||||
public function getCurrentPrice(): float
|
||||
public function getCurrentPrice(): int
|
||||
{
|
||||
return $this->currentPrice;
|
||||
}
|
||||
@@ -436,11 +415,11 @@ of products purchased and maybe even the current price.
|
||||
#[Id, ManyToOne(targetEntity: Product::class)]
|
||||
private Product|null $product = null;
|
||||
|
||||
#[Column(type: 'integer')]
|
||||
#[Column]
|
||||
private int $amount = 1;
|
||||
|
||||
#[Column(type: 'decimal')]
|
||||
private float $offeredPrice;
|
||||
#[Column]
|
||||
private int $offeredPrice;
|
||||
|
||||
public function __construct(Order $order, Product $product, int $amount = 1)
|
||||
{
|
||||
|
||||
@@ -43,14 +43,15 @@ What are Entities?
|
||||
|
||||
Entities are PHP Objects that can be identified over many requests
|
||||
by a unique identifier or primary key. These classes don't need to extend any
|
||||
abstract base class or interface. An entity class must not be final
|
||||
or contain final methods. Additionally it must not implement
|
||||
**clone** nor **wakeup**, unless it :doc:`does so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
abstract base class or interface.
|
||||
|
||||
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 must not be final nor read-only, although
|
||||
it can contain final methods or read-only properties.
|
||||
|
||||
An Example Model: Bug Tracker
|
||||
-----------------------------
|
||||
|
||||
@@ -136,6 +137,7 @@ step:
|
||||
|
||||
<?php
|
||||
// bootstrap.php
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
|
||||
@@ -160,14 +162,14 @@ step:
|
||||
// isDevMode: true,
|
||||
// );
|
||||
|
||||
// database configuration parameters
|
||||
$conn = array(
|
||||
// configuring the database connection
|
||||
$connection = DriverManager::getConnection([
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => __DIR__ . '/db.sqlite',
|
||||
);
|
||||
], $config);
|
||||
|
||||
// obtaining the entity manager
|
||||
$entityManager = EntityManager::create($conn, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
@@ -888,18 +890,6 @@ domain model to match the requirements:
|
||||
understand the changes that have happened to the collection that are
|
||||
noteworthy for persistence.
|
||||
|
||||
.. warning::
|
||||
|
||||
Lazy load proxies always contain an instance of
|
||||
Doctrine's EntityManager and all its dependencies. Therefore a
|
||||
``var_dump()`` will possibly dump a very large recursive structure
|
||||
which is impossible to render and read. You have to use
|
||||
``Doctrine\Common\Util\Debug::dump()`` to restrict the dumping to a
|
||||
human readable level. Additionally you should be aware that dumping
|
||||
the EntityManager to a Browser may take several minutes, and the
|
||||
``Debug::dump()`` method just ignores any occurrences of it in Proxy
|
||||
instances.
|
||||
|
||||
Because we only work with collections for the references we must be
|
||||
careful to implement a bidirectional reference in the domain model.
|
||||
The concept of owning or inverse side of a relation is central to
|
||||
@@ -1588,39 +1578,8 @@ The output of the engineer’s name is fetched from the database! What is happen
|
||||
|
||||
Since we only retrieved the bug by primary key both the engineer and reporter
|
||||
are not immediately loaded from the database but are replaced by LazyLoading
|
||||
proxies. These proxies will load behind the scenes, when the first method
|
||||
is called on them.
|
||||
|
||||
Sample code of this proxy generated code can be found in the specified Proxy
|
||||
Directory, it looks like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Proxies;
|
||||
|
||||
/**
|
||||
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
|
||||
**/
|
||||
class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy
|
||||
{
|
||||
// .. lazy load code here
|
||||
|
||||
public function addReportedBug($bug)
|
||||
{
|
||||
$this->_load();
|
||||
return parent::addReportedBug($bug);
|
||||
}
|
||||
|
||||
public function assignedToBug($bug)
|
||||
{
|
||||
$this->_load();
|
||||
return parent::assignedToBug($bug);
|
||||
}
|
||||
}
|
||||
|
||||
See how upon each method call the proxy is lazily loaded from the
|
||||
database?
|
||||
proxies. These proxies will load behind the scenes, when attempting to access
|
||||
any of their un-initialized state.
|
||||
|
||||
The call prints:
|
||||
|
||||
|
||||
@@ -337,6 +337,7 @@
|
||||
<xs:attribute name="field-name" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="length" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="column-definition" type="xs:string" />
|
||||
<xs:attribute name="enum-type" type="xs:string" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
@@ -1321,9 +1321,11 @@ abstract class AbstractQuery
|
||||
protected function getHydrationCacheId()
|
||||
{
|
||||
$parameters = [];
|
||||
$types = [];
|
||||
|
||||
foreach ($this->getParameters() as $parameter) {
|
||||
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
|
||||
$types[$parameter->getName()] = $parameter->getType();
|
||||
}
|
||||
|
||||
$sql = $this->getSQL();
|
||||
@@ -1335,7 +1337,7 @@ abstract class AbstractQuery
|
||||
ksort($hints);
|
||||
assert($queryCacheProfile !== null);
|
||||
|
||||
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
|
||||
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $types, $hints);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
/**
|
||||
* Defines entity / collection / query key to be stored in the cache region.
|
||||
* Allows multiple roles to be stored in the same cache region.
|
||||
@@ -17,4 +19,18 @@ abstract class CacheKey
|
||||
* @var string
|
||||
*/
|
||||
public $hash;
|
||||
|
||||
public function __construct(?string $hash = null)
|
||||
{
|
||||
if ($hash === null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10212',
|
||||
'Calling %s() without providing a value for the $hash parameter is deprecated.',
|
||||
__METHOD__
|
||||
);
|
||||
} else {
|
||||
$this->hash = $hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ class CollectionCacheKey extends CacheKey
|
||||
$this->ownerIdentifier = $ownerIdentifier;
|
||||
$this->entityClass = (string) $entityClass;
|
||||
$this->association = (string) $association;
|
||||
$this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association;
|
||||
|
||||
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Cache\Exception\FeatureNotImplemented;
|
||||
use Doctrine\ORM\Cache\Exception\NonCacheableEntity;
|
||||
@@ -17,6 +16,7 @@ use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
|
||||
@@ -42,6 +42,7 @@ class EntityCacheKey extends CacheKey
|
||||
|
||||
$this->identifier = $identifier;
|
||||
$this->entityClass = $entityClass;
|
||||
$this->hash = str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier));
|
||||
|
||||
parent::__construct(str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*
|
||||
* @param string $query
|
||||
* @param string[]|Criteria $criteria
|
||||
* @param string[] $orderBy
|
||||
* @param string[]|null $orderBy
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
*
|
||||
|
||||
@@ -41,9 +41,10 @@ class QueryCacheKey extends CacheKey
|
||||
int $cacheMode = Cache::MODE_NORMAL,
|
||||
?TimestampCacheKey $timestampKey = null
|
||||
) {
|
||||
$this->hash = $cacheId;
|
||||
$this->lifetime = $lifetime;
|
||||
$this->cacheMode = $cacheMode;
|
||||
$this->timestampKey = $timestampKey;
|
||||
|
||||
parent::__construct($cacheId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@ class TimestampCacheKey extends CacheKey
|
||||
/** @param string $space Result cache id */
|
||||
public function __construct($space)
|
||||
{
|
||||
$this->hash = (string) $space;
|
||||
parent::__construct((string) $space);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Doctrine\Common\Annotations\CachedReader;
|
||||
use Doctrine\Common\Annotations\SimpleAnnotationReader;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
@@ -13,7 +13,6 @@ use Doctrine\Common\Cache\Cache as CacheDriver;
|
||||
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
||||
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
|
||||
use Doctrine\Common\Persistence\PersistentObject;
|
||||
use Doctrine\Common\Proxy\AbstractProxyFactory;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\ORM\Cache\Exception\CacheException;
|
||||
@@ -36,6 +35,7 @@ use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
|
||||
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;
|
||||
@@ -44,14 +44,17 @@ use Doctrine\ORM\Repository\DefaultRepositoryFactory;
|
||||
use Doctrine\ORM\Repository\RepositoryFactory;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use Doctrine\Persistence\Reflection\RuntimeReflectionProperty;
|
||||
use LogicException;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\VarExporter\LazyGhostTrait;
|
||||
|
||||
use function class_exists;
|
||||
use function is_a;
|
||||
use function method_exists;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
use function trait_exists;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
@@ -92,18 +95,18 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the strategy for automatically generating proxy classes.
|
||||
*
|
||||
* @return int Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
|
||||
* @return int Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
|
||||
* @psalm-return AutogenerateMode
|
||||
*/
|
||||
public function getAutoGenerateProxyClasses()
|
||||
{
|
||||
return $this->_attributes['autoGenerateProxyClasses'] ?? AbstractProxyFactory::AUTOGENERATE_ALWAYS;
|
||||
return $this->_attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the strategy for automatically generating proxy classes.
|
||||
*
|
||||
* @param bool|int $autoGenerate Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
|
||||
* @param bool|int $autoGenerate Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
|
||||
* @psalm-param bool|AutogenerateMode $autoGenerate
|
||||
* True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
|
||||
*
|
||||
@@ -172,16 +175,21 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
);
|
||||
|
||||
if (! class_exists(AnnotationReader::class)) {
|
||||
throw new LogicException(sprintf(
|
||||
throw new LogicException(
|
||||
'The annotation metadata driver cannot be enabled because the "doctrine/annotations" library'
|
||||
. ' is not installed. Please run "composer require doctrine/annotations" or choose a different'
|
||||
. ' metadata driver.'
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
if ($useSimpleAnnotationReader) {
|
||||
if (! class_exists(SimpleAnnotationReader::class)) {
|
||||
throw new BadMethodCallException(
|
||||
'SimpleAnnotationReader has been removed in doctrine/annotations 2.'
|
||||
. ' Downgrade to version 1 or set $useSimpleAnnotationReader to false.'
|
||||
);
|
||||
}
|
||||
|
||||
// Register the ORM Annotations in the AnnotationRegistry
|
||||
$reader = new SimpleAnnotationReader();
|
||||
$reader->addNamespace('Doctrine\ORM\Mapping');
|
||||
@@ -189,7 +197,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
$reader = new AnnotationReader();
|
||||
}
|
||||
|
||||
if (class_exists(ArrayCache::class)) {
|
||||
if (class_exists(ArrayCache::class) && class_exists(CachedReader::class)) {
|
||||
$reader = new CachedReader($reader, new ArrayCache());
|
||||
}
|
||||
|
||||
@@ -219,7 +227,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
$alias
|
||||
);
|
||||
} else {
|
||||
NotSupported::createForPersistence3(sprintf(
|
||||
throw NotSupported::createForPersistence3(sprintf(
|
||||
'Using short namespace alias "%s" by calling %s',
|
||||
$alias,
|
||||
__METHOD__
|
||||
@@ -572,7 +580,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
throw QueryCacheUsesNonPersistentCache::fromDriver($queryCacheImpl);
|
||||
}
|
||||
|
||||
if ($this->getAutoGenerateProxyClasses() !== AbstractProxyFactory::AUTOGENERATE_NEVER) {
|
||||
if ($this->getAutoGenerateProxyClasses() !== ProxyFactory::AUTOGENERATE_NEVER) {
|
||||
throw ProxyClassesAlwaysRegenerating::create();
|
||||
}
|
||||
|
||||
@@ -717,7 +725,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* @param string $name
|
||||
*
|
||||
* @return string|callable|null
|
||||
* @psalm-return class-string|callable|null $name
|
||||
* @psalm-return class-string|callable|null
|
||||
*/
|
||||
public function getCustomDatetimeFunction($name)
|
||||
{
|
||||
@@ -746,6 +754,22 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a TypedFieldMapper for php typed fields to DBAL types auto-completion.
|
||||
*/
|
||||
public function setTypedFieldMapper(?TypedFieldMapper $typedFieldMapper): void
|
||||
{
|
||||
$this->_attributes['typedFieldMapper'] = $typedFieldMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a TypedFieldMapper for php typed fields to DBAL types auto-completion.
|
||||
*/
|
||||
public function getTypedFieldMapper(): ?TypedFieldMapper
|
||||
{
|
||||
return $this->_attributes['typedFieldMapper'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom hydrator modes in one pass.
|
||||
*
|
||||
@@ -1073,4 +1097,28 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
$this->_attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses;
|
||||
}
|
||||
|
||||
public function isLazyGhostObjectEnabled(): bool
|
||||
{
|
||||
return $this->_attributes['isLazyGhostObjectEnabled'] ?? false;
|
||||
}
|
||||
|
||||
public function setLazyGhostObjectEnabled(bool $flag): void
|
||||
{
|
||||
if ($flag && ! trait_exists(LazyGhostTrait::class)) {
|
||||
throw new LogicException(
|
||||
'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library'
|
||||
. ' version 6.2 or higher is not installed. Please run "composer require symfony/var-exporter:^6.2".'
|
||||
);
|
||||
}
|
||||
|
||||
if ($flag && ! class_exists(RuntimeReflectionProperty::class)) {
|
||||
throw new LogicException(
|
||||
'Lazy ghost objects cannot be enabled because the "doctrine/persistence" library'
|
||||
. ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".'
|
||||
);
|
||||
}
|
||||
|
||||
$this->_attributes['isLazyGhostObjectEnabled'] = $flag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Persistence\ObjectManagerDecorator;
|
||||
|
||||
use function func_get_arg;
|
||||
use function func_num_args;
|
||||
use function get_debug_type;
|
||||
use function method_exists;
|
||||
use function sprintf;
|
||||
@@ -211,6 +213,20 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
|
||||
$this->wrapped->flush($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function refresh($object)
|
||||
{
|
||||
$lockMode = null;
|
||||
|
||||
if (func_num_args() > 1) {
|
||||
$lockMode = func_get_arg(1);
|
||||
}
|
||||
|
||||
$this->wrapped->refresh($object, $lockMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -60,8 +60,8 @@ use function strpos;
|
||||
* $paths = ['/path/to/entity/mapping/files'];
|
||||
*
|
||||
* $config = ORMSetup::createAttributeMetadataConfiguration($paths);
|
||||
* $dbParams = ['driver' => 'pdo_sqlite', 'memory' => true];
|
||||
* $entityManager = EntityManager::create($dbParams, $config);
|
||||
* $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
|
||||
* $entityManager = new EntityManager($connection, $config);
|
||||
*
|
||||
* For more information see
|
||||
* {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/configuration.html}
|
||||
@@ -156,7 +156,7 @@ class EntityManager implements EntityManagerInterface
|
||||
* Creates a new EntityManager that operates on the given database connection
|
||||
* and uses the given Configuration and EventManager implementations.
|
||||
*/
|
||||
public function __construct(Connection $conn, Configuration $config)
|
||||
public function __construct(Connection $conn, Configuration $config, ?EventManager $eventManager = null)
|
||||
{
|
||||
if (! $config->getMetadataDriverImpl()) {
|
||||
throw MissingMappingDriverImplementation::create();
|
||||
@@ -164,7 +164,7 @@ class EntityManager implements EntityManagerInterface
|
||||
|
||||
$this->conn = $conn;
|
||||
$this->config = $config;
|
||||
$this->eventManager = $conn->getEventManager();
|
||||
$this->eventManager = $eventManager ?? $conn->getEventManager();
|
||||
|
||||
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
|
||||
|
||||
@@ -693,14 +693,16 @@ class EntityManager implements EntityManagerInterface
|
||||
* Refreshes the persistent state of an entity from the database,
|
||||
* overriding any local changes that have not yet been persisted.
|
||||
*
|
||||
* @param object $entity The entity to refresh.
|
||||
* @param object $entity The entity to refresh
|
||||
* @psalm-param LockMode::*|null $lockMode
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
* @throws TransactionRequiredException
|
||||
*/
|
||||
public function refresh($entity)
|
||||
public function refresh($entity, ?int $lockMode = null)
|
||||
{
|
||||
if (! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity);
|
||||
@@ -708,7 +710,7 @@ class EntityManager implements EntityManagerInterface
|
||||
|
||||
$this->errorIfClosed();
|
||||
|
||||
$this->unitOfWork->refresh($entity);
|
||||
$this->unitOfWork->refresh($entity, $lockMode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -812,7 +814,7 @@ class EntityManager implements EntityManagerInterface
|
||||
$entityName
|
||||
);
|
||||
} else {
|
||||
NotSupported::createForPersistence3(sprintf(
|
||||
throw NotSupported::createForPersistence3(sprintf(
|
||||
'Using short namespace alias "%s" when calling %s',
|
||||
$entityName,
|
||||
__METHOD__
|
||||
@@ -954,6 +956,8 @@ class EntityManager implements EntityManagerInterface
|
||||
/**
|
||||
* Factory method to create EntityManager instances.
|
||||
*
|
||||
* @deprecated Use {@see DriverManager::getConnection()} to bootstrap the connection and call the constructor.
|
||||
*
|
||||
* @param mixed[]|Connection $connection An array with the connection parameters or an existing Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager|null $eventManager The EventManager instance to use.
|
||||
@@ -966,6 +970,15 @@ class EntityManager implements EntityManagerInterface
|
||||
*/
|
||||
public static function create($connection, Configuration $config, ?EventManager $eventManager = null)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9961',
|
||||
'%s() is deprecated. To boostrap a DBAL connection, call %s::getConnection() instead. Use the constructor to create an instance of %s.',
|
||||
__METHOD__,
|
||||
DriverManager::class,
|
||||
self::class
|
||||
);
|
||||
|
||||
$connection = static::createConnection($connection, $config, $eventManager);
|
||||
|
||||
return new EntityManager($connection, $config);
|
||||
@@ -974,6 +987,8 @@ class EntityManager implements EntityManagerInterface
|
||||
/**
|
||||
* Factory method to create Connection instances.
|
||||
*
|
||||
* @deprecated Use {@see DriverManager::getConnection()} to bootstrap the connection.
|
||||
*
|
||||
* @param mixed[]|Connection $connection An array with the connection parameters or an existing Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager|null $eventManager The EventManager instance to use.
|
||||
@@ -986,6 +1001,14 @@ class EntityManager implements EntityManagerInterface
|
||||
*/
|
||||
protected static function createConnection($connection, Configuration $config, ?EventManager $eventManager = null)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9961',
|
||||
'%s() is deprecated, call %s::getConnection() instead.',
|
||||
__METHOD__,
|
||||
DriverManager::class
|
||||
);
|
||||
|
||||
if (is_array($connection)) {
|
||||
return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager());
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use Doctrine\Persistence\ObjectManager;
|
||||
*
|
||||
* @method Mapping\ClassMetadataFactory getMetadataFactory()
|
||||
* @method mixed wrapInTransaction(callable $func)
|
||||
* @method void refresh(object $object, ?int $lockMode = null)
|
||||
*/
|
||||
interface EntityManagerInterface extends ObjectManager
|
||||
{
|
||||
|
||||
@@ -12,6 +12,8 @@ use Doctrine\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs;
|
||||
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
|
||||
* of entities.
|
||||
*
|
||||
* @deprecated This class will be removed in ORM 3.0. Use one of the dedicated classes instead.
|
||||
*
|
||||
* @extends BaseLifecycleEventArgs<EntityManagerInterface>
|
||||
*/
|
||||
class LifecycleEventArgs extends BaseLifecycleEventArgs
|
||||
|
||||
9
lib/Doctrine/ORM/Event/PostLoadEventArgs.php
Normal file
9
lib/Doctrine/ORM/Event/PostLoadEventArgs.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
final class PostLoadEventArgs extends LifecycleEventArgs
|
||||
{
|
||||
}
|
||||
9
lib/Doctrine/ORM/Event/PostPersistEventArgs.php
Normal file
9
lib/Doctrine/ORM/Event/PostPersistEventArgs.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
final class PostPersistEventArgs extends LifecycleEventArgs
|
||||
{
|
||||
}
|
||||
9
lib/Doctrine/ORM/Event/PostRemoveEventArgs.php
Normal file
9
lib/Doctrine/ORM/Event/PostRemoveEventArgs.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
final class PostRemoveEventArgs extends LifecycleEventArgs
|
||||
{
|
||||
}
|
||||
9
lib/Doctrine/ORM/Event/PostUpdateEventArgs.php
Normal file
9
lib/Doctrine/ORM/Event/PostUpdateEventArgs.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
final class PostUpdateEventArgs extends LifecycleEventArgs
|
||||
{
|
||||
}
|
||||
9
lib/Doctrine/ORM/Event/PrePersistEventArgs.php
Normal file
9
lib/Doctrine/ORM/Event/PrePersistEventArgs.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
final class PrePersistEventArgs extends LifecycleEventArgs
|
||||
{
|
||||
}
|
||||
9
lib/Doctrine/ORM/Event/PreRemoveEventArgs.php
Normal file
9
lib/Doctrine/ORM/Event/PreRemoveEventArgs.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
final class PreRemoveEventArgs extends LifecycleEventArgs
|
||||
{
|
||||
}
|
||||
@@ -12,7 +12,7 @@ use function get_debug_type;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class that holds event arguments for a preInsert/preUpdate event.
|
||||
* Class that holds event arguments for a preUpdate event.
|
||||
*/
|
||||
class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
{
|
||||
|
||||
@@ -73,7 +73,7 @@ final class Events
|
||||
* has been applied to it.
|
||||
*
|
||||
* Note that the postLoad event occurs for an entity before any associations have been
|
||||
* initialized. Therefore it is not safe to access associations in a postLoad callback
|
||||
* initialized. Therefore, it is not safe to access associations in a postLoad callback
|
||||
* or event handler.
|
||||
*
|
||||
* This is an entity lifecycle event.
|
||||
|
||||
34
lib/Doctrine/ORM/Internal/CommitOrder/Edge.php
Normal file
34
lib/Doctrine/ORM/Internal/CommitOrder/Edge.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\CommitOrder;
|
||||
|
||||
/** @internal */
|
||||
final class Edge
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @readonly
|
||||
*/
|
||||
public $from;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @readonly
|
||||
*/
|
||||
public $to;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @readonly
|
||||
*/
|
||||
public $weight;
|
||||
|
||||
public function __construct(string $from, string $to, int $weight)
|
||||
{
|
||||
$this->from = $from;
|
||||
$this->to = $to;
|
||||
$this->weight = $weight;
|
||||
}
|
||||
}
|
||||
38
lib/Doctrine/ORM/Internal/CommitOrder/Vertex.php
Normal file
38
lib/Doctrine/ORM/Internal/CommitOrder/Vertex.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\CommitOrder;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/** @internal */
|
||||
final class Vertex
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @readonly
|
||||
*/
|
||||
public $hash;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @psalm-var VertexState::*
|
||||
*/
|
||||
public $state = VertexState::NOT_VISITED;
|
||||
|
||||
/**
|
||||
* @var ClassMetadata
|
||||
* @readonly
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/** @var array<string, Edge> */
|
||||
public $dependencyList = [];
|
||||
|
||||
public function __construct(string $hash, ClassMetadata $value)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
17
lib/Doctrine/ORM/Internal/CommitOrder/VertexState.php
Normal file
17
lib/Doctrine/ORM/Internal/CommitOrder/VertexState.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\CommitOrder;
|
||||
|
||||
/** @internal */
|
||||
final class VertexState
|
||||
{
|
||||
public const NOT_VISITED = 0;
|
||||
public const IN_PROGRESS = 1;
|
||||
public const VISITED = 2;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal;
|
||||
|
||||
use stdClass;
|
||||
use Doctrine\ORM\Internal\CommitOrder\Edge;
|
||||
use Doctrine\ORM\Internal\CommitOrder\Vertex;
|
||||
use Doctrine\ORM\Internal\CommitOrder\VertexState;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
use function array_reverse;
|
||||
|
||||
@@ -17,33 +20,28 @@ use function array_reverse;
|
||||
*/
|
||||
class CommitOrderCalculator
|
||||
{
|
||||
public const NOT_VISITED = 0;
|
||||
public const IN_PROGRESS = 1;
|
||||
public const VISITED = 2;
|
||||
/** @deprecated */
|
||||
public const NOT_VISITED = VertexState::NOT_VISITED;
|
||||
|
||||
/** @deprecated */
|
||||
public const IN_PROGRESS = VertexState::IN_PROGRESS;
|
||||
|
||||
/** @deprecated */
|
||||
public const VISITED = VertexState::VISITED;
|
||||
|
||||
/**
|
||||
* Matrix of nodes (aka. vertex).
|
||||
*
|
||||
* Keys are provided hashes and values are the node definition objects.
|
||||
*
|
||||
* The node state definition contains the following properties:
|
||||
*
|
||||
* - <b>state</b> (integer)
|
||||
* Whether the node is NOT_VISITED or IN_PROGRESS
|
||||
*
|
||||
* - <b>value</b> (object)
|
||||
* Actual node value
|
||||
*
|
||||
* - <b>dependencyList</b> (array<string>)
|
||||
* Map of node dependencies defined as hashes.
|
||||
*
|
||||
* @var array<stdClass>
|
||||
* @var array<string, Vertex>
|
||||
*/
|
||||
private $nodeList = [];
|
||||
|
||||
/**
|
||||
* Volatile variable holding calculated nodes during sorting process.
|
||||
*
|
||||
* @psalm-var list<object>
|
||||
* @psalm-var list<ClassMetadata>
|
||||
*/
|
||||
private $sortedNodeList = [];
|
||||
|
||||
@@ -62,21 +60,14 @@ class CommitOrderCalculator
|
||||
/**
|
||||
* Adds a new node (vertex) to the graph, assigning its hash and value.
|
||||
*
|
||||
* @param string $hash
|
||||
* @param object $node
|
||||
* @param string $hash
|
||||
* @param ClassMetadata $node
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addNode($hash, $node)
|
||||
{
|
||||
$vertex = new stdClass();
|
||||
|
||||
$vertex->hash = $hash;
|
||||
$vertex->state = self::NOT_VISITED;
|
||||
$vertex->value = $node;
|
||||
$vertex->dependencyList = [];
|
||||
|
||||
$this->nodeList[$hash] = $vertex;
|
||||
$this->nodeList[$hash] = new Vertex($hash, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,14 +81,8 @@ class CommitOrderCalculator
|
||||
*/
|
||||
public function addDependency($fromHash, $toHash, $weight)
|
||||
{
|
||||
$vertex = $this->nodeList[$fromHash];
|
||||
$edge = new stdClass();
|
||||
|
||||
$edge->from = $fromHash;
|
||||
$edge->to = $toHash;
|
||||
$edge->weight = $weight;
|
||||
|
||||
$vertex->dependencyList[$toHash] = $edge;
|
||||
$this->nodeList[$fromHash]->dependencyList[$toHash]
|
||||
= new Edge($fromHash, $toHash, $weight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,12 +91,12 @@ class CommitOrderCalculator
|
||||
*
|
||||
* {@internal Highly performance-sensitive method.}
|
||||
*
|
||||
* @psalm-return list<object>
|
||||
* @psalm-return list<ClassMetadata>
|
||||
*/
|
||||
public function sort()
|
||||
{
|
||||
foreach ($this->nodeList as $vertex) {
|
||||
if ($vertex->state !== self::NOT_VISITED) {
|
||||
if ($vertex->state !== VertexState::NOT_VISITED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -131,19 +116,19 @@ class CommitOrderCalculator
|
||||
*
|
||||
* {@internal Highly performance-sensitive method.}
|
||||
*/
|
||||
private function visit(stdClass $vertex): void
|
||||
private function visit(Vertex $vertex): void
|
||||
{
|
||||
$vertex->state = self::IN_PROGRESS;
|
||||
$vertex->state = VertexState::IN_PROGRESS;
|
||||
|
||||
foreach ($vertex->dependencyList as $edge) {
|
||||
$adjacentVertex = $this->nodeList[$edge->to];
|
||||
|
||||
switch ($adjacentVertex->state) {
|
||||
case self::VISITED:
|
||||
case VertexState::VISITED:
|
||||
// Do nothing, since node was already visited
|
||||
break;
|
||||
|
||||
case self::IN_PROGRESS:
|
||||
case VertexState::IN_PROGRESS:
|
||||
if (
|
||||
isset($adjacentVertex->dependencyList[$vertex->hash]) &&
|
||||
$adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight
|
||||
@@ -153,25 +138,25 @@ class CommitOrderCalculator
|
||||
foreach ($adjacentVertex->dependencyList as $adjacentEdge) {
|
||||
$adjacentEdgeVertex = $this->nodeList[$adjacentEdge->to];
|
||||
|
||||
if ($adjacentEdgeVertex->state === self::NOT_VISITED) {
|
||||
if ($adjacentEdgeVertex->state === VertexState::NOT_VISITED) {
|
||||
$this->visit($adjacentEdgeVertex);
|
||||
}
|
||||
}
|
||||
|
||||
$adjacentVertex->state = self::VISITED;
|
||||
$adjacentVertex->state = VertexState::VISITED;
|
||||
|
||||
$this->sortedNodeList[] = $adjacentVertex->value;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case self::NOT_VISITED:
|
||||
case VertexState::NOT_VISITED:
|
||||
$this->visit($adjacentVertex);
|
||||
}
|
||||
}
|
||||
|
||||
if ($vertex->state !== self::VISITED) {
|
||||
$vertex->state = self::VISITED;
|
||||
if ($vertex->state !== VertexState::VISITED) {
|
||||
$vertex->state = VertexState::VISITED;
|
||||
|
||||
$this->sortedNodeList[] = $vertex->value;
|
||||
}
|
||||
|
||||
@@ -617,6 +617,7 @@ abstract class AbstractHydrator
|
||||
'fieldName' => $fieldName,
|
||||
'type' => $type,
|
||||
'dqlAlias' => $dqlAlias,
|
||||
'enumType' => $this->_rsm->enumMappings[$key] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -697,7 +698,7 @@ abstract class AbstractHydrator
|
||||
*
|
||||
* @return BackedEnum|array<BackedEnum>
|
||||
*/
|
||||
private function buildEnum($value, string $enumType)
|
||||
final protected function buildEnum($value, string $enumType)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return array_map(static function ($value) use ($enumType): BackedEnum {
|
||||
|
||||
@@ -4,12 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\Hydration;
|
||||
|
||||
use BackedEnum;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
|
||||
use function array_fill_keys;
|
||||
use function array_keys;
|
||||
@@ -246,7 +247,12 @@ class ObjectHydrator extends AbstractHydrator
|
||||
}
|
||||
|
||||
$discrMap = $this->_metadataCache[$className]->discriminatorMap;
|
||||
$discriminatorValue = (string) $data[$discrColumn];
|
||||
$discriminatorValue = $data[$discrColumn];
|
||||
if ($discriminatorValue instanceof BackedEnum) {
|
||||
$discriminatorValue = $discriminatorValue->value;
|
||||
}
|
||||
|
||||
$discriminatorValue = (string) $discriminatorValue;
|
||||
|
||||
if (! isset($discrMap[$discriminatorValue])) {
|
||||
throw HydrationException::invalidDiscriminatorValue($discriminatorValue, array_keys($discrMap));
|
||||
|
||||
@@ -6,9 +6,11 @@ namespace Doctrine\ORM\Internal\Hydration;
|
||||
|
||||
use Doctrine\ORM\Internal\SQLResultCasing;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Query;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
use ValueError;
|
||||
|
||||
use function array_keys;
|
||||
use function array_search;
|
||||
@@ -140,6 +142,21 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
$value = $type->convertToPHPValue($value, $this->_platform);
|
||||
}
|
||||
|
||||
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
|
||||
$originalValue = $value;
|
||||
try {
|
||||
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
|
||||
} catch (ValueError $e) {
|
||||
throw MappingException::invalidEnumValue(
|
||||
$entityName,
|
||||
$cacheKeyInfo['fieldName'],
|
||||
(string) $originalValue,
|
||||
$cacheKeyInfo['enumType'],
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$fieldName = $cacheKeyInfo['fieldName'];
|
||||
|
||||
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
|
||||
|
||||
@@ -5,8 +5,8 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Internal;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\ListenersInvoker;
|
||||
use Doctrine\ORM\Event\PostLoadEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
@@ -67,7 +67,7 @@ final class HydrationCompleteHandler
|
||||
$class,
|
||||
Events::postLoad,
|
||||
$entity,
|
||||
new LifecycleEventArgs($entity, $this->em),
|
||||
new PostLoadEventArgs($entity, $this->em),
|
||||
$invoke
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/** @deprecated Use {@see MappingAttribute} instead. */
|
||||
interface Annotation
|
||||
{
|
||||
}
|
||||
|
||||
@@ -5,53 +5,60 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* This annotation is used to override association mapping of property for an entity relationship.
|
||||
* This attribute is used to override association mapping of property for an entity relationship.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class AssociationOverride implements Annotation
|
||||
final class AssociationOverride implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The name of the relationship property whose mapping is being overridden.
|
||||
*
|
||||
* @var string
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The join column that is being mapped to the persistent attribute.
|
||||
*
|
||||
* @var array<\Doctrine\ORM\Mapping\JoinColumn>|null
|
||||
* @var array<JoinColumn>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $joinColumns;
|
||||
|
||||
/**
|
||||
* The join column that is being mapped to the persistent attribute.
|
||||
*
|
||||
* @var array<\Doctrine\ORM\Mapping\JoinColumn>|null
|
||||
* @var array<JoinColumn>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $inverseJoinColumns;
|
||||
|
||||
/**
|
||||
* The join table that maps the relationship.
|
||||
*
|
||||
* @var \Doctrine\ORM\Mapping\JoinTable|null
|
||||
* @var JoinTable|null
|
||||
* @readonly
|
||||
*/
|
||||
public $joinTable;
|
||||
|
||||
/**
|
||||
* The name of the association-field on the inverse-side.
|
||||
*
|
||||
* @var ?string
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $inversedBy;
|
||||
|
||||
/**
|
||||
* The fetching strategy to use for the association.
|
||||
*
|
||||
* @var ?string
|
||||
* @var string|null
|
||||
* @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY'|null
|
||||
* @readonly
|
||||
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
|
||||
*/
|
||||
public $fetch;
|
||||
@@ -59,6 +66,7 @@ final class AssociationOverride implements Annotation
|
||||
/**
|
||||
* @param JoinColumn|array<JoinColumn> $joinColumns
|
||||
* @param JoinColumn|array<JoinColumn> $inverseJoinColumns
|
||||
* @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY'|null $fetch
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
|
||||
@@ -6,22 +6,24 @@ namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
use function array_values;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* This annotation is used to override association mappings of relationship properties.
|
||||
* This attribute is used to override association mappings of relationship properties.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class AssociationOverrides implements Annotation
|
||||
final class AssociationOverrides implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* Mapping overrides of relationship properties.
|
||||
*
|
||||
* @var array<AssociationOverride>
|
||||
* @var list<AssociationOverride>
|
||||
* @readonly
|
||||
*/
|
||||
public $overrides = [];
|
||||
|
||||
@@ -36,8 +38,8 @@ final class AssociationOverrides implements Annotation
|
||||
if (! ($override instanceof AssociationOverride)) {
|
||||
throw MappingException::invalidOverrideType('AssociationOverride', $override);
|
||||
}
|
||||
|
||||
$this->overrides[] = $override;
|
||||
}
|
||||
|
||||
$this->overrides = array_values($overrides);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,25 +5,27 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* This annotation is used to override the mapping of a entity property.
|
||||
* This attribute is used to override the mapping of a entity property.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class AttributeOverride implements Annotation
|
||||
final class AttributeOverride implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The name of the property whose mapping is being overridden.
|
||||
*
|
||||
* @var string
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The column definition.
|
||||
*
|
||||
* @var \Doctrine\ORM\Mapping\Column
|
||||
* @var Column
|
||||
* @readonly
|
||||
*/
|
||||
public $column;
|
||||
|
||||
|
||||
@@ -6,22 +6,24 @@ namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
use function array_values;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* This annotation is used to override the mapping of a entity property.
|
||||
* This attribute is used to override the mapping of a entity property.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class AttributeOverrides implements Annotation
|
||||
final class AttributeOverrides implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* One or more field or property mapping overrides.
|
||||
*
|
||||
* @var array<AttributeOverride>
|
||||
* @var list<AttributeOverride>
|
||||
* @readonly
|
||||
*/
|
||||
public $overrides = [];
|
||||
|
||||
@@ -36,8 +38,8 @@ final class AttributeOverrides implements Annotation
|
||||
if (! ($override instanceof AttributeOverride)) {
|
||||
throw MappingException::invalidOverrideType('AttributeOverride', $override);
|
||||
}
|
||||
|
||||
$this->overrides[] = $override;
|
||||
}
|
||||
|
||||
$this->overrides = array_values($overrides);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
use BackedEnum;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
@@ -218,16 +219,19 @@ class ClassMetadataBuilder
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param int $length
|
||||
* @psalm-param class-string<BackedEnum>|null $enumType
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDiscriminatorColumn($name, $type = 'string', $length = 255)
|
||||
public function setDiscriminatorColumn($name, $type = 'string', $length = 255, ?string $columnDefinition = null, ?string $enumType = null)
|
||||
{
|
||||
$this->cm->setDiscriminatorColumn(
|
||||
[
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'length' => $length,
|
||||
'columnDefinition' => $columnDefinition,
|
||||
'enumType' => $enumType,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -15,17 +15,21 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
* @Target({"CLASS","PROPERTY"})
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
|
||||
final class Cache implements Annotation
|
||||
final class Cache implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The concurrency strategy.
|
||||
*
|
||||
* @Enum({"READ_ONLY", "NONSTRICT_READ_WRITE", "READ_WRITE"})
|
||||
* @var string The concurrency strategy.
|
||||
* @var string
|
||||
* @psalm-var 'READ_ONLY'|'NONSTRICT_READ_WRITE'|'READ_WRITE'
|
||||
*/
|
||||
public $usage = 'READ_ONLY';
|
||||
|
||||
/** @var string|null Cache region name. */
|
||||
public $region;
|
||||
|
||||
/** @psalm-param 'READ_ONLY'|'NONSTRICT_READ_WRITE'|'READ_WRITE' $usage */
|
||||
public function __construct(string $usage = 'READ_ONLY', ?string $region = null)
|
||||
{
|
||||
$this->usage = $usage;
|
||||
|
||||
33
lib/Doctrine/ORM/Mapping/ChainTypedFieldMapper.php
Normal file
33
lib/Doctrine/ORM/Mapping/ChainTypedFieldMapper.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use ReflectionProperty;
|
||||
|
||||
final class ChainTypedFieldMapper implements TypedFieldMapper
|
||||
{
|
||||
/**
|
||||
* @readonly
|
||||
* @var TypedFieldMapper[] $typedFieldMappers
|
||||
*/
|
||||
private array $typedFieldMappers;
|
||||
|
||||
public function __construct(TypedFieldMapper ...$typedFieldMappers)
|
||||
{
|
||||
$this->typedFieldMappers = $typedFieldMappers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
|
||||
{
|
||||
foreach ($this->typedFieldMappers as $typedFieldMapper) {
|
||||
$mapping = $typedFieldMapper->validateAndComplete($mapping, $field);
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,19 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class ChangeTrackingPolicy implements Annotation
|
||||
final class ChangeTrackingPolicy implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The change tracking policy.
|
||||
*
|
||||
* @var string
|
||||
* @psalm-var 'DEFERRED_IMPLICIT'|'DEFERRED_EXPLICIT'|'NOTIFY'
|
||||
* @readonly
|
||||
* @Enum({"DEFERRED_IMPLICIT", "DEFERRED_EXPLICIT", "NOTIFY"})
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/** @psalm-param 'DEFERRED_IMPLICIT'|'DEFERRED_EXPLICIT'|'NOTIFY' $value */
|
||||
public function __construct(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
@@ -21,8 +21,8 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
* @param string $entityName The name of the entity class the new instance is used for.
|
||||
* @psalm-param class-string<T> $entityName
|
||||
*/
|
||||
public function __construct($entityName, ?NamingStrategy $namingStrategy = null)
|
||||
public function __construct($entityName, ?NamingStrategy $namingStrategy = null, ?TypedFieldMapper $typedFieldMapper = null)
|
||||
{
|
||||
parent::__construct($entityName, $namingStrategy);
|
||||
parent::__construct($entityName, $namingStrategy, $typedFieldMapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$class->setVersioned($parent->isVersioned);
|
||||
$class->setVersionField($parent->versionField);
|
||||
$class->setDiscriminatorMap($parent->discriminatorMap);
|
||||
$class->addSubClasses($parent->subClasses);
|
||||
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
|
||||
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
|
||||
|
||||
@@ -219,11 +220,16 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$this->addDefaultDiscriminatorMap($class);
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
|
||||
$eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
|
||||
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
|
||||
}
|
||||
|
||||
$this->findAbstractEntityClassesNotListedInDiscriminatorMap($class);
|
||||
|
||||
if ($class->changeTrackingPolicy === ClassMetadata::CHANGETRACKING_NOTIFY) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
@@ -293,7 +299,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
*/
|
||||
protected function newClassMetadataInstance($className)
|
||||
{
|
||||
return new ClassMetadata($className, $this->em->getConfiguration()->getNamingStrategy());
|
||||
return new ClassMetadata(
|
||||
$className,
|
||||
$this->em->getConfiguration()->getNamingStrategy(),
|
||||
$this->em->getConfiguration()->getTypedFieldMapper()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,6 +344,57 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$class->setDiscriminatorMap($map);
|
||||
}
|
||||
|
||||
private function findAbstractEntityClassesNotListedInDiscriminatorMap(ClassMetadata $rootEntityClass): void
|
||||
{
|
||||
// Only root classes in inheritance hierarchies need contain a discriminator map,
|
||||
// so skip for other classes.
|
||||
if (! $rootEntityClass->isRootEntity() || $rootEntityClass->isInheritanceTypeNone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$processedClasses = [$rootEntityClass->name => true];
|
||||
foreach ($rootEntityClass->subClasses as $knownSubClass) {
|
||||
$processedClasses[$knownSubClass] = true;
|
||||
}
|
||||
|
||||
foreach ($rootEntityClass->discriminatorMap as $declaredClassName) {
|
||||
// This fetches non-transient parent classes only
|
||||
$parentClasses = $this->getParentClasses($declaredClassName);
|
||||
|
||||
foreach ($parentClasses as $parentClass) {
|
||||
if (isset($processedClasses[$parentClass])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$processedClasses[$parentClass] = true;
|
||||
|
||||
// All non-abstract entity classes must be listed in the discriminator map, and
|
||||
// this will be validated/enforced at runtime (possibly at a later time, when the
|
||||
// subclass is loaded, but anyways). Also, subclasses is about entity classes only.
|
||||
// That means we can ignore non-abstract classes here. The (expensive) driver
|
||||
// check for mapped superclasses need only be run for abstract candidate classes.
|
||||
if (! (new ReflectionClass($parentClass))->isAbstract() || $this->peekIfIsMappedSuperclass($parentClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have found a non-transient, non-mapped-superclass = an entity class (possibly abstract, but that does not matter)
|
||||
$rootEntityClass->addSubClass($parentClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param class-string $className */
|
||||
private function peekIfIsMappedSuperclass(string $className): bool
|
||||
{
|
||||
$reflService = $this->getReflectionService();
|
||||
$class = $this->newClassMetadataInstance($className);
|
||||
$this->initializeReflection($class, $reflService);
|
||||
|
||||
$this->driver->loadMetadataForClass($className, $class);
|
||||
|
||||
return $class->isMappedSuperclass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the lower-case short name of a class.
|
||||
*
|
||||
@@ -380,15 +441,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void
|
||||
{
|
||||
foreach ($parentClass->associationMappings as $field => $mapping) {
|
||||
if ($parentClass->isMappedSuperclass) {
|
||||
if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) {
|
||||
throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
|
||||
}
|
||||
|
||||
$mapping['sourceEntity'] = $subClass->name;
|
||||
}
|
||||
|
||||
//$subclassMapping = $mapping;
|
||||
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
|
||||
$mapping['inherited'] = $parentClass->name;
|
||||
}
|
||||
@@ -397,6 +449,20 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$mapping['declared'] = $parentClass->name;
|
||||
}
|
||||
|
||||
// When the class inheriting the relation ($subClass) is the first entity class since the
|
||||
// relation has been defined in a mapped superclass (or in a chain
|
||||
// of mapped superclasses) above, then declare this current entity class as the source of
|
||||
// the relationship.
|
||||
// According to the definitions given in https://github.com/doctrine/orm/pull/10396/,
|
||||
// this is the case <=> ! isset($mapping['inherited']).
|
||||
if (! isset($mapping['inherited'])) {
|
||||
if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) {
|
||||
throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
|
||||
}
|
||||
|
||||
$mapping['sourceEntity'] = $subClass->name;
|
||||
}
|
||||
|
||||
$subClass->addInheritedAssociationMapping($mapping);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,8 @@ namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use BackedEnum;
|
||||
use BadMethodCallException;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\Instantiator\Instantiator;
|
||||
use Doctrine\Instantiator\InstantiatorInterface;
|
||||
@@ -23,7 +19,6 @@ use Doctrine\Persistence\Mapping\ReflectionService;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use ReflectionClass;
|
||||
use ReflectionEnum;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionProperty;
|
||||
use RuntimeException;
|
||||
@@ -87,7 +82,7 @@ use const PHP_VERSION_ID;
|
||||
* columnDefinition?: string,
|
||||
* precision?: int,
|
||||
* scale?: int,
|
||||
* unique?: string,
|
||||
* unique?: bool,
|
||||
* inherited?: class-string,
|
||||
* originalClass?: class-string,
|
||||
* originalField?: string,
|
||||
@@ -148,6 +143,7 @@ use const PHP_VERSION_ID;
|
||||
* type: string,
|
||||
* length?: int,
|
||||
* columnDefinition?: string|null,
|
||||
* enumType?: class-string<BackedEnum>|null,
|
||||
* }
|
||||
*/
|
||||
class ClassMetadataInfo implements ClassMetadata
|
||||
@@ -401,7 +397,27 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
public $parentClasses = [];
|
||||
|
||||
/**
|
||||
* READ-ONLY: The names of all subclasses (descendants).
|
||||
* READ-ONLY: For classes in inheritance mapping hierarchies, this field contains the names of all
|
||||
* <em>entity</em> subclasses of this class. These may also be abstract classes.
|
||||
*
|
||||
* This list is used, for example, to enumerate all necessary tables in JTI when querying for root
|
||||
* or subclass entities, or to gather all fields comprised in an entity inheritance tree.
|
||||
*
|
||||
* For classes that do not use STI/JTI, this list is empty.
|
||||
*
|
||||
* Implementation note:
|
||||
*
|
||||
* In PHP, there is no general way to discover all subclasses of a given class at runtime. For that
|
||||
* reason, the list of classes given in the discriminator map at the root entity is considered
|
||||
* authoritative. The discriminator map must contain all <em>concrete</em> classes that can
|
||||
* appear in the particular inheritance hierarchy tree. Since there can be no instances of abstract
|
||||
* entity classes, users are not required to list such classes with a discriminator value.
|
||||
*
|
||||
* The possibly remaining "gaps" for abstract entity classes are filled after the class metadata for the
|
||||
* root entity has been loaded.
|
||||
*
|
||||
* For subclasses of such root entities, the list can be reused/passed downwards, it only needs to
|
||||
* be filtered accordingly (only keep remaining subclasses)
|
||||
*
|
||||
* @psalm-var list<class-string>
|
||||
*/
|
||||
@@ -410,6 +426,22 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
/**
|
||||
* READ-ONLY: The names of all embedded classes based on properties.
|
||||
*
|
||||
* The value (definition) array may contain, among others, the following values:
|
||||
*
|
||||
* - <b>'inherited'</b> (string, optional)
|
||||
* This is set when this embedded-class field is inherited by this class from another (inheritance) parent
|
||||
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
|
||||
* mapping information for this field. (If there are transient classes in the
|
||||
* class hierarchy, these are ignored, so the class property may in fact come
|
||||
* from a class further up in the PHP class hierarchy.)
|
||||
* Fields initially declared in mapped superclasses are
|
||||
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
|
||||
*
|
||||
* - <b>'declared'</b> (string, optional)
|
||||
* This is set when the embedded-class field does not appear for the first time in this class, but is originally
|
||||
* declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
|
||||
* of the topmost non-transient class that contains mapping information for this field.
|
||||
*
|
||||
* @psalm-var array<string, mixed[]>
|
||||
*/
|
||||
public $embeddedClasses = [];
|
||||
@@ -524,9 +556,23 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* - <b>scale</b> (integer, optional, schema-only)
|
||||
* The scale of a decimal column. Only valid if the column type is decimal.
|
||||
*
|
||||
* - <b>'unique'</b> (string, optional, schema-only)
|
||||
* - <b>'unique'</b> (boolean, optional, schema-only)
|
||||
* Whether a unique constraint should be generated for the column.
|
||||
*
|
||||
* - <b>'inherited'</b> (string, optional)
|
||||
* This is set when the field is inherited by this class from another (inheritance) parent
|
||||
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
|
||||
* mapping information for this field. (If there are transient classes in the
|
||||
* class hierarchy, these are ignored, so the class property may in fact come
|
||||
* from a class further up in the PHP class hierarchy.)
|
||||
* Fields initially declared in mapped superclasses are
|
||||
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
|
||||
*
|
||||
* - <b>'declared'</b> (string, optional)
|
||||
* This is set when the field does not appear for the first time in this class, but is originally
|
||||
* declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
|
||||
* of the topmost non-transient class that contains mapping information for this field.
|
||||
*
|
||||
* @var mixed[]
|
||||
* @psalm-var array<string, FieldMapping>
|
||||
*/
|
||||
@@ -629,6 +675,11 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* - <b>fieldName</b> (string)
|
||||
* The name of the field in the entity the association is mapped to.
|
||||
*
|
||||
* - <b>sourceEntity</b> (string)
|
||||
* The class name of the source entity. In the case of to-many associations initially
|
||||
* present in mapped superclasses, the nearest <em>entity</em> subclasses will be
|
||||
* considered the respective source entities.
|
||||
*
|
||||
* - <b>targetEntity</b> (string)
|
||||
* The class name of the target entity. If it is fully-qualified it is used as is.
|
||||
* If it is a simple, unqualified class name the namespace is assumed to be the same
|
||||
@@ -665,6 +716,20 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* This field HAS to be either the primary key or a unique column. Otherwise the collection
|
||||
* does not contain all the entities that are actually related.
|
||||
*
|
||||
* - <b>'inherited'</b> (string, optional)
|
||||
* This is set when the association is inherited by this class from another (inheritance) parent
|
||||
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
|
||||
* this association. (If there are transient classes in the
|
||||
* class hierarchy, these are ignored, so the class property may in fact come
|
||||
* from a class further up in the PHP class hierarchy.)
|
||||
* To-many associations initially declared in mapped superclasses are
|
||||
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
|
||||
*
|
||||
* - <b>'declared'</b> (string, optional)
|
||||
* This is set when the association does not appear in the current class for the first time, but
|
||||
* is initially declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
|
||||
* of the topmost non-transient class that contains association information for this relationship.
|
||||
*
|
||||
* A join table definition has the following structure:
|
||||
* <pre>
|
||||
* array(
|
||||
@@ -808,6 +873,9 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
/** @var InstantiatorInterface|null */
|
||||
private $instantiator;
|
||||
|
||||
/** @var TypedFieldMapper $typedFieldMapper */
|
||||
private $typedFieldMapper;
|
||||
|
||||
/**
|
||||
* Initializes a new ClassMetadata instance that will hold the object-relational mapping
|
||||
* metadata of the class with the given name.
|
||||
@@ -815,12 +883,13 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* @param string $entityName The name of the entity class the new instance is used for.
|
||||
* @psalm-param class-string<T> $entityName
|
||||
*/
|
||||
public function __construct($entityName, ?NamingStrategy $namingStrategy = null)
|
||||
public function __construct($entityName, ?NamingStrategy $namingStrategy = null, ?TypedFieldMapper $typedFieldMapper = null)
|
||||
{
|
||||
$this->name = $entityName;
|
||||
$this->rootEntityName = $entityName;
|
||||
$this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy();
|
||||
$this->instantiator = new Instantiator();
|
||||
$this->name = $entityName;
|
||||
$this->rootEntityName = $entityName;
|
||||
$this->namingStrategy = $namingStrategy ?? new DefaultNamingStrategy();
|
||||
$this->instantiator = new Instantiator();
|
||||
$this->typedFieldMapper = $typedFieldMapper ?? new DefaultTypedFieldMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1284,7 +1353,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
/**
|
||||
* @param string $fieldName
|
||||
* @param array $cache
|
||||
* @psalm-param array{usage?: int, region?: string|null} $cache
|
||||
* @psalm-param array{usage?: int|null, region?: string|null} $cache
|
||||
*
|
||||
* @return int[]|string[]
|
||||
* @psalm-return array{usage: int, region: string|null}
|
||||
@@ -1582,56 +1651,15 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
/**
|
||||
* Validates & completes the given field mapping based on typed property.
|
||||
*
|
||||
* @param mixed[] $mapping The field mapping to validate & complete.
|
||||
* @param array{fieldName: string, type?: mixed} $mapping The field mapping to validate & complete.
|
||||
*
|
||||
* @return mixed[] The updated mapping.
|
||||
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
|
||||
*/
|
||||
private function validateAndCompleteTypedFieldMapping(array $mapping): array
|
||||
{
|
||||
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
|
||||
$field = $this->reflClass->getProperty($mapping['fieldName']);
|
||||
|
||||
if ($type) {
|
||||
if (
|
||||
! isset($mapping['type'])
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
) {
|
||||
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {
|
||||
$mapping['enumType'] = $type->getName();
|
||||
|
||||
$reflection = new ReflectionEnum($type->getName());
|
||||
$type = $reflection->getBackingType();
|
||||
|
||||
assert($type instanceof ReflectionNamedType);
|
||||
}
|
||||
|
||||
switch ($type->getName()) {
|
||||
case DateInterval::class:
|
||||
$mapping['type'] = Types::DATEINTERVAL;
|
||||
break;
|
||||
case DateTime::class:
|
||||
$mapping['type'] = Types::DATETIME_MUTABLE;
|
||||
break;
|
||||
case DateTimeImmutable::class:
|
||||
$mapping['type'] = Types::DATETIME_IMMUTABLE;
|
||||
break;
|
||||
case 'array':
|
||||
$mapping['type'] = Types::JSON;
|
||||
break;
|
||||
case 'bool':
|
||||
$mapping['type'] = Types::BOOLEAN;
|
||||
break;
|
||||
case 'float':
|
||||
$mapping['type'] = Types::FLOAT;
|
||||
break;
|
||||
case 'int':
|
||||
$mapping['type'] = Types::INTEGER;
|
||||
break;
|
||||
case 'string':
|
||||
$mapping['type'] = Types::STRING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$mapping = $this->typedFieldMapper->validateAndComplete($mapping, $field);
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
@@ -1639,7 +1667,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
/**
|
||||
* Validates & completes the basic mapping information based on typed property.
|
||||
*
|
||||
* @param mixed[] $mapping The mapping.
|
||||
* @param array{type: self::ONE_TO_ONE|self::MANY_TO_ONE|self::ONE_TO_MANY|self::MANY_TO_MANY, fieldName: string, targetEntity?: class-string} $mapping The mapping.
|
||||
*
|
||||
* @return mixed[] The updated mapping.
|
||||
*/
|
||||
@@ -1661,7 +1689,13 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
/**
|
||||
* Validates & completes the given field mapping.
|
||||
*
|
||||
* @psalm-param array<string, mixed> $mapping The field mapping to validate & complete.
|
||||
* @psalm-param array{
|
||||
* fieldName?: string,
|
||||
* columnName?: string,
|
||||
* id?: bool,
|
||||
* generated?: int,
|
||||
* enumType?: class-string,
|
||||
* } $mapping The field mapping to validate & complete.
|
||||
*
|
||||
* @return mixed[] The updated mapping.
|
||||
*
|
||||
@@ -1759,25 +1793,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* @psalm-param array<string, mixed> $mapping The mapping.
|
||||
*
|
||||
* @return mixed[] The updated mapping.
|
||||
* @psalm-return array{
|
||||
* mappedBy: mixed|null,
|
||||
* inversedBy: mixed|null,
|
||||
* isOwningSide: bool,
|
||||
* sourceEntity: class-string,
|
||||
* targetEntity: string,
|
||||
* fieldName: mixed,
|
||||
* fetch: mixed,
|
||||
* cascade: array<array-key,string>,
|
||||
* isCascadeRemove: bool,
|
||||
* isCascadePersist: bool,
|
||||
* isCascadeRefresh: bool,
|
||||
* isCascadeMerge: bool,
|
||||
* isCascadeDetach: bool,
|
||||
* type: int,
|
||||
* originalField: string,
|
||||
* originalClass: class-string,
|
||||
* ?orphanRemoval: bool
|
||||
* }
|
||||
* @psalm-return AssociationMapping
|
||||
*
|
||||
* @throws MappingException If something is wrong with the mapping.
|
||||
*/
|
||||
@@ -1905,10 +1921,8 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* Validates & completes a one-to-one association mapping.
|
||||
*
|
||||
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
|
||||
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
|
||||
*
|
||||
* @return mixed[] The validated & completed mapping.
|
||||
* @psalm-return array{isOwningSide: mixed, orphanRemoval: bool, isCascadeRemove: bool}
|
||||
* @psalm-return array{
|
||||
* mappedBy: mixed|null,
|
||||
* inversedBy: mixed|null,
|
||||
@@ -2063,7 +2077,6 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* Validates & completes a many-to-many association mapping.
|
||||
*
|
||||
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
|
||||
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
|
||||
*
|
||||
* @return mixed[] The validated & completed mapping.
|
||||
* @psalm-return array{
|
||||
@@ -3223,7 +3236,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* @see getDiscriminatorColumn()
|
||||
*
|
||||
* @param mixed[]|null $columnDef
|
||||
* @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null}|null $columnDef
|
||||
* @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null, enumType?: class-string<BackedEnum>|null}|null $columnDef
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
@@ -3311,6 +3324,21 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
|
||||
}
|
||||
|
||||
$this->addSubClass($className);
|
||||
}
|
||||
|
||||
/** @param array<class-string> $classes */
|
||||
public function addSubClasses(array $classes): void
|
||||
{
|
||||
foreach ($classes as $className) {
|
||||
$this->addSubClass($className);
|
||||
}
|
||||
}
|
||||
|
||||
public function addSubClass(string $className): void
|
||||
{
|
||||
// By ignoring classes that are not subclasses of the current class, we simplify inheriting
|
||||
// the subclass list from a parent class at the beginning of \Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata.
|
||||
if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses, true)) {
|
||||
$this->subClasses[] = $className;
|
||||
}
|
||||
|
||||
@@ -5,29 +5,40 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use BackedEnum;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @NamedArgumentConstructor
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class Column implements Annotation
|
||||
final class Column implements MappingAttribute
|
||||
{
|
||||
/** @var string|null */
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/** @var mixed */
|
||||
/**
|
||||
* @var mixed
|
||||
* @readonly
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/** @var int|null */
|
||||
/**
|
||||
* @var int|null
|
||||
* @readonly
|
||||
*/
|
||||
public $length;
|
||||
|
||||
/**
|
||||
* The precision for a decimal (exact numeric) column (Applies only for decimal column).
|
||||
*
|
||||
* @var int|null
|
||||
* @readonly
|
||||
*/
|
||||
public $precision = 0;
|
||||
|
||||
@@ -35,40 +46,63 @@ final class Column implements Annotation
|
||||
* The scale for a decimal (exact numeric) column (Applies only for decimal column).
|
||||
*
|
||||
* @var int|null
|
||||
* @readonly
|
||||
*/
|
||||
public $scale = 0;
|
||||
|
||||
/** @var bool */
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $unique = false;
|
||||
|
||||
/** @var bool */
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $nullable = false;
|
||||
|
||||
/** @var bool */
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $insertable = true;
|
||||
|
||||
/** @var bool */
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $updatable = true;
|
||||
|
||||
/** @var class-string<\BackedEnum>|null */
|
||||
/**
|
||||
* @var class-string<BackedEnum>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $enumType = null;
|
||||
|
||||
/** @var array<string,mixed> */
|
||||
/**
|
||||
* @var array<string,mixed>
|
||||
* @readonly
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/** @var string|null */
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $columnDefinition;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
* @psalm-var 'NEVER'|'INSERT'|'ALWAYS'|null
|
||||
* @Enum({"NEVER", "INSERT", "ALWAYS"})
|
||||
*/
|
||||
public $generated;
|
||||
|
||||
/**
|
||||
* @param class-string<\BackedEnum>|null $enumType
|
||||
* @param array<string,mixed> $options
|
||||
* @param class-string<BackedEnum>|null $enumType
|
||||
* @param array<string,mixed> $options
|
||||
* @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated
|
||||
*/
|
||||
public function __construct(
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Doctrine\ORM\Mapping;
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class ColumnResult implements Annotation
|
||||
final class ColumnResult implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The name of a column in the SELECT clause of a SQL query.
|
||||
|
||||
@@ -13,9 +13,12 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class CustomIdGenerator implements Annotation
|
||||
final class CustomIdGenerator implements MappingAttribute
|
||||
{
|
||||
/** @var string|null */
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $class;
|
||||
|
||||
public function __construct(?string $class = null)
|
||||
|
||||
72
lib/Doctrine/ORM/Mapping/DefaultTypedFieldMapper.php
Normal file
72
lib/Doctrine/ORM/Mapping/DefaultTypedFieldMapper.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use ReflectionEnum;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionProperty;
|
||||
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function enum_exists;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
/** @psalm-type ScalarName = 'array'|'bool'|'float'|'int'|'string' */
|
||||
final class DefaultTypedFieldMapper implements TypedFieldMapper
|
||||
{
|
||||
/** @var array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
|
||||
private $typedFieldMappings;
|
||||
|
||||
private const DEFAULT_TYPED_FIELD_MAPPINGS = [
|
||||
DateInterval::class => Types::DATEINTERVAL,
|
||||
DateTime::class => Types::DATETIME_MUTABLE,
|
||||
DateTimeImmutable::class => Types::DATETIME_IMMUTABLE,
|
||||
'array' => Types::JSON,
|
||||
'bool' => Types::BOOLEAN,
|
||||
'float' => Types::FLOAT,
|
||||
'int' => Types::INTEGER,
|
||||
'string' => Types::STRING,
|
||||
];
|
||||
|
||||
/** @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
|
||||
public function __construct(array $typedFieldMappings = [])
|
||||
{
|
||||
$this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
|
||||
{
|
||||
$type = $field->getType();
|
||||
|
||||
if (
|
||||
! isset($mapping['type'])
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
) {
|
||||
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {
|
||||
$mapping['enumType'] = $type->getName();
|
||||
|
||||
$reflection = new ReflectionEnum($type->getName());
|
||||
$type = $reflection->getBackingType();
|
||||
|
||||
assert($type instanceof ReflectionNamedType);
|
||||
}
|
||||
|
||||
if (isset($this->typedFieldMappings[$type->getName()])) {
|
||||
$mapping['type'] = $this->typedFieldMappings[$type->getName()];
|
||||
}
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
}
|
||||
@@ -13,36 +13,50 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class DiscriminatorColumn implements Annotation
|
||||
final class DiscriminatorColumn implements MappingAttribute
|
||||
{
|
||||
/** @var string|null */
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/** @var string|null */
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/** @var int|null */
|
||||
/**
|
||||
* @var int|null
|
||||
* @readonly
|
||||
*/
|
||||
public $length;
|
||||
|
||||
/**
|
||||
* Field name used in non-object hydration (array/scalar).
|
||||
*
|
||||
* @var mixed
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $fieldName;
|
||||
|
||||
/** @var string */
|
||||
public $columnDefinition;
|
||||
|
||||
/**
|
||||
* @var class-string<\BackedEnum>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $enumType = null;
|
||||
|
||||
/** @param class-string<\BackedEnum>|null $enumType */
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
?string $type = null,
|
||||
?int $length = null,
|
||||
?string $columnDefinition = null
|
||||
?string $columnDefinition = null,
|
||||
?string $enumType = null
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->length = $length;
|
||||
$this->columnDefinition = $columnDefinition;
|
||||
$this->enumType = $enumType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,12 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class DiscriminatorMap implements Annotation
|
||||
final class DiscriminatorMap implements MappingAttribute
|
||||
{
|
||||
/** @var array<int|string, string> */
|
||||
/**
|
||||
* @var array<int|string, string>
|
||||
* @readonly
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/** @param array<int|string, string> $value */
|
||||
|
||||
@@ -62,6 +62,11 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
*/
|
||||
public function __construct($reader, $paths = null)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/10098',
|
||||
'The annotation mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to the attribute or XML driver.'
|
||||
);
|
||||
$this->reader = $reader;
|
||||
|
||||
$this->addPaths((array) $paths);
|
||||
@@ -314,6 +319,7 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
'type' => $discrColumnAnnot->type ?: 'string',
|
||||
'length' => $discrColumnAnnot->length ?? 255,
|
||||
'columnDefinition' => $discrColumnAnnot->columnDefinition,
|
||||
'enumType' => $discrColumnAnnot->enumType,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
@@ -541,8 +547,9 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $joinColumns
|
||||
* @psalm-param array<string, mixed> $mapping
|
||||
* @param mixed[] $joinColumns
|
||||
* @param class-string $className
|
||||
* @param array<string, mixed> $mapping
|
||||
*/
|
||||
private function loadRelationShipMapping(
|
||||
ReflectionProperty $property,
|
||||
@@ -655,6 +662,8 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
/**
|
||||
* Attempts to resolve the fetch mode.
|
||||
*
|
||||
* @param class-string $className
|
||||
*
|
||||
* @psalm-return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
|
||||
*
|
||||
* @throws MappingException If the fetch mode is not valid.
|
||||
@@ -687,8 +696,8 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
/**
|
||||
* Parses the given method.
|
||||
*
|
||||
* @return callable[]
|
||||
* @psalm-return list<callable-array>
|
||||
* @return list<array{string, string}>
|
||||
* @psalm-return list<array{string, (Events::*)}>
|
||||
*/
|
||||
private function getMethodCallbacks(ReflectionMethod $method): array
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingAttribute;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
|
||||
@@ -30,15 +31,20 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
{
|
||||
use ColocatedMappingDriver;
|
||||
|
||||
/** @var array<string,int> */
|
||||
// @phpcs:ignore
|
||||
protected $entityAnnotationClasses = [
|
||||
private const ENTITY_ATTRIBUTE_CLASSES = [
|
||||
Mapping\Entity::class => 1,
|
||||
Mapping\MappedSuperclass::class => 2,
|
||||
];
|
||||
|
||||
/**
|
||||
* The annotation reader.
|
||||
* @deprecated override isTransient() instead of overriding this property
|
||||
*
|
||||
* @var array<class-string<MappingAttribute>, int>
|
||||
*/
|
||||
protected $entityAnnotationClasses = self::ENTITY_ATTRIBUTE_CLASSES;
|
||||
|
||||
/**
|
||||
* The attribute reader.
|
||||
*
|
||||
* @internal this property will be private in 3.0
|
||||
*
|
||||
@@ -58,6 +64,15 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
|
||||
$this->reader = new AttributeReader();
|
||||
$this->addPaths($paths);
|
||||
|
||||
if ($this->entityAnnotationClasses !== self::ENTITY_ATTRIBUTE_CLASSES) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10204',
|
||||
'Changing the value of %s::$entityAnnotationClasses is deprecated and will have no effect in Doctrine ORM 3.0.',
|
||||
self::class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,11 +99,11 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
*/
|
||||
public function isTransient($className)
|
||||
{
|
||||
$classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
|
||||
$classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className));
|
||||
|
||||
foreach ($classAnnotations as $a) {
|
||||
$annot = $a instanceof RepeatableAttributeCollection ? $a[0] : $a;
|
||||
if (isset($this->entityAnnotationClasses[get_class($annot)])) {
|
||||
foreach ($classAttributes as $a) {
|
||||
$attr = $a instanceof RepeatableAttributeCollection ? $a[0] : $a;
|
||||
if (isset($this->entityAnnotationClasses[get_class($attr)])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -107,13 +122,13 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
|
||||
{
|
||||
$reflectionClass = $metadata->getReflectionClass()
|
||||
// this happens when running annotation driver in combination with
|
||||
// this happens when running attribute driver in combination with
|
||||
// static reflection services. This is not the nicest fix
|
||||
?? new ReflectionClass($metadata->name);
|
||||
|
||||
$classAttributes = $this->reader->getClassAnnotations($reflectionClass);
|
||||
$classAttributes = $this->reader->getClassAttributes($reflectionClass);
|
||||
|
||||
// Evaluate Entity annotation
|
||||
// Evaluate Entity attribute
|
||||
if (isset($classAttributes[Mapping\Entity::class])) {
|
||||
$entityAttribute = $classAttributes[Mapping\Entity::class];
|
||||
if ($entityAttribute->repositoryClass !== null) {
|
||||
@@ -226,7 +241,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
|
||||
$metadata->setPrimaryTable($primaryTable);
|
||||
|
||||
// Evaluate @Cache annotation
|
||||
// Evaluate #[Cache] attribute
|
||||
if (isset($classAttributes[Mapping\Cache::class])) {
|
||||
$cacheAttribute = $classAttributes[Mapping\Cache::class];
|
||||
$cacheMap = [
|
||||
@@ -237,7 +252,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
$metadata->enableCache($cacheMap);
|
||||
}
|
||||
|
||||
// Evaluate InheritanceType annotation
|
||||
// Evaluate InheritanceType attribute
|
||||
if (isset($classAttributes[Mapping\InheritanceType::class])) {
|
||||
$inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class];
|
||||
|
||||
@@ -246,7 +261,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
);
|
||||
|
||||
if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
// Evaluate DiscriminatorColumn annotation
|
||||
// Evaluate DiscriminatorColumn attribute
|
||||
if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
|
||||
$discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class];
|
||||
|
||||
@@ -256,13 +271,14 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string',
|
||||
'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255,
|
||||
'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null,
|
||||
'enumType' => isset($discrColumnAttribute->enumType) ? (string) $discrColumnAttribute->enumType : null,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
|
||||
}
|
||||
|
||||
// Evaluate DiscriminatorMap annotation
|
||||
// Evaluate DiscriminatorMap attribute
|
||||
if (isset($classAttributes[Mapping\DiscriminatorMap::class])) {
|
||||
$discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class];
|
||||
$metadata->setDiscriminatorMap($discrMapAttribute->value);
|
||||
@@ -270,7 +286,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate DoctrineChangeTrackingPolicy annotation
|
||||
// Evaluate DoctrineChangeTrackingPolicy attribute
|
||||
if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) {
|
||||
$changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class];
|
||||
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value));
|
||||
@@ -293,8 +309,8 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
$mapping = [];
|
||||
$mapping['fieldName'] = $property->getName();
|
||||
|
||||
// Evaluate @Cache annotation
|
||||
$cacheAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Cache::class);
|
||||
// Evaluate #[Cache] attribute
|
||||
$cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class);
|
||||
if ($cacheAttribute !== null) {
|
||||
assert($cacheAttribute instanceof Mapping\Cache);
|
||||
|
||||
@@ -307,10 +323,10 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
);
|
||||
}
|
||||
|
||||
// Check for JoinColumn/JoinColumns annotations
|
||||
// Check for JoinColumn/JoinColumns attributes
|
||||
$joinColumns = [];
|
||||
|
||||
$joinColumnAttributes = $this->reader->getPropertyAnnotationCollection($property, Mapping\JoinColumn::class);
|
||||
$joinColumnAttributes = $this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class);
|
||||
|
||||
foreach ($joinColumnAttributes as $joinColumnAttribute) {
|
||||
$joinColumns[] = $this->joinColumnToArray($joinColumnAttribute);
|
||||
@@ -318,35 +334,35 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
|
||||
// Field can only be attributed with one of:
|
||||
// Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded
|
||||
$columnAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Column::class);
|
||||
$oneToOneAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OneToOne::class);
|
||||
$oneToManyAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OneToMany::class);
|
||||
$manyToOneAttribute = $this->reader->getPropertyAnnotation($property, Mapping\ManyToOne::class);
|
||||
$manyToManyAttribute = $this->reader->getPropertyAnnotation($property, Mapping\ManyToMany::class);
|
||||
$embeddedAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Embedded::class);
|
||||
$columnAttribute = $this->reader->getPropertyAttribute($property, Mapping\Column::class);
|
||||
$oneToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToOne::class);
|
||||
$oneToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToMany::class);
|
||||
$manyToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToOne::class);
|
||||
$manyToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToMany::class);
|
||||
$embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class);
|
||||
|
||||
if ($columnAttribute !== null) {
|
||||
$mapping = $this->columnToArray($property->getName(), $columnAttribute);
|
||||
|
||||
if ($this->reader->getPropertyAnnotation($property, Mapping\Id::class)) {
|
||||
if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
$generatedValueAttribute = $this->reader->getPropertyAnnotation($property, Mapping\GeneratedValue::class);
|
||||
$generatedValueAttribute = $this->reader->getPropertyAttribute($property, Mapping\GeneratedValue::class);
|
||||
|
||||
if ($generatedValueAttribute !== null) {
|
||||
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy));
|
||||
}
|
||||
|
||||
if ($this->reader->getPropertyAnnotation($property, Mapping\Version::class)) {
|
||||
if ($this->reader->getPropertyAttribute($property, Mapping\Version::class)) {
|
||||
$metadata->setVersionMapping($mapping);
|
||||
}
|
||||
|
||||
$metadata->mapField($mapping);
|
||||
|
||||
// Check for SequenceGenerator/TableGenerator definition
|
||||
$seqGeneratorAttribute = $this->reader->getPropertyAnnotation($property, Mapping\SequenceGenerator::class);
|
||||
$customGeneratorAttribute = $this->reader->getPropertyAnnotation($property, Mapping\CustomIdGenerator::class);
|
||||
$seqGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\SequenceGenerator::class);
|
||||
$customGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\CustomIdGenerator::class);
|
||||
|
||||
if ($seqGeneratorAttribute !== null) {
|
||||
$metadata->setSequenceGeneratorDefinition(
|
||||
@@ -364,7 +380,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
);
|
||||
}
|
||||
} elseif ($oneToOneAttribute !== null) {
|
||||
if ($this->reader->getPropertyAnnotation($property, Mapping\Id::class)) {
|
||||
if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
@@ -384,7 +400,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
$mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval;
|
||||
$mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch);
|
||||
|
||||
$orderByAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
|
||||
$orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
|
||||
|
||||
if ($orderByAttribute !== null) {
|
||||
$mapping['orderBy'] = $orderByAttribute->value;
|
||||
@@ -392,7 +408,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
|
||||
$metadata->mapOneToMany($mapping);
|
||||
} elseif ($manyToOneAttribute !== null) {
|
||||
$idAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
|
||||
$idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class);
|
||||
|
||||
if ($idAttribute !== null) {
|
||||
$mapping['id'] = true;
|
||||
@@ -406,7 +422,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
$metadata->mapManyToOne($mapping);
|
||||
} elseif ($manyToManyAttribute !== null) {
|
||||
$joinTable = [];
|
||||
$joinTableAttribute = $this->reader->getPropertyAnnotation($property, Mapping\JoinTable::class);
|
||||
$joinTableAttribute = $this->reader->getPropertyAttribute($property, Mapping\JoinTable::class);
|
||||
|
||||
if ($joinTableAttribute !== null) {
|
||||
$joinTable = [
|
||||
@@ -419,11 +435,11 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->reader->getPropertyAnnotationCollection($property, Mapping\JoinColumn::class) as $joinColumn) {
|
||||
foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) {
|
||||
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
|
||||
}
|
||||
|
||||
foreach ($this->reader->getPropertyAnnotationCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) {
|
||||
foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) {
|
||||
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
|
||||
}
|
||||
|
||||
@@ -436,7 +452,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
$mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval;
|
||||
$mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch);
|
||||
|
||||
$orderByAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
|
||||
$orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
|
||||
|
||||
if ($orderByAttribute !== null) {
|
||||
$mapping['orderBy'] = $orderByAttribute->value;
|
||||
@@ -509,7 +525,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate AttributeOverrides annotation
|
||||
// Evaluate AttributeOverrides attribute
|
||||
if (isset($classAttributes[Mapping\AttributeOverrides::class])) {
|
||||
$attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class];
|
||||
|
||||
@@ -520,7 +536,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate EntityListeners annotation
|
||||
// Evaluate EntityListeners attribute
|
||||
if (isset($classAttributes[Mapping\EntityListeners::class])) {
|
||||
$entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class];
|
||||
|
||||
@@ -552,7 +568,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate @HasLifecycleCallbacks annotation
|
||||
// Evaluate #[HasLifecycleCallbacks] attribute
|
||||
if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) {
|
||||
foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
|
||||
assert($method instanceof ReflectionMethod);
|
||||
@@ -566,8 +582,8 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
/**
|
||||
* Attempts to resolve the fetch mode.
|
||||
*
|
||||
* @param string $className The class name.
|
||||
* @param string $fetchMode The fetch mode.
|
||||
* @param class-string $className The class name.
|
||||
* @param string $fetchMode The fetch mode.
|
||||
*
|
||||
* @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
|
||||
*
|
||||
@@ -599,12 +615,13 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
/**
|
||||
* Parses the given method.
|
||||
*
|
||||
* @return callable[]
|
||||
* @return list<array{string, string}>
|
||||
* @psalm-return list<array{string, (Events::*)}>
|
||||
*/
|
||||
private function getMethodCallbacks(ReflectionMethod $method): array
|
||||
{
|
||||
$callbacks = [];
|
||||
$attributes = $this->reader->getMethodAnnotations($method);
|
||||
$attributes = $this->reader->getMethodAttributes($method);
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if ($attribute instanceof Mapping\PrePersist) {
|
||||
|
||||
@@ -28,7 +28,7 @@ final class AttributeReader
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getClassAnnotations(ReflectionClass $class): array
|
||||
public function getClassAttributes(ReflectionClass $class): array
|
||||
{
|
||||
return $this->convertToAttributeInstances($class->getAttributes());
|
||||
}
|
||||
@@ -38,7 +38,7 @@ final class AttributeReader
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getMethodAnnotations(ReflectionMethod $method): array
|
||||
public function getMethodAttributes(ReflectionMethod $method): array
|
||||
{
|
||||
return $this->convertToAttributeInstances($method->getAttributes());
|
||||
}
|
||||
@@ -48,50 +48,50 @@ final class AttributeReader
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getPropertyAnnotations(ReflectionProperty $property): array
|
||||
public function getPropertyAttributes(ReflectionProperty $property): array
|
||||
{
|
||||
return $this->convertToAttributeInstances($property->getAttributes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<T> $annotationName The name of the annotation.
|
||||
* @param class-string<T> $attributeName The name of the annotation.
|
||||
*
|
||||
* @return T|null
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
|
||||
public function getPropertyAttribute(ReflectionProperty $property, $attributeName)
|
||||
{
|
||||
if ($this->isRepeatable($annotationName)) {
|
||||
if ($this->isRepeatable($attributeName)) {
|
||||
throw new LogicException(sprintf(
|
||||
'The attribute "%s" is repeatable. Call getPropertyAnnotationCollection() instead.',
|
||||
$annotationName
|
||||
'The attribute "%s" is repeatable. Call getPropertyAttributeCollection() instead.',
|
||||
$attributeName
|
||||
));
|
||||
}
|
||||
|
||||
return $this->getPropertyAnnotations($property)[$annotationName]
|
||||
?? ($this->isRepeatable($annotationName) ? new RepeatableAttributeCollection() : null);
|
||||
return $this->getPropertyAttributes($property)[$attributeName]
|
||||
?? ($this->isRepeatable($attributeName) ? new RepeatableAttributeCollection() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<T> $annotationName The name of the annotation.
|
||||
* @param class-string<T> $attributeName The name of the annotation.
|
||||
*
|
||||
* @return RepeatableAttributeCollection<T>
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getPropertyAnnotationCollection(
|
||||
public function getPropertyAttributeCollection(
|
||||
ReflectionProperty $property,
|
||||
string $annotationName
|
||||
string $attributeName
|
||||
): RepeatableAttributeCollection {
|
||||
if (! $this->isRepeatable($annotationName)) {
|
||||
if (! $this->isRepeatable($attributeName)) {
|
||||
throw new LogicException(sprintf(
|
||||
'The attribute "%s" is not repeatable. Call getPropertyAnnotation() instead.',
|
||||
$annotationName
|
||||
'The attribute "%s" is not repeatable. Call getPropertyAttribute() instead.',
|
||||
$attributeName
|
||||
));
|
||||
}
|
||||
|
||||
return $this->getPropertyAnnotations($property)[$annotationName] ?? new RepeatableAttributeCollection();
|
||||
return $this->getPropertyAttributes($property)[$attributeName] ?? new RepeatableAttributeCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +108,7 @@ final class AttributeReader
|
||||
foreach ($attributes as $attribute) {
|
||||
$attributeName = $attribute->getName();
|
||||
assert(is_string($attributeName));
|
||||
// Make sure we only get Doctrine Annotations
|
||||
// Make sure we only get Doctrine Attributes
|
||||
if (! is_subclass_of($attributeName, Annotation::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class DatabaseDriver implements MappingDriver
|
||||
/** @var array<string,Table>|null */
|
||||
private $tables = null;
|
||||
|
||||
/** @var mixed[] */
|
||||
/** @var array<class-string, string> */
|
||||
private $classToTableNames = [];
|
||||
|
||||
/** @psalm-var array<string, Table> */
|
||||
@@ -304,14 +304,15 @@ class DatabaseDriver implements MappingDriver
|
||||
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
|
||||
}
|
||||
|
||||
if (! $table->hasPrimaryKey()) {
|
||||
$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."
|
||||
);
|
||||
}
|
||||
|
||||
$pkColumns = $table->getPrimaryKey()->getColumns();
|
||||
$pkColumns = $primaryKey->getColumns();
|
||||
|
||||
sort($pkColumns);
|
||||
sort($allForeignKeyColumns);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../Annotation.php';
|
||||
require_once __DIR__ . '/../Entity.php';
|
||||
require_once __DIR__ . '/../Embeddable.php';
|
||||
require_once __DIR__ . '/../Embedded.php';
|
||||
require_once __DIR__ . '/../MappedSuperclass.php';
|
||||
require_once __DIR__ . '/../InheritanceType.php';
|
||||
require_once __DIR__ . '/../DiscriminatorColumn.php';
|
||||
require_once __DIR__ . '/../DiscriminatorMap.php';
|
||||
require_once __DIR__ . '/../Id.php';
|
||||
require_once __DIR__ . '/../GeneratedValue.php';
|
||||
require_once __DIR__ . '/../Version.php';
|
||||
require_once __DIR__ . '/../JoinColumn.php';
|
||||
require_once __DIR__ . '/../JoinColumns.php';
|
||||
require_once __DIR__ . '/../Column.php';
|
||||
require_once __DIR__ . '/../OneToOne.php';
|
||||
require_once __DIR__ . '/../OneToMany.php';
|
||||
require_once __DIR__ . '/../ManyToOne.php';
|
||||
require_once __DIR__ . '/../ManyToMany.php';
|
||||
require_once __DIR__ . '/../Table.php';
|
||||
require_once __DIR__ . '/../UniqueConstraint.php';
|
||||
require_once __DIR__ . '/../Index.php';
|
||||
require_once __DIR__ . '/../JoinTable.php';
|
||||
require_once __DIR__ . '/../SequenceGenerator.php';
|
||||
require_once __DIR__ . '/../CustomIdGenerator.php';
|
||||
require_once __DIR__ . '/../ChangeTrackingPolicy.php';
|
||||
require_once __DIR__ . '/../OrderBy.php';
|
||||
require_once __DIR__ . '/../NamedQueries.php';
|
||||
require_once __DIR__ . '/../NamedQuery.php';
|
||||
require_once __DIR__ . '/../HasLifecycleCallbacks.php';
|
||||
require_once __DIR__ . '/../PrePersist.php';
|
||||
require_once __DIR__ . '/../PostPersist.php';
|
||||
require_once __DIR__ . '/../PreUpdate.php';
|
||||
require_once __DIR__ . '/../PostUpdate.php';
|
||||
require_once __DIR__ . '/../PreRemove.php';
|
||||
require_once __DIR__ . '/../PostRemove.php';
|
||||
require_once __DIR__ . '/../PostLoad.php';
|
||||
require_once __DIR__ . '/../PreFlush.php';
|
||||
require_once __DIR__ . '/../FieldResult.php';
|
||||
require_once __DIR__ . '/../ColumnResult.php';
|
||||
require_once __DIR__ . '/../EntityResult.php';
|
||||
require_once __DIR__ . '/../NamedNativeQuery.php';
|
||||
require_once __DIR__ . '/../NamedNativeQueries.php';
|
||||
require_once __DIR__ . '/../SqlResultSetMapping.php';
|
||||
require_once __DIR__ . '/../SqlResultSetMappings.php';
|
||||
require_once __DIR__ . '/../AssociationOverride.php';
|
||||
require_once __DIR__ . '/../AssociationOverrides.php';
|
||||
require_once __DIR__ . '/../AttributeOverride.php';
|
||||
require_once __DIR__ . '/../AttributeOverrides.php';
|
||||
require_once __DIR__ . '/../EntityListeners.php';
|
||||
require_once __DIR__ . '/../Cache.php';
|
||||
@@ -16,10 +16,10 @@ class SimplifiedXmlDriver extends XmlDriver
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION)
|
||||
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION, bool $isXsdValidationEnabled = false)
|
||||
{
|
||||
$locator = new SymfonyFileLocator((array) $prefixes, $fileExtension);
|
||||
|
||||
parent::__construct($locator, $fileExtension);
|
||||
parent::__construct($locator, $fileExtension, $isXsdValidationEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +210,7 @@ class XmlDriver extends FileDriver
|
||||
'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string',
|
||||
'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255,
|
||||
'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null,
|
||||
'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
@@ -966,7 +967,7 @@ class XmlDriver extends FileDriver
|
||||
foreach ($cascadeElement->children() as $action) {
|
||||
// According to the JPA specifications, XML uses "cascade-persist"
|
||||
// instead of "persist". Here, both variations
|
||||
// are supported because both YAML and Annotation use "persist"
|
||||
// are supported because YAML, Annotation and Attribute use "persist"
|
||||
// and we want to make sure that this driver doesn't need to know
|
||||
// anything about the supported cascading actions
|
||||
$cascades[] = str_replace('cascade-', '', $action->getName());
|
||||
|
||||
@@ -201,6 +201,7 @@ class YamlDriver extends FileDriver
|
||||
'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string',
|
||||
'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255,
|
||||
'columnDefinition' => isset($discrColumn['columnDefinition']) ? (string) $discrColumn['columnDefinition'] : null,
|
||||
'enumType' => isset($discrColumn['enumType']) ? (string) $discrColumn['enumType'] : null,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
@@ -902,11 +903,10 @@ class YamlDriver extends FileDriver
|
||||
* Parse / Normalize the cache configuration
|
||||
*
|
||||
* @param mixed[] $cacheMapping
|
||||
* @psalm-param array{usage: mixed, region: (string|null)} $cacheMapping
|
||||
* @psalm-param array{usage: string, region?: string} $cacheMapping
|
||||
* @psalm-param array{usage: string|null, region?: mixed} $cacheMapping
|
||||
*
|
||||
* @return mixed[]
|
||||
* @psalm-return array{usage: int, region: string|null}
|
||||
* @psalm-return array{usage: int|null, region: string|null}
|
||||
*/
|
||||
private function cacheToArray(array $cacheMapping): array
|
||||
{
|
||||
|
||||
@@ -11,6 +11,6 @@ use Attribute;
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class Embeddable implements Annotation
|
||||
final class Embeddable implements MappingAttribute
|
||||
{
|
||||
}
|
||||
|
||||
@@ -13,12 +13,18 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class Embedded implements Annotation
|
||||
final class Embedded implements MappingAttribute
|
||||
{
|
||||
/** @var string|null */
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/** @var string|bool|null */
|
||||
/**
|
||||
* @var string|bool|null
|
||||
* @readonly
|
||||
*/
|
||||
public $columnPrefix;
|
||||
|
||||
public function __construct(?string $class = null, $columnPrefix = null)
|
||||
|
||||
@@ -15,15 +15,19 @@ use Doctrine\ORM\EntityRepository;
|
||||
* @template T of object
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class Entity implements Annotation
|
||||
final class Entity implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @psalm-var class-string<EntityRepository<T>>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $repositoryClass;
|
||||
|
||||
/** @var bool */
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $readOnly = false;
|
||||
|
||||
/** @psalm-param class-string<EntityRepository<T>>|null $repositoryClass */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user