Compare commits

..

249 Commits

Author SHA1 Message Date
Grégoire Paris
59938cae57 Merge pull request #12238 from mpdude/fix-collections-deprecation
Avoid triggering a deprecation notice in doctrine/collections
2025-10-27 22:19:59 +01:00
Grégoire Paris
298dc9bb6a Merge pull request #12183 from mpdude/paginator-confused-result-set-mapping-initialized
Paginator with output walker returns count 0 when the query has previously been executed
2025-10-27 21:54:38 +01:00
Matthias Pigulla
da67f323e0 Add PHPStan errors to persistence2 baseline file 2025-10-27 19:51:11 +01:00
Matthias Pigulla
63635cad0e Remove PHPStan error suppressions 2025-10-27 12:28:48 +01:00
Grégoire Paris
a0d401b688 Merge pull request #12239 from doctrine/dependabot/github_actions/2.20.x/actions/download-artifact-6
Bump actions/download-artifact from 5 to 6
2025-10-27 08:38:07 +01:00
Grégoire Paris
6acbadfbbe Merge pull request #12240 from doctrine/dependabot/github_actions/2.20.x/actions/upload-artifact-5
Bump actions/upload-artifact from 4 to 5
2025-10-27 08:37:52 +01:00
dependabot[bot]
c64dcb4d38 Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 06:41:11 +00:00
dependabot[bot]
3304290b21 Bump actions/download-artifact from 5 to 6
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 06:22:30 +00:00
Matthias Pigulla
1865717721 Avoid triggering a deprecation notice in doctrine/collections
This updates the code to avoid triggering the deprecation introduced in https://github.com/doctrine/collections/pull/472.
2025-10-26 23:20:27 +01:00
Matthias Pigulla
828b06e20f Update the DQL walker cookbook example 2025-10-26 22:13:51 +01:00
Matthias Pigulla
c2b844d2e3 Update src/Tools/Pagination/Paginator.php
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2025-10-26 22:08:56 +01:00
dependabot[bot]
b45d5329f8 Bump doctrine/.github from 10.1.0 to 12.0.0 (#12227) 2025-10-23 08:10:27 +02:00
Grégoire Paris
c1047b30e3 Merge pull request #12216 from paulinevos/docs-builder
Use docs builder in ORM repo
2025-10-23 07:48:31 +02:00
Grégoire Paris
aa62efa30a Adapt to latest coding standard 2025-10-22 21:12:17 +02:00
Pauline Vos
f71956f001 Use docs-builder to generate ORM docs
Introduces the `composer docs` command to generate the docs locally, and
uses the same tool (`docs-builder`) in the documentation GH workflow.
2025-10-22 21:09:03 +02:00
Ali Sol
96f9b29573 Merge pull request #12233 from alisolphp/fix-docs-typos-2-20
Docs: fix typos and grammar across reference docs
2025-10-21 17:09:12 +02:00
Grégoire Paris
9a55cf4f30 Merge pull request #12190 from mpdude/criteria-matching-custom-type-retry
Fix collection filtering API for `IN`/`NOT IN` comparisons that require type conversions
2025-10-16 07:56:07 +02:00
Matthias Pigulla
9d680a6de4 Do not eagerly set metadata from ResolveTargetEntityListener (#12174)
* Do not eagerly set metadata from ResolveTargetEntityListener

When using the `ResolveTargetEntityListener` to substitute `targetEntities` in association mappings, do not eagerly put the resolved (target) entity into the class metadata cache under the class name of the original entity.

#### Motivation

I have a library that wants to distribute a MappedSuperclass as the base for some functionality. It will be necessary that clients using the library will extend the MappedSuperclass to fill in some blanks, creating the first real `#[Entity]` instance of it.

This client-provided entity will be the primary means of working with the class. Thus, I was following the [note in the documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/reference/inheritance-mapping.html#mapped-superclasses:~:text=It%20is%2C%20however) and using the `ResolveTargetEntityListener` to declare that whenever an association refers to that mapped superclass, the particular entity class shall be used instead.

>  One-To-Many associations are not generally possible on a mapped superclass, since they require the "many" side to hold the foreign key.
> It is, however, possible to use the [ResolveTargetEntityListener](https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/cookbook/resolve-target-entity-listener.html) to replace references to a mapped superclass with an entity class at runtime. As long as there is only one entity subclass inheriting from the mapped superclass and all references to the mapped superclass are resolved to that entity class at runtime, the mapped superclass can use One-To-Many associations and be named as the targetEntity on the owning sides.

#### Changes made

The `ResolveTargetEntityListener` primarily does what its name suggests: For newly loaded class metadata, it inspects all associations declared and replaces the `targetEntity` with new (resolved) values.

But additionally, when a loaded class is the target of such a resolution, it would also put the class metadata into the cache under the name of the original entity.

I think that extra step is wrong, and this PR removes it. It had the side effect that when other classes extending the MappedSuperclass were loaded _after_ the resolve target class has been seen for the first time, the metadata for those classes would not inherit from the mapped superclass anymore, but from the target entity class instead. In my real-life use case, this causes weird mapping errors down the road; as of ^3.0, it would throw a mapping exception asking to configure inheritance mapping. But note that there would be no inheritance between the two entity classes at all.

#### More background

The documentation [describes the use of `ResolveTargetEntityListener`](https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/cookbook/resolve-target-entity-listener.html) with an interface that is resolved to an entity class. For an interface, adding the extra metadata does not make a difference, since it never interferes with actual entity or mapped superclasses.

The initial idea of adding a copy of the entity class metadata under the interface name came from commit
9c7f3f2747 in #385. The goal was to make it possible to also find entities by interface names, like so:

```
$em->find('Foo\BarBundle\Entity\PersonInterface', 1);
```

It then [turned out that this only worked when the resolution had already been applied](https://github.com/doctrine/orm/pull/385#issuecomment-6658893). So, the new `onClassMetadataNotFound` event was added and the resolution map would be checked in that case as well (#1181). The inital code stayed in place, possibly giving a small performance gain.

In my real-world use case and the test case I added in this PR, the associations are even self-referencing. That should not really be necessary for the problem to surface. I decided to keep it this way to show that the `targetEntity` need not be an interface after all, and that a MappedSuperclass can be used in the same way.
2025-10-15 16:10:51 +02:00
Matthias Pigulla
7a59281157 Fix collection filtering API for IN/NOT IN comparisons that require type conversions
This PR fixes the `Criteria` matching API for `IN` and `NIN` conditions with values that are arrays, by making sure that type information for the matched field is passed to the DBAL level correctly.

Passing the right parameter type to DBAL is important to make sure parameter conversions are applied before matching at the database level.

Memory-based collections (`ArrayCollection`s or initialized collection fields) would perform matching on the objects in memory where no type conversion to the database representation is required, giving correct results.

But uninitialized collections that have their conditions evaluated at the database level need to convert parameter values to the database representation before performing the comparison.

One extra challenge is that the DBAL type system does currently not support array-valued parameters for custom types. Only a [limited list of types](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.2/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion) is supported.

I discussed this with @morozov at the Doctrine Hackathon and came to the conclusion that it would be best to work around this limitation at the ORM level. Thus, this fix recognizes array-valued parameters and creates multiple placeholders (like `?, ?, ?`) for them, flattening out the arrays in the parameter list and repeating the type information for each one of them.

Previous stalled attempt to fix this was in #11897.
2025-10-13 23:11:56 +02:00
Grégoire Paris
5def068fe9 Merge pull request #12223 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-10.1.0
Bump doctrine/.github from 8.0.0 to 10.1.0
2025-10-13 08:49:34 +02:00
dependabot[bot]
693acbf812 Bump doctrine/.github from 8.0.0 to 10.1.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 8.0.0 to 10.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/8.0.0...10.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-version: 10.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 06:11:35 +00:00
Grégoire Paris
c1ce2bb687 Merge pull request #12220 from mbeccati/fix-mysql9-library
Escape library as a table name in tests (#12170)
2025-10-11 17:08:47 +02:00
Matteo Beccati
5f6896a2f9 Avoid using LIBRARY as table name (#12170)
It is a reserved word since MySQL 9.2:
https://dev.mysql.com/doc/refman/9.2/en/create-library.html
2025-10-11 11:56:29 +02:00
Grégoire Paris
930a790a5a Merge pull request #12212 from mpdude/revert-11769
Revert "Fix fields of transient classes being considered duplicate with `reportFieldsWhereDeclared` (#11769)"
2025-10-10 08:17:09 +02:00
Christian Flothmann
3b7de17f2e add jobs using PHP 8.5 in the CI (#12180) 2025-10-09 16:39:28 +02:00
Matthias Pigulla
8afaa63d73 Add a recommendation not to use multiple private fields of the same name in entity hierarchies 2025-10-09 11:11:51 +02:00
Matthias Pigulla
2ad720b304 Revert "Fix fields of transient classes being considered duplicate with reportFieldsWhereDeclared (#11769)"
This reverts commit 4feaa470af.
2025-10-09 10:23:55 +02:00
Benjamin Eberlei
a939dc2e0d [GH-9219] Add support for toIterable over mixed or scalar results. (#12187)
* [GH-9219] Add support for toIterable over mixed or scalar results.

* Housekeeping: phpcs

* Update test names
2025-10-08 15:07:52 +02:00
Christophe Coevoet
f8186b1203 Merge pull request #12200 from mpdude/fix-invalid-dql-test-case
Fix DQL JOIN syntax in two test cases
2025-10-08 11:08:24 +02:00
Matthias Pigulla
048e308241 Fix DQL JOIN syntax in two test cases 2025-10-07 17:54:33 +02:00
Alexander M. Turek
4274dac8a2 Remove calls to getMockForAbstractClass() (#12003) 2025-10-07 16:34:22 +02:00
Grégoire Paris
c1af765960 Merge pull request #12185 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-8.0.0
Bump doctrine/.github from 7.3.0 to 8.0.0
2025-10-07 15:40:44 +02:00
Christian Flothmann
5f3551852f use the empty string instead of null as an array offset (#12181) 2025-10-07 14:50:23 +02:00
Grégoire Paris
8144cad07c Upgrade to doctrine/coding-standard 14 2025-10-06 09:01:36 +02:00
dependabot[bot]
70fd68cf7f Bump doctrine/.github from 7.3.0 to 8.0.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 7.3.0 to 8.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/7.3.0...8.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-version: 8.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 06:14:08 +00:00
Matthias Pigulla
437259556c Fix CS 2025-10-01 19:48:04 +02:00
Matthias Pigulla
e7e2fef56c Fix creation of the count query, to that a new RSM is being created 2025-10-01 19:46:58 +02:00
Matthias Pigulla
3e34b8e86a Add a test to reproduce the issue 2025-10-01 19:35:18 +02:00
Christian Flothmann
7d950aba62 do not call setAccessible() on PHP >= 8.1 (#12182) 2025-10-01 16:17:00 +02:00
Grégoire Paris
8fe1200edf Merge pull request #11895 from mpdude/fix-many-to-many-in-expression
Fix `IN`/`NOT IN` expression handling and support enums when matching on to-many-collections
2025-08-26 15:32:52 +02:00
Grégoire Paris
397358c308 Merge pull request #12133 from greg0ire/update-phpstan
PHPStan 2.1.22
2025-08-19 07:36:10 +02:00
Grégoire Paris
ac37a87a3d Merge pull request #12139 from doctrine/dependabot/github_actions/2.20.x/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-18 17:31:47 +02:00
dependabot[bot]
613f52db5a Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 09:50:01 +00:00
Grégoire Paris
cf5503b0d8 PHPCS 3.13.2 (#12134) 2025-08-17 21:53:23 +02:00
Grégoire Paris
ae5e9c8c6c PHPStan 2.1.22 2025-08-17 19:24:33 +02:00
Grégoire Paris
1a2826d147 Merge pull request #12053 from alexislefebvre/chore-remove-run-all.sh
chore: remove run-all.sh
2025-08-11 14:14:20 +02:00
Grégoire Paris
05760f9454 Merge pull request #12127 from doctrine/dependabot/github_actions/2.20.x/actions/download-artifact-5
Bump actions/download-artifact from 4 to 5
2025-08-11 13:06:03 +02:00
dependabot[bot]
26af013842 Bump actions/download-artifact from 4 to 5
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 10:22:17 +00:00
Grégoire Paris
c322c71cd4 Merge pull request #12120 from greg0ire/fix-broken-comments
Fix broken comments
2025-08-08 08:55:44 +02:00
Grégoire Paris
3c733a2fee Add missing phpdoc 2025-08-08 08:34:07 +02:00
Grégoire Paris
5984ad586a Fix broken phpdoc comments 2025-08-08 08:33:39 +02:00
Grégoire Paris
ee2c3a506b Merge pull request #12097 from mvorisek/add_fixed_id_insert_count_query_test
Add 2nd level cache test for insert without post-inserted ID
2025-08-08 08:26:23 +02:00
Grégoire Paris
04694a9f7b Merge pull request #11835 from gseidel/fix-pre-persist-call-persist
fix: calling scheduleForInsert twice
2025-08-07 06:25:35 +02:00
Michael Voříšek
5b2060e25f Add 2nd level cache test for insert without post-inserted ID
Inserts without post-inserted ID can be sent to DB grouped together
hence the extra test.
2025-08-06 08:22:48 +02:00
Grégoire Paris
39e35fc06c Merge pull request #12099 from alexislefebvre/2.20.x-update-supported-branches-on-README
doc: update supported branches on README (2.20.x)
2025-08-04 16:59:49 +02:00
Alexis Lefebvre
7f061c3870 doc: update supported branches on README 2025-08-04 16:38:55 +02:00
Grégoire Paris
74495711fb Merge pull request #11934 from mvorisek/fix_joined_subclass_persister_insert_of_multiple_entities
Fix JoinedSubclassPersister when multiple entities are inserted
2025-08-02 08:34:29 +02:00
Michael Voříšek
97a7cb8d2f Unify JoinedSubclassPersister dequeue
Fix JoinedSubclassPersister as BasicEntityPersister was already fixed in GH-10735.

The fix can be verified by modifying UnitOfWork to execute `BasicEntityPersister::executeInserts()` for multiple entities at once for the same entity class/persister instance - https://github.com/doctrine/orm/blob/2.20.3/src/UnitOfWork.php#L1186 - then reproducible on `Doctrine\Tests\ORM\Functional\Ticket\GH10531Test::testInserts` test.

As extending/modifying UnitOfWork in tests in not easily possible, I submit this fix for v2.x without a test.
2025-08-01 15:31:18 +02:00
Grégoire Paris
e0052390e1 Merge pull request #12087 from mvorisek/improve_basic_entity_persister
Improve BasicEntityPersister to be more flexible and cleaner
2025-07-30 09:44:24 +02:00
Michael Voříšek
8c6419e0e0 Prefer strict empty-array comparison over empty() call 2025-07-29 15:15:31 +02:00
Michael Voříšek
6f5ce1aca2 BasicEntityPersister: refactor $values variable into $placeholders
The new variable name is much more clearer.
2025-07-29 15:15:31 +02:00
Michael Voříšek
98e7a53b42 Remove BasicEntityPersister::$insertSql cache property
When the persister is extended to do a multi update, the caching is not
wanted. The impact is minimal as the CPU/time overhead per query is
much bigger and the prepared statement is not cached anyway.
2025-07-29 15:15:31 +02:00
Gerhard Seidel
3aaaf37dfb fix: PrePersistEventTest typos and unnecessary comments 2025-07-29 14:40:20 +02:00
Grégoire Paris
154a4652ee Merge pull request #12086 from mvorisek/add_cache_rw_strict_locking_test
Add functional strict-locking 2nd level cache test
2025-07-29 11:48:25 +02:00
Michael Voříšek
ae7489ff19 Add functional strict-locking 2nd level cache test 2025-07-28 12:14:50 +02:00
Grégoire Paris
1ee01f4473 Merge pull request #12078 from stlgaits/2.20.x
Fix embedded classes display in orm:mapping:command output
2025-07-22 08:48:52 +02:00
stlgaits
8a9ed138a8 Fix embedded classes display in orm:mapping:command output 2025-07-21 15:44:46 +02:00
Alexis Lefebvre
41ea59ac66 chore: use a shorter name for CI on GitHub Actions (#12055) 2025-07-04 00:19:01 +02:00
Alexis Lefebvre
e605e6d569 doc: add links to GitHub Actions on README (#12054) 2025-07-04 00:13:51 +02:00
Alexis Lefebvre
d68c1dcd6d chore: remove run-all.sh 2025-07-02 00:14:28 +02:00
Grégoire Paris
6307b4fa7d Merge pull request #8012 from sgehrig/bug/#8011-ordering-with-arithmetic-expression
Bug/#8011 ordering with arithmetic expression
2025-06-24 19:50:46 +02:00
Alexander M. Turek
5d21bb158b Fix calls to Application::add() (#12006) 2025-06-18 08:58:26 +02:00
Grégoire Paris
71550106d4 Merge pull request #11975 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-7.3.0
Bump doctrine/.github from 7.2.2 to 7.3.0
2025-06-09 22:24:12 +02:00
dependabot[bot]
36011f0d0f Bump doctrine/.github from 7.2.2 to 7.3.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 7.2.2 to 7.3.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/7.2.2...7.3.0)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 07:13:58 +00:00
Grégoire Paris
c97d775370 Merge pull request #11963 from dbu/update-doc-building
cleanup doc building instructions
2025-06-09 07:45:09 +02:00
Grégoire Paris
e9f0345a97 Merge pull request #11966 from greg0ire/partial-revert-10162-2
Partially revert to stdout
2025-06-07 09:41:03 +02:00
Grégoire Paris
0feb09d0d6 Partially revert to stdout
This command's purpose is to provide structured data, except for a call
to caution() that warns the user in case they do not have any mapped
entities or they have errors.
2025-06-06 07:57:38 +02:00
Grégoire Paris
fe5f8bbaa1 Merge pull request #11965 from greg0ire/partial-revert-10162
Revert to stdout for MappingDescribeCommand
2025-06-06 00:04:07 +02:00
Grégoire Paris
ecf3cec376 chore: ignore deprecations from Symfony
Symfony 7.3 is not available to all of our users, so we cannot switch to
native lazy objects, which require a PHP version higher than the lowest
PHP version we support.
2025-06-05 23:19:32 +02:00
Grégoire Paris
0a714db4d9 Revert to stdout for MappingDescribeCommand
In f256d996cc, I did a global move to
stderr for notifications, and went a bit overboard for
MappingDescribeCommand, which purpose is to output a description.
2025-06-05 23:07:28 +02:00
David Buchmann
471fda8d0b cleanup doc building instructions 2025-06-05 07:44:37 +02:00
Grégoire Paris
dfe32c2f74 Unwrap literalinclude block (#11962)
For some reason, it does not appear to work when nested inside a
code-block directive. Anyway, if you specify the language attribute, you
get markup identical to what you obtain when using code-block and
literalinclude, so this wrapping seems unneeded.
2025-06-04 00:18:59 +02:00
Grégoire Paris
c51ba3ce6b Merge pull request #11951 from dbu/fix-doc-syntax
insert blank line before code in code-block
2025-05-27 14:08:44 +02:00
David Buchmann
fe025e8d23 insert blank line before code in code-block 2025-05-27 08:59:14 +02:00
Grégoire Paris
ae2957cf7e Merge pull request #11932 from dbannik/2.20.3-issue-11931
#11931 Bug when change sql filter [Related issue #11694]
2025-05-06 07:53:48 +02:00
Dmitry Bannik
e172b3bf9c #11931 Bug when change sql filter [Related issue #11694]
This fix takes into account the invalidation of the filter sql for SingleTablePersister and JoinedSubclassPersister
2025-05-05 23:43:27 +03:00
Grégoire Paris
c9c6e8da2e Merge pull request #11834 from dbu/document-generated-columns
document how to work with generated columns
2025-05-05 10:05:55 +02:00
Grégoire Paris
528b8837e1 Merge pull request #11929 from doctrine/2.20.x-merge-up-into-2.21.x_KkdqS0u7
Merge release 2.20.3 into 2.21.x
2025-05-02 21:57:23 +02:00
Grégoire Paris
17d28b5c4c Merge pull request #11917 from stof/lazy_ghost_postload
Fix the initialization of lazy-ghost proxies with postLoad listeners
2025-05-02 19:07:53 +02:00
Christophe Coevoet
a2d510c6f4 Fix the initialization of lazy-ghost proxies with postLoad listeners
PostLoad listeners might initialize values for transient properties, so
the proxy should not skip initialization when using transient
properties.

Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
2025-05-02 15:27:59 +02:00
Christophe Coevoet
2a4ebca90e Refactor tests to avoid using instance properties to track postLoad
The old proxy implementation of doctrine/common was triggered by public
methods rather than access to properties (making public properties
unsupported in entities), so tests could use public instance properties
to track the state of postLoad lifecycle callbacks without triggering
the proxy initialization when reading that state (which then changes the
state of triggering the postLoad callback).
As the new proxy implementation hooks into properties instead, the tests
now use a static method (ensuring it is reset properly before loading
the instance for which we care about the tracking) instead of an
instance property.
2025-04-22 17:39:29 +02:00
Matthias Pigulla
9bf407f336 Fix IN/NOT IN expression handling and support enums when matching on to-many-collections
This fixes that using a `Criteria` with an `IN` or `NIN` expression on a to-many collection currently leads to an SQL error (#6173). The `ManyToMany` persister needs to know about the slightly different SQL syntax for `[NOT] IN ()`.

In the case of `[NOT] IN` expressions, the value will be an array, which also required me to change (I guess "fix") the parameter type handling. I have pulled the necessary code from the `BasicEntityPersister` and placed it as static helper methods in `PersisterHelper`.

This is somewhat inspired by #11516, which aims at fixing #11481: By re-using the parameter type handling code, it also fixes using backed enums in `EQ`, `IN` and `NIN` expressions within `Criteria` when `matching()` on one-to-many and many-to-many collections.
2025-03-30 22:44:12 +02:00
Grégoire Paris
cc29ae0d36 Merge pull request #11891 from mpdude/expression-matching-caveats
Add more detailed caveats for using the Collection filtering API
2025-03-29 11:31:36 +01:00
Grégoire Paris
bd4a053d29 Merge pull request #11894 from DavidPetrasek/3.3.x
Fix URL's in xml-mapping.rst
2025-03-27 20:59:26 +01:00
David Petrásek
52fbfb3785 Revert to http for namespace name
These URLs are meant as identifiers rather than actual urls intended to
be used to perform an HTTP request.
2025-03-27 17:39:10 +01:00
Matthias Pigulla
c259371e5f Remove property hooks mention 2025-03-26 18:58:17 +01:00
Matthias Pigulla
dcdd58b642 working-with-associations.rst aktualisieren
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2025-03-26 18:14:01 +01:00
Matthias Pigulla
7b9c53854f Add more detailed caveats for using the Collection filtering API 2025-03-26 13:38:52 +01:00
Grégoire Paris
cdc5fe11dd Merge pull request #11889 from Rixafy/docs-typo-fix
Fix docs typo (nulable -> nullable)
2025-03-25 23:16:01 +01:00
Rixafy
69ece00564 Fix docs typo (nulable -> nullable) 2025-03-25 22:25:00 +01:00
Grégoire Paris
c679d1b007 Merge pull request #11885 from greg0ire/no-triple-stars
Avoid triple stars
2025-03-25 07:51:08 +01:00
Grégoire Paris
1e15b22dcb Avoid triple stars
They don't have a special meaning, and are rendered like this:
<strong>*REQUIRED</strong>*.
2025-03-25 07:49:53 +01:00
Grégoire Paris
44057b4683 Merge pull request #11845 from lacatoire/update-message-annotation-to-attribute
Update message of `ORMInvalidArgumentException`
2025-03-24 10:58:02 +01:00
Grégoire Paris
013df03795 Upgrade to doctrine/coding-standard 13 (#11881) 2025-03-24 07:18:07 +01:00
Louis-Arnaud
2d2a34407c Use attributes in exception message 2025-03-23 16:07:31 +01:00
Stefan Gehrig
067ad51b3f fixes sqlite sql inconsistency 2025-03-17 08:48:30 +01:00
Stefan Gehrig
00c77213fb fixes codesniffer violation 2025-03-15 09:42:21 +01:00
Matteo Beccati
3303cd3b5d Fix non-deterministic test (#11866) 2025-03-14 00:09:36 +01:00
Grégoire Paris
afcf91e839 Merge pull request #11863 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-7.2.2
Bump doctrine/.github from 7.2.1 to 7.2.2
2025-03-10 09:24:12 +01:00
dependabot[bot]
c61a9b3b6d Bump doctrine/.github from 7.2.1 to 7.2.2
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 7.2.1 to 7.2.2.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/7.2.1...7.2.2)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 06:43:02 +00:00
Stefan Gehrig
c68b8f90b3 adds a test for postgres that uses a HIDDEN result variable for ordering based on arithmetic expression 2025-03-05 09:28:52 +01:00
Stefan Gehrig
aa4f9ce9e9 CS fix based on PHP_CodeSniffer report 2025-03-05 09:22:57 +01:00
Stefan Gehrig
d96fc23327 skips tests when running on postgres 2025-02-27 10:30:21 +01:00
Louis-Arnaud
3aed6912a3 Update ORMInvalidArgumentException.php
update message to use attribute instead of annotation
2025-02-21 10:10:22 +01:00
Gerhard Seidel
4fb044d5f6 fix: cs 2025-02-20 10:01:35 +08:00
David Buchmann
8ce7b310c5 document how to work with generated columns 2025-02-17 15:06:18 +01:00
Gerhard Seidel
2a953c5e2b fix: PrePersistEventTest and cs 2025-02-17 14:01:08 +08:00
Gerhard Seidel
abc6a40ccb fix: calling scheduleForInsert twice
If scheduleForInsert was called in prePersist hook already, then persistNew need to check this case first, otherwise a ORMInvalidArgumentException will be thrown
2025-02-14 12:45:13 +08:00
Grégoire Paris
158605bf24 Merge pull request #11833 from HypeMC/fix-dql
Fix DQL example with composite key
2025-02-13 23:49:22 +01:00
Grégoire Paris
2c2ef65817 Merge pull request #11826 from aprat84/gh-11741
Clone query hints and parameters in `LimitSubqueryOutputWalker` constructor
2025-02-12 08:52:33 +01:00
HypeMC
1c33a86983 Fix DQL example with composite key 2025-02-11 16:10:59 +01:00
Albert Prat
310fe1cccb Clone query hints and parameters in LimitSubqueryOutputWalker constructor
This fixes a bug that arises when using Pagination and an entity relation is mapped with fetch-mode EAGER but setFetchMode LAZY (or anything that is not EAGER) has been used on the query. If the query use WITH condition, an exception is incorrectly raised (Associations with fetch-mode=EAGER may not be using WITH conditions).
The class LimitSubqueryOutputWalker clones the query, but not its parameters and hints, so the generated subquery does not know that fetch-mode has been overridden.

Fixes #11741
2025-02-11 10:48:14 +01:00
Grégoire Paris
a67f677747 Merge pull request #11707 from jorenMartens/2.20.x
[DDC-551] fix, add filter support in oneToOne relation 2.20.x
2025-02-07 08:23:53 +01:00
Grégoire Paris
73e68f3c7d Merge pull request #11821 from doctrine/2.20.x-merge-up-into-2.21.x_8O8nHxqC
Merge release 2.20.2 into 2.21.x
2025-02-04 20:24:01 +01:00
Grégoire Paris
19912de927 Merge pull request #11820 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-7.2.1
Bump doctrine/.github from 7.1.0 to 7.2.1
2025-02-04 20:17:01 +01:00
dependabot[bot]
737cca5b78 Bump doctrine/.github from 7.1.0 to 7.2.1
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 7.1.0 to 7.2.1.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/7.1.0...7.2.1)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 18:23:54 +00:00
Grégoire Paris
9e999ea1ff Merge pull request #11792 from dbannik/11783-failure-with-indexed-relation
11783 failure with indexed relation
2025-01-28 15:32:25 +01:00
Bob van de Vijver
6755bb0c7b Fix Hydration when use ManyToMany[indexBy]
The bug related (#11694) and fixed mapping of sql column alias to field in entity (#11783) and
invalidate cache [cache/persisted/entity|cache/persisted/collection] when sql filter changes
2025-01-27 15:35:59 +03:00
Alexander M. Turek
73777d0bd4 Merge branch '2.20.x' into 2.21.x
* 2.20.x:
  Introduce testNotListedValueInEnumArray
  Fix documentation for JoinColumn nullable (#11798)
  Ignore deprecations from doctrine/common
  Fix fields of transient classes being considered duplicate with `reportFieldsWhereDeclared`
2025-01-26 19:56:20 +01:00
Grégoire Paris
c2a49327a7 Merge pull request #11799 from HypeMC/enum-array-error
Fix invalid enum value in array of enums
2025-01-22 11:54:59 +01:00
Maxime COLIN
9bd7242376 Introduce testNotListedValueInEnumArray 2025-01-22 02:25:34 +01:00
pawel-slowik
fff085b63f Fix documentation for JoinColumn nullable (#11798)
Nullability is not inherited from the PHP type. The change that enabled
this feature was reversed in https://github.com/doctrine/orm/pull/8732.
2025-01-22 00:12:00 +01:00
Grégoire Paris
5ad5b11ae1 Merge pull request #11769 from HypeMC/fix-reportfieldswheredeclared
Fix fields of transient classes being considered duplicate with `reportFieldsWhereDeclared`
2025-01-20 23:48:32 +01:00
Grégoire Paris
c12fd2cb94 Merge pull request #11793 from greg0ire/doctrine-common-support
Ignore deprecations from doctrine/common
2025-01-18 22:29:13 +01:00
Grégoire Paris
44d5d4a779 Ignore deprecations from doctrine/common
These new issues are caused by doctrine/common 3.5.0, released 2 weeks
ago.
2025-01-17 08:33:24 +01:00
Stefan Gehrig
ec6d1b9f72 fixes whitespace
Signed-off-by: Stefan Gehrig <stefan.gehrig.hn@googlemail.com>
2025-01-07 08:51:19 +01:00
Stefan Gehrig
d809fed52a fixes code sniffer complaints
Signed-off-by: Stefan Gehrig <stefan.gehrig.hn@googlemail.com>
2025-01-07 08:48:42 +01:00
Stefan Gehrig
0e4786dfa8 adds testcases for order by items enclosed in ((...)) (double brackets - just one bracket does not work)
just one bracket (...) gives

Exception : [Doctrine\ORM\Query\QueryException] [Syntax Error] line 0, col xx: Error: Expected Doctrine\ORM\Query\Lexer::T_IDENTIFIER, got '('
2025-01-03 10:45:08 +01:00
Stefan Gehrig
c429262f02 adds detection of literals/result variables at the beginning of an order by item with arithmetic expression
Not sure whether this covers the whole problem regarding complex expressions in order by items but it fixes the provided test cases
2025-01-03 10:45:07 +01:00
Stefan Gehrig
f4fdcbcdcb adds more test cases 2025-01-03 10:44:17 +01:00
Stefan Gehrig
b0806469d5 adds test case for GH issue #8011 2025-01-03 10:44:17 +01:00
Grégoire Paris
e89b58a13f Merge pull request #11771 from doctrine/2.20.x-merge-up-into-2.21.x_3Yg2ZYgM
Merge release 2.20.1 into 2.21.x
2024-12-19 08:16:04 +01:00
Grégoire Paris
e3cabade99 Merge pull request #11768 from pbreteche/HINT_READ_ONLY-use-its-boolean-value
Check hint value before considering instance read-only
2024-12-19 07:48:36 +01:00
HypeMC
9402f9e0f7 Fix docs examples for mappings overrides (#11770) 2024-12-18 20:41:44 +01:00
HypeMC
4feaa470af Fix fields of transient classes being considered duplicate with reportFieldsWhereDeclared 2024-12-18 15:42:12 +01:00
Pierre Bretéché
4a9101f383 Check hint value before considering instance read-only
This fixes a bug that occurs when calling setHint(Query::HINT_READ_ONLY, false) from a query object.
UnitOfWork checks if this hint exists without considering the value passed as second argument.
Handling the second parameter improves consistency with documentation.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.20/reference/improving-performance.html#read-only-entities
2024-12-18 14:55:22 +01:00
Grégoire Paris
f91da5b950 Merge pull request #11767 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-7.1.0
Bump doctrine/.github from 6.0.0 to 7.1.0
2024-12-16 08:30:26 +01:00
dependabot[bot]
66f654d4e2 Bump doctrine/.github from 6.0.0 to 7.1.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 6.0.0 to 7.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/6.0.0...7.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 06:51:45 +00:00
Grégoire Paris
53b51ae40e Merge pull request #11613 from alexander-schranz/patch-2
Add missing generated option to documentation
2024-12-11 19:51:14 +01:00
Alexander Schranz
95b0f5c328 Add missing generated option 2024-12-11 11:55:34 +01:00
Grégoire Paris
2b94ec18b9 Merge pull request #11759 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-12-08 14:33:31 +01:00
Grégoire Paris
a5c80a4c75 Provide XSD for phpcs file (#11752)
It unlocks autocompletion and validation in some IDEs.
2024-12-07 23:21:04 +01:00
Grégoire Paris
6fd26a3933 Order result (#11757)
This avoids a test flakyness observed when using PostgreSQL in the CI.
2024-12-07 22:07:23 +01:00
Grégoire Paris
8ef9253999 Upgrade to PHPStan 2 (#11756)
Some calls to assert() are no longer necessary.
2024-12-07 20:42:54 +01:00
Grégoire Paris
2a662149f4 Merge pull request #11754 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-12-07 15:39:29 +01:00
Grégoire Paris
0ed0be089c Run static analysis checks on PHP 8.4 (#11753)
* Run static analysis checks on PHP 8.4

* Remove forgotten references to Psalm

* Remove invalid annotation

I do not think it achieves anything.
2024-12-07 13:36:37 +01:00
Grégoire Paris
8fb1043e96 Merge pull request #11704 from beberlei/DropPsalm-2.20
Drop Psalm
2024-12-07 12:43:18 +01:00
Benjamin Eberlei
fd041fbe80 Drop Psalm 2024-12-07 12:29:58 +01:00
Grégoire Paris
eadf96c879 Merge pull request #11743 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-6.0.0
Bump doctrine/.github from 5.3.0 to 6.0.0
2024-12-02 08:07:33 +01:00
dependabot[bot]
0d770c89d6 Bump doctrine/.github from 5.3.0 to 6.0.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 5.3.0 to 6.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/5.3.0...6.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 06:07:53 +00:00
Grégoire Paris
37051d57ce Merge pull request #11739 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-11-28 08:23:12 +01:00
Grégoire Paris
4bda5147f3 Merge pull request #11736 from greg0ire/avoid-coverage-upload
Avoid coverage upload for merge up pull requests
2024-11-28 07:54:05 +01:00
Grégoire Paris
4563f2f9a7 Merge pull request #11737 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-11-27 22:10:21 +01:00
Grégoire Paris
38c6569645 Avoid coverage upload for merge up pull requests
When there are no conflicts between branches, we create pull requests
where the head branch is a branch on the origin repository. That branch
points to a commit that should already have coverage information
provided by the build that happens after merging a regular pull request.

The thing is, coverage information provided by builds that happen before
merging a pull request are associated with the commit of the head
repository. This means that when merging up 1.2 into 1.3, the build
produces coverage information that is the result of a merge between 1.2
and 1.3, and associates it with 1.2, although it is run on with a
codebase that is much closer to 1.3 (and is in fact supposed to become
1.3 after the merge).

This means that when we create a merge up PR from 1.2 to anything else,
the coverage information is going to be wrong until a PR targeting 1.2
gets merged.

I do not think we need coverage about conflictless merge up PRs more
than we need accurate numbers, so I propose we disable the upload for
those instead of, say, trying to associate them with the temporary merge
commit.
2024-11-27 21:39:44 +01:00
Grégoire Paris
7c0eebe90a Merge pull request #11733 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-5.3.0
Bump doctrine/.github from 5.2.0 to 5.3.0
2024-11-25 09:11:24 +01:00
dependabot[bot]
8784f2bce9 Bump doctrine/.github from 5.2.0 to 5.3.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/5.2.0...5.3.0)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 06:38:50 +00:00
Grégoire Paris
fbcac42ebd Merge pull request #11732 from greg0ire/phpstan-lvl-7
Raise PHPStan level to 7
2024-11-24 23:44:33 +01:00
Grégoire Paris
619302dc9a Raise PHPStan level to 7
We have a plan to drop Psalm. Before we do that, let us ensure we run
PHPStan at the level we agreed upon during the Hackathon.
2024-11-24 21:50:03 +01:00
Grégoire Paris
91201c094a Merge pull request #11722 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-11-23 19:35:45 +01:00
Grégoire Paris
82e2c981da Merge pull request #11556 from k00ni/patch-1
[Docs] unitofwork.rst: php => PHP
2024-11-19 13:07:44 +01:00
Konrad Abicht
8422a41423 unitofwork.rst: php => PHP 2024-11-19 13:06:17 +01:00
Grégoire Paris
58ad1d9678 Merge pull request #11709 from lyrixx/fix-event-doc
Fix `Events::onFlush` and `PostFlush()` documentation: events are always raised
2024-11-18 22:06:18 +01:00
Grégoire Pineau
346c49832c Fix Events::onFlush and PostFlush() documentation: events are always raised
see 9e2bfa8169/src/UnitOfWork.php (L399-L413)
2024-11-18 21:51:27 +01:00
Grégoire Paris
f140651ff0 Merge pull request #11719 from greg0ire/ignore-deprecation
Ignore deprecation about StaticReflectionService
2024-11-18 21:50:12 +01:00
Grégoire Paris
bb5b2a3300 Ignore deprecation about StaticReflectionService
It is from a class that is deprecated and removed in later branches.
2024-11-18 21:46:52 +01:00
Grégoire Paris
ba11851ac4 Merge pull request #11718 from doctrine/dependabot/github_actions/2.20.x/codecov/codecov-action-5
Bump codecov/codecov-action from 4 to 5
2024-11-18 08:42:38 +01:00
dependabot[bot]
4fbce94999 Bump codecov/codecov-action from 4 to 5
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 06:30:54 +00:00
Grégoire Paris
486e406236 Merge pull request #10065 from goetas/lazy-eager-collection-refresh
Lazy and eager collection refresh inconsistency
2024-11-14 23:43:20 +01:00
Asmir Mustafic
7d1b24f3b1 attempt a fix 2024-11-14 20:15:56 +01:00
Asmir Mustafic
43ce0bef78 lazy and eager collection refresh 2024-11-14 20:06:32 +01:00
Grégoire Paris
d9aa6ef6dc Merge pull request #11716 from acasademont/patch-1
Add `isEmpty()` method to the Extra Lazy Associations tutorial
2024-11-14 08:03:31 +01:00
Albert Casademont
ff3ccff36a Add isEmpty() method to the Extra Lazy Associations tutorial
Extra lazy support for it was added a long time ago
(see https://github.com/doctrine/orm/pull/912) but was never properly
documented.
2024-11-13 23:59:12 +01:00
Joren Martens
14866461c5 [DDC-551] fix, add filter support in oneToOne relation 2024-11-07 10:48:16 +01:00
Indra Gunawan
9e2bfa8169 Run tests against PostgreSQL 17 (#11697)
* Run tests against PostgreSQL 17

* remove pgsql 15
2024-10-25 14:57:39 +02:00
Grégoire Paris
3ca9529c32 Merge pull request #11694 from dbannik/Bug-join-sql-when-change-sqlFilter-parameters
BUG: When changing SQLFilter parameter, resulting SQL query is not generated correctly
2024-10-23 21:23:17 +02:00
Dzmitry Bannik
439b4dacf4 Is not correctly generated sql when changed/switched sqlFilter parameters
CachedPersisterContext::$selectJoinSql should be clear or regenerated when sqlFilter changed
The problem reproduce when in use fetch=EAGER and use additional sql filter on this property
2024-10-23 12:02:03 +03:00
Grégoire Paris
a4a15ad243 Merge pull request #11687 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-10-16 23:37:08 +02:00
Grégoire Paris
182469b346 Fix copy/paste/adapt mistake (#11684)
The last step was missing.
2024-10-16 22:59:19 +02:00
Grégoire Paris
e47398ecc5 Remove leftovers from Sphinx (#11683)
We use phpDocumentor/guides now, no need for this.
2024-10-16 22:08:16 +02:00
Grégoire Paris
9e884ccf1f Merge pull request #11453 from MatteoFeltrin/allow-fqcn-in-value-attribute-of-discriminator-mapping
Allow classname in 'value' attribute of xml discriminator-mapping field
2024-10-16 08:06:49 +02:00
dependabot[bot]
982d6060a3 Bump doctrine/.github from 5.1.0 to 5.2.0 (#11680) 2024-10-14 08:56:04 +02:00
Grégoire Paris
013f850c76 Merge pull request #11676 from derrabus/chore/phpunit-deprecations
Fix PHPUnit deprecations
2024-10-14 08:17:49 +02:00
Grégoire Paris
ef4508e52f Merge pull request #11667 from greg0ire/literalinclude
Experiment with literalinclude
2024-10-14 08:15:17 +02:00
Alexander M. Turek
f53350934f Psalm 5.26.1 (#11677) 2024-10-13 22:04:07 +02:00
Grégoire Paris
4ff909044e Update README (#11673) 2024-10-13 21:45:35 +02:00
Alexander M. Turek
32682aa14d Fix PHPUnit deprecations 2024-10-13 21:17:33 +02:00
dependabot[bot]
bd20df1043 Bump doctrine/.github from 5.1.0 to 5.2.0 (#11671) 2024-10-13 12:47:29 +02:00
Grégoire Paris
2ec2030ab2 Experiment with literalinclude
I think it would be great to use literalinclude for big code snippets,
because our IDEs could warn us about issues, and it would be easily to
showcase our coding standard. Before we do that though, let us validate
that it renders as expected. I have picked a complex example where we
have a configuration block.
2024-10-12 15:46:50 +02:00
Grégoire Paris
8ed6c2234a Merge pull request #11661 from doctrine/2.19.x
Merge 2.19.x up into 2.20.x
2024-10-11 13:47:24 +02:00
Grégoire Paris
ff612b9678 Merge pull request #11660 from simPod/test-method
test: cover all transactional methods in `EntityManagerTest::testItPreservesTheOriginalExceptionOnRollbackFailure()`
2024-10-11 13:11:31 +02:00
Simon Podlipsky
ee0d7197dd test: cover all transactional methods in EntityManagerTest::testItPreservesTheOriginalExceptionOnRollbackFailure() 2024-10-11 13:00:52 +02:00
Matthias Pigulla
39d2136f46 Fix different first/max result values taking up query cache space (#11188)
* Add a test covering the #11112 issue

* Add new OutputWalker and SqlFinalizer interfaces

* Add a SingleSelectSqlFinalizer that can take care of adding offset/limit as well as locking mode statements to a given SQL query.

Add a FinalizedSelectExecutor that executes given, finalized SQL statements.

* In SqlWalker, split SQL query generation into the two parts that shall happen before and after the finalization phase.

Move the part that generates "pre-finalization" SQL into a dedicated method. Use a side channel in SingleSelectSqlFinalizer to access the "finalization" logic and avoid duplication.

* Fix CS violations

* Skip the GH11112 test while applying refactorings

* Avoid a Psalm complaint due to invalid (?) docblock syntax

* Establish alternate code path - queries can obtain the sql executor through the finalizer, parser knows about output walkers yielding finalizers

* Remove a possibly premature comment

* Re-enable the #11112 test

* Fix CS

* Make RootTypeWalker inherit from SqlOutputWalker so it becomes finalizer-aware

* Update QueryCacheTest, since first/max results no longer need extra cache entries

* Fix ParserResultSerializationTest by forcing the parser to produce a ParserResult of the old kind (with the executor already constructed)

* Fix WhereInWalkerTest

* Update lib/Doctrine/ORM/Query/Exec/PreparedExecutorFinalizer.php

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

* Fix tests

* Fix a Psalm complaint

* Fix a test

* Fix CS

* Make the NullSqlWalker an instance of SqlOutputWalker

* Avoid multiple cache entries caused by LimitSubqueryOutputWalker

* Fix Psalm complaints

* Fix static analysis complaints

* Remove experimental code that I committed accidentally

* Remove unnecessary baseline entry

* Make AddUnknownQueryComponentWalker subclass SqlOutputWalker

That way, we have no remaining classes in the codebase subclassing SqlWalker but not SqlOutputWalker

* Use more expressive exception classes

* Add a deprecation message

* Move SqlExecutor creation to ParserResult, to minimize public methods available on it

* Avoid keeping the SqlExecutor in the Query, since it must be generated just in time (e. g. in case Query parameters change)

* Address PHPStan complaints

* Fix tests

* Small refactorings

* Add an upgrade notice

* Small refactorings

* Update the Psalm baseline

* Add a missing namespace import

* Update Psalm baseline

* Fix CS

* Fix Psalm baseline

---------

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2024-10-10 15:15:08 +02:00
Benjamin Eberlei
bea454eefc [GH-8471] undeprecate partials completly (#11647)
* [GH-8471] Undeprecate all PARTIAL object usage.
2024-10-10 13:54:34 +02:00
Grégoire Paris
14f2572e4e Merge pull request #11656 from doctrine/2.19.x
Merge 2.19.x up into 2.20.x
2024-10-10 13:53:05 +02:00
Grégoire Paris
c2c500077b Merge pull request #11646 from greg0ire/finally-fix-bug
Run risky code in finally block
2024-10-10 11:46:49 +02:00
Grégoire Paris
6281c2b79f Merge pull request #11655 from greg0ire/submodule-cleanup
Submodule cleanup
2024-10-10 11:12:12 +02:00
Grégoire Paris
bac1c17eab Remove submodule remnant
This should make a warning we have in the CI go away.

>  fatal: No url found for submodule path 'docs/en/_theme' in .gitmodules
2024-10-10 11:07:38 +02:00
Grégoire Paris
b6137c8911 Add guard clause
It maybe happen that the SQL COMMIT statement is successful, but then
something goes wrong. In that kind of case, you do not want to attempt a
rollback.

This was implemented in UnitOfWork::commit(), but for some reason not in
the similar EntityManager methods.
2024-10-10 10:58:24 +02:00
Grégoire Paris
51be1b1d52 Run risky code in finally block
catch blocks are not supposed to fail. If you want to do something
despite an exception happening, you should do it in a finally block.

Closes #7545
2024-10-10 10:06:12 +02:00
Alexander M. Turek
488a5dd3bf Remove vendor prefix of PHPDoc referencing class-string (#11643) 2024-10-09 21:58:37 +02:00
Matthias Pigulla
896c65504d Deprecate the \Doctrine\ORM\Query\Parser::setCustomOutputTreeWalker() method (#11641)
We use this method only from within one of our own test cases, and I don't see how it would be useful to anybody else outside – it has to be called on the `Parser` instance which exists internally in the `Query` only.

Deprecating and removing it in 3.x allows for a slight simplification in the `Parser` there, since we do no longer need the field (it can be a local variable).
2024-10-09 16:12:41 +02:00
Matthias Pigulla
16a8f10fd2 Remove a misleading comment (#11644) 2024-10-09 15:37:04 +02:00
Alexander M. Turek
d80a831157 Stop recommending vendor-prefixed PHPDoc (#11640) 2024-10-09 14:48:42 +02:00
Alexander M. Turek
52660297ab Let PHPStan detect deprecated usages (#11639) 2024-10-09 14:47:57 +02:00
Alexander M. Turek
58287bb731 Merge branch '2.19.x' into 2.20.x
* 2.19.x:
  PHPStan 1.12.6 (#11635)
2024-10-09 11:08:42 +02:00
Alexander M. Turek
bc37f75b41 PHPStan 1.12.6 (#11635) 2024-10-09 11:08:02 +02:00
Grégoire Paris
8a25b264f7 Add upgrade note about property hooks (#11636)
People that might have experimented with property hooks while still
using ORM < 2.20.0 need to know that they need to remove their
experiment or upgrade to a version that explicitly supports them.
2024-10-09 11:05:58 +02:00
Benjamin Eberlei
0e48b19cd3 Prepare PHP 8.4 support: Prevent property hooks from being used (#11628)
Prevent property hooks from being used as they currently would work on external non-raw values without explicit code.
2024-10-09 10:36:21 +02:00
Grégoire Paris
109042e5af Merge pull request #11631 from greg0ire/php84-ci
Add CI job for PHP 8.4
2024-10-09 09:42:08 +02:00
Grégoire Paris
08328adc6c Use E_ALL instead of E_ALL | E_STRICT
E_STRICT is deprecated as of PHP 8.4
2024-10-09 09:19:32 +02:00
Grégoire Paris
65806884b0 Add CI job for PHP 8.4
For now doctrine/common generates proxies that trigger deprecation, so
let us only test with lazy ghosts only.
2024-10-08 17:56:38 +02:00
Alexander M. Turek
ad80e8281a Merge branch '2.19.x' into 2.20.x
* 2.19.x:
  Replace custom directives with native option
2024-10-08 15:54:57 +02:00
Grégoire Paris
0c0c61c51b Merge pull request #11627 from greg0ire/no-custom-directives
Replace custom directives with native option
2024-10-08 15:26:44 +02:00
Grégoire Paris
cc28fed9f5 Replace custom directives with native option 2024-10-08 14:43:18 +02:00
Alexander M. Turek
2245149588 Merge branch '2.19.x' into 2.20.x
* 2.19.x:
  Make nullable parameters explicit in generated entities (#11625)
  Update attributes-reference.rst
  Bump doctrine/.github from 5.0.1 to 5.1.0 (#11616)
  Move orphan metadata to where it belongs
  PHPStan 1.12 (#11585)
2024-10-08 12:26:50 +02:00
Alexander M. Turek
b13564c6c0 Make nullable parameters explicit in generated entities (#11625) 2024-10-08 12:25:31 +02:00
Max Mustermann
91709c1275 fix generating duplicate method stubs
When adding the same lifecycle event callback to two or more lifecycle events, the generator will create a stub for each event resulting in fatal 'Cannot redeclare' errors. That is, only if the callback name contains uppercase letters.
2024-10-05 13:40:04 +02:00
Grégoire Paris
d18126aac5 Merge pull request #11618 from n0099/patch-1
unclosed `]` in attributes-reference.rst
2024-10-01 17:27:04 +02:00
n0099
b7fd8241cf Update attributes-reference.rst 2024-10-01 21:19:44 +08:00
dependabot[bot]
2432939e4f Bump doctrine/.github from 5.0.1 to 5.1.0 (#11616)
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 5.0.1 to 5.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/5.0.1...5.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 09:04:06 +02:00
Grégoire Paris
1bf4603422 Merge pull request #11615 from greg0ire/move-orphan
Move orphan metadata to where it belongs
2024-09-29 09:22:54 +02:00
Grégoire Paris
25d5bc5b46 Move orphan metadata to where it belongs
The goal here was to retain compatibility with doctrine/rst-parser,
which is no longer in use in the website.
2024-09-27 19:42:13 +02:00
Alexander M. Turek
6cde337777 PHPStan 1.12 (#11585) 2024-08-27 12:10:07 +02:00
Grégoire Paris
74ef28295a Merge pull request #11582 from doctrine/2.19.x-merge-up-into-2.20.x_0oKsBvVN
Merge release 2.19.7 into 2.20.x
2024-08-23 12:29:43 +02:00
Grégoire Paris
7d01f19667 Merge pull request #11531 from doctrine/2.19.x-merge-up-into-2.20.x_QMtlHSin
Merge release 2.19.6 into 2.20.x
2024-06-27 17:50:50 +02:00
Grégoire Paris
d0e9177121 Merge pull request #11514 from doctrine/2.19.x
Merge 2.19.x up into 2.20.x
2024-06-20 22:51:33 +02:00
Alexander M. Turek
83851a9716 Merge branch '2.19.x' into 2.20.x
* 2.19.x:
  Fix OneToManyPersister::deleteEntityCollection missing discriminator column/value. (GH-11500)
  Skip joined entity creation for empty relation (#10889)
  ci: maintained and stable mariadb version (11.4 current lts) (#11490)
  fix(docs): use string value in `addAttribute`
  Replace assertion with exception (#11489)
  Use ramsey/composer-install in PHPBench workflow
  update EntityManager#transactional to EntityManager#wrapInTransaction
  Fix cloning entities
  Consider usage of setFetchMode when checking for simultaneous usage of fetch-mode EAGER and WITH condition.
2024-06-18 14:19:19 +02:00
Alexander M. Turek
066ec1ac81 Fix upgrade guide for 2.20 (#11504) 2024-06-18 14:18:37 +02:00
Benjamin Eberlei
68744489f0 Undeprecate PARTIAL for array hydration. (#11366)
* Undeprecate PARTIAL for array hydration.

* note about undeprecate partial in UPGRADE.md
2024-06-18 14:15:31 +02:00
Alexander M. Turek
bf3e082c00 Merge branch '2.19.x' into 2.20.x
* 2.19.x:
  Psalm 5.24.0 (#11467)
  PHPStan 1.11.1 (#11466)
  Test with actual lock modes (#11465)
  Backport test for Query::setLockMode() (#11463)
2024-05-21 14:22:18 +02:00
Alexander M. Turek
eb49f66926 Merge branch '2.19.x' into 2.20.x
* 2.19.x:
  Bump ramsey/composer-install from 2 to 3 (#11442)
  Bump doctrine/.github from 3.0.0 to 5.0.1
  Upgrade codecov/codecov-action
2024-05-21 08:40:37 +02:00
MatteoFeltrin
73e30df52b allow classname in 'value' attribute of xml discriminator-mapping field 2024-05-20 11:00:23 +02:00
Grégoire Paris
8b6a58fa0e Merge pull request #11432 from doctrine/2.19.x-merge-up-into-2.20.x_IfraK93L
Merge release 2.19.5 into 2.20.x
2024-04-30 09:04:52 +02:00
Alexander M. Turek
b725908c83 Merge branch '2.19.x' into 2.20.x
* 2.19.x:
  Fix BIGINT validation (#11414)
  Fix templated phpdoc return type (#11407)
  [Documentation] Merging "Query Result Formats" with "Hydration Modes"
  Fix psalm errors: remove override of template type
  Update dql-doctrine-query-language.rst
  Adding `NonUniqueResultException`
  [Documentation] Query Result Formats
2024-04-15 16:26:53 +02:00
Alexander M. Turek
be307edba8 Merge release 2.19.3 into 2.20.x (#11398) 2024-03-22 12:11:39 +01:00
Alexander M. Turek
083f642cfa Merge branch '2.19.x' into 2.20.x
* 2.19.x:
  Remove unused variable (#11391)
2024-03-21 10:33:34 +01:00
Alexander M. Turek
716da7e538 Merge branch '2.19.x' into 2.20.x
* 2.19.x:
  [Documentation] Removing "Doctrine Mapping Types" ... (#11384)
  [GH-11185] Bugfix: do not use collection batch loading for indexBy assocations. (#11380)
  Improve lazy ghost performance by avoiding self-referencing closure. (#11376)
2024-03-21 10:12:37 +01:00
Grégoire Paris
bcdc5bdaf4 Merge pull request #11378 from doctrine/2.19.x-merge-up-into-2.20.x_eyF2lMAL
Merge release 2.19.2 into 2.20.x
2024-03-18 20:22:04 +01:00
Grégoire Paris
a3e3a3bbf3 Merge pull request #11360 from doctrine/2.19.x-merge-up-into-2.20.x_aXnS7Xw9
Merge release 2.19.1 into 2.20.x
2024-03-16 21:32:18 +01:00
1655 changed files with 85745 additions and 30825 deletions

View File

@@ -11,23 +11,17 @@
"slug": "latest",
"upcoming": true
},
{
"name": "3.3",
"branchName": "3.3.x",
"slug": "3.3",
"upcoming": true
},
{
"name": "3.2",
"branchName": "3.2.x",
"slug": "3.2",
"current": true
"upcoming": true
},
{
"name": "3.1",
"branchName": "3.1.x",
"slug": "3.1",
"maintained": false
"current": true
},
{
"name": "3.0",
@@ -100,6 +94,42 @@
"branchName": "2.10.x",
"slug": "2.10",
"maintained": false
},
{
"name": "2.9",
"branchName": "2.9.x",
"slug": "2.9",
"maintained": false
},
{
"name": "2.8",
"branchName": "2.8.x",
"slug": "2.8",
"maintained": false
},
{
"name": "2.7",
"branchName": "2.7",
"slug": "2.7",
"maintained": false
},
{
"name": "2.6",
"branchName": "2.6",
"slug": "2.6",
"maintained": false
},
{
"name": "2.5",
"branchName": "2.5",
"slug": "2.5",
"maintained": false
},
{
"name": "2.4",
"branchName": "2.4",
"slug": "2.4",
"maintained": false
}
]
}

3
.gitattributes vendored
View File

@@ -11,7 +11,6 @@ build.properties.dev export-ignore
build.xml export-ignore
CONTRIBUTING.md export-ignore
phpunit.xml.dist export-ignore
run-all.sh export-ignore
phpcs.xml.dist export-ignore
phpbench.json export-ignore
phpstan.neon export-ignore
@@ -19,5 +18,3 @@ phpstan-baseline.neon export-ignore
phpstan-dbal2.neon export-ignore
phpstan-params.neon export-ignore
phpstan-persistence2.neon export-ignore
psalm.xml export-ignore
psalm-baseline.xml export-ignore

View File

@@ -1,9 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "CI"
target-branch: "2.19.x"

View File

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

View File

@@ -1,4 +1,4 @@
name: "Continuous Integration"
name: "CI"
on:
pull_request:
@@ -33,32 +33,39 @@ jobs:
strategy:
matrix:
php-version:
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
extension:
- "sqlite3"
- "pdo_sqlite"
deps:
- "highest"
proxy:
- "common"
include:
- php-version: "8.2"
dbal-version: "4@dev"
- php-version: "8.0"
dbal-version: "2.13"
extension: "pdo_sqlite"
- php-version: "8.2"
dbal-version: "4@dev"
dbal-version: "3@dev"
extension: "pdo_sqlite"
- php-version: "8.2"
dbal-version: "default"
extension: "sqlite3"
- php-version: "8.1"
dbal-version: "default"
deps: "lowest"
proxy: "lazy-ghost"
extension: "pdo_sqlite"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
fetch-depth: 2
@@ -78,22 +85,23 @@ jobs:
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
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@v4"
uses: "actions/upload-artifact@v5"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.proxy }}-coverage"
path: "coverage*.xml"
@@ -107,21 +115,23 @@ jobs:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
- "3@dev"
postgres-version:
- "15"
- "17"
extension:
- pdo_pgsql
- pgsql
include:
- php-version: "8.2"
dbal-version: "4@dev"
- php-version: "8.0"
dbal-version: "2.13"
postgres-version: "14"
extension: pdo_pgsql
- php-version: "8.2"
dbal-version: "3.7"
dbal-version: "default"
postgres-version: "9.6"
extension: pdo_pgsql
@@ -139,7 +149,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
fetch-depth: 2
@@ -164,7 +174,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v5"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
path: "coverage.xml"
@@ -180,15 +190,21 @@ jobs:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
- "4@dev"
- "3@dev"
mariadb-version:
- "11.4"
extension:
- "mysqli"
- "pdo_mysql"
include:
- php-version: "8.0"
dbal-version: "2.13"
mariadb-version: "10.6"
extension: "pdo_mysql"
services:
mariadb:
@@ -205,7 +221,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
fetch-depth: 2
@@ -230,7 +246,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v5"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
@@ -246,9 +262,11 @@ jobs:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
- "3@dev"
mysql-version:
- "5.7"
- "8.0"
@@ -256,12 +274,8 @@ jobs:
- "mysqli"
- "pdo_mysql"
include:
- php-version: "8.2"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "mysqli"
- php-version: "8.2"
dbal-version: "4@dev"
- php-version: "8.0"
dbal-version: "2.13"
mysql-version: "8.0"
extension: "pdo_mysql"
@@ -279,7 +293,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
fetch-depth: 2
@@ -311,14 +325,50 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v5"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
phpunit-lower-php-versions:
name: "PHPUnit with SQLite"
runs-on: "ubuntu-22.04"
strategy:
matrix:
php-version:
- "7.1"
deps:
- "highest"
- "lowest"
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_sqlite.xml"
upload_coverage:
name: "Upload coverage to Codecov"
runs-on: "ubuntu-22.04"
# Only run on PRs from forks
if: "github.event.pull_request.head.repo.full_name != github.repository"
needs:
- "phpunit-smoke-check"
- "phpunit-postgres"
@@ -327,17 +377,17 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v4"
uses: "actions/download-artifact@v6"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v4"
uses: "codecov/codecov-action@v5"
with:
directory: reports
env:

View File

@@ -5,45 +5,16 @@ on:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**
- ".github/workflows/documentation.yml"
- "docs/**"
push:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**
- ".github/workflows/documentation.yml"
- "docs/**"
jobs:
validate-with-guides:
name: "Validate documentation with phpDocumentor/guides"
runs-on: "ubuntu-22.04"
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.3"
- name: "Remove existing composer file"
run: "rm composer.json"
- name: "Require phpdocumentor/guides-cli"
run: "composer require --dev phpdocumentor/guides-cli --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "highest"
- name: "Add orphan metadata where needed"
run: |
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/reference/installation.rst)" > docs/en/reference/installation.rst
- name: "Run guides-cli"
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )"
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@12.1.0"

View File

@@ -32,11 +32,11 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "7.4"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
fetch-depth: 2

View File

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

View File

@@ -9,7 +9,6 @@ on:
- composer.*
- src/**
- phpstan*
- psalm*
- tests/StaticAnalysis/**
push:
branches:
@@ -19,71 +18,56 @@ on:
- composer.*
- src/**
- phpstan*
- psalm*
- tests/StaticAnalysis/**
jobs:
static-analysis-phpstan:
name: Static Analysis with PHPStan
runs-on: ubuntu-22.04
strategy:
matrix:
include:
- dbal-version: default
config: phpstan.neon
- dbal-version: 3.8.2
config: phpstan-dbal3.neon
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
coverage: none
php-version: "8.3"
tools: cs2pr
- name: Require specific DBAL version
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: Install dependencies with Composer
uses: ramsey/composer-install@v2
- name: Run static analysis with phpstan/phpstan
run: "vendor/bin/phpstan analyse -c ${{ matrix.config }} --error-format=checkstyle | cs2pr"
static-analysis-psalm:
name: Static Analysis with Psalm
runs-on: ubuntu-22.04
name: "Static Analysis with PHPStan"
runs-on: "ubuntu-22.04"
strategy:
fail-fast: false
matrix:
dbal-version:
- default
- 3.8.2
- "default"
persistence-version:
- "default"
include:
- dbal-version: "2.13"
persistence-version: "default"
- dbal-version: "default"
persistence-version: "2.5"
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
- name: Install PHP
uses: shivammathur/setup-php@v2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: none
php-version: "8.3"
tools: cs2pr
coverage: "none"
php-version: "8.4"
- name: Require specific DBAL version
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: Install dependencies with Composer
uses: ramsey/composer-install@v3
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^$([ ${{ matrix.persistence-version }} = default ] && echo '3.1' || echo ${{ matrix.persistence-version }}) --no-update"
- name: Run static analysis with Vimeo Psalm
run: vendor/bin/psalm --shepherd
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "highest"
- name: "Run a static analysis with phpstan/phpstan"
run: "vendor/bin/phpstan analyse"
if: "${{ matrix.dbal-version == 'default' && matrix.persistence-version == 'default'}}"
- name: "Run a static analysis with phpstan/phpstan"
run: "vendor/bin/phpstan analyse -c phpstan-dbal2.neon"
if: "${{ matrix.dbal-version == '2.13' }}"
- name: "Run a static analysis with phpstan/phpstan"
run: "vendor/bin/phpstan analyse -c phpstan-persistence2.neon"
if: "${{ matrix.dbal-version == 'default' && matrix.persistence-version != 'default'}}"

View File

@@ -57,7 +57,7 @@ sqlite database.
Tips for creating unit tests:
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
See `https://github.com/doctrine/orm/tree/3.0.x/tests/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
See `https://github.com/doctrine/orm/tree/2.8.x/tests/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
example.
## Getting merged

View File

@@ -1,11 +1,11 @@
| [4.0.x][4.0] | [3.3.x][3.3] | [3.2.x][3.2] | [2.20.x][2.20] | [2.19.x][2.19] |
| [4.0.x][4.0] | [3.6.x][3.6] | [3.5.x][3.5] | [2.21.x][2.21] | [2.20.x][2.20] |
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
| [![Build status][4.0 image]][4.0] | [![Build status][3.3 image]][3.3] | [![Build status][3.2 image]][3.2] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.3 coverage image]][3.3 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] |
| [![Build status][4.0 image]][4.0 workflow] | [![Build status][3.6 image]][3.6 workflow] | [![Build status][3.5 image]][3.5 workflow] | [![Build status][2.21 image]][2.21 workflow] | [![Build status][2.20 image]][2.20 workflow] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.6 coverage image]][3.6 coverage] | [![Coverage Status][3.5 coverage image]][3.5 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
Doctrine ORM is an object-relational mapper for PHP 7.1+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
@@ -20,21 +20,26 @@ without requiring unnecessary code duplication.
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
[4.0 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A4.0.x
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
[3.3 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.3.x
[3.3]: https://github.com/doctrine/orm/tree/3.3.x
[3.3 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.3.x/graph/badge.svg
[3.3 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.3.x
[3.2 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.2.x
[3.2]: https://github.com/doctrine/orm/tree/3.2.x
[3.2 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.2.x/graph/badge.svg
[3.2 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.2.x
[3.6 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.6.x
[3.6]: https://github.com/doctrine/orm/tree/3.6.x
[3.6 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.6.x
[3.6 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.6.x/graph/badge.svg
[3.6 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.6.x
[3.5 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.5.x
[3.5]: https://github.com/doctrine/orm/tree/3.5.x
[3.5 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.5.x
[3.5 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.5.x/graph/badge.svg
[3.5 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.5.x
[2.21 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.21.x
[2.21]: https://github.com/doctrine/orm/tree/2.21.x
[2.21 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.21.x
[2.21 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.21.x/graph/badge.svg
[2.21 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.21.x
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
[2.20 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.20.x
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
[2.19 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.19.x
[2.19]: https://github.com/doctrine/orm/tree/2.19.x
[2.19 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.19.x/graph/badge.svg
[2.19 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.19.x

View File

@@ -1,727 +1,38 @@
# Upgrade to 3.2
# Upgrade to 2.20
## Deprecate the `NotSupported` exception
## Add `Doctrine\ORM\Query\OutputWalker` interface, deprecate `Doctrine\ORM\Query\SqlWalker::getExecutor()`
The class `Doctrine\ORM\Exception\NotSupported` is deprecated without replacement.
Output walkers should implement the new `\Doctrine\ORM\Query\OutputWalker` interface and create
`Doctrine\ORM\Query\Exec\SqlFinalizer` instances instead of `Doctrine\ORM\Query\Exec\AbstractSqlExecutor`s.
The output walker must not base its workings on the query `firstResult`/`maxResult` values, so that the
`SqlFinalizer` can be kept in the query cache and used regardless of the actual `firstResult`/`maxResult` values.
Any operation dependent on `firstResult`/`maxResult` should take place within the `SqlFinalizer::createExecutor()`
method. Details can be found at https://github.com/doctrine/orm/pull/11188.
## Deprecate remaining `Serializable` implementation
## Explictly forbid property hooks
Relying on `SequenceGenerator` implementing the `Serializable` is deprecated
because that interface won't be implemented in ORM 4 anymore.
Property hooks are not supported yet by Doctrine ORM. Until support is added,
they are explicitly forbidden because the support would result in a breaking
change in behavior.
The following methods are deprecated:
Progress on this is tracked at https://github.com/doctrine/orm/issues/11624 .
* `SequenceGenerator::serialize()`
* `SequenceGenerator::unserialize()`
## PARTIAL DQL syntax is undeprecated
## `orm:schema-tool:update` option `--complete` is deprecated
Use of the PARTIAL keyword is not deprecated anymore in DQL, because we will be
able to support PARTIAL objects with PHP 8.4 Lazy Objects and
Symfony/VarExporter in a better way. When we decided to remove this feature
these two abstractions did not exist yet.
That option behaves as a no-op, and is deprecated. It will be removed in 4.0.
WARNING: If you want to upgrade to 3.x and still use PARTIAL keyword in DQL
with array or object hydrators, then you have to directly migrate to ORM 3.3.x or higher.
PARTIAL keyword in DQL is not available in 3.0, 3.1 and 3.2 of ORM.
## Deprecate properties `$indexes` and `$uniqueConstraints` of `Doctrine\ORM\Mapping\Table`
## Deprecate `\Doctrine\ORM\Query\Parser::setCustomOutputTreeWalker()`
The properties `$indexes` and `$uniqueConstraints` have been deprecated since they had no effect at all.
The preferred way of defining indices and unique constraints is by
using the `\Doctrine\ORM\Mapping\UniqueConstraint` and `\Doctrine\ORM\Mapping\Index` attributes.
# Upgrade to 3.1
## Deprecate `Doctrine\ORM\Mapping\ReflectionEnumProperty`
This class is deprecated and will be removed in 4.0.
Instead, use `Doctrine\Persistence\Reflection\EnumReflectionProperty` from
`doctrine/persistence`.
## Deprecate passing null to `ClassMetadata::fullyQualifiedClassName()`
Passing `null` to `Doctrine\ORM\ClassMetadata::fullyQualifiedClassName()` is
deprecated and will no longer be possible in 4.0.
## Deprecate array access
Using array access on instances of the following classes is deprecated:
- `Doctrine\ORM\Mapping\DiscriminatorColumnMapping`
- `Doctrine\ORM\Mapping\EmbedClassMapping`
- `Doctrine\ORM\Mapping\FieldMapping`
- `Doctrine\ORM\Mapping\JoinColumnMapping`
- `Doctrine\ORM\Mapping\JoinTableMapping`
# Upgrade to 3.0
## BC BREAK: Calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association now throws an exception
Previously, calling
`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with
the owning side of an association returned `null`, which was undocumented, and
wrong according to the phpdoc of the parent method.
If you do not know whether you are on the owning or inverse side of an association,
you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()`
to find out.
## BC BREAK: `Doctrine\ORM\Proxy\Autoloader` no longer extends `Doctrine\Common\Proxy\Autoloader`
Make sure to use the former when writing a type declaration or an `instanceof` check.
## Minor BC BREAK: Changed order of arguments passed to `OneToOne`, `ManyToOne` and `Index` mapping PHP attributes
To keep PHP mapping attributes consistent, order of arguments passed to above attributes has been changed
so `$targetEntity` is a first argument now. This change affects only non-named arguments usage.
## BC BREAK: AUTO keyword for identity generation defaults to IDENTITY for PostgreSQL when using `doctrine/dbal` 4
When using the `AUTO` strategy to let Doctrine determine the identity generation mechanism for
an entity, and when using `doctrine/dbal` 4, PostgreSQL now uses `IDENTITY`
instead of `SEQUENCE` or `SERIAL`.
* If you want to upgrade your existing tables to identity columns, you will need to follow [migration to identity columns on PostgreSQL](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.0/how-to/postgresql-identity-migration.html)
* If you want to keep using SQL sequences, you need to configure the ORM this way:
```php
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\Mapping\ClassMetadata;
assert($configuration instanceof Configuration);
$configuration->setIdentityGenerationPreferences([
PostgreSQLPlatform::CLASS => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
]);
```
## BC BREAK: Throw exceptions when using illegal attributes on Embeddable
There are only a few attributes allowed on an embeddable such as `#[Column]` or
`#[Embedded]`. Previously all others that target entity classes where ignored,
now they throw an exception.
## BC BREAK: Partial objects are removed
- The `PARTIAL` keyword in DQL no longer exists.
- `Doctrine\ORM\Query\AST\PartialObjectExpression`is removed.
- `Doctrine\ORM\Query\SqlWalker::HINT_PARTIAL` and
`Doctrine\ORM\Query::HINT_FORCE_PARTIAL_LOAD` are removed.
- `Doctrine\ORM\EntityManager*::getPartialReference()` is removed.
## BC BREAK: `Doctrine\ORM\Persister\Entity\EntityPersister::executeInserts()` return type changed to `void`
Implementors should adapt to the new signature, and should call
`UnitOfWork::assignPostInsertId()` for each entry in the previously returned
array.
## BC BREAK: `Doctrine\ORM\Proxy\ProxyFactory` no longer extends abstract factory from `doctrine/common`
It is no longer possible to call methods, constants or properties inherited
from that class on a `ProxyFactory` instance.
`Doctrine\ORM\Proxy\ProxyFactory::createProxyDefinition()` and
`Doctrine\ORM\Proxy\ProxyFactory::resetUninitializedProxy()` are removed as well.
## BC BREAK: lazy ghosts are enabled unconditionally
`Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()` and
`Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()` are now no-ops and
will be deprecated in 3.1.0
## BC BREAK: collisions in identity map are unconditionally rejected
`Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()` and
`Doctrine\ORM\Configuration::isRejectIdCollisionInIdentityMapEnabled()` are now
no-ops and will be deprecated in 3.1.0.
## BC BREAK: Lifecycle callback mapping on embedded classes is now explicitly forbidden
Lifecycle callback mapping on embedded classes produced no effect, and is now
explicitly forbidden to point out mistakes.
## BC BREAK: The `NOTIFY` change tracking policy is removed
You should use `DEFERRED_EXPLICIT` instead.
## BC BREAK: `Mapping\Driver\XmlDriver::__construct()` third argument is now enabled by default
The third argument to
`Doctrine\ORM\Mapping\Driver\XmlDriver::__construct()` was introduced to
let users opt-in to XML validation, that is now always enabled by default.
As a consequence, the same goes for
`Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver`, and for
`Doctrine\ORM\ORMSetup::createXMLMetadataConfiguration()`.
## BC BREAK: `Mapping\Driver\AttributeDriver::__construct()` second argument is now a no-op
The second argument to
`Doctrine\ORM\Mapping\Driver\AttributeDriver::__construct()` was introduced to
let users opt-in to a new behavior, that is now always enforced, regardless of
the value of that argument.
## BC BREAK: `Query::setDQL()` and `Query::setFirstResult()` no longer accept `null`
The `$dqlQuery` argument of `Doctrine\ORM\Query::setDQL()` must always be a
string.
The `$firstResult` argument of `Doctrine\ORM\Query::setFirstResult()` must
always be an integer.
## BC BREAK: `orm:schema-tool:update` option `--complete` is now a no-op
`orm:schema-tool:update` now behaves as if `--complete` was provided,
regardless of whether it is provided or not.
## BC BREAK: Removed `Doctrine\ORM\Proxy\Proxy` interface.
Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized.
## BC BREAK: Overriding fields or associations declared in other than mapped superclasses
As stated in the documentation, fields and associations may only be overridden when being inherited
from mapped superclasses. Overriding them for parent entity classes now throws a `MappingException`.
## BC BREAK: Undeclared entity inheritance now throws a `MappingException`
As soon as an entity class inherits from another entity class, inheritance has to
be declared by adding the appropriate configuration for the root entity.
## Removed `getEntityManager()` in `Doctrine\ORM\Event\OnClearEventArgs` and `Doctrine\ORM\Event\*FlushEventArgs`
Use `getObjectManager()` instead.
## BC BREAK: Removed `Doctrine\ORM\Mapping\ClassMetadataInfo` class
Use `Doctrine\ORM\Mapping\ClassMetadata` instead.
## BC BREAK: Removed `Doctrine\ORM\Event\LifecycleEventArgs` class.
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`
## BC BREAK: Removed `AttributeDriver::$entityAnnotationClasses` and `AttributeDriver::getReader()`
* If you need to change the behavior of `AttributeDriver::isTransient()`,
override that method instead.
* The attribute reader is internal to the driver and should not be accessed from outside.
## BC BREAK: Removed `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 replaced by
`SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`.
## BC BREAK: Changed `EntityManagerInterface#refresh($entity)`, `EntityManagerDecorator#refresh($entity)` and `UnitOfWork#refresh($entity)` signatures
The new signatures of these methods add an optional `LockMode|int|null $lockMode`
param with default `null` value (no lock).
## BC Break: Removed AnnotationDriver
The annotation driver and anything related to annotation has been removed.
Please migrate to another mapping driver.
The `Doctrine\ORM\Mapping\Annotation` maker interface has been removed in favor of the new
`Doctrine\ORM\Mapping\MappingAttribute` interface.
## BC BREAK: Removed `EntityManager::create()`
The constructor of `EntityManager` is now public and must 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.
## BC BREAK: Removed `QueryBuilder` methods and constants.
The following `QueryBuilder` constants and methods have been removed:
1. `SELECT`,
2. `DELETE`,
3. `UPDATE`,
4. `STATE_DIRTY`,
5. `STATE_CLEAN`,
6. `getState()`,
7. `getType()`.
## BC BREAK: Omitting only the alias argument for `QueryBuilder::update` and `QueryBuilder::delete` is not supported anymore
When building an UPDATE or DELETE query and when passing a class/type to the function, the alias argument must not be omitted.
### Before
```php
$qb = $em->createQueryBuilder()
->delete('User u')
->where('u.id = :user_id')
->setParameter('user_id', 1);
```
### After
```php
$qb = $em->createQueryBuilder()
->delete('User', 'u')
->where('u.id = :user_id')
->setParameter('user_id', 1);
```
## BC BREAK: Split output walkers and tree walkers
`SqlWalker` and its child classes don't implement the `TreeWalker` interface
anymore.
The following methods have been removed from the `TreeWalker` interface and
from the `TreeWalkerAdapter` and `TreeWalkerChain` classes:
* `setQueryComponent()`
* `walkSelectClause()`
* `walkFromClause()`
* `walkFunction()`
* `walkOrderByClause()`
* `walkOrderByItem()`
* `walkHavingClause()`
* `walkJoin()`
* `walkSelectExpression()`
* `walkQuantifiedExpression()`
* `walkSubselect()`
* `walkSubselectFromClause()`
* `walkSimpleSelectClause()`
* `walkSimpleSelectExpression()`
* `walkAggregateExpression()`
* `walkGroupByClause()`
* `walkGroupByItem()`
* `walkDeleteClause()`
* `walkUpdateClause()`
* `walkUpdateItem()`
* `walkWhereClause()`
* `walkConditionalExpression()`
* `walkConditionalTerm()`
* `walkConditionalFactor()`
* `walkConditionalPrimary()`
* `walkExistsExpression()`
* `walkCollectionMemberExpression()`
* `walkEmptyCollectionComparisonExpression()`
* `walkNullComparisonExpression()`
* `walkInExpression()`
* `walkInstanceOfExpression()`
* `walkLiteral()`
* `walkBetweenExpression()`
* `walkLikeExpression()`
* `walkStateFieldPathExpression()`
* `walkComparisonExpression()`
* `walkInputParameter()`
* `walkArithmeticExpression()`
* `walkArithmeticTerm()`
* `walkStringPrimary()`
* `walkArithmeticFactor()`
* `walkSimpleArithmeticExpression()`
* `walkPathExpression()`
* `walkResultVariable()`
* `getExecutor()`
The following changes have been made to the abstract `TreeWalkerAdapter` class:
* The method `setQueryComponent()` is now protected.
* The method `_getQueryComponents()` has been removed in favor of
`getQueryComponents()`.
## BC BREAK: Removed identity columns emulation through sequences
If the platform you are using does not support identity columns, you should
switch to the `SEQUENCE` strategy.
## BC BREAK: Made setters parameters mandatory
The following methods require an argument when being called. Pass `null`
instead of omitting the argument.
* `Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs::setFoundMetadata()`
* `Doctrine\ORM\AbstractQuery::setHydrationCacheProfile()`
* `Doctrine\ORM\AbstractQuery::setResultCache()`
* `Doctrine\ORM\AbstractQuery::setResultCacheProfile()`
## BC BREAK: New argument to `NamingStrategy::joinColumnName()`
### Before
```php
<?php
class MyStrategy implements NamingStrategy
{
/**
* @param string $propertyName A property name.
*/
public function joinColumnName($propertyName): string
{
// …
}
}
```
### After
The `class-string` type for `$className` can be inherited from the signature of
the interface.
```php
<?php
class MyStrategy implements NamingStrategy
{
/**
* {@inheritdoc}
*/
public function joinColumnName(string $propertyName, string $className): string
{
// …
}
}
```
## BC BREAK: Remove `StaticPHPDriver` and `DriverChain`
Use `Doctrine\Persistence\Mapping\Driver\StaticPHPDriver` and
`Doctrine\Persistence\Mapping\Driver\MappingDriverChain` from
`doctrine/persistence` instead.
## BC BREAK: `UnderscoreNamingStrategy` is number aware only
The second argument to `UnderscoreNamingStrategy::__construct()` was dropped,
the strategy can no longer be unaware of numbers.
## BC BREAK: Remove `Doctrine\ORM\Tools\DisconnectedClassMetadataFactory`
No replacement is provided.
## BC BREAK: Remove support for `Type::canRequireSQLConversion()`
This feature was deprecated in DBAL 3.3.0 and will be removed in DBAL 4.0.
The value conversion methods are now called regardless of the type.
The `MappingException::sqlConversionNotAllowedForIdentifiers()` method has been removed
as no longer relevant.
## BC Break: Removed the `doctrine` binary.
The documentation explains how the console tools can be bootstrapped for
standalone usage:
https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/tools.html
The method `ConsoleRunner::printCliConfigTemplate()` has been removed as well
because it was only useful in the context of the `doctrine` binary.
## BC Break: Removed `EntityManagerHelper` and related logic
All console commands require a `$entityManagerProvider` to be passed via the
constructor. Commands won't try to get the entity manager from a previously
registered `em` console helper.
The following classes have been removed:
* `Doctrine\ORM\Tools\Console\EntityManagerProvider\HelperSetManagerProvider`
* `Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper`
The following breaking changes have been applied to `Doctrine\ORM\Tools\Console\ConsoleRunner`:
* The method `createHelperSet()` has been removed.
* The methods `run()` and `createApplication()` don't accept an instance of
`HelperSet` as first argument anymore.
* The method `addCommands()` requires an instance of `EntityManagerProvider`
as second argument now.
## BC Break: `Exception\ORMException` is no longer a class, but an interface
All methods in `Doctrine\ORM\ORMException` have been extracted to dedicated exceptions.
* `missingMappingDriverImpl()` => `Exception\MissingMappingDriverImplementation::create()`
* `unrecognizedField()` => `Persisters\Exception\UnrecognizedField::byName()`
* `unexpectedAssociationValue()` => `Exception\UnexpectedAssociationValue::create()`
* `invalidOrientation()` => `Persisters\Exception\InvalidOrientation::fromClassNameAndField()`
* `entityManagerClosed()` => `Exception\EntityManagerClosed::create()`
* `invalidHydrationMode()` => `Exception\InvalidHydrationMode::fromMode()`
* `mismatchedEventManager()` => `Exception\MismatchedEventManager::create()`
* `findByRequiresParameter()` => `Repository\Exception\InvalidMagicMethodCall::onMissingParameter()`
* `invalidMagicCall()` => `Repository\Exception\InvalidMagicMethodCall::becauseFieldNotFoundIn()`
* `invalidFindByInverseAssociation()` => `Repository\Exception\InvalidFindByCall::fromInverseSideUsage()`
* `invalidResultCacheDriver()` => `Cache\Exception\InvalidResultCacheDriver::create()`
* `notSupported()` => `Exception\NotSupported::create()`
* `queryCacheNotConfigured()` => `QueryCacheNotConfigured::create()`
* `metadataCacheNotConfigured()` => `Cache\Exception\MetadataCacheNotConfigured::create()`
* `queryCacheUsesNonPersistentCache()` => `Cache\Exception\QueryCacheUsesNonPersistentCache::fromDriver()`
* `metadataCacheUsesNonPersistentCache()` => `Cache\Exception\MetadataCacheUsesNonPersistentCache::fromDriver()`
* `proxyClassesAlwaysRegenerating()` => `Exception\ProxyClassesAlwaysRegenerating::create()`
* `invalidEntityRepository()` => `Exception\InvalidEntityRepository::fromClassName()`
* `missingIdentifierField()` => `Exception\MissingIdentifierField::fromFieldAndClass()`
* `unrecognizedIdentifierFields()` => `Exception\UnrecognizedIdentifierFields::fromClassAndFieldNames()`
* `cantUseInOperatorOnCompositeKeys()` => `Persisters\Exception\CantUseInOperatorOnCompositeKeys::create()`
## BC Break: `CacheException` is no longer a class, but an interface
All methods in `Doctrine\ORM\Cache\CacheException` have been extracted to dedicated exceptions.
* `updateReadOnlyCollection()` => `Cache\Exception\CannotUpdateReadOnlyCollection::fromEntityAndField()`
* `updateReadOnlyEntity()` => `Cache\Exception\CannotUpdateReadOnlyEntity::fromEntity()`
* `nonCacheableEntity()` => `Cache\Exception\NonCacheableEntity::fromEntity()`
* `nonCacheableEntityAssociation()` => `Cache\Exception\NonCacheableEntityAssociation::fromEntityAndField()`
## BC Break: Missing type declaration added for identifier generators
Although undocumented, it was possible to configure a custom repository
class that implements `ObjectRepository` but does not extend the
`EntityRepository` base class. Repository classes have to extend
`EntityRepository` now.
## BC BREAK: Removed support for entity namespace alias
- `EntityManager::getRepository()` no longer accepts the entity namespace alias
notation.
- `Configuration::addEntityNamespace()` and
`Configuration::getEntityNamespace()` have been removed.
## BC BREAK: Remove helper methods from `AbstractCollectionPersister`
The following protected methods of
`Doctrine\ORM\Cache\Persister\Collection\AbstractCollectionPersister`
have been removed.
* `evictCollectionCache()`
* `evictElementCache()`
## BC BREAK: `Doctrine\ORM\Query\TreeWalkerChainIterator`
This class has been removed without replacement.
## BC BREAK: Remove quoting methods from `ClassMetadata`
The following methods have been removed from the class metadata because
quoting is handled by implementations of `Doctrine\ORM\Mapping\QuoteStrategy`:
* `getQuotedIdentifierColumnNames()`
* `getQuotedColumnName()`
* `getQuotedTableName()`
* `getQuotedJoinTableName()`
## BC BREAK: Remove ability to merge detached entities
Merge semantics was a poor fit for the PHP "share-nothing" architecture.
In addition to that, merging caused multiple issues with data integrity
in the managed entity graph, which was constantly spawning more edge-case
bugs/scenarios.
The method `UnitOfWork::merge()` has been removed. The method
`EntityManager::merge()` will throw an exception on each call.
## BC BREAK: Removed ability to partially flush/commit entity manager and unit of work
The following methods don't accept a single entity or an array of entities anymore:
* `Doctrine\ORM\EntityManager::flush()`
* `Doctrine\ORM\Decorator\EntityManagerDecorator::flush()`
* `Doctrine\ORM\UnitOfWork::commit()`
The semantics of `flush()` and `commit()` will remain the same, but the change
tracking will be performed on all entities managed by the unit of work, and not
just on the provided entities, as the parameter is now completely ignored.
## BC BREAK: Removed ability to partially clear entity manager and unit of work
* Passing an argument other than `null` to `EntityManager::clear()` will raise
an exception.
* The unit of work cannot be cleared partially anymore. Passing an argument to
`UnitOfWork::clear()` does not have any effect anymore; the unit of work is
cleared completely.
* The method `EntityRepository::clear()` has been removed.
* The methods `getEntityClass()` and `clearsAllEntities()` have been removed
from `OnClearEventArgs`.
## BC BREAK: Remove support for Doctrine Cache
The Doctrine Cache library is not supported anymore. The following methods
have been removed from `Doctrine\ORM\Configuration`:
* `getQueryCacheImpl()`
* `setQueryCacheImpl()`
* `getHydrationCacheImpl()`
* `setHydrationCacheImpl()`
* `getMetadataCacheImpl()`
* `setMetadataCacheImpl()`
The methods have been replaced by PSR-6 compatible counterparts
(just strip the `Impl` suffix from the old name to get the new one).
## BC BREAK: Remove `Doctrine\ORM\Configuration::newDefaultAnnotationDriver`
This functionality has been moved to the new `ORMSetup` class. Call
`Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create
a new annotation driver.
## BC BREAK: Remove `Doctrine\ORM\Tools\Setup`
In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which
accepted a Doctrine Cache instance in each method has been removed.
The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6
cache instead.
## BC BREAK: Removed named queries
All APIs related to named queries have been removed.
## BC BREAK: Remove old cache accessors and mutators from query classes
The following methods have been removed from `AbstractQuery`:
* `setResultCacheDriver()`
* `getResultCacheDriver()`
* `useResultCache()`
* `getResultCacheLifetime()`
* `getResultCacheId()`
The following methods have been removed from `Query`:
* `setQueryCacheDriver()`
* `getQueryCacheDriver()`
## BC BREAK: Remove `Doctrine\ORM\Cache\MultiGetRegion`
The interface has been merged into `Doctrine\ORM\Cache\Region`.
## BC BREAK: Rename `AbstractIdGenerator::generate()` to `generateId()`
* Implementations of `AbstractIdGenerator` have to implement the method
`generateId()`.
* The method `generate()` has been removed from `AbstractIdGenerator`.
## BC BREAK: Remove cache settings inspection
Doctrine does not provide its own cache implementation anymore and relies on
the PSR-6 standard instead. As a consequence, we cannot determine anymore
whether a given cache adapter is suitable for a production environment.
Because of that, functionality that aims to do so has been removed:
* `Configuration::ensureProductionSettings()`
* the `orm:ensure-production-settings` console command
## BC BREAK: PSR-6-based second level cache
The second level cache has been reworked to consume a PSR-6 cache. Using a
Doctrine Cache instance is not supported anymore.
* `DefaultCacheFactory`: The constructor expects a PSR-6 cache item pool as
second argument now.
* `DefaultMultiGetRegion`: This class has been removed.
* `DefaultRegion`:
* The constructor expects a PSR-6 cache item pool as second argument now.
* The protected `$cache` property is removed.
* The properties `$name` and `$lifetime` as well as the constant
`REGION_KEY_SEPARATOR` and the method `getCacheEntryKey()` are
`private` now.
* The method `getCache()` has been removed.
## BC Break: Remove `Doctrine\ORM\Mapping\Driver\PHPDriver`
Use `StaticPHPDriver` instead when you want to programmatically configure
entity metadata.
## BC BREAK: Remove `Doctrine\ORM\EntityManagerInterface#transactional()`
This method has been replaced by `Doctrine\ORM\EntityManagerInterface#wrapInTransaction()`.
## BC BREAK: Removed support for schema emulation.
The ORM no longer attempts to emulate schemas on SQLite.
## BC BREAK: Remove `Setup::registerAutoloadDirectory()`
Use Composer's autoloader instead.
## BC BREAK: Remove YAML mapping drivers.
If your code relies on `YamlDriver` or `SimpleYamlDriver`, you **MUST** migrate to
attribute, annotation or XML drivers instead.
You can use the `orm:convert-mapping` command to convert your metadata mapping to XML
_before_ upgrading to 3.0:
```sh
php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
```
## BC BREAK: Remove code generators and related console commands
These console commands have been removed:
* `orm:convert-d1-schema`
* `orm:convert-mapping`
* `orm:generate:entities`
* `orm:generate-repositories`
These classes have been deprecated:
* `Doctrine\ORM\Tools\ConvertDoctrine1Schema`
* `Doctrine\ORM\Tools\EntityGenerator`
* `Doctrine\ORM\Tools\EntityRepositoryGenerator`
The entire `Doctrine\ORM\Tools\Export` namespace has been removed as well.
## BC BREAK: Removed `Doctrine\ORM\Version`
Use Composer's runtime API if you _really_ need to check the version of the ORM package at runtime.
## BC BREAK: EntityRepository::count() signature change
The argument `$criteria` of `Doctrine\ORM\EntityRepository::count()` is now
optional. Overrides in child classes should be made compatible.
## BC BREAK: changes in exception hierarchy
- `Doctrine\ORM\ORMException` has been removed
- `Doctrine\ORM\Exception\ORMException` is now an interface
## Variadic methods now use native variadics
The following methods were using `func_get_args()` to simulate a variadic argument:
- `Doctrine\ORM\Query\Expr#andX()`
- `Doctrine\ORM\Query\Expr#orX()`
- `Doctrine\ORM\QueryBuilder#select()`
- `Doctrine\ORM\QueryBuilder#addSelect()`
- `Doctrine\ORM\QueryBuilder#where()`
- `Doctrine\ORM\QueryBuilder#andWhere()`
- `Doctrine\ORM\QueryBuilder#orWhere()`
- `Doctrine\ORM\QueryBuilder#groupBy()`
- `Doctrine\ORM\QueryBuilder#andGroupBy()`
- `Doctrine\ORM\QueryBuilder#having()`
- `Doctrine\ORM\QueryBuilder#andHaving()`
- `Doctrine\ORM\QueryBuilder#orHaving()`
A variadic argument is now actually used in their signatures signature (`...$x`).
Signatures of overridden methods should be changed accordingly
## Minor BC BREAK: removed `Doctrine\ORM\EntityManagerInterface#copy()`
Method `Doctrine\ORM\EntityManagerInterface#copy()` never got its implementation and is removed in 3.0.
## BC BREAK: Removed classes related to UUID and TABLE generator strategies
The following classes have been removed:
- `Doctrine\ORM\Id\TableGenerator`
- `Doctrine\ORM\Id\UuidGenerator`
Using the `UUID` strategy for generating identifiers is not supported anymore.
## BC BREAK: Removed `Query::iterate()`
The deprecated method `Query::iterate()` has been removed along with the
following classes and methods:
- `AbstractHydrator::iterate()`
- `AbstractHydrator::hydrateRow()`
- `IterableResult`
Use `toIterable()` instead.
Use the `\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER` query hint to set the output walker
class instead of setting it through the `\Doctrine\ORM\Query\Parser::setCustomOutputTreeWalker()` method
on the parser instance.
# Upgrade to 2.19

4
bin/doctrine Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env php
<?php
include(__DIR__ . '/doctrine.php');

43
bin/doctrine-pear.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
fwrite(
STDERR,
'[Warning] The use of this script is discouraged. See'
. ' https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#doctrine-console'
. ' for instructions on bootstrapping the console runner.'
. PHP_EOL
);
echo PHP_EOL . PHP_EOL;
require_once 'Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Symfony');
$classLoader->register();
$configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php';
$helperSet = null;
if (file_exists($configFile)) {
if ( ! is_readable($configFile)) {
trigger_error(
'Configuration file [' . $configFile . '] does not have read permission.', E_USER_ERROR
);
}
require $configFile;
foreach ($GLOBALS as $helperSetCandidate) {
if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
$helperSet = $helperSetCandidate;
break;
}
}
}
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);

9
bin/doctrine.bat Normal file
View File

@@ -0,0 +1,9 @@
@echo off
if "%PHPBIN%" == "" set PHPBIN=@php_bin@
if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH
GOTO RUN
:USE_PEAR_PATH
set PHPBIN=%PHP_PEAR_PHP_BIN%
:RUN
"%PHPBIN%" "@bin_dir@\doctrine" %*

62
bin/doctrine.php Normal file
View File

@@ -0,0 +1,62 @@
<?php
use Symfony\Component\Console\Helper\HelperSet;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
fwrite(
STDERR,
'[Warning] The use of this script is discouraged. See'
. ' https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#doctrine-console'
. ' for instructions on bootstrapping the console runner.'
. PHP_EOL
);
echo PHP_EOL . PHP_EOL;
$autoloadFiles = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php'
];
foreach ($autoloadFiles as $autoloadFile) {
if (file_exists($autoloadFile)) {
require_once $autoloadFile;
break;
}
}
$directories = [getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config'];
$configFile = null;
foreach ($directories as $directory) {
$configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php';
if (file_exists($configFile)) {
break;
}
}
if ( ! file_exists($configFile)) {
ConsoleRunner::printCliConfigTemplate();
exit(1);
}
if ( ! is_readable($configFile)) {
echo 'Configuration file [' . $configFile . '] does not have read permission.' . "\n";
exit(1);
}
$commands = [];
$helperSet = require $configFile;
if ( ! ($helperSet instanceof HelperSet)) {
foreach ($GLOBALS as $helperSetCandidate) {
if ($helperSetCandidate instanceof HelperSet) {
$helperSet = $helperSetCandidate;
break;
}
}
}
ConsoleRunner::run($helperSet, $commands);

View File

@@ -3,8 +3,9 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
@@ -13,9 +14,6 @@
<var name="db_port" value="3306"/>
<var name="db_user" value="root" />
<var name="db_dbname" value="doctrine_tests" />
<var name="db_default_table_option_charset" value="utf8mb4" />
<var name="db_default_table_option_collation" value="utf8mb4_unicode_ci" />
<var name="db_default_table_option_engine" value="InnoDB" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
@@ -27,11 +25,11 @@
</testsuite>
</testsuites>
<source>
<include>
<filter>
<whitelist>
<directory suffix=".php">../../../src</directory>
</include>
</source>
</whitelist>
</filter>
<groups>
<exclude>

View File

@@ -3,8 +3,9 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
@@ -13,9 +14,6 @@
<var name="db_port" value="3306"/>
<var name="db_user" value="root" />
<var name="db_dbname" value="doctrine_tests" />
<var name="db_default_table_option_charset" value="utf8mb4" />
<var name="db_default_table_option_collation" value="utf8mb4_unicode_ci" />
<var name="db_default_table_option_engine" value="InnoDB" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
@@ -27,11 +25,12 @@
</testsuite>
</testsuites>
<source>
<include>
<filter>
<whitelist>
<directory suffix=".php">../../../src</directory>
</include>
</source>
</whitelist>
</filter>
<groups>
<exclude>

View File

@@ -3,8 +3,9 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
@@ -24,11 +25,11 @@
</testsuite>
</testsuites>
<source>
<include>
<filter>
<whitelist>
<directory suffix=".php">../../../src</directory>
</include>
</source>
</whitelist>
</filter>
<groups>
<exclude>

View File

@@ -3,8 +3,9 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
@@ -22,11 +23,11 @@
</testsuite>
</testsuites>
<source>
<include>
<filter>
<whitelist>
<directory suffix=".php">../../../src</directory>
</include>
</source>
</whitelist>
</filter>
<groups>
<exclude>

View File

@@ -3,8 +3,9 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
@@ -24,11 +25,11 @@
</testsuite>
</testsuites>
<source>
<include>
<filter>
<whitelist>
<directory suffix=".php">../../../src</directory>
</include>
</source>
</whitelist>
</filter>
<groups>
<exclude>

View File

@@ -3,8 +3,9 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
@@ -22,11 +23,11 @@
</testsuite>
</testsuites>
<source>
<include>
<filter>
<whitelist>
<directory suffix=".php">../../../src</directory>
</include>
</source>
</whitelist>
</filter>
<groups>
<exclude>

View File

@@ -12,42 +12,56 @@
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
],
"scripts": {
"docs": "composer --working-dir docs update && ./docs/vendor/bin/build-docs.sh @additional_args"
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true
},
"sort-packages": true
},
"require": {
"php": "^8.1",
"php": "^7.1 || ^8.0",
"composer-runtime-api": "^2",
"ext-ctype": "*",
"doctrine/collections": "^2.2",
"doctrine/dbal": "^3.8.2 || ^4",
"doctrine/cache": "^1.12.1 || ^2.1.1",
"doctrine/collections": "^1.5 || ^2.1",
"doctrine/common": "^3.0.3",
"doctrine/dbal": "^2.13.1 || ^3.2",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3 || ^2",
"doctrine/lexer": "^3",
"doctrine/persistence": "^3.3.1",
"doctrine/lexer": "^2 || ^3",
"doctrine/persistence": "^2.4 || ^3",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/var-exporter": "^6.3.9 || ^7.0"
"symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0",
"symfony/polyfill-php72": "^1.23",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"doctrine/coding-standard": "^12.0",
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "1.11.1",
"phpunit/phpunit": "^10.4.0",
"doctrine/annotations": "^1.13 || ^2",
"doctrine/coding-standard": "^9.0.2 || ^14.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/extension-installer": "~1.1.0 || ^1.4",
"phpstan/phpstan": "~1.4.10 || 2.1.22",
"phpstan/phpstan-deprecation-rules": "^1 || ^2",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^5.4 || ^6.2 || ^7.0",
"vimeo/psalm": "5.24.0"
"symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 3.0"
},
"suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files",
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0"
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0",
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
"autoload": {
"psr-4": { "Doctrine\\ORM\\": "src" }
@@ -59,6 +73,7 @@
"Doctrine\\Performance\\": "tests/Performance"
}
},
"bin": ["bin/doctrine"],
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
}

3
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
composer.lock
vendor/
output/

View File

@@ -1,18 +1,18 @@
# Doctrine ORM Documentation
The documentation is written in [ReStructured Text](https://docutils.sourceforge.io/rst.html).
## How to Generate:
Using Ubuntu 14.04 LTS:
1. Run ./bin/install-dependencies.sh
2. Run ./bin/generate-docs.sh
In the project root, run
It will generate the documentation into the build directory of the checkout.
composer docs
This will generate the documentation into the `docs/output` subdirectory.
## Theme issues
To browse the documentation, you need to run a webserver:
If you get a "Theme error", check if the `en/_theme` subdirectory is empty,
in which case you will need to run:
cd docs/output
php -S localhost:8000
1. git submodule init
2. git submodule update
Now the documentation is available at [http://localhost:8000](http://localhost:8000).

View File

@@ -1,10 +0,0 @@
#!/bin/bash
EXECPATH=`dirname $0`
cd $EXECPATH
cd ..
rm build -Rf
sphinx-build en build
sphinx-build -b latex en build/pdf
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex

View File

@@ -1,2 +0,0 @@
#!/bin/bash
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments

9
docs/composer.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "doctrine/orm-docs",
"description": "Documentation for the Object-Relational Mapper\"",
"type": "library",
"license": "MIT",
"require-dev": {
"doctrine/docs-builder": "^1.0"
}
}

View File

@@ -1,89 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORM.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

@@ -1,91 +0,0 @@
#Copyright (c) 2010 Fabien Potencier
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is furnished
#to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
from docutils.parsers.rst import Directive, directives
from docutils import nodes
from string import upper
class configurationblock(nodes.General, nodes.Element):
pass
class ConfigurationBlock(Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
formats = {
'html': 'HTML',
'xml': 'XML',
'php': 'PHP',
'jinja': 'Twig',
'html+jinja': 'Twig',
'jinja+html': 'Twig',
'php+html': 'PHP',
'html+php': 'PHP',
'ini': 'INI',
}
def run(self):
env = self.state.document.settings.env
node = nodes.Element()
node.document = self.state.document
self.state.nested_parse(self.content, self.content_offset, node)
entries = []
for i, child in enumerate(node):
if isinstance(child, nodes.literal_block):
# add a title (the language name) before each block
#targetid = "configuration-block-%d" % env.new_serialno('configuration-block')
#targetnode = nodes.target('', '', ids=[targetid])
#targetnode.append(child)
innernode = nodes.emphasis(self.formats[child['language']], self.formats[child['language']])
para = nodes.paragraph()
para += [innernode, child]
entry = nodes.list_item('')
entry.append(para)
entries.append(entry)
resultnode = configurationblock()
resultnode.append(nodes.bullet_list('', *entries))
return [resultnode]
def visit_configurationblock_html(self, node):
self.body.append(self.starttag(node, 'div', CLASS='configuration-block'))
def depart_configurationblock_html(self, node):
self.body.append('</div>\n')
def visit_configurationblock_latex(self, node):
pass
def depart_configurationblock_latex(self, node):
pass
def setup(app):
app.add_node(configurationblock,
html=(visit_configurationblock_html, depart_configurationblock_html),
latex=(visit_configurationblock_latex, depart_configurationblock_latex))
app.add_directive('configuration-block', ConfigurationBlock)

Submodule docs/en/_theme deleted from 6f1bc8bead

View File

@@ -140,6 +140,11 @@ Now we're going to create the ``point`` type and implement all required methods.
return $value;
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('AsText(%s)', $sqlExpr);

View File

@@ -101,8 +101,16 @@ The ``Paginate::count(Query $query)`` looks like:
{
static public function count(Query $query)
{
/** @var Query $countQuery */
$countQuery = clone $query;
/*
To avoid changing the $query passed into the method and to make sure a possibly existing
ResultSetMapping is discarded, we create a new query object any copy relevant data over.
*/
$countQuery = new Query($query->getEntityManager());
$countQuery->setDQL($query->getDQL());
$countQuery->setParameters(clone $query->getParameters());
foreach ($query->getHints() as $name => $value) {
$countQuery->setHint($name, $value);
}
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
@@ -111,7 +119,7 @@ The ``Paginate::count(Query $query)`` looks like:
}
}
It clones the query, resets the limit clause first and max results
This resets the limit clause first and max results
and registers the ``CountSqlWalker`` custom tree walker which
will modify the AST to execute a count query. The walkers
implementation is:

View File

@@ -232,33 +232,6 @@ vendors SQL parser to show us further errors in the parsing
process, for example if the Unit would not be one of the supported
values by MySql.
Typed functions
---------------
By default, result of custom functions is fetched as-is from the database driver.
If you want to be sure that the type is always the same, then your custom function needs to
implement ``Doctrine\ORM\Query\AST\TypedExpression``. Then, the result is wired
through ``Doctrine\DBAL\Types\Type::convertToPhpValue()`` of the ``Type`` returned in ``getReturnType()``.
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\TypedExpression;
class DateDiff extends FunctionNode implements TypedExpression
{
// ...
public function getReturnType(): Type
{
return Type::getType(Types::INTEGER);
}
}
Conclusion
----------

View File

@@ -0,0 +1,44 @@
Generated Columns
=================
Generated columns, sometimes also called virtual columns, are populated by
the database engine itself. They are a tool for performance optimization, to
avoid calculating a value on each query.
You can define generated columns on entities and have Doctrine map the values
to your entity.
Declaring a generated column
----------------------------
There is no explicit mapping instruction for generated columns. Instead, you
specify that the column should not be written to, and define a custom column
definition.
.. literalinclude:: generated-columns/Person.php
:language: php
* ``insertable``, ``updatable``: Setting these to false tells Doctrine to never
write this column - writing to a generated column would result in an error
from the database.
* ``columnDefinition``: We specify the full DDL to create the column. To allow
to use database specific features, this attribute does not use Doctrine Query
Language but native SQL. Note that you need to reference columns by their
database name (either explicitly set in the mapping or per the current
:doc:`naming strategy <../reference/namingstrategy>`).
Be aware that specifying a column definition makes the ``SchemaTool``
completely ignore all other configuration for this column. See also
:ref:`#[Column] <attrref_column>`
* ``generated``: Specifying that this column is always generated tells Doctrine
to update the field on the entity with the value from the database after
every write operation.
Advanced example: Extracting a value from a JSON structure
----------------------------------------------------------
Lets assume we have an entity that stores a blogpost as structured JSON.
To avoid extracting all titles on the fly when listing the posts, we create a
generated column with the field.
.. literalinclude:: generated-columns/Article.php
:language: php

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Article
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;
/**
* When working with Postgres, it is recommended to use the jsonb
* format for better performance.
*/
#[ORM\Column(options: ['jsonb' => true])]
private array $content;
/**
* Because we specify NOT NULL, inserting will fail if the content does
* not have a string in the title field.
*/
#[ORM\Column(
insertable: false,
updatable: false,
columnDefinition: "VARCHAR(255) generated always as (content->>'title') stored NOT NULL",
generated: 'ALWAYS',
)]
private string $title;
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Person
{
#[ORM\Column(type: 'string')]
private string $firstName;
#[ORM\Column(type: 'string', name: 'name')]
private string $lastName;
#[ORM\Column(
type: 'string',
insertable: false,
updatable: false,
columnDefinition: "VARCHAR(255) GENERATED ALWAYS AS (concat(firstName, ' ', name) stored NOT NULL",
generated: 'ALWAYS',
)]
private string $fullName;
}

View File

@@ -0,0 +1,75 @@
Implementing the Notify ChangeTracking Policy
=============================================
.. sectionauthor:: Roman Borschel <roman@code-factory.org>
The NOTIFY change-tracking policy is the most effective
change-tracking policy provided by Doctrine but it requires some
boilerplate code. This recipe will show you how this boilerplate
code should look like. We will implement it on a
`Layer Supertype <https://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
.. note::
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
(\ `Details <https://github.com/doctrine/orm/issues/8383>`_)
Implementing NotifyPropertyChanged
----------------------------------
The NOTIFY policy is based on the assumption that the entities
notify interested listeners of changes to their properties. For
that purpose, a class that wants to use this policy needs to
implement the ``NotifyPropertyChanged`` interface from the
``Doctrine\Common`` namespace.
.. code-block:: php
<?php
use Doctrine\Persistence\NotifyPropertyChanged;
use Doctrine\Persistence\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->listeners[] = $listener;
}
/** Notifies listeners of a change. */
protected function onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
}
Then, in each property setter of concrete, derived domain classes,
you need to invoke onPropertyChanged as follows to notify
listeners:
.. code-block:: php
<?php
// Mapping not shown, either in attributes, annotations, xml or yaml as usual
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
The check whether the new value is different from the old one is
not mandatory but recommended. That way you can avoid unnecessary
updates and also have full control over when you consider a
property changed.

View File

@@ -43,13 +43,13 @@ entities:
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Article
{
const STATUS_VISIBLE = 'visible';
const STATUS_INVISIBLE = 'invisible';
#[Column(type: "string")]
/** @Column(type="string") */
private $status;
public function setStatus($status)
@@ -67,10 +67,10 @@ the **columnDefinition** attribute.
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Article
{
#[Column(type: "string", columnDefinition: "ENUM('visible', 'invisible')")]
/** @Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
private $status;
}
@@ -131,10 +131,10 @@ Then in your entity you can just use this type:
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Article
{
#[Column(type: "enumvisibility")]
/** @Column(type="enumvisibility") */
private $status;
}
@@ -196,3 +196,4 @@ With this base class you can define an enum as easily as:
protected $name = 'enumvisibility';
protected $values = array('visible', 'invisible');
}

View File

@@ -155,7 +155,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine ORM)
*
* This is a doctrine field, so make sure that you use a
#[Column] attribute or setup your xml files correctly
#[Column] attribute or setup your yaml or xml files correctly
* @var string
*/
protected $strategyClassName;

View File

@@ -71,6 +71,23 @@ First Attributes:
public function assertCustomerAllowedBuying() {}
}
As Annotations:
.. code-block:: php
<?php
/**
* @Entity
* @HasLifecycleCallbacks
*/
class Order
{
/**
* @PrePersist @PreUpdate
*/
public function assertCustomerAllowedBuying() {}
}
In XML Mappings:
.. code-block:: xml

View File

@@ -162,13 +162,15 @@ requiring timezoned datetimes:
<?php
namespace Shipping;
#[Entity]
/**
* @Entity
*/
class Event
{
#[Column(type: 'datetime')]
/** @Column(type="datetime") */
private $created;
#[Column(type: 'string')]
/** @Column(type="string") */
private $timezone;
/**

View File

@@ -1,4 +1,4 @@
Welcome to Doctrine ORM's documentation!
Welcome to Doctrine 2 ORM's documentation!
==========================================
The Doctrine documentation is comprised of tutorials, a reference section and
@@ -39,8 +39,10 @@ Mapping Objects onto a Database
:doc:`Inheritance <reference/inheritance-mapping>`
* **Drivers**:
:doc:`Docblock Annotations <reference/annotations-reference>` \|
:doc:`Attributes <reference/attributes-reference>` \|
:doc:`XML <reference/xml-mapping>` \|
:doc:`YAML <reference/yaml-mapping>` \|
:doc:`PHP <reference/php-mapping>`
Working with Objects
@@ -73,6 +75,7 @@ Advanced Topics
* :doc:`TypedFieldMapper <reference/typedfieldmapper>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
* :doc:`Best Practices <reference/best-practices>`
* :doc:`Metadata Drivers <reference/metadata-drivers>`
@@ -100,6 +103,7 @@ Cookbook
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` \|
:doc:`Generated/Virtual Columns <cookbook/generated-columns>` \|
:doc:`Decorator Pattern <cookbook/decorator-pattern>` \|
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
@@ -109,6 +113,7 @@ Cookbook
* **Implementation**:
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` \|
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` \|
:doc:`Working with DateTime <cookbook/working-with-datetime>` \|
:doc:`Validation <cookbook/validation-of-entities>` \|
:doc:`Entities in the Session <cookbook/entities-in-session>` \|
@@ -119,4 +124,5 @@ Cookbook
* **Custom Datatypes**
:doc:`MySQL Enums <cookbook/mysql-enums>`
:doc:`Custom Mapping Types <cookbook/custom-mapping-types>`
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`

View File

@@ -1,113 +0,0 @@
@ECHO OFF
REM Command file for Sphinx documentation
set SPHINXBUILD=sphinx-build
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

View File

@@ -71,8 +71,8 @@ Configuration Options
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
Proxy Directory (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy Directory (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -85,8 +85,8 @@ classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Proxy Namespace (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy Namespace (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -98,8 +98,8 @@ Gets or sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
Metadata Driver (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Metadata Driver (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -111,16 +111,21 @@ Gets or sets the metadata driver implementation that is used by
Doctrine to acquire the object-relational metadata for your
classes.
There are currently 3 available implementations:
There are currently 5 available implementations:
- ``Doctrine\ORM\Mapping\Driver\AttributeDriver``
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
- ``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
XmlDriver please refer to the dedicated chapter ``XML Mapping``.
AnnotationDriver, XmlDriver or YamlDriver please refer to the dedicated
chapters ``Annotation Reference``, ``XML Mapping`` and ``YAML Mapping``.
The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
@@ -139,8 +144,8 @@ accept either a single directory as a string or an array of
directories. With this feature a single driver can support multiple
directories of Entities.
Metadata Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Metadata Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -150,9 +155,9 @@ Metadata Cache (***RECOMMENDED***)
Gets or sets the cache adapter to use for caching metadata
information, that is, all the information you supply via attributes,
xml, so that they do not need to be parsed and loaded from scratch on
every single request which is a waste of resources. The cache
implementation must implement the PSR-6
annotations, xml or yaml, so that they do not need to be parsed and
loaded from scratch on every single request which is a waste of
resources. The cache implementation must implement the PSR-6
``Psr\Cache\CacheItemPoolInterface`` interface.
Usage of a metadata cache is highly recommended.
@@ -161,8 +166,8 @@ For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
Query Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Query Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -184,8 +189,8 @@ For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
SQL Logger (***Optional***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL Logger (**Optional**)
~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -197,8 +202,8 @@ Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
Auto-generating Proxy Classes (***OPTIONAL***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Auto-generating Proxy Classes (**OPTIONAL**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy classes can either be generated manually through the Doctrine
Console or automatically at runtime by Doctrine. The configuration
@@ -274,7 +279,7 @@ requests.
Connection
----------
The ``$connection`` passed as the first argument to he constructor of
The ``$connection`` passed as the first argument to the 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
@@ -420,7 +425,7 @@ Multiple Metadata Sources
When using different components using Doctrine ORM you may end up
with them using two different metadata drivers, for example XML and
PHP. You can use the MappingDriverChain Metadata implementations to
YAML. You can use the MappingDriverChain Metadata implementations to
aggregate these drivers based on namespaces:
.. code-block:: php
@@ -430,7 +435,7 @@ aggregate these drivers based on namespaces:
$chain = new MappingDriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($phpDriver, 'Doctrine\Tests\ORM\Mapping');
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');
Based on the namespace of the entity the loading of entities is
delegated to the appropriate driver. The chain semantics come from
@@ -441,7 +446,7 @@ correctly if sub-namespaces use different metadata driver
implementations.
Default Repository (***OPTIONAL***)
Default Repository (**OPTIONAL**)
-----------------------------------
Specifies the FQCN of a subclass of the EntityRepository.
@@ -456,7 +461,7 @@ That will be available for all entities without a custom repository class.
The default value is ``Doctrine\ORM\EntityRepository``.
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
Ignoring entities (***OPTIONAL***)
Ignoring entities (**OPTIONAL**)
-----------------------------------
Specifies the Entity FQCNs to ignore.

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@ well.
Requirements
------------
Doctrine ORM requires a minimum of PHP 8.1. For greatly improved
Doctrine ORM requires a minimum of PHP 7.1. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine ORM Packages
@@ -168,7 +168,7 @@ recommended, at least not as long as an entity instance still holds
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
manager and cannot be initialized 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.

View File

@@ -56,6 +56,27 @@ A many-to-one association is the most common association between objects. Exampl
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
class User
{
// ...
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
private Address|null $address = null;
}
/** @Entity */
class Address
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -66,6 +87,18 @@ A many-to-one association is the most common association between objects. Exampl
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
.. note::
The above ``#[JoinColumn]`` is optional as it would default
@@ -121,6 +154,30 @@ references one ``Shipment`` entity.
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
class Product
{
// ...
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private Shipment|null $shipment = null;
// ...
}
/** @Entity */
class Shipment
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -131,6 +188,17 @@ references one ``Shipment`` entity.
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
joinColumn:
name: shipment_id
referencedColumnName: id
Note that the ``#[JoinColumn]`` is not really necessary in this example,
as the defaults would be the same.
@@ -191,6 +259,38 @@ object.
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
class Customer
{
// ...
/**
* One Customer has One Cart.
* @OneToOne(targetEntity="Cart", mappedBy="customer")
*/
private Cart|null $cart = null;
// ...
}
/** @Entity */
class Cart
{
// ...
/**
* One Cart has One Customer.
* @OneToOne(targetEntity="Customer", inversedBy="cart")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
private Customer|null $customer = null;
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -204,6 +304,22 @@ object.
</entity>
</doctrine-mapping>
.. code-block:: yaml
Customer:
oneToOne:
cart:
targetEntity: Cart
mappedBy: customer
Cart:
oneToOne:
customer:
targetEntity: Customer
inversedBy: cart
joinColumn:
name: customer_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
as the defaults would be the same.
@@ -312,6 +428,41 @@ bidirectional many-to-one.
// ...
}
.. code-block:: annotation
<?php
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
class Product
{
// ...
/**
* One product has many features. This is the inverse side.
* @var Collection<int, Feature>
* @OneToMany(targetEntity="Feature", mappedBy="product")
*/
private Collection $features;
// ...
public function __construct() {
$this->features = new ArrayCollection();
}
}
/** @Entity */
class Feature
{
// ...
/**
* Many features have one product. This is the owning side.
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
*/
private Product|null $product = null;
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -325,6 +476,24 @@ bidirectional many-to-one.
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToMany:
features:
targetEntity: Feature
mappedBy: product
Feature:
type: entity
manyToOne:
product:
targetEntity: Product
inversedBy: features
joinColumn:
name: product_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
as the defaults would be the same.
@@ -387,6 +556,39 @@ The following example sets up such a unidirectional one-to-many association:
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
class User
{
// ...
/**
* Many Users have Many Phonenumbers.
* @ManyToMany(targetEntity="Phonenumber")
* @JoinTable(name="users_phonenumbers",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
* @var Collection<int, Phonenumber>
*/
private Collection $phonenumbers;
public function __construct()
{
$this->phonenumbers = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
/** @Entity */
class Phonenumber
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -404,6 +606,24 @@ The following example sets up such a unidirectional one-to-many association:
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToMany:
phonenumbers:
targetEntity: Phonenumber
joinTable:
name: users_phonenumbers
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
phonenumber_id:
referencedColumnName: id
unique: true
Generates the following MySQL Schema:
.. code-block:: sql
@@ -464,6 +684,33 @@ database perspective is known as an adjacency list approach.
}
}
.. code-block:: annotation
<?php
/** @Entity */
class Category
{
// ...
/**
* One Category has Many Categories.
* @OneToMany(targetEntity="Category", mappedBy="parent")
* @var Collection<int, Category>
*/
private Collection $children;
/**
* Many Categories have One Category.
* @ManyToOne(targetEntity="Category", inversedBy="children")
* @JoinColumn(name="parent_id", referencedColumnName="id")
*/
private Category|null $parent = null;
// ...
public function __construct() {
$this->children = new \Doctrine\Common\Collections\ArrayCollection();
}
}
.. code-block:: xml
<doctrine-mapping>
@@ -473,6 +720,19 @@ database perspective is known as an adjacency list approach.
</entity>
</doctrine-mapping>
.. code-block:: yaml
Category:
type: entity
oneToMany:
children:
targetEntity: Category
mappedBy: parent
manyToOne:
parent:
targetEntity: Category
inversedBy: children
Note that the @JoinColumn is not really necessary in this example,
as the defaults would be the same.
@@ -527,6 +787,38 @@ entities:
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
private Collection $groups;
// ...
public function __construct() {
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/** @Entity */
class Group
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -544,6 +836,22 @@ entities:
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToMany:
groups:
targetEntity: Group
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
Generated MySQL Schema:
.. code-block:: sql
@@ -631,6 +939,47 @@ one is bidirectional.
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups")
* @var Collection<int, Group>
*/
private Collection $groups;
public function __construct() {
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
/** @Entity */
class Group
{
// ...
/**
* Many Groups have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="groups")
* @var Collection<int, User>
*/
private Collection $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -652,6 +1001,30 @@ one is bidirectional.
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToMany:
groups:
targetEntity: Group
inversedBy: users
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
Group:
type: entity
manyToMany:
users:
targetEntity: User
mappedBy: groups
The MySQL schema is exactly the same as for the Many-To-Many
uni-directional case above.
@@ -799,6 +1172,12 @@ As an example, consider this mapping:
#[OneToOne(targetEntity: Shipment::class)]
private Shipment|null $shipment = null;
.. code-block:: annotation
<?php
/** @OneToOne(targetEntity="Shipment") */
private Shipment|null $shipment = null;
.. code-block:: xml
<doctrine-mapping>
@@ -807,6 +1186,14 @@ As an example, consider this mapping:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
This is essentially the same as the following, more verbose,
mapping:
@@ -820,6 +1207,16 @@ mapping:
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment|null $shipment = null;
.. code-block:: annotation
<?php
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private Shipment|null $shipment = null;
.. code-block:: xml
<doctrine-mapping>
@@ -830,6 +1227,17 @@ mapping:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
joinColumn:
name: shipment_id
referencedColumnName: id
The @JoinTable definition used for many-to-many mappings has
similar defaults. As an example, consider this mapping:
@@ -847,6 +1255,20 @@ similar defaults. As an example, consider this mapping:
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @var Collection<int, Group>
*/
private Collection $groups;
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -855,6 +1277,14 @@ similar defaults. As an example, consider this mapping:
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToMany:
groups:
targetEntity: Group
This is essentially the same as the following, more verbose, mapping:
.. configuration-block::
@@ -877,6 +1307,25 @@ This is essentially the same as the following, more verbose, mapping:
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="User_Group",
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
private Collection $groups;
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -894,6 +1343,22 @@ This is essentially the same as the following, more verbose, mapping:
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToMany:
groups:
targetEntity: Group
joinTable:
name: User_Group
joinColumns:
User_id:
referencedColumnName: id
inverseJoinColumns:
Group_id:
referencedColumnName: id
In that case, the name of the join table defaults to a combination
of the simple, unqualified class names of the participating
classes, separated by an underscore character. The names of the
@@ -903,8 +1368,7 @@ defaults to "id", just as in one-to-one or many-to-one mappings.
Additionally, when using typed properties with Doctrine 2.9 or newer
you can skip ``targetEntity`` in ``ManyToOne`` and ``OneToOne``
associations as they will be set based on type. Also ``nullable``
attribute on ``JoinColumn`` will be inherited from PHP type. So that:
associations as they will be set based on type. So that:
.. configuration-block::
@@ -914,6 +1378,12 @@ attribute on ``JoinColumn`` will be inherited from PHP type. So that:
#[OneToOne]
private Shipment $shipment;
.. code-block:: annotation
<?php
/** @OneToOne */
private Shipment $shipment;
.. code-block:: xml
<doctrine-mapping>
@@ -922,6 +1392,13 @@ attribute on ``JoinColumn`` will be inherited from PHP type. So that:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToOne:
shipment: ~
Is essentially the same as following:
.. configuration-block::
@@ -931,7 +1408,7 @@ Is essentially the same as following:
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment $shipment;
.. code-block:: annotation
@@ -940,7 +1417,7 @@ Is essentially the same as following:
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id", nullable=false)
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private Shipment $shipment;
@@ -949,11 +1426,22 @@ Is essentially the same as following:
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" nulable=false />
<join-column name="shipment_id" referenced-column-name="id" nullable=false />
</one-to-one>
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
joinColumn:
name: shipment_id
referencedColumnName: id
If you accept these defaults, you can reduce the mapping code to a
minimum.

View File

@@ -4,9 +4,8 @@ Attributes Reference
PHP 8 adds native support for metadata with its "Attributes" feature.
Doctrine ORM provides support for mapping metadata using PHP attributes as of version 2.9.
The attributes metadata support is closely modelled after the already
existing and now removed annotation metadata supported since the first
version 2.0.
The attributes metadata support is closely modelled after the already existing
annotation metadata supported since the first version 2.0.
Index
-----
@@ -15,7 +14,7 @@ Index
- :ref:`#[AttributeOverride] <attrref_attributeoverride>`
- :ref:`#[Column] <attrref_column>`
- :ref:`#[Cache] <attrref_cache>`
- :ref:`#[ChangeTrackingPolicy <attrref_changetrackingpolicy>`
- :ref:`#[ChangeTrackingPolicy] <attrref_changetrackingpolicy>`
- :ref:`#[CustomIdGenerator] <attrref_customidgenerator>`
- :ref:`#[DiscriminatorColumn] <attrref_discriminatorcolumn>`
- :ref:`#[DiscriminatorMap] <attrref_discriminatormap>`
@@ -214,12 +213,15 @@ Optional parameters:
- ``check``: Adds a check constraint type to the column (might not
be supported by all vendors).
- **columnDefinition**: DDL SQL snippet that starts after the column
- **columnDefinition**: Specify the DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute allows to make use of advanced RMDBS features.
However you should make careful use of this feature and the
consequences. ``SchemaTool`` will not detect changes on the column correctly
anymore if you use ``columnDefinition``.
However, as this needs to be specified in the DDL native to the database,
the resulting schema changes are no longer portable. If you specify a
``columnDefinition``, the ``SchemaTool`` ignores all other attributes
that are normally used to build the definition DDL. Changes to the
``columnDefinition`` are not detected, you will need to manually create a
migration to apply changes.
Additionally you should remember that the ``type``
attribute still handles the conversion between PHP and Database
@@ -262,10 +264,11 @@ Examples:
)]
protected $loginCount;
// MySQL example: full_name char(41) GENERATED ALWAYS AS (concat(firstname,' ',lastname)),
// columnDefinition is raw SQL, not DQL. This example works for MySQL:
#[Column(
type: "string",
name: "user_fullname",
columnDefinition: "VARCHAR(255) GENERATED ALWAYS AS (concat(firstname,' ',lastname))",
insertable: false,
updatable: false
)]
@@ -311,6 +314,7 @@ Example:
Entity,
ChangeTrackingPolicy("DEFERRED_IMPLICIT"),
ChangeTrackingPolicy("DEFERRED_EXPLICIT"),
ChangeTrackingPolicy("NOTIFY")
]
class User {}
@@ -366,7 +370,7 @@ 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.
- **columnDefinition**: Allows to override how the column is generated. See the "columnDefinition" attribute on :ref:`#[Column] <attrref_column>`
- **enumType**: By default this is `null`. Allows to map discriminatorColumn value to PHP enum
- **options**: See "options" attribute on :ref:`#[Column] <attrref_column>`.
@@ -485,8 +489,9 @@ used as default.
Optional parameters:
- **strategy**: Set the name of the identifier generation strategy.
Valid values are ``AUTO``, ``SEQUENCE``, ``IDENTITY``, ``CUSTOM`` and
``NONE``. If not specified, the default value is ``AUTO``.
Valid values are ``AUTO``, ``SEQUENCE``, ``IDENTITY``, ``UUID``
(deprecated), ``CUSTOM`` and ``NONE``.
If not specified, the default value is ``AUTO``.
Example:
@@ -677,8 +682,10 @@ Optional parameters:
- **onDelete**: Cascade Action (Database-level)
- **columnDefinition**: DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute enables the use of advanced RMDBS features. Using
this attribute on ``#[JoinColumn]`` is necessary if you need slightly
This attribute enables the use of advanced RMDBS features. Note that you
need to reference columns by their database name (either explicitly set in
the mapping or per the current :doc:`naming strategy <namingstrategy>`).
Using this attribute on ``#[JoinColumn]`` is necessary if you need
different column definitions for joining columns, for example
regarding NULL/NOT NULL defaults. However by default a
"columnDefinition" attribute on :ref:`#[Column] <attrref_column>` also sets
@@ -711,6 +718,10 @@ details of the database join table. If you do not specify
``#[JoinTable]`` on these relations reasonable mapping defaults apply
using the affected table and the column names.
A notable difference to the annotation metadata support, ``#[JoinColumn]``
and ``#[InverseJoinColumn]`` can be specified at the property level and are not
nested within the ``#[JoinTable]`` attribute.
Required attribute:
- **name**: Database name of the join-table
@@ -926,7 +937,7 @@ Example:
#[OneToMany(
targetEntity: "Phonenumber",
mappedBy: "user",
cascade: ["persist", "remove"],
cascade: ["persist", "remove", "merge"],
orphanRemoval: true)
]
public $phonenumbers;

View File

@@ -47,14 +47,17 @@ mapping metadata:
- :doc:`Attributes <attributes-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
many examples also show the equivalent configuration in XML.
many examples also show the equivalent configuration in annotations,
YAML and XML.
.. note::
All metadata drivers perform equally. Once the metadata of a class has been
read from the source (attributes, XML, etc.) it is stored in an instance
read from the source (attributes, annotations, XML, etc.) it is stored in an instance
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class which are
stored in the metadata cache. If you're not using a metadata cache (not
recommended!) then the XML driver is the fastest.
@@ -74,6 +77,17 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
// ...
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Mapping\Entity;
/** @Entity */
class Message
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -82,6 +96,12 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
# ...
With no additional information, Doctrine expects the entity to be saved
into a table with the same name as the class in our case ``Message``.
You can change this by configuring information about the table:
@@ -101,6 +121,21 @@ You can change this by configuring information about the table:
// ...
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
/**
* @Entity
* @Table(name="message")
*/
class Message
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -109,6 +144,13 @@ You can change this by configuring information about the table:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
table: message
# ...
Now the class ``Message`` will be saved and fetched from the table ``message``.
Property Mapping
@@ -140,6 +182,23 @@ specified, ``string`` is used as the default.
private $postedAt;
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Column;
/** @Entity */
class Message
{
/** @Column(type="integer") */
private $id;
/** @Column(length=140) */
private $text;
/** @Column(type="datetime", name="posted_at") */
private $postedAt;
}
.. code-block:: xml
<doctrine-mapping>
@@ -150,6 +209,19 @@ specified, ``string`` is used as the default.
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
fields:
id:
type: integer
text:
length: 140
postedAt:
type: datetime
column: posted_at
When we don't explicitly specify a column name via the ``name`` option, Doctrine
assumes the field name is also the column name. So in this example:
@@ -167,6 +239,7 @@ Here is a complete list of ``Column``s attributes (all optional):
- ``nullable`` (default: ``false``): Whether the column is nullable.
- ``insertable`` (default: ``true``): Whether the column should be inserted.
- ``updatable`` (default: ``true``): Whether the column should be updated.
- ``generated`` (default: ``null``): Whether the generated strategy should be ``'NEVER'``, ``'INSERT'`` and ``ALWAYS``.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
(applies only for decimal column),
@@ -274,6 +347,20 @@ the field that serves as the identifier with the ``#[Id]`` attribute.
// ...
}
.. code-block:: annotation
<?php
class Message
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
private int|null $id = null;
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -285,27 +372,24 @@ the field that serves as the identifier with the ``#[Id]`` attribute.
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
fields:
# fields here
In most cases using the automatic generator strategy (``#[GeneratedValue]``) is
what you want, but for backwards-compatibility reasons it might not. It
defaults to the identifier generation mechanism your current database
vendor preferred at the time that strategy was introduced:
``AUTO_INCREMENT`` with MySQL, sequences with PostgreSQL and Oracle and
so on.
If you are using `doctrine/dbal` 4, we now recommend using ``IDENTITY``
for PostgreSQL, and ``AUTO`` resolves to it because of that.
You can stick with ``SEQUENCE`` while still using the ``AUTO``
strategy, by configuring what it defaults to.
.. code-block:: php
<?php
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Configuration;
$config = new Configuration();
$config->setIdentityGenerationPreferences([
PostgreSQLPlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
]);
.. _identifier-generation-strategies:
@@ -334,12 +418,14 @@ Here is the list of possible generation strategies:
generation. This strategy does currently not provide full
portability. Sequences are supported by Oracle, PostgreSql and
SQL Anywhere.
- ``UUID`` (deprecated): Tells Doctrine to use the built-in Universally
Unique Identifier generator. This strategy provides full portability.
- ``NONE``: Tells Doctrine that the identifiers are assigned (and
thus generated) by your code. The assignment must take place before
a new entity is passed to ``EntityManager#persist``. NONE is the
same as leaving off the ``#[GeneratedValue]`` entirely.
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
It will allow you to pass a :ref:`class of your own to generate the identifiers. <attrref_customidgenerator>`
It will allow you to pass a :ref:`class of your own to generate the identifiers. <annref_customidgenerator>`
Sequence Generator
^^^^^^^^^^^^^^^^^^
@@ -362,6 +448,20 @@ besides specifying the sequence's name:
// ...
}
.. code-block:: annotation
<?php
class Message
{
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
*/
protected int|null $id = null;
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -373,6 +473,20 @@ besides specifying the sequence's name:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
id:
id:
type: integer
generator:
strategy: SEQUENCE
sequenceGenerator:
sequenceName: message_seq
allocationSize: 100
initialValue: 1
The initial value specifies at which value the sequence should
start.

View File

@@ -43,7 +43,7 @@ should use events judiciously.
Use cascades judiciously
------------------------
Automatic cascades of the persist/remove/etc. operations are
Automatic cascades of the persist/remove/merge/etc. operations are
very handy but should be used wisely. Do NOT simply add all
cascades to all associations. Think about which cascades actually
do make sense for you for a particular association, given the

View File

@@ -109,7 +109,7 @@ Metadata Cache
~~~~~~~~~~~~~~
Your class metadata can be parsed from a few different sources like
XML, Attributes, etc. Instead of parsing this
YAML, XML, Attributes, Annotations etc. Instead of parsing this
information on each request we should cache it using one of the cache
drivers.

View File

@@ -5,7 +5,7 @@ Change tracking is the process of determining what has changed in
managed entities since the last time they were synchronized with
the database.
Doctrine provides 2 different change tracking policies, each having
Doctrine provides 3 different change tracking policies, each having
its particular advantages and disadvantages. The change tracking
policy can be defined on a per-class basis (or more precisely,
per-hierarchy).
@@ -56,3 +56,122 @@ This policy can be configured as follows:
{
// ...
}
Notify
~~~~~~
.. note::
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
(\ `Details <https://github.com/doctrine/orm/issues/8383>`_)
This policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that
purpose, a class that wants to use this policy needs to implement
the ``NotifyPropertyChanged`` interface from the Doctrine
namespace. As a guideline, such an implementation can look as
follows:
.. code-block:: php
<?php
use Doctrine\Persistence\NotifyPropertyChanged,
Doctrine\Persistence\PropertyChangedListener;
#[Entity]
#[ChangeTrackingPolicy('NOTIFY')]
class MyEntity implements NotifyPropertyChanged
{
// ...
private array $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener): void
{
$this->_listeners[] = $listener;
}
}
Then, in each property setter of this class or derived classes, you
need to notify all the ``PropertyChangedListener`` instances. As an
example we add a convenience method on ``MyEntity`` that shows this
behaviour:
.. code-block:: php
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue): void
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
public function setData($data): void
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
You have to invoke ``_onPropertyChanged`` inside every method that
changes the persistent state of ``MyEntity``.
The check whether the new value is different from the old one is
not mandatory but recommended. That way you also have full control
over when you consider a property changed.
If your entity contains an embeddable, you will need to notify
separately for each property in the embeddable when it changes
for example:
.. code-block:: php
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
public function setEmbeddable(MyValueObject $embeddable): void
{
if (!$embeddable->equals($this->embeddable)) {
// notice the entityField.embeddableField notation for referencing the property
$this->_onPropertyChanged('embeddable.prop1', $this->embeddable->getProp1(), $embeddable->getProp1());
$this->_onPropertyChanged('embeddable.prop2', $this->embeddable->getProp2(), $embeddable->getProp2());
$this->embeddable = $embeddable;
}
}
}
This would update all the fields of the embeddable, you may wish to
implement a diff method on your embedded object which returns only
the changed fields.
The negative point of this policy is obvious: You need implement an
interface and write some plumbing code. But also note that we tried
hard to keep this notification functionality abstract. Strictly
speaking, it has nothing to do with the persistence layer and the
Doctrine ORM or DBAL. You may find that property notification
events come in handy in many other scenarios as well. As mentioned
earlier, the ``Doctrine\Common`` namespace is not that evil and
consists solely of very small classes and interfaces that have
almost no external dependencies (none to the DBAL and none to the
ORM) and that you can easily take with you should you want to swap
out the persistence layer. This change tracking policy does not
introduce a dependency on the Doctrine DBAL/ORM or the persistence
layer.
The positive point and main advantage of this policy is its
effectiveness. It has the best performance characteristics of the 3
policies with larger units of work and a flush() operation is very
cheap when nothing has changed.

View File

@@ -60,6 +60,11 @@ access point to ORM functionality provided by Doctrine.
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
.. note::
The ``ORMSetup`` class has been introduced with ORM 2.12. It's predecessor ``Setup`` is deprecated and will
be removed in version 3.0.
Or if you prefer XML:
.. code-block:: php
@@ -70,6 +75,23 @@ Or if you prefer XML:
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
Or if you prefer YAML:
.. code-block:: php
<?php
$paths = ['/path/to/yml-mappings'];
$config = ORMSetup::createYAMLMetadataConfiguration($paths, $isDevMode);
$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`:
::
"symfony/yaml": "*"
Inside the ``ORMSetup`` methods several assumptions are made:
- If ``$isDevMode`` is true caching is done in memory with the ``ArrayAdapter``. Proxy objects are recreated on every request.

View File

@@ -523,6 +523,33 @@ when the DQL is switched to an arbitrary join.
- HAVING is applied to the results of a query after
aggregation (GROUP BY)
Partial Object Syntax
^^^^^^^^^^^^^^^^^^^^^
By default when you run a DQL query in Doctrine and select only a
subset of the fields for a given entity, you do not receive objects
back. Instead, you receive only arrays as a flat rectangular result
set, similar to how you would if you were just using SQL directly
and joining some data.
If you want to select partial objects you can use the ``partial``
DQL keyword:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT partial u.{id, username} FROM CmsUser u');
$users = $query->getResult(); // array of partially loaded CmsUser objects
You use the partial syntax when joining as well:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a');
$users = $query->getResult(); // array of partially loaded CmsUser objects
"NEW" Operator Syntax
^^^^^^^^^^^^^^^^^^^^^
@@ -1324,6 +1351,15 @@ exist mostly internal query hints that are not be consumed in
userland. However the following few hints are to be used in
userland:
- ``Query::HINT_FORCE_PARTIAL_LOAD`` - Allows to hydrate objects
although not all their columns are fetched. This query hint can be
used to handle memory consumption problems with large result-sets
that contain char or binary data. Doctrine has no way of implicitly
reloading this data. Partially loaded objects have to be passed to
``EntityManager::refresh()`` if they are to be reloaded fully from
the database. This query hint is deprecated and will be removed
in the future (\ `Details <https://github.com/doctrine/orm/issues/8471>`_)
- ``Query::HINT_REFRESH`` - This query is used internally by
``EntityManager::refresh()`` and can be used in userland as well.
If you specify this hint and a query returns the data for an entity
@@ -1480,8 +1516,8 @@ Identifiers
/* Alias Identification declaration (the "u" of "FROM User u") */
AliasIdentificationVariable :: = identifier
/* identifier that must be a class name (the "User" of "FROM User u"), possibly as a fully qualified class name */
AbstractSchemaName ::= fully_qualified_name | identifier
/* identifier that must be a class name (the "User" of "FROM User u"), possibly as a fully qualified class name or namespace-aliased */
AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
AliasResultVariable = identifier
@@ -1576,8 +1612,10 @@ Select Expressions
.. code-block:: php
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectArg ::= ScalarExpression | "(" Subselect ")"

View File

@@ -260,12 +260,46 @@ specific to a particular entity class's lifecycle.
$this->value = 'changed from preUpdate callback!';
}
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Event\PrePersistEventArgs;
/**
* @Entity
* @HasLifecycleCallbacks
*/
class User
{
// ...
/** @Column(type="string", length=255) */
public $value;
/** @PrePersist */
public function doStuffOnPrePersist(PrePersistEventArgs $eventArgs)
{
$this->createdAt = date('Y-m-d H:i:s');
}
/** @PrePersist */
public function doOtherStuffOnPrePersist()
{
$this->value = 'changed from prePersist callback!';
}
/** @PreUpdate */
public function doStuffOnPreUpdate(PreUpdateEventArgs $eventArgs)
{
$this->value = 'changed from preUpdate callback!';
}
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="User">
@@ -277,6 +311,17 @@ specific to a particular entity class's lifecycle.
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
fields:
# ...
value:
type: string(255)
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersist ]
preUpdate: [ doStuffOnPreUpdate ]
Lifecycle Callbacks Event Argument
----------------------------------
@@ -749,6 +794,16 @@ An entity listener is a lifecycle listener class used for an entity.
{
// ....
}
.. code-block:: annotation
<?php
namespace MyProject\Entity;
/** @Entity @EntityListeners({"UserListener"}) */
class User
{
// ....
}
.. code-block:: xml
<doctrine-mapping>
@@ -759,6 +814,13 @@ An entity listener is a lifecycle listener class used for an entity.
<!-- .... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Entity\User:
type: entity
entityListeners:
UserListener:
# ....
.. _reference-entity-listeners:
@@ -831,6 +893,45 @@ you need to map the listener method using the event type mapping:
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
}
.. code-block:: annotation
<?php
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\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreRemoveEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
class UserListener
{
/** @PrePersist */
public function prePersistHandler(User $user, PrePersistEventArgs $event): void { // ... }
/** @PostPersist */
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
/** @PreUpdate */
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
/** @PostUpdate */
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
/** @PostRemove */
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
/** @PreRemove */
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
/** @PreFlush */
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
/** @PostLoad */
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
}
.. code-block:: xml
<doctrine-mapping>
@@ -853,6 +954,24 @@ you need to map the listener method using the event type mapping:
<!-- .... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Entity\User:
type: entity
entityListeners:
UserListener:
preFlush: [preFlushHandler]
postLoad: [postLoadHandler]
postPersist: [postPersistHandler]
prePersist: [prePersistHandler]
postUpdate: [postUpdateHandler]
preUpdate: [preUpdateHandler]
postRemove: [postRemoveHandler]
preRemove: [preRemoveHandler]
# ....
.. note::
@@ -875,8 +994,7 @@ Specifying an entity listener instance :
// User.php
#[Entity]
#[EntityListeners(["UserListener"])
/** @Entity @EntityListeners({"UserListener"}) */
class User
{
// ....
@@ -934,7 +1052,7 @@ Load ClassMetadata Event
``loadClassMetadata`` - The ``loadClassMetadata`` event occurs after the
mapping metadata for a class has been loaded from a mapping source
(attributes/xml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
(attributes/annotations/xml/yaml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
You can hook in to this process and manipulate the instance.
This event is not a lifecycle callback.

View File

@@ -30,7 +30,7 @@ table alias of the SQL table of the entity.
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.
2. The filter must be deterministic. Don't change the values based on external inputs.
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.

View File

@@ -208,6 +208,44 @@ Example:
// ...
}
.. code-block:: annotation
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/**
* @Entity
*/
class Employee extends Person
{
// ...
}
.. code-block:: yaml
MyProject\Model\Person:
type: entity
inheritanceType: SINGLE_TABLE
discriminatorColumn:
name: discr
type: string
discriminatorMap:
person: Person
employee: Employee
MyProject\Model\Employee:
type: entity
In this example, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
@@ -401,6 +439,58 @@ Example:
{
}
.. code-block:: annotation
<?php
// user mapping
namespace MyProject\Model;
/**
* @MappedSuperclass
*/
class User
{
// other fields mapping
/**
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
protected Collection $groups;
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
protected Address|null $address = null;
}
// admin mapping
namespace MyProject\Model;
/**
* @Entity
* @AssociationOverrides({
* @AssociationOverride(name="groups",
* joinTable=@JoinTable(
* name="users_admingroups",
* joinColumns=@JoinColumn(name="adminuser_id"),
* inverseJoinColumns=@JoinColumn(name="admingroup_id")
* )
* ),
* @AssociationOverride(name="address",
* joinColumns=@JoinColumn(
* name="adminaddress_id", referencedColumnName="id"
* )
* )
* })
*/
class Admin extends User
{
}
.. code-block:: xml
<!-- user mapping -->
@@ -410,6 +500,7 @@ Example:
<many-to-many field="groups" target-entity="Group" inversed-by="users">
<cascade>
<cascade-persist/>
<cascade-merge/>
<cascade-detach/>
</cascade>
<join-table name="users_groups">
@@ -446,6 +537,51 @@ Example:
</association-overrides>
</entity>
</doctrine-mapping>
.. code-block:: yaml
# user mapping
MyProject\Model\User:
type: mappedSuperclass
# other fields mapping
manyToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
cascade: [ persist, merge ]
manyToMany:
groups:
targetEntity: Group
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
cascade: [ persist, merge, detach ]
# admin mapping
MyProject\Model\Admin:
type: entity
associationOverride:
address:
joinColumn:
adminaddress_id:
name: adminaddress_id
referencedColumnName: id
groups:
joinTable:
name: users_admingroups
joinColumns:
adminuser_id:
referencedColumnName: id
inverseJoinColumns:
admingroup_id:
referencedColumnName: id
Things to note:
@@ -509,6 +645,51 @@ Could be used by an entity that extends a mapped superclass to override a field
{
}
.. code-block:: annotation
<?php
// user mapping
namespace MyProject\Model;
/**
* @MappedSuperclass
*/
class User
{
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
protected int|null $id = null;
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
protected string $name;
// other fields mapping
}
// guest mapping
namespace MyProject\Model;
/**
* @Entity
* @AttributeOverrides({
* @AttributeOverride(name="id",
* column=@Column(
* name = "guest_id",
* type = "integer",
* length = 140
* )
* ),
* @AttributeOverride(name="name",
* column=@Column(
* name = "guest_name",
* nullable = false,
* unique = true,
* length = 240
* )
* )
* })
*/
class Guest extends User
{
}
.. code-block:: xml
<!-- user mapping -->
@@ -521,6 +702,7 @@ Could be used by an entity that extends a mapped superclass to override a field
<many-to-one field="address" target-entity="Address">
<cascade>
<cascade-persist/>
<cascade-merge/>
</cascade>
<join-column name="address_id" referenced-column-name="id"/>
</many-to-one>
@@ -541,6 +723,42 @@ Could be used by an entity that extends a mapped superclass to override a field
</attribute-overrides>
</entity>
</doctrine-mapping>
.. code-block:: yaml
# user mapping
MyProject\Model\User:
type: mappedSuperclass
id:
id:
type: integer
column: user_id
length: 150
generator:
strategy: AUTO
fields:
name:
type: string
column: user_name
length: 250
nullable: true
unique: false
#other fields mapping
# guest mapping
MyProject\Model\Guest:
type: entity
attributeOverride:
id:
column: guest_id
type: integer
length: 140
name:
column: guest_name
type: string
length: 240
nullable: false
unique: true
Things to note:

View File

@@ -1,3 +1,5 @@
:orphan:
Installation
============

View File

@@ -65,6 +65,15 @@ Where the ``attribute_name`` column contains the key and
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.
Cascade Merge with Bi-directional Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
Make sure to study the behavior of cascade merge if you are using it:
- `DDC-875 <https://github.com/doctrine/orm/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <https://github.com/doctrine/orm/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
Custom Persisters
~~~~~~~~~~~~~~~~~
@@ -178,6 +187,14 @@ internally by the ORM currently refers to fields by their name only, without tak
class containing the field into consideration. This makes it impossible to keep separate
mapping configuration for both fields.
Apart from that, in the case of having multiple ``private`` fields of the same name within
the class hierarchy an entity or mapped superclass, the Collection filtering API cannot determine
the right field to look at. Even if only one of these fields is actually mapped, the ``ArrayCollection``
will not be able to tell, since it does not have access to any metadata.
Thus, to avoid problems in this regard, it is best to avoid having multiple ``private`` fields of the
same name in class hierarchies containing entity and mapped superclasses.
Known Issues
------------

View File

@@ -16,6 +16,13 @@ metadata:
- **Attributes** (AttributeDriver)
- **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``
@@ -37,7 +44,11 @@ an entity.
$em->getConfiguration()->setMetadataCacheImpl(new ApcuCache());
All the drivers are in the ``Doctrine\ORM\Mapping\Driver`` namespace:
If you want to use one of the included core metadata drivers you need to
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
@@ -70,8 +81,8 @@ implements the ``MappingDriver`` interface:
/**
* Loads the metadata for the specified class into the provided container.
*
* @psalm-param class-string<T> $className
* @psalm-param ClassMetadata<T> $metadata
* @param class-string<T> $className
* @param ClassMetadata<T> $metadata
*
* @return void
*
@@ -82,8 +93,7 @@ implements the ``MappingDriver`` interface:
/**
* Gets the names of all mapped classes known to this driver.
*
* @return array<int, string> The names of all mapped classes known to this driver.
* @psalm-return list<class-string>
* @return list<class-string> The names of all mapped classes known to this driver.
*/
public function getAllClassNames();
@@ -91,7 +101,7 @@ implements the ``MappingDriver`` interface:
* Returns whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a MappedSuperclass.
*
* @psalm-param class-string $className
* @param class-string $className
*
* @return bool
*/

View File

@@ -110,28 +110,28 @@ You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrateg
<?php
class MyAppNamingStrategy implements NamingStrategy
{
public function classToTableName(string $className): string
public function classToTableName($className)
{
return 'MyApp_' . substr($className, strrpos($className, '\\') + 1);
}
public function propertyToColumnName(string $propertyName): string
public function propertyToColumnName($propertyName)
{
return $propertyName;
}
public function referenceColumnName(): string
public function referenceColumnName()
{
return 'id';
}
public function joinColumnName(string $propertyName, ?string $className = null): string
public function joinColumnName($propertyName, $className = null)
{
return $propertyName . '_' . $this->referenceColumnName();
}
public function joinTableName(string $sourceEntity, string $targetEntity, string $propertyName): string
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
{
return strtolower($this->classToTableName($sourceEntity) . '_' .
$this->classToTableName($targetEntity));
}
public function joinKeyColumnName(string $entityName, ?string $referencedColumnName): string
public function joinKeyColumnName($entityName, $referencedColumnName = null)
{
return strtolower($this->classToTableName($entityName) . '_' .
($referencedColumnName ?: $this->referenceColumnName()));

View File

@@ -465,3 +465,477 @@ above would result in partial objects if any objects in the result
are actually a subtype of User. When using DQL, Doctrine
automatically includes the necessary joins for this mapping
strategy but with native SQL it is your responsibility.
Named Native Query
------------------
.. note::
Named Native Queries are deprecated as of version 2.9 and will be removed in ORM 3.0
You can also map a native query using a named native query mapping.
To achieve that, you must describe the SQL resultset structure
using named native query (and sql resultset mappings if is a several resultset mappings).
Like named query, a named native query can be defined at class level or in a XML or YAML file.
A resultSetMapping parameter is defined in @NamedNativeQuery,
it represents the name of a defined @SqlResultSetMapping.
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "fetchMultipleJoinsEntityResults",
* resultSetMapping= "mappingMultipleJoinsEntityResults",
* query = "SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username"
* ),
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingMultipleJoinsEntityResults",
* entities= {
* @EntityResult(
* entityClass = "__CLASS__",
* fields = {
* @FieldResult(name = "id", column="u_id"),
* @FieldResult(name = "name", column="u_name"),
* @FieldResult(name = "status", column="u_status"),
* }
* ),
* @EntityResult(
* entityClass = "Address",
* fields = {
* @FieldResult(name = "id", column="a_id"),
* @FieldResult(name = "zip", column="a_zip"),
* @FieldResult(name = "country", column="a_country"),
* }
* )
* },
* columns = {
* @ColumnResult("numphones")
* }
* )
*})
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column(type="string", length=50, nullable=true) */
public $status;
/** @Column(type="string", length=255, unique=true) */
public $username;
/** @Column(type="string", length=255) */
public $name;
/** @OneToMany(targetEntity="Phonenumber") */
public $phonenumbers;
/** @OneToOne(targetEntity="Address") */
public $address;
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\User">
<named-native-queries>
<named-native-query name="fetchMultipleJoinsEntityResults" result-set-mapping="mappingMultipleJoinsEntityResults">
<query>SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username</query>
</named-native-query>
</named-native-queries>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingMultipleJoinsEntityResults">
<entity-result entity-class="__CLASS__">
<field-result name="id" column="u_id"/>
<field-result name="name" column="u_name"/>
<field-result name="status" column="u_status"/>
</entity-result>
<entity-result entity-class="Address">
<field-result name="id" column="a_id"/>
<field-result name="zip" column="a_zip"/>
<field-result name="country" column="a_country"/>
</entity-result>
<column-result name="numphones"/>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\User:
type: entity
namedNativeQueries:
fetchMultipleJoinsEntityResults:
name: fetchMultipleJoinsEntityResults
resultSetMapping: mappingMultipleJoinsEntityResults
query: SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username
sqlResultSetMappings:
mappingMultipleJoinsEntityResults:
name: mappingMultipleJoinsEntityResults
columnResult:
0:
name: numphones
entityResult:
0:
entityClass: __CLASS__
fieldResult:
0:
name: id
column: u_id
1:
name: name
column: u_name
2:
name: status
column: u_status
1:
entityClass: Address
fieldResult:
0:
name: id
column: a_id
1:
name: zip
column: a_zip
2:
name: country
column: a_country
Things to note:
- The resultset mapping declares the entities retrieved by this native query.
- Each field of the entity is bound to a SQL alias (or column name).
- All fields of the entity including the ones of subclasses
and the foreign key columns of related entities have to be present in the SQL query.
- Field definitions are optional provided that they map to the same
column name as the one declared on the class property.
- ``__CLASS__`` is an alias for the mapped class
In the above example,
the ``fetchJoinedAddress`` named query use the joinMapping result set mapping.
This mapping returns 2 entities, User and Address, each property is declared and associated to a column name,
actually the column name retrieved by the query.
Let's now see an implicit declaration of the property / column.
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "findAll",
* resultSetMapping = "mappingFindAll",
* query = "SELECT * FROM addresses"
* ),
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingFindAll",
* entities= {
* @EntityResult(
* entityClass = "Address"
* )
* }
* )
* })
*/
class Address
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column() */
public $country;
/** @Column() */
public $zip;
/** @Column()*/
public $city;
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Address">
<named-native-queries>
<named-native-query name="findAll" result-set-mapping="mappingFindAll">
<query>SELECT * FROM addresses</query>
</named-native-query>
</named-native-queries>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingFindAll">
<entity-result entity-class="Address"/>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Address:
type: entity
namedNativeQueries:
findAll:
resultSetMapping: mappingFindAll
query: SELECT * FROM addresses
sqlResultSetMappings:
mappingFindAll:
name: mappingFindAll
entityResult:
address:
entityClass: Address
In this example, we only describe the entity member of the result set mapping.
The property / column mappings is done using the entity mapping values.
In this case the model property is bound to the model_txt column.
If the association to a related entity involve a composite primary key,
a @FieldResult element should be used for each foreign key column.
The @FieldResult name is composed of the property name for the relationship,
followed by a dot ("."), followed by the name or the field or property of the primary key.
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "fetchJoinedAddress",
* resultSetMapping= "mappingJoinedAddress",
* query = "SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?"
* ),
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingJoinedAddress",
* entities= {
* @EntityResult(
* entityClass = "__CLASS__",
* fields = {
* @FieldResult(name = "id"),
* @FieldResult(name = "name"),
* @FieldResult(name = "status"),
* @FieldResult(name = "address.id", column = "a_id"),
* @FieldResult(name = "address.zip", column = "a_zip"),
* @FieldResult(name = "address.city", column = "a_city"),
* @FieldResult(name = "address.country", column = "a_country"),
* }
* )
* }
* )
* })
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column(type="string", length=50, nullable=true) */
public $status;
/** @Column(type="string", length=255, unique=true) */
public $username;
/** @Column(type="string", length=255) */
public $name;
/** @OneToOne(targetEntity="Address") */
public $address;
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\User">
<named-native-queries>
<named-native-query name="fetchJoinedAddress" result-set-mapping="mappingJoinedAddress">
<query>SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?</query>
</named-native-query>
</named-native-queries>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingJoinedAddress">
<entity-result entity-class="__CLASS__">
<field-result name="id"/>
<field-result name="name"/>
<field-result name="status"/>
<field-result name="address.id" column="a_id"/>
<field-result name="address.zip" column="a_zip"/>
<field-result name="address.city" column="a_city"/>
<field-result name="address.country" column="a_country"/>
</entity-result>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\User:
type: entity
namedNativeQueries:
fetchJoinedAddress:
name: fetchJoinedAddress
resultSetMapping: mappingJoinedAddress
query: SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?
sqlResultSetMappings:
mappingJoinedAddress:
entityResult:
0:
entityClass: __CLASS__
fieldResult:
0:
name: id
1:
name: name
2:
name: status
3:
name: address.id
column: a_id
4:
name: address.zip
column: a_zip
5:
name: address.city
column: a_city
6:
name: address.country
column: a_country
If you retrieve a single entity and if you use the default mapping,
you can use the resultClass attribute instead of resultSetMapping:
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "find-by-id",
* resultClass = "Address",
* query = "SELECT * FROM addresses"
* ),
* })
*/
class Address
{
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Address">
<named-native-queries>
<named-native-query name="find-by-id" result-class="Address">
<query>SELECT * FROM addresses WHERE id = ?</query>
</named-native-query>
</named-native-queries>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Address:
type: entity
namedNativeQueries:
findAll:
name: findAll
resultClass: Address
query: SELECT * FROM addresses
In some of your native queries, you'll have to return scalar values,
for example when building report queries.
You can map them in the @SqlResultsetMapping through @ColumnResult.
You actually can even mix, entities and scalar returns in the same native query (this is probably not that common though).
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "count",
* resultSetMapping= "mappingCount",
* query = "SELECT COUNT(*) AS count FROM addresses"
* )
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingCount",
* columns = {
* @ColumnResult(
* name = "count"
* )
* }
* )
* })
*/
class Address
{
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Address">
<named-native-query name="count" result-set-mapping="mappingCount">
<query>SELECT COUNT(*) AS count FROM addresses</query>
</named-native-query>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingCount">
<column-result name="count"/>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Address:
type: entity
namedNativeQueries:
count:
name: count
resultSetMapping: mappingCount
query: SELECT COUNT(*) AS count FROM addresses
sqlResultSetMappings:
mappingCount:
name: mappingCount
columnResult:
count:
name: count

View File

@@ -0,0 +1,98 @@
Partial Objects
===============
.. note::
Creating Partial Objects through DQL is deprecated and
will be removed in the future, use data transfer object
support in DQL instead. (\ `Details
<https://github.com/doctrine/orm/issues/8471>`_)
A partial object is an object whose state is not fully initialized
after being reconstituted from the database and that is
disconnected from the rest of its data. The following section will
describe why partial objects are problematic and what the approach
of Doctrine2 to this problem is.
.. note::
The partial object problem in general does not apply to
methods or queries where you do not retrieve the query result as
objects. Examples are: ``Query#getArrayResult()``,
``Query#getScalarResult()``, ``Query#getSingleScalarResult()``,
etc.
.. warning::
Use of partial objects is tricky. Fields that are not retrieved
from the database will not be updated by the UnitOfWork even if they
get changed in your objects. You can only promote a partial object
to a fully-loaded object by calling ``EntityManager#refresh()``
or a DQL query with the refresh flag.
What is the problem?
--------------------
In short, partial objects are problematic because they are usually
objects with broken invariants. As such, code that uses these
partial objects tends to be very fragile and either needs to "know"
which fields or methods can be safely accessed or add checks around
every field access or method invocation. The same holds true for
the internals, i.e. the method implementations, of such objects.
You usually simply assume the state you need in the method is
available, after all you properly constructed this object before
you pushed it into the database, right? These blind assumptions can
quickly lead to null reference errors when working with such
partial objects.
It gets worse with the scenario of an optional association (0..1 to
1). When the associated field is NULL, you don't know whether this
object does not have an associated object or whether it was simply
not loaded when the owning object was loaded from the database.
These are reasons why many ORMs do not allow partial objects at all
and instead you always have to load an object with all its fields
(associations being proxied). One secure way to allow partial
objects is if the programming language/platform allows the ORM tool
to hook deeply into the object and instrument it in such a way that
individual fields (not only associations) can be loaded lazily on
first access. This is possible in Java, for example, through
bytecode instrumentation. In PHP though this is not possible, so
there is no way to have "secure" partial objects in an ORM with
transparent persistence.
Doctrine, by default, does not allow partial objects. That means,
any query that only selects partial object data and wants to
retrieve the result as objects (i.e. ``Query#getResult()``) will
raise an exception telling you that partial objects are dangerous.
If you want to force a query to return you partial objects,
possibly as a performance tweak, you can use the ``partial``
keyword as follows:
.. code-block:: php
<?php
$q = $em->createQuery("select partial u.{id,name} from MyApp\Domain\User u");
You can also get a partial reference instead of a proxy reference by
calling:
.. code-block:: php
<?php
$reference = $em->getPartialReference('MyApp\Domain\User', 1);
Partial references are objects with only the identifiers set as they
are passed to the second argument of the ``getPartialReference()`` method.
All other fields are null.
When should I force partial objects?
------------------------------------
Mainly for optimization purposes, but be careful of premature
optimization as partial objects lead to potentially more fragile
code.

View File

@@ -1,20 +1,97 @@
PHP Mapping
===========
Doctrine ORM also allows you to provide the ORM metadata in the form of plain
PHP code using the ``ClassMetadata`` API. You can write the code in inside of a
static function named ``loadMetadata($class)`` on the entity class itself.
Doctrine ORM also allows you to provide the ORM metadata in the form
of plain PHP code using the ``ClassMetadata`` API. You can write
the code in PHP files or inside of a static function named
``loadMetadata($class)`` on the entity class itself.
PHP Files
---------
.. note::
PHPDriver is deprecated and will be removed in 3.0, use StaticPHPDriver
instead.
If you wish to write your mapping information inside PHP files that
are named after the entity and included to populate the metadata
for an entity you can do so by using the ``PHPDriver``:
.. code-block:: php
<?php
$driver = new PHPDriver('/path/to/php/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Now imagine we had an entity named ``Entities\User`` and we wanted
to write a mapping file for it using the above configured
``PHPDriver`` instance:
.. code-block:: php
<?php
namespace Entities;
class User
{
private $id;
private $username;
}
To write the mapping information you just need to create a file
named ``Entities.User.php`` inside of the
``/path/to/php/mapping/files`` folder:
.. code-block:: php
<?php
// /path/to/php/mapping/files/Entities.User.php
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string',
'options' => array(
'fixed' => true,
'comment' => "User's login name"
)
));
$metadata->mapField(array(
'fieldName' => 'login_count',
'type' => 'integer',
'nullable' => false,
'options' => array(
'unsigned' => true,
'default' => 0
)
));
Now we can easily retrieve the populated ``ClassMetadata`` instance
where the ``PHPDriver`` includes the file and the
``ClassMetadataFactory`` caches it for later retrieval:
.. code-block:: php
<?php
$class = $em->getClassMetadata('Entities\User');
// or
$class = $em->getMetadataFactory()->getMetadataFor('Entities\User');
Static Function
---------------
In addition to other drivers using configuration languages you can also
programatically specify your mapping information inside of a static function
defined on the entity class itself.
This is useful for cases where you want to keep your entity and mapping
information together but don't want to use attributes. For this you just
need to use the ``StaticPHPDriver``:
In addition to the PHP files you can also specify your mapping
information inside of a static function defined on the entity class
itself. This is useful for cases where you want to keep your entity
and mapping information together but don't want to use attributes or
annotations. For this you just need to use the ``StaticPHPDriver``:
.. code-block:: php
@@ -87,11 +164,13 @@ The API of the ClassMetadataBuilder has the following methods with a fluent inte
- ``setTable($name)``
- ``addIndex(array $columns, $indexName)``
- ``addUniqueConstraint(array $columns, $constraintName)``
- ``addNamedQuery($name, $dqlQuery)``
- ``setJoinedTableInheritance()``
- ``setSingleTableInheritance()``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255, $columnDefinition = null, $enumType = null, $options = [])``
- ``addDiscriminatorMapClass($name, $class)``
- ``setChangeTrackingPolicyDeferredExplicit()``
- ``setChangeTrackingPolicyNotify()``
- ``addLifecycleEvent($methodName, $event)``
- ``addManyToOne($name, $targetEntity, $inversedBy = null)``
- ``addInverseOneToOne($name, $targetEntity, $mappedBy)``
@@ -193,6 +272,7 @@ Inheritance Getters
- ``isInheritanceTypeNone()``
- ``isInheritanceTypeJoined()``
- ``isInheritanceTypeSingleTable()``
- ``isInheritanceTypeTablePerClass()``
- ``isInheritedField($fieldName)``
- ``isInheritedAssociation($fieldName)``
@@ -202,6 +282,7 @@ Change Tracking Getters
- ``isChangeTrackingDeferredExplicit()``
- ``isChangeTrackingDeferredImplicit()``
- ``isChangeTrackingNotify()``
Field & Association Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -44,7 +44,7 @@ Something like below for an entity region:
If the entity holds a collection that also needs to be cached.
An collection region could look something like:
A collection region could look something like:
.. code-block:: php
@@ -295,11 +295,35 @@ level cache region.
// other properties and methods
}
.. code-block:: annotation
<?php
/**
* @Entity
* @Cache("NONSTRICT_READ_WRITE")
*/
class State
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected int|null $id = null;
/**
* @Column(unique=true)
*/
protected string $name;
// other properties and methods
}
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Country">
@@ -311,6 +335,24 @@ level cache region.
</entity>
</doctrine-mapping>
.. code-block:: yaml
Country:
type: entity
cache:
usage: READ_ONLY
region: my_entity_region
id:
id:
type: integer
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
Association cache definition
----------------------------
The most common use case is to cache entities. But we can also cache relationships.
@@ -347,11 +389,49 @@ It caches the primary keys of association and cache each element will be cached
// other properties and methods
}
.. code-block:: annotation
<?php
/**
* @Entity
* @Cache("NONSTRICT_READ_WRITE")
*/
class State
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected int|null $id = null;
/**
* @Column(unique=true)
*/
protected string $name;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country_id", referencedColumnName="id")
*/
protected Country|null $country;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
* @var Collection<int, City>
*/
protected Collection $cities;
// other properties and methods
}
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="State">
@@ -378,6 +458,38 @@ It caches the primary keys of association and cache each element will be cached
</entity>
</doctrine-mapping>
.. code-block:: yaml
State:
type: entity
cache:
usage: NONSTRICT_READ_WRITE
id:
id:
type: integer
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
manyToOne:
state:
targetEntity: Country
joinColumns:
country_id:
referencedColumnName: id
cache:
usage: NONSTRICT_READ_WRITE
oneToMany:
cities:
targetEntity:City
mappedBy: state
cache:
usage: NONSTRICT_READ_WRITE
.. note::
for this to work, the target entity must also be marked as cacheable.
@@ -518,7 +630,7 @@ DELETE / UPDATE queries
DQL UPDATE / DELETE statements are ported directly into a database and bypass
the second-level cache.
Entities that are already cached will NOT be invalidated.
However the cached data could be evicted using the cache API or an special query hint.
However the cached data could be evicted using the cache API or a special query hint.
Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``

View File

@@ -118,7 +118,7 @@ entity might look like this:
}
}
Now the possiblity of mass-assignment exists on this entity and can
Now the possibility of mass-assignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:

View File

@@ -83,8 +83,18 @@ The following Commands are currently available:
cache drivers.
- ``orm:clear-cache:result`` Clear result cache of the various
cache drivers.
- ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a
Doctrine 2.X schema.
- ``orm:convert-mapping`` Convert mapping information between
supported formats.
- ``orm:ensure-production-settings`` Verify that Doctrine is
properly configured for a production environment.
- ``orm:generate-entities`` Generate entity classes and method
stubs from your mapping information.
- ``orm:generate-proxies`` Generates proxy classes for entity
classes.
- ``orm:generate-repositories`` Generate repository classes from
your mapping information.
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
line.
- ``orm:schema-tool:create`` Processes the schema and either
@@ -97,10 +107,14 @@ The following Commands are currently available:
update the database schema of EntityManager Storage Connection or
generate the SQL output.
The following alias is defined:
For these commands are also available aliases:
- ``orm:convert:d1-schema`` is alias for ``orm:convert-d1-schema``.
- ``orm:convert:mapping`` is alias for ``orm:convert-mapping``.
- ``orm:generate:entities`` is alias for ``orm:generate-entities``.
- ``orm:generate:proxies`` is alias for ``orm:generate-proxies``.
- ``orm:generate:repositories`` is alias for ``orm:generate-repositories``.
.. note::
@@ -211,6 +225,163 @@ will output the SQL for the ran operation.
$ php bin/doctrine orm:schema-tool:create --dump-sql
Entity Generation
-----------------
Generate entity classes and method stubs from your mapping information.
.. code-block:: php
$ php bin/doctrine orm:generate-entities
$ php bin/doctrine orm:generate-entities --update-entities
$ php bin/doctrine orm:generate-entities --regenerate-entities
This command is not suited for constant usage. It is a little helper and does
not support all the mapping edge cases very well. You still have to put work
in your entities after using this command.
It is possible to use the EntityGenerator on code that you have already written. It will
not be lost. The EntityGenerator will only append new code to your
file and will not delete the old code. However this approach may still be prone
to error and we suggest you use code repositories such as GIT or SVN to make
backups of your code.
It makes sense to generate the entity code if you are using entities as Data
Access Objects only and don't put much additional logic on them. If you are
however putting much more logic on the entities you should refrain from using
the entity-generator and code your entities manually.
.. note::
Even if you specified Inheritance options in your
XML or YAML Mapping files the generator cannot generate the base and
child classes for you correctly, because it doesn't know which
class is supposed to extend which. You have to adjust the entity
code manually for inheritance to work!
Convert Mapping Information
---------------------------
Convert mapping information between supported formats.
This is an **execute one-time** command. It should not be necessary for
you to call this method multiple times, especially when using the ``--from-database``
flag.
Converting an existing database schema into mapping files only solves about 70-80%
of the necessary mapping information. Additionally the detection from an existing
database cannot detect inverse associations, inheritance types,
entities with foreign keys as primary keys and many of the
semantical operations on associations such as cascade.
.. note::
There is no need to convert YAML or XML mapping files to annotations
every time you make changes. All mapping drivers are first class citizens
in Doctrine 2 and can be used as runtime mapping for the ORM. See the
docs on XML and YAML Mapping for an example how to register this metadata
drivers as primary mapping source.
To convert some mapping information between the various supported
formats you can use the ``ClassMetadataExporter`` to get exporter
instances for the different formats:
.. code-block:: php
<?php
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
Once you have a instance you can use it to get an exporter. For
example, the yml exporter:
.. code-block:: php
<?php
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
Now you can export some ``ClassMetadata`` instances:
.. code-block:: php
<?php
$classes = array(
$em->getClassMetadata('Entities\User'),
$em->getClassMetadata('Entities\Profile')
);
$exporter->setMetadata($classes);
$exporter->export();
This functionality is also available from the command line to
convert your loaded mapping information to another format. The
``orm:convert-mapping`` command accepts two arguments, the type to
convert to and the path to generate it:
.. code-block:: php
$ php bin/doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
Reverse Engineering
-------------------
You can use the ``DatabaseDriver`` to reverse engineer a database to an
array of ``ClassMetadata`` instances and generate YAML, XML, etc. from
them.
.. note::
Reverse Engineering is a **one-time** process that can get you started with a project.
Converting an existing database schema into mapping files only detects about 70-80%
of the necessary mapping information. Additionally the detection from an existing
database cannot detect inverse associations, inheritance types,
entities with foreign keys as primary keys and many of the
semantical operations on associations such as cascade.
First you need to retrieve the metadata instances with the
``DatabaseDriver``:
.. code-block:: php
<?php
$em->getConfiguration()->setMetadataDriverImpl(
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
$em->getConnection()->getSchemaManager()
)
);
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadata = $cmf->getAllMetadata();
Now you can get an exporter instance and export the loaded metadata
to yml:
.. code-block:: php
<?php
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
$exporter->setMetadata($metadata);
$exporter->export();
You can also reverse engineer a database using the
``orm:convert-mapping`` command:
.. code-block:: php
$ php bin/doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
.. note::
Reverse Engineering is not always working perfectly
depending on special cases. It will only detect Many-To-One
relations (even if they are One-To-One) and will try to create
entities from Many-To-Many tables. It also has problems with naming
of foreign keys that have multiple column names. Any Reverse
Engineered Database-Schema needs considerable manual work to become
a useful domain model.
Runtime vs Development Mapping Validation
-----------------------------------------

View File

@@ -202,6 +202,17 @@ example we'll use an integer.
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private int $version;
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -210,6 +221,15 @@ example we'll use an integer.
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
fields:
version:
type: integer
version: true
Alternatively a datetime type can be used (which maps to a SQL
timestamp or datetime):
@@ -226,6 +246,17 @@ timestamp or datetime):
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private DateTime $version;
// ...
}
.. code-block:: xml
<doctrine-mapping>
@@ -234,6 +265,15 @@ timestamp or datetime):
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
fields:
version:
type: datetime
version: true
Version numbers (not timestamps) should however be preferred as
they can not potentially conflict in a highly concurrent
environment, unlike timestamps where this is a possibility,

View File

@@ -47,6 +47,21 @@ Then, an entity using the ``CustomIdObject`` typed field will be correctly assig
// ...
}
.. 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>
@@ -161,4 +176,4 @@ You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMap
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.
that behaves like the ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.

View File

@@ -102,7 +102,7 @@ How Doctrine Detects Changes
----------------------------
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
This means you map php objects into a relational database that don't
This means you map PHP objects into a relational database that don't
necessarily know about the database at all. A natural question would now be,
"how does Doctrine even detect objects have changed?".
@@ -129,10 +129,16 @@ optimize the performance of the Flush Operation:
- Temporarily mark entities as read only. If you have a very large UnitOfWork
but know that a large set of entities has not changed, just mark them as read
only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``.
- Flush only a single entity with ``$entityManager->flush($entity)``.
- Use :doc:`Change Tracking Policies <change-tracking-policies>` to use more
explicit strategies of notifying the UnitOfWork what objects/properties
changed.
.. note::
Flush only a single entity with ``$entityManager->flush($entity)`` is deprecated and will be removed in ORM 3.0.
(\ `Details <https://github.com/doctrine/orm/issues/8459>`_)
Query Internals
---------------

View File

@@ -415,7 +415,7 @@ Transitive persistence / Cascade Operations
Doctrine ORM provides a mechanism for transitive persistence through cascading of certain operations.
Each association to another entity or a collection of
entities can be configured to automatically cascade the following operations to the associated entities:
``persist``, ``remove``, ``detach``, ``refresh`` or ``all``.
``persist``, ``remove``, ``merge``, ``detach``, ``refresh`` or ``all``.
The main use case for ``cascade: persist`` is to avoid "exposing" associated entities to your PHP application.
Continuing with the User-Comment example of this chapter, this is how the creation of a new user and a new
@@ -736,6 +736,35 @@ methods:
.. note::
There is a limitation on the compatibility of Criteria comparisons.
You have to use scalar values only as the value in a comparison or
the behaviour between different backends is not the same.
Depending on whether the collection has already been loaded from the
database or not, criteria matching may happen at the database/SQL level
or on objects in memory. This may lead to different results and come
surprising, for example when a code change in one place leads to a collection
becoming initialized and, as a side effect, returning a different result
or even breaking a ``matching()`` call somewhere else. Also, collection
initialization state in practical use cases may differ from the one covered
in unit tests.
Database level comparisons are based on scalar representations of the values
stored in entity properties. The field names passed to expressions correspond
to property names. Comparison and sorting may be affected by
database-specific behavior. For example, MySQL enum types sort by index position,
not lexicographically by value.
In-memory handling is based on the ``Selectable`` API of `Doctrine Collections <https://www.doctrine-project.org/projects/doctrine-collections/en/stable/index.html#matching>`.
In this case, field names passed to expressions are being used to derive accessor
method names. Strict type comparisons are used for equal and not-equal checks,
and generally PHP language rules are being used for other comparison operators
or sorting.
As a general guidance, for consistent results use the Criteria API with scalar
values only. Note that ``DateTime`` and ``DateTimeImmutable`` are two predominant
examples of value objects that are *not* scalars.
Refrain from using special database-level column types or custom Doctrine Types
that may lead to database-specific comparison or sorting rules being applied, or
to database-level values being different from object field values.
Provide accessor methods for all entity fields used in criteria expressions,
and implement those methods in a way that their return value is the
same as the database-level value.

View File

@@ -414,6 +414,77 @@ automatically without invoking the ``detach`` method:
The ``detach`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``.
Merging entities
----------------
Merging entities refers to the merging of (usually detached)
entities into the context of an EntityManager so that they become
managed again. To merge the state of an entity into an
EntityManager use the ``EntityManager#merge($entity)`` method. The
state of the passed entity will be merged into a managed copy of
this entity and this copy will subsequently be returned.
Example:
.. code-block:: php
<?php
$detachedEntity = unserialize($serializedEntity); // some detached entity
$entity = $em->merge($detachedEntity);
// $entity now refers to the fully managed copy returned by the merge operation.
// The EntityManager $em now manages the persistence of $entity as usual.
The semantics of the merge operation, applied to an entity X, are
as follows:
- If X is a detached entity, the state of X is copied onto a
pre-existing managed entity instance X' of the same identity.
- If X is a new entity instance, a new managed copy X' will be
created and the state of X is copied onto this managed instance.
- If X is a removed entity instance, an InvalidArgumentException
will be thrown.
- If X is a managed entity, it is ignored by the merge operation,
however, the merge operation is cascaded to entities referenced by
relationships from X if these relationships have been mapped with
the cascade element value MERGE or ALL (see ":ref:`transitive-persistence`").
- For all entities Y referenced by relationships from X having the
cascade element value MERGE or ALL, Y is merged recursively as Y'.
For all such Y referenced by X, X' is set to reference Y'. (Note
that if X is managed then X is the same object as X'.)
- If X is an entity merged to X', with a reference to another
entity Y, where cascade=MERGE or cascade=ALL is not specified, then
navigation of the same association from X' yields a reference to a
managed object Y' with the same persistent identity as Y.
The ``merge`` operation will throw an ``OptimisticLockException``
if the entity being merged uses optimistic locking through a
version field and the versions of the entity being merged and the
managed copy don't match. This usually means that the entity has
been modified while being detached.
The ``merge`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``. The most common scenario for
the ``merge`` operation is to reattach entities to an EntityManager
that come from some cache (and are therefore detached) and you want
to modify and persist such an entity.
.. warning::
If you need to perform multiple merges of entities that share certain subparts
of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the
successive calls to ``EntityManager#merge()``. Otherwise you might end up with
multiple copies of the "same" object in the database, however with different ids.
.. note::
If you load some detached entities from a cache and you do
not need to persist or delete them or otherwise make use of them
without the need for persistence services there is no need to use
``merge``. I.e. you can simply pass detached objects from a cache
directly to the view.
Synchronization with the Database
---------------------------------
@@ -524,7 +595,7 @@ during development.
.. note::
Do not invoke ``flush`` after every change to an entity
or every single invocation of persist/remove/... This is an
or every single invocation of persist/remove/merge/... This is an
anti-pattern and unnecessarily reduces the performance of your
application. Instead, form units of work that operate on your
objects and call ``flush`` when you are done. While serving a
@@ -792,7 +863,7 @@ By default the EntityManager returns a default implementation of
``Doctrine\ORM\EntityRepository`` when you call
``EntityManager#getRepository($entityClass)``. You can overwrite
this behaviour by specifying the class name of your own Entity
Repository in the Attribute or XML metadata. In large
Repository in the Attribute, Annotation, XML or YAML metadata. In large
applications that require lots of specialized DQL queries using a
custom repository is one recommended way of grouping these queries
in a central location.

View File

@@ -2,8 +2,7 @@ XML Mapping
===========
The XML mapping driver enables you to provide the ORM metadata in
form of XML documents. It requires the ``dom`` extension in order to be
able to validate your mapping documents against its XML Schema.
form of XML documents.
The XML driver is backed by an XML Schema document that describes
the structure of a mapping document. The most recent version of the
@@ -18,7 +17,7 @@ setup for the latest code in trunk.
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -104,7 +103,7 @@ of several common elements:
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -692,6 +691,7 @@ specified by their respective tags:
- ``<cascade-persist />``
- ``<cascade-merge />``
- ``<cascade-remove />``
- ``<cascade-refresh />``
- ``<cascade-detach />``
@@ -770,7 +770,7 @@ entity relationship. You can define this in XML with the "association-key" attri
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

View File

@@ -0,0 +1,158 @@
YAML Mapping
============
.. 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.
The YAML mapping driver enables you to provide the ORM metadata in
form of YAML documents.
The YAML mapping document of a class is loaded on-demand the first
time it is requested and subsequently stored in the metadata cache.
In order to work, this requires certain conventions:
- Each entity/mapped superclass must get its own dedicated YAML
mapping document.
- The name of the mapping document must consist of the fully
qualified name of the class, where namespace separators are
replaced by dots (.).
- All mapping documents should get the extension ".dcm.yml" to
identify it as a Doctrine mapping file. This is more of a
convention and you are not forced to do this. You can change the
file extension easily enough.
.. code-block:: php
<?php
$driver->setFileExtension('.yml');
It is recommended to put all YAML mapping documents in a single
folder but you can spread the documents over several folders if you
want to. In order to tell the YamlDriver where to look for your
mapping documents, supply an array of paths as the first argument
of the constructor, like this:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\YamlDriver;
// $config instanceof Doctrine\ORM\Configuration
$driver = new YamlDriver(array('/path/to/files'));
$config->setMetadataDriverImpl($driver);
Simplified YAML Driver
~~~~~~~~~~~~~~~~~~~~~~
The Symfony project sponsored a driver that simplifies usage of the YAML Driver.
The changes between the original driver are:
- File Extension is .orm.yml
- Filenames are shortened, "MyProject\\Entities\\User" will become User.orm.yml
- You can add a global file and add multiple entities in this file.
Configuration of this client works a little bit different:
.. code-block:: php
<?php
$namespaces = array(
'/path/to/files1' => 'MyProject\Entities',
'/path/to/files2' => 'OtherProject\Entities'
);
$driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver($namespaces);
$driver->setGlobalBasename('global'); // global.orm.yml
Example
-------
As a quick start, here is a small example document that makes use
of several common elements:
.. code-block:: yaml
# Doctrine.Tests.ORM.Mapping.User.dcm.yml
Doctrine\Tests\ORM\Mapping\User:
type: entity
repositoryClass: Doctrine\Tests\ORM\Mapping\UserRepository
table: cms_users
schema: schema_name # The schema the table lies in, for platforms that support schemas (Optional, >= 2.5)
readOnly: true
indexes:
name_index:
columns: [ name ]
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type: string
length: 50
email:
type: string
length: 32
column: user_email
unique: true
options:
fixed: true
comment: User's email address
loginCount:
type: integer
column: login_count
nullable: false
options:
unsigned: true
default: 0
oneToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
onDelete: CASCADE
oneToMany:
phonenumbers:
targetEntity: Phonenumber
mappedBy: user
cascade: ["persist", "merge"]
manyToMany:
groups:
targetEntity: Group
joinTable:
name: cms_users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
Be aware that class-names specified in the YAML files should be
fully qualified.
Reference
~~~~~~~~~~~~~~~~~~~~~~
Unique Constraints
------------------
It is possible to define unique constraints by the following declaration:
.. code-block:: yaml
# ECommerceProduct.orm.yml
ECommerceProduct:
type: entity
fields:
# definition of some fields
uniqueConstraints:
search_idx:
columns: [ name, email ]

View File

@@ -1,80 +1,78 @@
.. toc::
:orphan:
.. tocheader:: Tutorials
.. toctree::
:caption: Tutorials
:depth: 3
.. toctree::
:depth: 3
tutorials/getting-started
tutorials/getting-started-database
tutorials/getting-started-models
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination
tutorials/embeddables
tutorials/getting-started
tutorials/getting-started-database
tutorials/getting-started-models
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination
tutorials/embeddables
.. toctree::
:caption: Reference
:depth: 3
.. toc::
reference/architecture
reference/configuration
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/typedfieldmapper
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/partial-objects
reference/annotations-reference
reference/attributes-reference
reference/xml-mapping
reference/yaml-mapping
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination
reference/filters
reference/namingstrategy
reference/advanced-configuration
reference/second-level-cache
reference/security
.. tocheader:: Reference
.. toctree::
:caption: Cookbook
:depth: 3
.. toctree::
:depth: 3
reference/architecture
reference/configuration
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/typedfieldmapper
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/attributes-reference
reference/xml-mapping
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination
reference/filters
reference/namingstrategy
reference/advanced-configuration
reference/second-level-cache
reference/security
.. toc::
.. tocheader:: Cookbook
.. toctree::
:depth: 3
cookbook/aggregate-fields
cookbook/custom-mapping-types
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session
cookbook/aggregate-fields
cookbook/custom-mapping-types
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/generated-columns
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session

View File

@@ -50,11 +50,43 @@ and year of production as primary keys:
}
}
.. code-block:: annotation
<?php
namespace VehicleCatalogue\Model;
/**
* @Entity
*/
class Car
{
/** @Id @Column(type="string") */
private string $name;
/** @Id @Column(type="integer") */
private int $year;
public function __construct($name, $year)
{
$this->name = $name;
$this->year = $year;
}
public function getModelName(): string
{
return $this->name;
}
public function getYearOfProduction(): int
{
return $this->year;
}
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -64,6 +96,16 @@ and year of production as primary keys:
</entity>
</doctrine-mapping>
.. code-block:: yaml
VehicleCatalogue\Model\Car:
type: entity
id:
name:
type: string
year:
type: integer
Now you can use this entity:
.. code-block:: php
@@ -85,11 +127,12 @@ And for querying you can use arrays to both DQL and EntityRepositories:
namespace VehicleCatalogue\Model;
// $em is the EntityManager
$audi = $em->find("VehicleCatalogue\Model\Car", array("name" => "Audi A8", "year" => 2010));
$audi = $em->find("VehicleCatalogue\Model\Car", ["name" => "Audi A8", "year" => 2010]);
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1";
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.name = ?1 AND c.year = ?2";
$audi = $em->createQuery($dql)
->setParameter(1, ["name" => "Audi A8", "year" => 2010])
->setParameter(1, "Audi A8")
->setParameter(2, 2010)
->getSingleResult();
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
@@ -118,6 +161,7 @@ The semantics of mapping identity through foreign entities are easy:
- Only allowed on Many-To-One or One-To-One associations.
- Plug an ``#[Id]`` attribute onto every association.
- Set an attribute ``association-key`` with the field name of the association in XML.
- Set a key ``associationKey:`` with the field name of the association in YAML.
Use-Case 1: Dynamic Attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -171,10 +215,61 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
}
}
.. code-block:: annotation
<?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;
/**
* @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
* @var Collection<int, ArticleAttribute>
*/
private Collection $attributes;
public function addAttribute($name, $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
/**
* @Entity
*/
class ArticleAttribute
{
/** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
private Article|null $article;
/** @Id @Column(type="string") */
private string $attribute;
/** @Column(type="string") */
private string $value;
public function __construct($name, $value, $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -189,6 +284,24 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
</doctrine-mapping>
.. code-block:: yaml
Application\Model\ArticleAttribute:
type: entity
id:
article:
associationKey: true
attribute:
type: string
fields:
value:
type: string
manyToOne:
article:
targetEntity: Article
inversedBy: attributes
Use-Case 2: Simple Derived Identity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -216,6 +329,26 @@ One good example for this is a user-address relationship:
private User|null $user = null;
}
.. code-block:: yaml
User:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
Address:
type: entity
id:
user:
associationKey: true
oneToOne:
user:
targetEntity: User
Use-Case 3: Join-Table with Metadata
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -44,6 +44,33 @@ instead of simply adding the respective columns to the ``User`` class.
private string $country;
}
.. code-block:: annotation
<?php
/** @Entity */
class User
{
/** @Embedded(class = "Address") */
private Address $address;
}
/** @Embeddable */
class Address
{
/** @Column(type = "string") */
private string $street;
/** @Column(type = "string") */
private string $postalCode;
/** @Column(type = "string") */
private string $city;
/** @Column(type = "string") */
private string $country;
}
.. code-block:: xml
<doctrine-mapping>
@@ -59,6 +86,22 @@ instead of simply adding the respective columns to the ``User`` class.
</embeddable>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
embedded:
address:
class: Address
Address:
type: embeddable
fields:
street: { type: string }
postalCode: { type: string }
city: { type: string }
country: { type: string }
In terms of your database schema, Doctrine will automatically inline all
columns from the ``Address`` class into the table of the ``User`` class,
just as if you had declared them directly there.
@@ -104,12 +147,32 @@ The following example shows you how to set your prefix to ``myPrefix_``:
private Address $address;
}
.. code-block:: annotation
<?php
/** @Entity */
class User
{
/** @Embedded(class = "Address", columnPrefix = "myPrefix_") */
private $address;
}
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" column-prefix="myPrefix_" />
</entity>
.. code-block:: yaml
User:
type: entity
embedded:
address:
class: Address
columnPrefix: myPrefix_
To have Doctrine drop the prefix and use the value object's property name
directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
@@ -126,12 +189,32 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
private Address $address;
}
.. code-block:: annotation
<?php
/** @Entity */
class User
{
/** @Embedded(class = "Address", columnPrefix = false) */
private Address $address;
}
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" use-column-prefix="false" />
</entity>
.. code-block:: yaml
User:
type: entity
embedded:
address:
class: Address
columnPrefix: false
DQL
---

View File

@@ -18,6 +18,7 @@ can be called without triggering a full load of the collection:
- ``Collection#containsKey($key)``
- ``Collection#count()``
- ``Collection#get($key)``
- ``Collection#isEmpty()``
- ``Collection#slice($offset, $length = null)``
For each of the above methods the following semantics apply:
@@ -65,11 +66,28 @@ switch to extra lazy as shown in these examples:
public Collection $users;
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\CMS;
/**
* @Entity
*/
class CmsGroup
{
/**
* @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
* @var Collection<int, CmsUser>
*/
public Collection $users;
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -78,3 +96,14 @@ switch to extra lazy as shown in these examples:
<many-to-many field="users" target-entity="CmsUser" mapped-by="groups" fetch="EXTRA_LAZY" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
Doctrine\Tests\Models\CMS\CmsGroup:
type: entity
# ...
manyToMany:
users:
targetEntity: CmsUser
mappedBy: groups
fetch: EXTRA_LAZY

View File

@@ -27,7 +27,7 @@ What is Doctrine?
-----------------
Doctrine ORM is an `object-relational mapper (ORM) <https://en.wikipedia.org/wiki/Object-relational_mapping>`_
for PHP that provides transparent persistence for PHP objects. It uses the Data Mapper
for PHP 7.1+ that provides transparent persistence for PHP objects. It uses the Data Mapper
pattern at the heart, aiming for a complete separation of your domain/business
logic from the persistence in a relational database management system.
@@ -82,9 +82,10 @@ that directory with the following contents:
{
"require": {
"doctrine/orm": "^3",
"doctrine/dbal": "^4",
"symfony/cache": "^7"
"doctrine/orm": "^2.11.0",
"doctrine/dbal": "^3.2",
"symfony/yaml": "^5.4",
"symfony/cache": "^5.4"
},
"autoload": {
"psr-0": {"": "src/"}
@@ -106,8 +107,12 @@ Add the following directories::
doctrine2-tutorial
|-- config
| `-- xml
| `-- yaml
`-- src
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
.. note::
It is strongly recommended that you require ``doctrine/dbal`` in your
``composer.json`` as well, because using the ORM means mapping objects
@@ -142,11 +147,19 @@ step:
paths: [__DIR__ . '/src'],
isDevMode: true,
);
// or if you prefer XML
// or if you prefer annotation, YAML or XML
// $config = ORMSetup::createAnnotationMetadataConfiguration(
// paths: array(__DIR__."/src"),
// isDevMode: true,
// );
// $config = ORMSetup::createXMLMetadataConfiguration(
// paths: [__DIR__ . '/config/xml'],
// isDevMode: true,
//);
// $config = ORMSetup::createYAMLMetadataConfiguration(
// paths: array(__DIR__."/config/yaml"),
// isDevMode: true,
// );
// configuring the database connection
$connection = DriverManager::getConnection([
@@ -157,6 +170,10 @@ step:
// obtaining the entity manager
$entityManager = new EntityManager($connection, $config);
.. note::
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.
The ``require_once`` statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
@@ -483,8 +500,8 @@ language describes how entities, their properties and references should be
persisted and what constraints should be applied to them.
Metadata for an Entity can be configured using attributes directly in
the Entity class itself, or in an external XML file. This
Getting Started guide will demonstrate metadata mappings using both
the Entity class itself, or in an external XML or YAML file. This
Getting Started guide will demonstrate metadata mappings using all three
methods, but you only need to choose one.
.. configuration-block::
@@ -510,11 +527,38 @@ methods, but you only need to choose one.
// .. (other code)
}
.. code-block:: annotation
<?php
// src/Product.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="products")
*/
class Product
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
private int|null $id = null;
/**
* @ORM\Column(type="string")
*/
private string $name;
// .. (other code)
}
.. code-block:: xml
<!-- config/xml/Product.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -527,6 +571,25 @@ methods, but you only need to choose one.
</entity>
</doctrine-mapping>
.. note::
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.
.. code-block:: yaml
# config/yaml/Product.dcm.yml
Product:
type: entity
table: products
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type: string
The top-level ``entity`` definition specifies information about
the class and table name. The primitive type ``Product#name`` is
defined as a ``field`` attribute. The ``id`` property is defined with
@@ -1019,11 +1082,64 @@ the ``Product`` before:
// ... (other code)
}
.. code-block:: annotation
<?php
// src/Bug.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="bugs")
*/
class Bug
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
private int|null $id = null;
/**
* @ORM\Column(type="string")
*/
private string $description;
/**
* @ORM\Column(type="datetime")
*/
private DateTime $created;
/**
* @ORM\Column(type="string")
*/
private string $status;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
*/
private User|null $engineer;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
*/
private User|null $reporter;
/**
* @ORM\ManyToMany(targetEntity="Product")
*/
private Collection $products;
// ... (other code)
}
.. code-block:: xml
<!-- config/xml/Bug.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -1043,6 +1159,40 @@ the ``Product`` before:
</entity>
</doctrine-mapping>
.. note::
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.
.. code-block:: yaml
# config/yaml/Bug.dcm.yml
Bug:
type: entity
table: bugs
id:
id:
type: integer
generator:
strategy: AUTO
fields:
description:
type: text
created:
type: datetime
status:
type: string
manyToOne:
reporter:
targetEntity: User
inversedBy: reportedBugs
engineer:
targetEntity: User
inversedBy: assignedBugs
manyToMany:
products:
targetEntity: Product
Here we have the entity, id and primitive type definitions.
For the "created" field we have used the ``datetime`` type,
which translates the YYYY-mm-dd HH:mm:ss database format
@@ -1099,11 +1249,52 @@ Finally, we'll add metadata mappings for the ``User`` entity.
// .. (other code)
}
.. code-block:: annotation
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="users")
*/
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @var int
*/
private int|null $id = null;
/**
* @ORM\Column(type="string")
* @var string
*/
private string $name;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
*/
private Collection $reportedBugs;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
*/
private Collection $assignedBugs;
// .. (other code)
}
.. code-block:: xml
<!-- config/xml/User.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -1119,7 +1310,33 @@ Finally, we'll add metadata mappings for the ``User`` entity.
</entity>
</doctrine-mapping>
Here are some new things to mention about the ``one-to-many`` tags.
.. note::
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.
.. code-block:: yaml
# config/yaml/User.dcm.yml
User:
type: entity
table: users
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type: string
oneToMany:
reportedBugs:
targetEntity: Bug
mappedBy: reporter
assignedBugs:
targetEntity: Bug
mappedBy: engineer
Here are some new things to mention about the ``OneToMany`` attribute.
Remember that we discussed about the inverse and owning side. Now
both reportedBugs and assignedBugs are inverse relations, which
means the join details have already been defined on the owning
@@ -1583,10 +1800,25 @@ we have to adjust the metadata slightly.
// ...
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="BugRepository")
* @ORM\Table(name="bugs")
*/
class Bug
{
// ...
}
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -1595,6 +1827,16 @@ we have to adjust the metadata slightly.
</entity>
</doctrine-mapping>
.. note::
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.
.. code-block:: yaml
Bug:
type: entity
repositoryClass: BugRepository
Now we can remove our query logic in all the places and instead use them through the EntityRepository.
As an example here is the code of the first use case "List of Bugs":

View File

@@ -27,6 +27,22 @@ can specify the ``#[OrderBy]`` in the following way:
private Collection $groups;
}
.. code-block:: annotation
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
* @var Collection<int, Group>
*/
private Collection $groups;
}
.. code-block:: xml
<doctrine-mapping>
@@ -39,6 +55,23 @@ can specify the ``#[OrderBy]`` in the following way:
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToMany:
groups:
orderBy: { 'name': 'ASC' }
targetEntity: Group
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
The DQL Snippet in OrderBy is only allowed to consist of
unqualified, unquoted field names and of an optional ASC/DESC
positional statement. Multiple Fields are separated by a comma (,).

View File

@@ -9,7 +9,7 @@ i.e. attributes and associations metadata in particular. The example here shows
the overriding of a class that uses a trait but is similar when extending a base
class as shown at the end of this tutorial.
Suppose we have a class ExampleEntityWithOverride. This class uses trait ExampleTrait:
Suppose we have a class ``ExampleEntityWithOverride``. This class uses trait ``ExampleTrait``:
.. code-block:: php
@@ -17,22 +17,20 @@ Suppose we have a class ExampleEntityWithOverride. This class uses trait Example
#[Entity]
#[AttributeOverrides([
new AttributeOverride('foo', [
'column' => new Column([
'name' => 'foo_overridden',
'type' => 'integer',
'length' => 140,
'nullable' => false,
'unique' => false,
]),
]),
new AttributeOverride('foo', new Column(
name: 'foo_overridden',
type: 'integer',
length: 140,
nullable: false,
unique: false,
)),
])]
#[AssociationOverrides([
new AssociationOverride('bar', [
'joinColumns' => new JoinColumn([
'name' => 'example_entity_overridden_bar_id',
'referencedColumnName' => 'id',
]),
new JoinColumn(
name: 'example_entity_overridden_bar_id',
referencedColumnName: 'id',
),
]),
])]
class ExampleEntityWithOverride
@@ -47,7 +45,7 @@ Suppose we have a class ExampleEntityWithOverride. This class uses trait Example
private $id;
}
The docblock is showing metadata override of the attribute and association type. It
``#[AttributeOverrides]`` contains metadata override of the attribute and association type. It
basically changes the names of the columns mapped for a property ``foo`` and for
the association ``bar`` which relates to Bar class shown above. Here is the trait
which has mapping metadata that is overridden by the attribute above:
@@ -66,7 +64,7 @@ which has mapping metadata that is overridden by the attribute above:
#[Column(name: 'trait_foo', type: 'integer', length: 100, nullable: true, unique: true)]
protected int $foo;
#[OneToOne(targetEntity: Bar::class, cascade: ['persist'])]
#[OneToOne(targetEntity: Bar::class, cascade: ['persist', 'merge'])]
#[JoinColumn(name: 'example_trait_bar_id', referencedColumnName: 'id')]
protected Bar|null $bar = null;
}
@@ -81,4 +79,4 @@ The case for just extending a class would be just the same but:
// ...
}
Overriding is also supported via XML (:ref:`examples <inheritence_mapping_overrides>`).
Overriding is also supported via XML and YAML (:ref:`examples <inheritence_mapping_overrides>`).

View File

@@ -24,88 +24,25 @@ Mapping Indexed Associations
You can map indexed associations by adding:
* ``indexBy`` argument to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute.
* ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation.
* ``index-by`` attribute to any ``<one-to-many />`` or ``<many-to-many />`` xml element.
* ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files.
The code and mappings for the Market entity looks like this:
.. configuration-block::
.. code-block:: attribute
.. literalinclude:: working-with-indexed-associations/Market.php
:language: attribute
<?php
namespace Doctrine\Tests\Models\StockExchange;
.. literalinclude:: working-with-indexed-associations/Market-annotations.php
:language: annotation
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
.. literalinclude:: working-with-indexed-associations/market.xml
:language: xml
#[Entity]
#[Table(name: 'exchange_markets')]
class Market
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
.. literalinclude:: working-with-indexed-associations/market.yaml
:language: yaml
#[Column(type: 'string')]
private string $name;
/** @var Collection<string, Stock> */
#[OneToMany(targetEntity: Stock::class, mappedBy: 'market', indexBy: 'symbol')]
private Collection $stocks;
public function __construct(string $name)
{
$this->name = $name;
$this->stocks = new ArrayCollection();
}
public function getId(): int|null
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock(string $symbol): Stock
{
if (!isset($this->stocks[$symbol])) {
throw new \InvalidArgumentException("Symbol is not traded on this market.");
}
return $this->stocks[$symbol];
}
/** @return array<string, Stock> */
public function getStocks(): array
{
return $this->stocks->toArray();
}
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\StockExchange\Market">
<id name="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="name" type="string"/>
<one-to-many target-entity="Stock" mapped-by="market" field="stocks" index-by="symbol" />
</entity>
</doctrine-mapping>
Inside the ``addStock()`` method you can see how we directly set the key of the association to the symbol,
so that we can work with the indexed association directly after invoking ``addStock()``. Inside ``getStock($symbol)``
@@ -146,11 +83,52 @@ here are the code and mappings for it:
}
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\StockExchange;
/**
* @Entity
* @Table(name="exchange_stocks")
*/
class Stock
{
/**
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
private int|null $id = null;
/**
* @Column(type="string", unique=true)
*/
private string $symbol;
/**
* @ManyToOne(targetEntity="Market", inversedBy="stocks")
* @var Market
*/
private Market|null $market = null;
public function __construct($symbol, Market $market)
{
$this->symbol = $symbol;
$this->market = $market;
$market->addStock($this);
}
public function getSymbol(): string
{
return $this->symbol;
}
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -164,6 +142,23 @@ here are the code and mappings for it:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Doctrine\Tests\Models\StockExchange\Stock:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
fields:
symbol:
type: string
manyToOne:
market:
targetEntity: Market
inversedBy: stocks
Querying indexed associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -0,0 +1,74 @@
<?php
namespace Doctrine\Tests\Models\StockExchange;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\Table;
use InvalidArgumentException;
/**
* @Entity
* @Table(name="exchange_markets")
*/
class Market
{
/**
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
private int|null $id = null;
/**
* @Column(type="string")
* @var string
*/
private string $name;
/**
* @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol")
* @var Collection<int, Stock>
*/
private Collection $stocks;
public function __construct($name)
{
$this->name = $name;
$this->stocks = new ArrayCollection();
}
public function getId(): int|null
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock($symbol): Stock
{
if (!isset($this->stocks[$symbol])) {
throw new InvalidArgumentException("Symbol is not traded on this market.");
}
return $this->stocks[$symbol];
}
/** @return array<string, Stock> */
public function getStocks(): array
{
return $this->stocks->toArray();
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\StockExchange;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\Table;
use InvalidArgumentException;
#[Entity]
#[Table(name: 'exchange_markets')]
class Market
{
#[Id]
#[Column(type: 'integer')]
#[GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
private string $name;
/** @var Collection<string, Stock> */
#[OneToMany(targetEntity: Stock::class, mappedBy: 'market', indexBy: 'symbol')]
private Collection $stocks;
public function __construct(string $name)
{
$this->name = $name;
$this->stocks = new ArrayCollection();
}
public function getId(): int|null
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock(string $symbol): Stock
{
if (! isset($this->stocks[$symbol])) {
throw new InvalidArgumentException('Symbol is not traded on this market.');
}
return $this->stocks[$symbol];
}
/** @return array<string, Stock> */
public function getStocks(): array
{
return $this->stocks->toArray();
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\StockExchange\Market">
<id name="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="name" type="string"/>
<one-to-many target-entity="Stock" mapped-by="market" field="stocks" index-by="symbol" />
</entity>
</doctrine-mapping>

View File

@@ -0,0 +1,15 @@
Doctrine\Tests\Models\StockExchange\Market:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type:string
oneToMany:
stocks:
targetEntity: Stock
mappedBy: market
indexBy: symbol

View File

@@ -35,6 +35,7 @@
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-detach" type="orm:emptyType" minOccurs="0"/>
@@ -81,6 +82,36 @@
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="named-query">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="query" type="xs:string" use="required" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="named-queries">
<xs:sequence>
<xs:element name="named-query" type="orm:named-query" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="named-native-query">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="query" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="result-class" type="orm:fqcn" />
<xs:attribute name="result-set-mapping" type="xs:string" />
</xs:complexType>
<xs:complexType name="named-native-queries">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="named-native-query" type="orm:named-native-query" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="entity-listener">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="0" maxOccurs="unbounded"/>
@@ -112,6 +143,23 @@
<xs:attribute name="discriminator-column" type="xs:string" use="optional" />
</xs:complexType>
<xs:complexType name="sql-result-set-mapping">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="entity-result" type="orm:entity-result"/>
<xs:element name="column-result" type="orm:column-result"/>
</xs:choice>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
<xs:complexType name="sql-result-set-mappings">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="sql-result-set-mapping" type="orm:sql-result-set-mapping" minOccurs="1" maxOccurs="unbounded" />
</xs:choice>
</xs:complexType>
<xs:complexType name="cache">
<xs:attribute name="usage" type="orm:cache-usage-type" />
<xs:attribute name="region" type="xs:string" />
@@ -127,6 +175,9 @@
<xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" />
<xs:element name="entity-listeners" type="orm:entity-listeners" minOccurs="0" maxOccurs="1" />
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
<xs:element name="named-native-queries" type="orm:named-native-queries" minOccurs="0" maxOccurs="1" />
<xs:element name="sql-result-set-mappings" type="orm:sql-result-set-mappings" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="embedded" type="orm:embedded" minOccurs="0" maxOccurs="unbounded"/>
@@ -188,6 +239,7 @@
<xs:restriction base="xs:token">
<xs:enumeration value="DEFERRED_IMPLICIT"/>
<xs:enumeration value="DEFERRED_EXPLICIT"/>
<xs:enumeration value="NOTIFY"/>
</xs:restriction>
</xs:simpleType>
@@ -195,6 +247,7 @@
<xs:restriction base="xs:token">
<xs:enumeration value="SINGLE_TABLE"/>
<xs:enumeration value="JOINED"/>
<xs:enumeration value="TABLE_PER_CLASS"/>
</xs:restriction>
</xs:simpleType>
@@ -204,6 +257,7 @@
<xs:enumeration value="SEQUENCE"/>
<xs:enumeration value="IDENTITY"/>
<xs:enumeration value="AUTO"/>
<xs:enumeration value="UUID"/>
<xs:enumeration value="CUSTOM" />
</xs:restriction>
</xs:simpleType>
@@ -321,7 +375,7 @@
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="value" type="xs:NMTOKEN" use="required"/>
<xs:attribute name="value" type="orm:type" use="required"/>
<xs:attribute name="class" type="orm:fqcn" use="required"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>

View File

@@ -1,5 +1,7 @@
<?xml version="1.0"?>
<ruleset>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="PHP_CodeSniffer"
xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
<arg name="basepath" value="."/>
<arg name="extensions" value="php"/>
<arg name="parallel" value="80"/>
@@ -9,7 +11,7 @@
<!-- Ignore warnings, show progress of the run and show sniff names -->
<arg value="nps"/>
<config name="php_version" value="80100"/>
<config name="php_version" value="70100"/>
<file>src</file>
<file>tests</file>
@@ -18,36 +20,38 @@
<exclude-pattern>*/tests/Tests/ORM/Tools/Export/export/*</exclude-pattern>
<rule ref="Doctrine">
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingAnyTypeHint" />
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint"/>
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint" />
<exclude name="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException"/>
<exclude name="SlevomatCodingStandard.ControlStructures.EarlyExit"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming"/>
<exclude name="SlevomatCodingStandard.Classes.ModernClassNameReference.ClassNameReferencedViaFunctionCall"/>
</rule>
<rule ref="SlevomatCodingStandard.Commenting.RequireOneLineDocComment.MultiLineDocComment">
<!-- Remove when dropping PHPUnit 7 -->
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint">
<exclude-pattern>*/src/*</exclude-pattern>
<!--
that class extends another one inside src/ and can therefore not
have more native typehints since its parent cannot have them: that
would break signature compatibility.
-->
<exclude-pattern>tests/Tests/Mocks/HydratorMockStatement.php</exclude-pattern>
<exclude-pattern>tests/Tests/Models/Cache/ComplexAction.php</exclude-pattern>
<exclude-pattern>tests/Tests/Models/DDC117/DDC117ArticleDetails.php</exclude-pattern>
<exclude-pattern>tests/Tests/Models/DDC117/DDC117Translation.php</exclude-pattern>
<exclude-pattern>tests/Tests/ORM/Functional/Ticket/DDC2579Test.php</exclude-pattern>
<exclude-pattern>tests/Tests/ORM/Functional/ValueObjectsTest.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingAnyTypeHint">
<exclude-pattern>tests/*</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint">
<exclude-pattern>tests/*</exclude-pattern>
<rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint">
<exclude-pattern>*/src/*</exclude-pattern>
</rule>
<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
<exclude-pattern>src/Mapping/Driver/CompatibilityAnnotationDriver.php</exclude-pattern>
<exclude-pattern>src/Tools/Console/CommandCompatibility.php</exclude-pattern>
<exclude-pattern>src/Tools/Console/Helper/EntityManagerHelper.php</exclude-pattern>
<exclude-pattern>tests/*</exclude-pattern>
</rule>
@@ -66,6 +70,14 @@
<exclude-pattern>src/Tools/ToolEvents.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingTraversableTypeHintSpecification">
<!-- https://github.com/doctrine/annotations/issues/129 -->
<exclude-pattern>src/Mapping/Column.php</exclude-pattern>
<exclude-pattern>src/Mapping/Index.php</exclude-pattern>
<exclude-pattern>src/Mapping/Table.php</exclude-pattern>
<exclude-pattern>src/Mapping/UniqueConstraint.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator">
<exclude-pattern>src/Internal/Hydration/AbstractHydrator.php</exclude-pattern>
</rule>
@@ -82,6 +94,7 @@
<exclude-pattern>src/Mapping/Cache.php</exclude-pattern>
<exclude-pattern>src/Mapping/ChangeTrackingPolicy.php</exclude-pattern>
<exclude-pattern>src/Mapping/Column.php</exclude-pattern>
<exclude-pattern>src/Mapping/ColumnResult.php</exclude-pattern>
<exclude-pattern>src/Mapping/CustomIdGenerator.php</exclude-pattern>
<exclude-pattern>src/Mapping/DiscriminatorColumn.php</exclude-pattern>
<exclude-pattern>src/Mapping/DiscriminatorMap.php</exclude-pattern>
@@ -89,6 +102,8 @@
<exclude-pattern>src/Mapping/Embedded.php</exclude-pattern>
<exclude-pattern>src/Mapping/Entity.php</exclude-pattern>
<exclude-pattern>src/Mapping/EntityListeners.php</exclude-pattern>
<exclude-pattern>src/Mapping/EntityResult.php</exclude-pattern>
<exclude-pattern>src/Mapping/FieldResult.php</exclude-pattern>
<exclude-pattern>src/Mapping/GeneratedValue.php</exclude-pattern>
<exclude-pattern>src/Mapping/HasLifecycleCallbacks.php</exclude-pattern>
<exclude-pattern>src/Mapping/Id.php</exclude-pattern>
@@ -100,6 +115,10 @@
<exclude-pattern>src/Mapping/ManyToMany.php</exclude-pattern>
<exclude-pattern>src/Mapping/ManyToOne.php</exclude-pattern>
<exclude-pattern>src/Mapping/MappedSuperclass.php</exclude-pattern>
<exclude-pattern>src/Mapping/NamedNativeQueries.php</exclude-pattern>
<exclude-pattern>src/Mapping/NamedNativeQuery.php</exclude-pattern>
<exclude-pattern>src/Mapping/NamedQueries.php</exclude-pattern>
<exclude-pattern>src/Mapping/NamedQuery.php</exclude-pattern>
<exclude-pattern>src/Mapping/OneToMany.php</exclude-pattern>
<exclude-pattern>src/Mapping/OneToOne.php</exclude-pattern>
<exclude-pattern>src/Mapping/OrderBy.php</exclude-pattern>
@@ -112,6 +131,8 @@
<exclude-pattern>src/Mapping/PreRemove.php</exclude-pattern>
<exclude-pattern>src/Mapping/PreUpdate.php</exclude-pattern>
<exclude-pattern>src/Mapping/SequenceGenerator.php</exclude-pattern>
<exclude-pattern>src/Mapping/SqlResultSetMapping.php</exclude-pattern>
<exclude-pattern>src/Mapping/SqlResultSetMappings.php</exclude-pattern>
<exclude-pattern>src/Mapping/Table.php</exclude-pattern>
<exclude-pattern>src/Mapping/UniqueConstraint.php</exclude-pattern>
<exclude-pattern>src/Mapping/Version.php</exclude-pattern>
@@ -121,36 +142,6 @@
<exclude-pattern>src/Cache/DefaultQueryCache.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations">
<properties>
<property name="forbiddenAnnotations" type="array">
<!--
From Doctrine Coding Standard:
Forbid useless annotations - Git and LICENCE file provide more accurate information
-->
<element value="@api"/>
<element value="@author"/>
<element value="@category"/>
<element value="@copyright"/>
<element value="@created"/>
<element value="@license"/>
<element value="@package"/>
<element value="@since"/>
<element value="@subpackage"/>
<element value="@version"/>
<!-- Additionally forbid oldschool PHPUnit annotations to force the usage of attributes -->
<element value="@covers"/>
<element value="@depends"/>
<element value="@dataProvider"/>
<element value="@group"/>
<element value="@requires"/>
<element value="@test"/>
<element value="@testWith"/>
</property>
</properties>
</rule>
<rule ref="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming">
<exclude-pattern>src/EntityManagerInterface.php</exclude-pattern>
</rule>
@@ -206,6 +197,9 @@
<exclude-pattern>tests/Tests/Models/DDC1590/DDC1590User.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations.AnnotationForbidden">
<exclude-pattern>tests/Tests/ORM/Functional/Ticket/DDC832Test.php</exclude-pattern>
</rule>
<rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
<!-- we need to test what happens with an stdClass proxy -->
@@ -230,11 +224,12 @@
<rule ref="PSR2.Methods.MethodDeclaration.Underscore">
<exclude-pattern>src/AbstractQuery.php</exclude-pattern>
<exclude-pattern>src/Mapping/ClassMetadata.php</exclude-pattern>
<exclude-pattern>src/Mapping/ClassMetadataInfo.php</exclude-pattern>
<exclude-pattern>src/NativeQuery.php</exclude-pattern>
<exclude-pattern>src/Query.php</exclude-pattern>
<exclude-pattern>src/Query/TreeWalkerAdapter.php</exclude-pattern>
<exclude-pattern>src/Tools/Export/Driver/AbstractExporter.php</exclude-pattern>
<exclude-pattern>src/Tools/Export/Driver/AnnotationExporter.php</exclude-pattern>
<exclude-pattern>src/Tools/Export/Driver/PhpExporter.php</exclude-pattern>
<!-- extending a class from another package -->
<exclude-pattern>tests/Tests/Mocks/DatabasePlatformMock.php</exclude-pattern>
@@ -244,6 +239,20 @@
</rule>
<rule ref="Squiz.NamingConventions.ValidVariableName.PublicHasUnderscore">
<exclude-pattern>src/AbstractQuery.php</exclude-pattern>
<exclude-pattern>src/Configuration.php</exclude-pattern>
<exclude-pattern>src/EntityRepository.php</exclude-pattern>
<exclude-pattern>src/Internal/Hydration/AbstractHydrator.php</exclude-pattern>
<exclude-pattern>src/Query/Exec/AbstractSqlExecutor.php</exclude-pattern>
<exclude-pattern>src/Query/Exec/AbstractSqlExecutor.php</exclude-pattern>
<exclude-pattern>src/Query/Printer.php</exclude-pattern>
<exclude-pattern>src/Tools/EntityRepositoryGenerator.php</exclude-pattern>
<exclude-pattern>src/Tools/Console/Helper/EntityManagerHelper.php</exclude-pattern>
<exclude-pattern>src/Tools/Export/Driver/AbstractExporter.php</exclude-pattern>
<exclude-pattern>src/Tools/Export/Driver/AnnotationExporter.php</exclude-pattern>
<exclude-pattern>src/Tools/Export/Driver/PhpExporter.php</exclude-pattern>
<exclude-pattern>src/Tools/Export/Driver/XmlExporter.php</exclude-pattern>
<exclude-pattern>src/Tools/Export/Driver/YamlExporter.php</exclude-pattern>
<!-- the impact of changing this would be too big -->
<exclude-pattern>tests/Tests/OrmFunctionalTestCase.php</exclude-pattern>
</rule>
@@ -258,7 +267,6 @@
<!-- Using @group and Group entity in the same file -->
<exclude-pattern>tests/Tests/ORM/Functional/Ticket/DDC1885Test.php</exclude-pattern>
<exclude-pattern>tests/Tests/ORM/Functional/Ticket/DDC1843Test.php</exclude-pattern>
<exclude-pattern>tests/Tests/ORM/Mapping/ClassMetadataFactoryTest.php</exclude-pattern>
</rule>
<rule ref="Generic.CodeAnalysis.EmptyStatement.DetectedElse">
@@ -273,4 +281,9 @@
<!-- https://github.com/doctrine/orm/issues/8537 -->
<exclude-pattern>src/QueryBuilder.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.PHP.UselessParentheses">
<!-- We need those parentheses to make enum access seem like valid syntax on PHP 7 -->
<exclude-pattern>src/Mapping/Driver/XmlDriver.php</exclude-pattern>
</rule>
</ruleset>

File diff suppressed because it is too large Load Diff

122
phpstan-dbal2.neon Normal file
View File

@@ -0,0 +1,122 @@
includes:
- phpstan-baseline.neon
- phpstan-params.neon
parameters:
reportUnmatchedIgnoredErrors: false
ignoreErrors:
# PHPStan doesn't understand our method_exists() safeguards.
- '/Call to function method_exists.*/'
- '/Call to an undefined method Doctrine\\DBAL\\Connection::createSchemaManager\(\)\./'
# Class name will change in DBAL 3.
- '/^Class Doctrine\\DBAL\\Platforms\\PostgreSQLPlatform not found\.$/'
- '/^Class Doctrine\\DBAL\\Platforms\\AbstractMySQLPlatform not found\.$/'
- '/^Class Doctrine\\DBAL\\Platforms\\MySQLPlatform not found\.$/'
-
message: '/Doctrine\\DBAL\\Platforms\\MyS(ql|QL)Platform/'
path: src/Mapping/ClassMetadataFactory.php
# Forward compatibility for DBAL 3.5
- '/^Call to an undefined method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getAlterSchemaSQL\(\).$/'
# Forward compatibility for DBAL 3.4
- '/^Call to an undefined method Doctrine\\DBAL\\Cache\\QueryCacheProfile::[gs]etResultCache\(\)\.$/'
-
message: '/^Call to an undefined static method Doctrine\\DBAL\\Configuration::[gs]etResultCache\(\)\.$/'
path: src/Configuration.php
-
message: '/^Parameter #3 \$resultCache of class Doctrine\\DBAL\\Cache\\QueryCacheProfile constructor/'
path: src/AbstractQuery.php
-
message: '/^Parameter #2 \$\w+ of method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getDateAdd\w+Expression\(\) expects int, string given\.$/'
path: src/Query/AST/Functions/DateAddFunction.php
-
message: '/^Parameter #2 \$\w+ of method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getDateSub\w+Expression\(\) expects int, string given\.$/'
path: src/Query/AST/Functions/DateSubFunction.php
# False positive
-
message: '/^Call to an undefined method Doctrine\\Common\\Cache\\Cache::deleteAll\(\)\.$/'
count: 1
path: src/Tools/Console/Command/ClearCache/ResultCommand.php
# See https://github.com/doctrine/dbal/pull/5129
-
message: '/^Parameter #3 \$startPos of method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getLocateExpression\(\) expects int\|false, string given\.$/'
count: 1
path: src/Query/AST/Functions/LocateFunction.php
# Won't get fixed in DBAL 2
-
message: '#.*deleteItem.*expects string.*#'
count: 1
path: src/Query.php
-
message: '#.*get(Drop|Create)SchemaS(ql|QL).*should return list.*but returns array.*#'
count: 2
path: src/Tools/SchemaTool.php
-
message: '#introspectSchema#'
count: 2
identifier: 'method.notFound'
path: src/Tools/SchemaTool.php
-
message: '#getMaxIdentifierLength#'
identifier: 'method.deprecatedClass'
path: src/Mapping/ClassMetadataFactory.php
-
message: "#^Parameter \\#2 \\$start of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getSubstringExpression\\(\\) expects int, string given\\.$#"
count: 1
path: src/Query/AST/Functions/SubstringFunction.php
-
message: "#^Parameter \\#3 \\$length of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getSubstringExpression\\(\\) expects int\\|null, string\\|null given\\.$#"
count: 1
path: src/Query/AST/Functions/SubstringFunction.php
-
message: '#^Class Doctrine\\DBAL\\Platforms\\MySQLPlatform not found\.$#'
count: 2
path: src/Mapping/ClassMetadataFactory.php
- '~^Call to deprecated method getSQLResultCasing\(\) of class Doctrine\\DBAL\\Platforms\\AbstractPlatform\.$~'
-
message: '~deprecated class Doctrine\\DBAL\\Tools\\Console\\Command\\ImportCommand\:~'
path: src/Tools/Console/ConsoleRunner.php
-
message: '#^Method Doctrine\\ORM\\AbstractQuery\:\:getHydrationCacheId\(\) should return array\{string, string\} but returns array\<string\>\.$#'
path: src/AbstractQuery.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:\w+\(\) has parameter \$stmt with no value type specified in iterable type Doctrine\\DBAL\\Driver\\ResultStatement\.$#'
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Parameter \#1 \$key of method Psr\\Cache\\CacheItemPoolInterface\:\:deleteItem\(\) expects string, string\|false given\.$#'
path: src/Query
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
# Persistence 2 support
-
message: '/clear.*invoked with 1 parameter/'
path: src/EntityRepository.php
-
message: '#^Class Doctrine\\Persistence\\ObjectManagerAware not found\.$#'
path: src/UnitOfWork.php
-
message: '#^Call to method injectObjectManager\(\) on an unknown class Doctrine\\Persistence\\ObjectManagerAware\.$#'
path: src/UnitOfWork.php
-
message: '#contains generic type.*but class.*is not generic#'
paths:
- src/Mapping/Driver/XmlDriver.php
- src/Mapping/Driver/YamlDriver.php

View File

@@ -1,29 +0,0 @@
includes:
- phpstan-baseline.neon
- phpstan-params.neon
parameters:
ignoreErrors:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
# We can be certain that those values are not matched.
-
message: '~^Match expression does not handle remaining values:~'
path: src/Persisters/Entity/BasicEntityPersister.php
# DBAL 4 compatibility
-
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Persisters/Entity/BasicEntityPersister.php
- '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~'
# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
paths:
- src/Mapping/Driver/AttributeDriver.php

View File

@@ -1,5 +1,5 @@
parameters:
level: 5
level: 7
paths:
- src
- tests/StaticAnalysis
@@ -8,4 +8,4 @@ parameters:
earlyTerminatingMethodCalls:
Doctrine\ORM\Query\Parser:
- syntaxError
phpVersion: 80200
phpVersion: 80400

118
phpstan-persistence2.neon Normal file
View File

@@ -0,0 +1,118 @@
includes:
- phpstan-baseline.neon
- phpstan-params.neon
parameters:
reportUnmatchedIgnoredErrors: false
ignoreErrors:
# deprecations from doctrine/dbal:3.x
- '/^Call to an undefined method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getGuidExpression\(\).$/'
# Fallback logic for DBAL 2
-
message: '/Parameter #2 \$command of static method Doctrine\\ORM\\Tools\\Console\\ConsoleRunner\:\:addCommandToApplication\(\) expects Symfony\\Component\\Console\\Command\\Command/'
path: src/Tools/Console/ConsoleRunner.php
- '/^Class Doctrine\\DBAL\\Platforms\\SQLAnywherePlatform not found\.$/'
- '/^Call to method \w+\(\) on an unknown class Doctrine\\DBAL\\Platforms\\SQLAnywherePlatform\.$/'
-
message: '/^Call to an undefined method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getSQLResultCasing\(\)\.$/'
path: src/Internal/SQLResultCasing.php
-
message: '/^Parameter \$stmt of method .* has invalid type Doctrine\\DBAL\\Driver\\ResultStatement\.$/'
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '/^Class Doctrine\\DBAL\\Driver\\ResultStatement not found\.$/'
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '/^Call to static method ensure\(\) on an unknown class Doctrine\\DBAL\\ForwardCompatibility\\Result\.$/'
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '/^Instanceof between Doctrine\\DBAL\\Platforms\\AbstractPlatform and Doctrine\\DBAL\\Platforms\\MySQLPlatform will always evaluate to false\.$/'
path: src/Utility/LockSqlHelper.php
# Forward compatibility with Collections 3
-
message: '#^Parameter \$order of anonymous function has invalid type Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Anonymous function has invalid return type Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Access to property \$value on an unknown class Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Call to static method from\(\) on an unknown class Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Call to an undefined method Doctrine\\Common\\Collections\\Criteria\:\:orderings\(\)\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Method .+\:\:mapToOrderEnumIfAvailable\(\) has invalid return type Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
# False positive
-
message: '/^Call to an undefined method Doctrine\\Common\\Cache\\Cache::deleteAll\(\)\.$/'
count: 1
path: src/Tools/Console/Command/ClearCache/ResultCommand.php
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
-
message: '#contains generic type.*but class.*is not generic#'
paths:
- src/Mapping/Driver/XmlDriver.php
- src/Mapping/Driver/YamlDriver.php
# Extending a deprecated class conditionally to maintain BC
-
message: '~deprecated class Doctrine\\Persistence\\Mapping\\Driver\\AnnotationDriver\:~'
path: src/Mapping/Driver/CompatibilityAnnotationDriver.php
# We're sniffing for this deprecated class in order to detect Persistence 2
-
message: '~deprecated class Doctrine\\Common\\Persistence\\PersistentObject\:~'
path: src/EntityManager.php
-
message: '#Cannot access offset \S+ on .*ClassMetadata.*#'
paths:
- src/Mapping/Driver/XmlDriver.php
- src/Mapping/Driver/YamlDriver.php
-
message: '#^Parameter \#1 \$orderings of method Doctrine\\Common\\Collections\\Criteria\:\:orderBy\(\) expects array\<string\>, array\<string, Doctrine\\Common\\Collections\\Order\|string\> given\.$#'
path: src/PersistentCollection.php
-
message: '#^Parameter \#5 \.\.\.\$args of static method Doctrine\\Deprecations\\Deprecation\:\:trigger\(\) expects float\|int\|string, string\|false given\.$#'
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Parameter \#1 \$classNames of method Doctrine\\ORM\\Mapping\\ClassMetadataInfo\<object\>\:\:setParentClasses\(\) expects list\<class\-string\>, array\<string\> given\.$#'
path: src/Mapping/ClassMetadataFactory.php
-
message: '#loadMappingFile#'
identifier: 'return.type'
path: src/Mapping/Driver/XmlDriver.php
-
message: '#injectObjectManager#'
identifier: 'method.deprecatedInterface'
path: src/UnitOfWork.php
-
message: '#^Static method Doctrine\\Common\\Collections\\Criteria\:\:create\(\) invoked with 1 parameter, 0 required\.$#'
identifier: arguments.count
count: 3
path: src/Persisters/Collection/OneToManyPersister.php

View File

@@ -4,44 +4,49 @@ includes:
parameters:
ignoreErrors:
# deprecations from doctrine/dbal:3.x
- '/^Call to an undefined method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getGuidExpression\(\).$/'
# Fallback logic for DBAL 2
-
message: '/Parameter #2 \$command of static method Doctrine\\ORM\\Tools\\Console\\ConsoleRunner\:\:addCommandToApplication\(\) expects Symfony\\Component\\Console\\Command\\Command/'
path: src/Tools/Console/ConsoleRunner.php
- '/^Class Doctrine\\DBAL\\Platforms\\SQLAnywherePlatform not found\.$/'
- '/^Call to method \w+\(\) on an unknown class Doctrine\\DBAL\\Platforms\\SQLAnywherePlatform\.$/'
-
message: '/^Call to an undefined method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getSQLResultCasing\(\)\.$/'
path: src/Internal/SQLResultCasing.php
-
message: '/^Parameter \$stmt of method .* has invalid type Doctrine\\DBAL\\Driver\\ResultStatement\.$/'
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '/^Class Doctrine\\DBAL\\Driver\\ResultStatement not found\.$/'
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '/^Call to static method ensure\(\) on an unknown class Doctrine\\DBAL\\ForwardCompatibility\\Result\.$/'
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '/^Instanceof between Doctrine\\DBAL\\Platforms\\AbstractPlatform and Doctrine\\DBAL\\Platforms\\MySQLPlatform will always evaluate to false\.$/'
path: src/Utility/LockSqlHelper.php
# False positive
-
message: '/^Call to an undefined method Doctrine\\Common\\Cache\\Cache::deleteAll\(\)\.$/'
count: 1
path: src/Tools/Console/Command/ClearCache/ResultCommand.php
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
# We can be certain that those values are not matched.
# Persistence 2 support
-
message: '~^Match expression does not handle remaining values:~'
path: src/Persisters/Entity/BasicEntityPersister.php
# DBAL 4 compatibility
message: '/clear.*invoked with 1 parameter/'
path: src/EntityRepository.php
-
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Persisters/Entity/BasicEntityPersister.php
# Compatibility with DBAL 3
# See https://github.com/doctrine/dbal/pull/3480
-
message: '~^Result of method Doctrine\\DBAL\\Connection::commit\(\) \(void\) is used\.$~'
message: '#^Class Doctrine\\Persistence\\ObjectManagerAware not found\.$#'
path: src/UnitOfWork.php
-
message: '~^Strict comparison using === between null and false will always evaluate to false\.$~'
message: '#^Call to method injectObjectManager\(\) on an unknown class Doctrine\\Persistence\\ObjectManagerAware\.$#'
path: src/UnitOfWork.php
-
message: '~^Variable \$e on left side of \?\? always exists and is not nullable\.$~'
path: src/UnitOfWork.php
-
message: '~^Parameter #1 \$command of method Symfony\\Component\\Console\\Application::add\(\) expects Symfony\\Component\\Console\\Command\\Command, Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand given\.$~'
path: src/Tools/Console/ConsoleRunner.php
-
message: '~Strict comparison using \=\=\= between callable\(\)\: mixed and null will always evaluate to false\.~'
path: src/Tools/SchemaTool.php
# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
paths:
- src/Mapping/Driver/AttributeDriver.php

View File

@@ -14,13 +14,10 @@
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
failOnNotice="true"
failOnWarning="true"
verbose="false"
failOnRisky="true"
convertDeprecationsToExceptions="true"
bootstrap="./tests/Tests/TestInit.php"
cacheDirectory=".phpunit.cache"
>
<testsuites>
<testsuite name="Doctrine ORM Test Suite">
@@ -34,38 +31,42 @@
<group>locking_functional</group>
</exclude>
</groups>
<php>
<ini name="error_reporting" value="-1"/>
<!-- "Real" test database -->
<var name="db_driver" value="pdo_sqlite"/>
<var name="db_memory" value="true"/>
<!-- to use another database driver / credentials, provide them like so:
<var name="db_driver" value="pdo_mysql"/>
<var name="db_host" value="localhost" />
<var name="db_user" value="root" />
<var name="db_password" value="" />
<var name="db_dbname" value="doctrine_tests" />
<var name="db_port" value="3306"/>-->
<!--<var name="db_event_subscribers" value="Doctrine\DBAL\Event\Listeners\OracleSessionInit">-->
<!--
At the start of each test run, we will drop and recreate the test database.
By default we assume that the `db_` config above has unrestricted access to the provided database
platform.
<php>
<ini name="error_reporting" value="-1" />
<!-- "Real" test database -->
<var name="db_driver" value="pdo_sqlite"/>
<var name="db_memory" value="true"/>
<!-- to use another database driver / credentials, provide them like so:
<var name="db_driver" value="pdo_mysql"/>
<var name="db_host" value="localhost" />
<var name="db_user" value="root" />
<var name="db_password" value="" />
<var name="db_dbname" value="doctrine_tests" />
<var name="db_port" value="3306"/>-->
<!--<var name="db_event_subscribers" value="Doctrine\DBAL\Event\Listeners\OracleSessionInit">-->
If you prefer, you can provide a restricted user above and a separate `privileged_db` config
block to provide details of a privileged connection to use for the setup / teardown actions.
<!--
At the start of each test run, we will drop and recreate the test database.
Note that these configurations are not merged - if you specify a `privileged_db_driver` then
you must also specify all the other options that your driver requires.
By default we assume that the `db_` config above has unrestricted access to the provided database
platform.
If you prefer, you can provide a restricted user above and a separate `privileged_db` config
block to provide details of a privileged connection to use for the setup / teardown actions.
Note that these configurations are not merged - if you specify a `privileged_db_driver` then
you must also specify all the other options that your driver requires.
<var name="privileged_db_driver" value="pdo_mysql"/>
<var name="privileged_db_host" value="localhost" />
<var name="privileged_db_user" value="root" />
<var name="privileged_db_password" value="" />
<var name="privileged_db_dbname" value="doctrine_tests_tmp" />
<var name="privileged_db_port" value="3306"/>
-->
<env name="COLUMNS" value="120"/>
</php>
<var name="privileged_db_driver" value="pdo_mysql"/>
<var name="privileged_db_host" value="localhost" />
<var name="privileged_db_user" value="root" />
<var name="privileged_db_password" value="" />
<var name="privileged_db_dbname" value="doctrine_tests_tmp" />
<var name="privileged_db_port" value="3306"/>
-->
<env name="COLUMNS" value="120"/>
</php>
</phpunit>

File diff suppressed because it is too large Load Diff

245
psalm.xml
View File

@@ -1,245 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
phpVersion="8.2"
resolveFromConfigFile="true"
findUnusedBaselineEntry="true"
findUnusedCode="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="src" />
<directory name="tests/StaticAnalysis" />
<ignoreFiles>
<directory name="vendor" />
<file name="src/Mapping/Driver/AttributeReader.php" />
</ignoreFiles>
</projectFiles>
<enableExtensions>
<extension name="simplexml" />
</enableExtensions>
<issueHandlers>
<DeprecatedClass>
<errorLevel type="suppress">
<!-- We wire the command as long as DBAL ships it -->
<referencedClass name="Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand" />
<!-- Remove on 3.0.x -->
<referencedClass name="Doctrine\ORM\Event\LifecycleEventArgs"/>
<referencedClass name="Doctrine\ORM\Exception\UnknownEntityNamespace"/>
<referencedClass name="Doctrine\ORM\Mapping\Driver\AnnotationDriver"/>
<referencedClass name="Doctrine\ORM\Mapping\Driver\YamlDriver"/>
<referencedClass name="Doctrine\ORM\Mapping\NamedNativeQueries"/>
<referencedClass name="Doctrine\ORM\Mapping\NamedNativeQuery"/>
<referencedClass name="Doctrine\ORM\Mapping\NamedQueries"/>
<referencedClass name="Doctrine\ORM\Mapping\NamedQuery"/>
<referencedClass name="Doctrine\ORM\Query\AST\InExpression"/>
<referencedClass name="Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand"/>
<referencedClass name="Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand"/>
<referencedClass name="Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand"/>
<referencedClass name="Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand"/>
<referencedClass name="Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand"/>
<referencedClass name="Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper"/>
<referencedClass name="Doctrine\ORM\Tools\Console\EntityManagerProvider\HelperSetManagerProvider"/>
</errorLevel>
</DeprecatedClass>
<DeprecatedMethod>
<errorLevel type="suppress">
<!-- Remove on 3.0.x -->
<!-- Compatibility with DBAL 3 -->
<referencedMethod name="Doctrine\DBAL\Connection::getEventManager"/>
<file name="src/Query/TreeWalkerChain.php"/>
</errorLevel>
</DeprecatedMethod>
<DocblockTypeContradiction>
<errorLevel type="suppress">
<!-- We're catching invalid input here. -->
<file name="src/Internal/Hydration/AbstractHydrator.php"/>
<!-- DBAL 3.2 forward compatibility -->
<file name="src/Tools/Pagination/CountOutputWalker.php"/>
<file name="src/Tools/Pagination/LimitSubqueryOutputWalker.php"/>
<!-- https://github.com/vimeo/psalm/issues/8520 -->
<file name="src/PersistentCollection.php"/>
<!-- Remove on 4.0.x -->
<file name="src/Mapping/Driver/AttributeDriver.php"/>
<file name="src/Mapping/Driver/XmlDriver.php"/>
<file name="src/ORMSetup.php"/>
</errorLevel>
</DocblockTypeContradiction>
<ForbiddenCode>
<errorLevel type="suppress">
<file name="src/Tools/Debug.php"/>
</errorLevel>
</ForbiddenCode>
<InvalidArgument>
<errorLevel type="suppress">
<referencedFunction name="Doctrine\ORM\Mapping\ClassMetadata::addInheritedAssociationMapping"/>
</errorLevel>
</InvalidArgument>
<InvalidArrayAccess>
<errorLevel type="suppress">
<!-- https://github.com/vimeo/psalm/issues/9160 -->
<file name="src/Mapping/ClassMetadataFactory.php"/>
</errorLevel>
</InvalidArrayAccess>
<InvalidArrayAssignment>
<errorLevel type="suppress">
<!-- https://github.com/vimeo/psalm/issues/9160 -->
<file name="src/Mapping/ClassMetadataFactory.php"/>
</errorLevel>
</InvalidArrayAssignment>
<LessSpecificReturnStatement>
<errorLevel type="suppress">
<!-- In DBAL 4, column precision is nullable. See https://github.com/doctrine/dbal/pull/3511 -->
<file name="src/Mapping/Driver/DatabaseDriver.php"/>
</errorLevel>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<errorLevel type="suppress">
<!-- In DBAL 4, the default column value is mixed. See https://github.com/doctrine/dbal/pull/3511 -->
<file name="src/Mapping/Driver/DatabaseDriver.php"/>
</errorLevel>
</MoreSpecificReturnType>
<InvalidReturnType>
<errorLevel type="suppress">
<!-- https://github.com/vimeo/psalm/issues/8819 -->
<file name="src/Internal/Hydration/AbstractHydrator.php"/>
</errorLevel>
</InvalidReturnType>
<InvalidParamDefault>
<errorLevel type="suppress">
<!-- Remove on 3.0.x -->
<file name="src/Query/AST/InstanceOfExpression.php"/>
</errorLevel>
</InvalidParamDefault>
<InvalidPropertyAssignmentValue>
<errorLevel type="suppress">
<!-- https://github.com/vimeo/psalm/issues/9155 -->
<file name="src/Mapping/ClassMetadataFactory.php"/>
</errorLevel>
</InvalidPropertyAssignmentValue>
<MethodSignatureMismatch>
<errorLevel type="suppress">
<!-- See https://github.com/vimeo/psalm/issues/7357 -->
<file name="src/Mapping/ReflectionReadonlyProperty.php"/>
</errorLevel>
</MethodSignatureMismatch>
<MissingParamType>
<errorLevel type="suppress">
<!-- Persistence 2 compatibility -->
<file name="src/EntityManager.php"/>
<file name="src/Mapping/ClassMetadataFactory.php"/>
<file name="src/Mapping/ClassMetadata.php"/>
</errorLevel>
</MissingParamType>
<PossiblyInvalidArgument>
<errorLevel type="suppress">
<!-- https://github.com/vimeo/psalm/issues/9155 -->
<file name="src/Mapping/ClassMetadataFactory.php"/>
</errorLevel>
</PossiblyInvalidArgument>
<PossiblyNullArrayOffset>
<errorLevel type="suppress">
<!-- https://github.com/vimeo/psalm/issues/7878 -->
<file name="src/Persisters/Collection/ManyToManyPersister.php"/>
</errorLevel>
</PossiblyNullArrayOffset>
<PropertyNotSetInConstructor>
<errorLevel type="suppress">
<directory name="src/Query/AST" />
</errorLevel>
</PropertyNotSetInConstructor>
<PropertyTypeCoercion>
<errorLevel type="suppress">
<file name="src/Mapping/ClassMetadata.php"/>
</errorLevel>
</PropertyTypeCoercion>
<RedundantCastGivenDocblockType>
<errorLevel type="suppress">
<!-- Can be removed once the "getMaxResults" methods of those classes have native parameter types -->
<file name="src/Query.php"/>
<file name="src/QueryBuilder.php"/>
</errorLevel>
</RedundantCastGivenDocblockType>
<ReferenceConstraintViolation>
<errorLevel type="suppress">
<!-- https://github.com/vimeo/psalm/issues/9155 -->
<file name="src/Mapping/ClassMetadataFactory.php"/>
</errorLevel>
</ReferenceConstraintViolation>
<RiskyTruthyFalsyComparison>
<!-- TODO: Enable this new rule on higher branches. -->
<errorLevel type="suppress">
<directory name="src" />
</errorLevel>
</RiskyTruthyFalsyComparison>
<TooManyArguments>
<errorLevel type="suppress">
<!-- Symfony cache supports passing a key prefix to the clear method. -->
<referencedFunction name="Psr\Cache\CacheItemPoolInterface::clear"/>
<!-- Persistence 2 compatibility -->
<referencedFunction name="Doctrine\Persistence\ObjectManager::clear"/>
<!-- See https://github.com/doctrine/orm/issues/8850 -->
<referencedFunction name="Doctrine\DBAL\Connection::lastInsertId"/>
<!-- FIXME -->
<referencedFunction name="Doctrine\DBAL\DriverManager::getConnection"/>
</errorLevel>
</TooManyArguments>
<TypeDoesNotContainNull>
<errorLevel type="suppress">
<!-- DBAL 3 compatibility -->
<file name="src/Tools/SchemaTool.php"/>
</errorLevel>
</TypeDoesNotContainNull>
<TypeDoesNotContainType>
<errorLevel type="suppress">
<file name="src/Internal/SQLResultCasing.php"/>
<file name="src/Mapping/ClassMetadataFactory.php"/>
<!-- DBAL 3 compatibility -->
<file name="src/UnitOfWork.php"/>
<file name="src/Utility/LockSqlHelper.php"/>
</errorLevel>
</TypeDoesNotContainType>
<UndefinedClass>
<errorLevel type="suppress">
<!-- Compatibility with DBAL 3 -->
<referencedClass name="Doctrine\DBAL\Platforms\SQLitePlatform"/>
</errorLevel>
</UndefinedClass>
<UndefinedMethod>
<errorLevel type="suppress">
<!-- Compatibility with DBAL 3 -->
<referencedMethod name="Doctrine\DBAL\Connection::getEventManager"/>
<!-- FIXME -->
<referencedMethod name="Doctrine\DBAL\Schema\SchemaDiff::toSaveSql"/>
</errorLevel>
</UndefinedMethod>
<UndefinedPropertyFetch>
<errorLevel type="suppress">
<!-- https://github.com/vimeo/psalm/issues/7878 -->
<file name="src/Persisters/Collection/ManyToManyPersister.php"/>
<file name="src/PersistentCollection.php"/>
<file name="src/Utility/PersisterHelper.php"/>
<file name="src/Tools/SchemaValidator.php"/>
</errorLevel>
</UndefinedPropertyFetch>
<UnhandledMatchCondition>
<errorLevel type="suppress">
<!-- We can be certain that those values are not matched. -->
<file name="src/Persisters/Entity/BasicEntityPersister.php"/>
</errorLevel>
</UnhandledMatchCondition>
<ArgumentTypeCoercion>
<errorLevel type="suppress">
<!-- See https://github.com/JetBrains/phpstorm-stubs/pull/1383 -->
<file name="src/Mapping/ClassMetadata.php"/>
</errorLevel>
</ArgumentTypeCoercion>
</issueHandlers>
</psalm>

View File

@@ -1,21 +0,0 @@
#!/bin/bash
# This script is a small convenience wrapper for running the doctrine testsuite against a large bunch of databases.
# Just create the phpunit.xmls as described in the array below and configure the specific files <php /> section
# to connect to that database. Just omit a file if you don't have that database and the tests will be skipped.
configs[1]="mysql.phpunit.xml"
configs[2]='postgres.phpunit.xml'
configs[3]='sqlite.phpunit.xml'
configs[4]='oracle.phpunit.xml'
configs[5]='db2.phpunit.xml'
configs[6]='pdo-ibm.phpunit.xml'
configs[7]='sqlsrv.phpunit.xml'
for i in "${configs[@]}"; do
if [ -f "$i" ];
then
echo "RUNNING TESTS WITH CONFIG $i"
phpunit -c "$i" "$@"
fi;
done

File diff suppressed because it is too large Load Diff

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