Compare commits

...

63 Commits
3.2.0 ... 3.2.3

Author SHA1 Message Date
Grégoire Paris
522863116a Update branch metadata (#11663)
2.20.0 just got released
2024-10-11 20:23:33 +02:00
Alexander M. Turek
b44774285b Merge branch '2.19.x' into 3.2.x
* 2.19.x:
  PHPStan 1.12.6 (#11635)
2024-10-09 11:09:19 +02:00
Alexander M. Turek
bc37f75b41 PHPStan 1.12.6 (#11635) 2024-10-09 11:08:02 +02:00
Grégoire Paris
191a5366b1 Merge pull request #11629 from greg0ire/3.2.x
Merge 2.19.x up into 3.2.x
2024-10-08 17:57:30 +02:00
Grégoire Paris
44dddb2eee Merge remote-tracking branch 'origin/2.19.x' into 3.2.x 2024-10-08 15:37:53 +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
b13564c6c0 Make nullable parameters explicit in generated entities (#11625) 2024-10-08 12:25:31 +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
93ce84fa6e Merge pull request #11614 from greg0ire/guides-as-dep
Guides as dev dependency
2024-09-29 09:23:14 +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
e6961bd968 Install guides-cli as a dev requirement
It is better if contributors can check the docs by themselves.
2024-09-27 19:44:34 +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
cfc0655a1c Fix compatibility with DBAL 4.2 (#11592) 2024-09-03 18:44:06 +02:00
Alexander M. Turek
6cde337777 PHPStan 1.12 (#11585) 2024-08-27 12:10:07 +02:00
Grégoire Paris
831a1eb7d2 Merge pull request #11581 from greg0ire/3.2.x
Merge 2.19.x up into 3.2.x
2024-08-23 12:03:52 +02:00
Grégoire Paris
3a82b153f3 Merge remote-tracking branch 'origin/2.19.x' into 3.2.x 2024-08-23 09:07:18 +02:00
Grégoire Paris
168ac31084 Merge pull request #11109 from mcurland/Fix11108
Original entity data resolves inverse 1-1 joins
2024-08-23 08:54:57 +02:00
Matthew Curland
fe4a2e83cf Original entity data resolves inverse 1-1 joins
If the source entity for an inverse (non-owning) 1-1 relationship is
identified by an association then the identifying association may not
be set when an inverse one-to-one association is resolved. This means
that no data is available in the entity to resolve the needed column
value for the join query.

The original entity data can be retrieved from the unit of work and
is used as a fallback to populate the query condition.

Fixes #11108
2024-08-17 11:50:56 +02:00
Grégoire Paris
205b2f5f20 Merge pull request #11550 from janedbal/patch-1
DQL custom functions: document TypedExpression
2024-08-09 22:50:14 +02:00
Jan Nedbal
3f550c19e3 DQL custom functions: document TypedExpression
Partially related to https://github.com/doctrine/orm/issues/11537

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2024-08-06 09:16:45 +02:00
Grégoire Paris
8ac6a13ca0 Merge pull request #11564 from gitbugr/GH11501_fix_o2m_persister_single_inheritence_parent_relation_bugfix
GH11551 - fix OneToManyPersister::deleteEntityCollection case where single-inheritence table parent entity is targetEntity.
2024-08-05 07:47:46 +02:00
gitbugr
2707b09a07 fix spacing
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2024-08-03 21:38:49 +01:00
Kyron Taylor
121158f92c GH11551 - fix OneToManyPersister::deleteEntityCollection when using
single-inheritence entity parent as targetEntity.

When using the parent entity for a single-inheritence table as the
targetEntity for a property, the discriminator value should be all
of the values in the discriminator map.
OneToManyPersister::deleteEntityCollection has been amended to
reflect this.
2024-08-03 16:55:14 +01:00
Grégoire Paris
51ad860a25 Merge pull request #11543 from stof/fix_native_query_parameter_type
Fix the support for custom parameter types in native queries
2024-07-04 20:12:59 +02:00
Christophe Coevoet
9bd51aaeb6 Fix the support for custom parameter types in native queries
The Query class (used for DQL queries) takes care of using the value and
type as is when a type was specified for a parameter instead of going
through the default processing of values.
The NativeQuery class was missing the equivalent check, making the
custom type work only if the default processing of values does not
convert the value to a different one.
2024-07-04 16:25:34 +02:00
Xesau
1fe1a6a048 Fix incorrect exception message for ManyToOne attribute in embeddable class (#11536)
When a ManyToOne attribute is encountered on an Embeddable class, the exception message reads "Attribute "Doctrine\ORM\Mapping\OneToMany" on embeddable [class] is not allowed.". This should be "Doctrine\ORM\Mapping\ManyToOne" on embeddable [class] is not allowed.".
2024-07-01 21:57:36 +02:00
Grégoire Paris
c37b115450 Merge pull request #11534 from k00ni/patch-1
working-with-objects.rst: added missing white space
2024-06-28 09:03:54 +02:00
Konrad Abicht
19129e9f8a working-with-objects.rst: added missing white space 2024-06-28 09:00:12 +02:00
Grégoire Paris
722cea6536 Merge pull request #11525 from greg0ire/3.2.x
Merge 2.19.x up into 3.2.x
2024-06-26 23:48:58 +02:00
Grégoire Paris
c1bb2ccf4b Merge pull request #11526 from GromNaN/patch-1
doc: Use modern array syntax in getting started
2024-06-26 19:24:40 +02:00
Jérôme Tamarelle
e3d7c6076c Use modern array syntax in the doc 2024-06-26 19:18:32 +02:00
Grégoire Paris
ce7d93f14d Merge remote-tracking branch 'origin/2.19.x' into 3.2.x 2024-06-26 16:53:24 +02:00
Alexander M. Turek
1153b9468c Fix deprecated array access usage (#11517) 2024-06-21 13:31:45 +02:00
Grégoire Paris
40f299f1eb Merge pull request #11506 from michalbundyra/composite-key-relations-3
[2.19.x] Fetching entities with Composite Key Relations and null values
2024-06-21 08:12:27 +02:00
Grégoire Paris
428032ca7c Merge remote-tracking branch 'origin/2.19.x' into HEAD 2024-06-20 22:18:24 +02:00
Grégoire Paris
68af854f46 Merge pull request #11513 from greg0ire/address-persistence-3.3.3-release
Address doctrine/persistence 3.3.3 release
2024-06-20 22:14:52 +02:00
Grégoire Paris
77467cd824 Address doctrine/persistence 3.3.3 release
FileDriver became templatable, and some very wrong phpdoc has been
fixed, causing Psalm to better understand the 2 FileDriver classes in
this project.
2024-06-20 22:00:33 +02:00
Grégoire Paris
ca3319c2f6 Merge pull request #11511 from doctrine/stof-patch-1
Add the proper void return type on the __load method of proxies
2024-06-20 11:46:52 +02:00
Christophe Coevoet
c06f6b9376 Add the propoer void return type on the __load method of proxies
When using ghost objects, the method was leaking a `static` return type due to the way it was implemented, which is incompatible with the native return type that will be added in doctrine/persistence v4.
2024-06-20 09:08:10 +02:00
Grégoire Paris
802f20b8e7 Merge pull request #11509 from greg0ire/remove-unneeded-rule
Remove unneeded CS rule
2024-06-19 23:49:15 +02:00
Michał Bundyra
96d13ac62a Fetching entities with Composite Key Relations and null values
Remove redundant condition to check if target class contains foreign
identifier in order to allow fetching a null for relations with
composite keys, when part of the key value is null.
2024-06-19 21:54:02 +01:00
Grégoire Paris
2ea6a1a5fb Remove unneeded CS rule 2024-06-19 21:47:55 +02:00
Alexander M. Turek
41cb5fbbbf Merge branch '2.19.x' into 3.2.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-19 12:21:35 +02:00
Grégoire Paris
cc2ad1993c Merge pull request #11501 from gitbugr/2.19.x
Fix OneToManyPersister::deleteEntityCollection missing discriminator column/value. (GH-11500)
2024-06-17 21:40:07 +02:00
Kyron Taylor
e4d46c4276 Fix OneToManyPersister::deleteEntityCollection missing discriminator column/value. (GH-11500) 2024-06-15 21:58:08 +01:00
Grégoire Paris
858a1adc3b Merge pull request #11194 from noemi-salaun/fix/gh10889
Skip joined entity creation for empty relation (#10889)
2024-06-14 20:06:59 +02:00
Noemi Salaun
3b499132d9 Skip joined entity creation for empty relation (#10889) 2024-06-14 14:34:04 +02:00
Daniel Black
39153fd88a ci: maintained and stable mariadb version (11.4 current lts) (#11490)
Also use MARIADB env names and the healthcheck.sh included in the container.
2024-06-13 19:34:46 +02:00
Grégoire Paris
bdc9679e37 Merge pull request #11493 from SamMousa/fix-docs-11492
fix(docs): use string value in `addAttribute`
2024-06-11 15:26:45 +01:00
Sam Mousa
87a8ee21c9 fix(docs): use string value in addAttribute 2024-06-11 16:21:28 +02:00
Grégoire Paris
59c8bc09ab Replace assertion with exception (#11489) 2024-06-03 23:08:27 +02:00
Grégoire Paris
3a7d7c9f57 Merge pull request #11484 from greg0ire/backport-ramsey
Use ramsey/composer-install in PHPBench workflow
2024-06-02 15:26:00 +02:00
Grégoire Paris
06eca40134 Use ramsey/composer-install in PHPBench workflow
It will handle caching for us.
2024-06-02 15:22:59 +02:00
Grégoire Paris
23b35e9554 Merge pull request #11475 from nicolas-grekas/fix-clone
Fix cloning entities
2024-06-01 22:47:57 +02:00
Grégoire Paris
e063926cbd Merge pull request #11445 from aprat84/gh-11128
Consider usage of setFetchMode when checking for simultaneous usage of fetch-mode EAGER and WITH condition
2024-05-30 17:24:11 +02:00
Grégoire Paris
4a01a76a17 Merge pull request #11460 from IndraGunawan/update-transactional-doc
docs: update EntityManager#transactional to EntityManager#wrapInTransaction
2024-05-28 14:07:06 +02:00
Indra Gunawan
93c2dd9d4b update EntityManager#transactional to EntityManager#wrapInTransaction
One has been deprecated in favor of the other.
2024-05-28 13:59:17 +02:00
Nicolas Grekas
75bc22980e Fix cloning entities 2024-05-27 14:53:58 +02:00
Alix Mauro
9696c3434d Consider usage of setFetchMode when checking for simultaneous usage of fetch-mode EAGER and WITH condition.
This fixes a bug that arises when 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).

Fixes #11128

Co-Authored-By: Albert Prat <albert.prat@interactiu.cat>
2024-05-25 14:22:20 +02:00
Grégoire Paris
9d4f54b9a4 Update branch metadata (#11474) 2024-05-24 00:25:01 +02:00
58 changed files with 1032 additions and 240 deletions

View File

@@ -11,17 +11,23 @@
"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",
"upcoming": true
"current": true
},
{
"name": "3.1",
"branchName": "3.1.x",
"slug": "3.1",
"current": true
"maintained": false
},
{
"name": "3.0",
@@ -29,17 +35,23 @@
"slug": "3.0",
"maintained": false
},
{
"name": "2.21",
"branchName": "2.21.x",
"slug": "2.21",
"upcoming": true
},
{
"name": "2.20",
"branchName": "2.20.x",
"slug": "2.20",
"upcoming": true
"maintained": true
},
{
"name": "2.19",
"branchName": "2.19.x",
"slug": "2.19",
"maintained": true
"maintained": false
},
{
"name": "2.18",

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@5.1.0"

View File

@@ -185,7 +185,7 @@ jobs:
- "3.7"
- "4@dev"
mariadb-version:
- "10.9"
- "11.4"
extension:
- "mysqli"
- "pdo_mysql"
@@ -194,11 +194,11 @@ jobs:
mariadb:
image: "mariadb:${{ matrix.mariadb-version }}"
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: "doctrine_tests"
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes
MARIADB_DATABASE: "doctrine_tests"
options: >-
--health-cmd "mysqladmin ping --silent"
--health-cmd "healthcheck.sh --connect --innodb_initialized"
ports:
- "3306:3306"

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@5.1.0"

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@5.1.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}

View File

@@ -1,7 +1,7 @@
| [4.0.x][4.0] | [3.2.x][3.2] | [3.1.x][3.1] | [2.20.x][2.20] | [2.19.x][2.19] |
| [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] |
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
| [![Build status][4.0 image]][4.0] | [![Build status][3.2 image]][3.2] | [![Build status][3.1 image]][3.1] | [![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.2 coverage image]][3.2 coverage] | [![Coverage Status][3.1 coverage image]][3.1 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] | [![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] |
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
@@ -22,14 +22,14 @@ without requiring unnecessary code duplication.
[4.0]: https://github.com/doctrine/orm/tree/4.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.1 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.1.x
[3.1]: https://github.com/doctrine/orm/tree/3.1.x
[3.1 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.1.x/graph/badge.svg
[3.1 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.1.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 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg

View File

@@ -38,7 +38,8 @@
"require-dev": {
"doctrine/coding-standard": "^12.0",
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "1.11.1",
"phpdocumentor/guides-cli": "^1.4",
"phpstan/phpstan": "1.12.6",
"phpunit/phpunit": "^10.4.0",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",

View File

@@ -232,6 +232,33 @@ 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

@@ -15,7 +15,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>`

View File

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

View File

@@ -88,7 +88,7 @@ requirement.
A more convenient alternative for explicit transaction demarcation is the use
of provided control abstractions in the form of
``Connection#transactional($func)`` and ``EntityManager#transactional($func)``.
``Connection#transactional($func)`` and ``EntityManager#wrapInTransaction($func)``.
When used, these control abstractions ensure that you never forget to rollback
the transaction, in addition to the obvious code reduction. An example that is
functionally equivalent to the previously shown code looks as follows:
@@ -96,21 +96,23 @@ functionally equivalent to the previously shown code looks as follows:
.. code-block:: php
<?php
// transactional with Connection instance
// $conn instanceof Connection
$conn->transactional(function($conn) {
// ... do some work
$user = new User;
$user->setName('George');
});
// transactional with EntityManager instance
// $em instanceof EntityManager
$em->transactional(function($em) {
$em->wrapInTransaction(function($em) {
// ... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
});
.. warning::
For historical reasons, ``EntityManager#transactional($func)`` will return
``true`` whenever the return value of ``$func`` is loosely false.
Some examples of this include ``array()``, ``"0"``, ``""``, ``0``, and
``null``.
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction

View File

@@ -338,10 +338,11 @@ Performance of different deletion strategies
Deleting an object with all its associated objects can be achieved
in multiple ways with very different performance impacts.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM
will fetch this association. If its a Single association it will
pass this entity to
``EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()``.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM will
fetch this association. If it's a Single association it will pass
this entity to ``EntityManager#remove()``. If the association is a
collection, Doctrine will loop over all its elements and pass them to
``EntityManager#remove()``.
In both cases the cascade remove semantics are applied recursively.
For large object graphs this removal strategy can be very costly.
2. Using a DQL ``DELETE`` statement allows you to delete multiple

View File

@@ -1,80 +1,73 @@
.. 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/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
.. 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/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

View File

@@ -145,7 +145,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
public function addAttribute(string $name, ArticleAttribute $value): void
public function addAttribute(string $name, string $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}

View File

@@ -139,12 +139,12 @@ step:
// Create a simple "default" Doctrine ORM configuration for Attributes
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: array(__DIR__."/src"),
paths: [__DIR__ . '/src'],
isDevMode: true,
);
// or if you prefer XML
// $config = ORMSetup::createXMLMetadataConfiguration(
// paths: array(__DIR__."/config/xml"),
// paths: [__DIR__ . '/config/xml'],
// isDevMode: true,
//);

View File

@@ -14,7 +14,6 @@
<file>src</file>
<file>tests</file>
<exclude-pattern>*/src/Mapping/InverseJoinColumn.php</exclude-pattern>
<exclude-pattern>*/tests/Tests/Proxies/__CG__*</exclude-pattern>
<exclude-pattern>*/tests/Tests/ORM/Tools/Export/export/*</exclude-pattern>

View File

@@ -497,13 +497,8 @@
<InvalidPropertyAssignmentValue>
<code><![CDATA[$metadata->table]]></code>
</InvalidPropertyAssignmentValue>
<InvalidPropertyFetch>
<code><![CDATA[$xmlRoot->{'discriminator-column'}]]></code>
<code><![CDATA[$xmlRoot->{'discriminator-map'}]]></code>
</InvalidPropertyFetch>
<InvalidReturnStatement>
<code><![CDATA[$mapping]]></code>
<code><![CDATA[$result]]></code>
<code><![CDATA[[
'usage' => $usage,
'region' => $region,
@@ -527,20 +522,10 @@
* options?: array
* }]]></code>
<code><![CDATA[array{usage: int|null, region?: string}]]></code>
<code><![CDATA[loadMappingFile]]></code>
</InvalidReturnType>
<MoreSpecificImplementedParamType>
<code><![CDATA[$metadata]]></code>
</MoreSpecificImplementedParamType>
<NoInterfaceProperties>
<code><![CDATA[$xmlRoot->{'discriminator-column'}]]></code>
<code><![CDATA[$xmlRoot->{'discriminator-map'}]]></code>
</NoInterfaceProperties>
<TypeDoesNotContainType>
<code><![CDATA[$xmlRoot->getName() === 'embeddable']]></code>
<code><![CDATA[$xmlRoot->getName() === 'entity']]></code>
<code><![CDATA[$xmlRoot->getName() === 'mapped-superclass']]></code>
</TypeDoesNotContainType>
</file>
<file src="src/Mapping/ManyToManyInverseSideMapping.php">
<PropertyNotSetInConstructor>
@@ -756,7 +741,9 @@
<code><![CDATA[$autoGenerate > 4]]></code>
</TypeDoesNotContainType>
<UndefinedMethod>
<code><![CDATA[self::createLazyGhost($initializer, $skippedProperties)]]></code>
<code><![CDATA[self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void {
$initializer($object, $identifier);
}, $skippedProperties)]]></code>
</UndefinedMethod>
<UnresolvableInclude>
<code><![CDATA[require $fileName]]></code>

View File

@@ -356,11 +356,15 @@ class ObjectHydrator extends AbstractHydrator
$parentObject = $this->resultPointers[$parentAlias];
} else {
// Parent object of relation not found, mark as not-fetched again
$element = $this->getEntity($data, $dqlAlias);
if (isset($nonemptyComponents[$dqlAlias])) {
$element = $this->getEntity($data, $dqlAlias);
// Update result pointer and provide initial fetch data for parent
$this->resultPointers[$dqlAlias] = $element;
$rowData['data'][$parentAlias][$relationField] = $element;
// Update result pointer and provide initial fetch data for parent
$this->resultPointers[$dqlAlias] = $element;
$rowData['data'][$parentAlias][$relationField] = $element;
} else {
$element = null;
}
// Mark as not-fetched again
unset($this->hints['fetched'][$parentAlias][$relationField]);

View File

@@ -390,7 +390,7 @@ class AttributeDriver implements MappingDriver
$metadata->mapOneToMany($mapping);
} elseif ($manyToOneAttribute !== null) {
if ($metadata->isEmbeddedClass) {
throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class);
throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ManyToOne::class);
}
$idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class);

View File

@@ -38,6 +38,8 @@ use function strtoupper;
* XmlDriver is a metadata driver that enables mapping through XML files.
*
* @link www.doctrine-project.org
*
* @template-extends FileDriver<SimpleXMLElement>
*/
class XmlDriver extends FileDriver
{
@@ -78,7 +80,6 @@ class XmlDriver extends FileDriver
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
{
$xmlRoot = $this->getElement($className);
assert($xmlRoot instanceof SimpleXMLElement);
if ($xmlRoot->getName() === 'entity') {
if (isset($xmlRoot['repository-class'])) {
@@ -134,6 +135,7 @@ class XmlDriver extends FileDriver
];
if (isset($discrColumn['options'])) {
assert($discrColumn['options'] instanceof SimpleXMLElement);
$columnDef['options'] = $this->parseOptions($discrColumn['options']->children());
}
@@ -145,6 +147,7 @@ class XmlDriver extends FileDriver
// Evaluate <discriminator-map...>
if (isset($xmlRoot->{'discriminator-map'})) {
$map = [];
assert($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} instanceof SimpleXMLElement);
foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
$map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
}

View File

@@ -2,7 +2,6 @@
declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Attribute;

View File

@@ -40,7 +40,15 @@ class NativeQuery extends AbstractQuery
$types = [];
foreach ($this->getParameters() as $parameter) {
$name = $parameter->getName();
$name = $parameter->getName();
if ($parameter->typeWasSpecified()) {
$parameters[$name] = $parameter->getValue();
$types[$name] = $parameter->getType();
continue;
}
$value = $this->processParameterValue($parameter->getValue());
$type = $parameter->getValue() === $value
? $parameter->getType()

View File

@@ -8,13 +8,18 @@ use BadMethodCallException;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityNotFoundException;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Mapping\OneToManyAssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Utility\PersisterHelper;
use function array_fill;
use function array_keys;
use function array_reverse;
use function array_values;
use function assert;
use function count;
use function implode;
use function is_int;
use function is_string;
@@ -146,7 +151,11 @@ class OneToManyPersister extends AbstractCollectionPersister
throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.');
}
/** @throws DBALException */
/**
* @throws DBALException
* @throws EntityNotFoundException
* @throws MappingException
*/
private function deleteEntityCollection(PersistentCollection $collection): int
{
$mapping = $this->getMapping($collection);
@@ -166,6 +175,16 @@ class OneToManyPersister extends AbstractCollectionPersister
$statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform)
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
if ($targetClass->isInheritanceTypeSingleTable()) {
$discriminatorColumn = $targetClass->getDiscriminatorColumn();
$discriminatorValues = $targetClass->discriminatorValue ? [$targetClass->discriminatorValue] : array_keys($targetClass->discriminatorMap);
$statement .= ' AND ' . $discriminatorColumn->name . ' IN (' . implode(', ', array_fill(0, count($discriminatorValues), '?')) . ')';
foreach ($discriminatorValues as $discriminatorValue) {
$parameters[] = $discriminatorValue;
$types[] = $discriminatorColumn->type;
}
}
$numAffected = $this->conn->executeStatement($statement, $parameters, $types);
assert(is_int($numAffected));

View File

@@ -792,17 +792,42 @@ class BasicEntityPersister implements EntityPersister
$computedIdentifier = [];
/** @var array<string,mixed>|null $sourceEntityData */
$sourceEntityData = null;
// TRICKY: since the association is specular source and target are flipped
foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
if (! isset($sourceClass->fieldNames[$sourceKeyColumn])) {
throw MappingException::joinColumnMustPointToMappedField(
$sourceClass->name,
$sourceKeyColumn,
);
}
// The likely case here is that the column is a join column
// in an association mapping. However, there is no guarantee
// at this point that a corresponding (generally identifying)
// association has been mapped in the source entity. To handle
// this case we directly reference the column-keyed data used
// to initialize the source entity before throwing an exception.
$resolvedSourceData = false;
if (! isset($sourceEntityData)) {
$sourceEntityData = $this->em->getUnitOfWork()->getOriginalEntityData($sourceEntity);
}
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
if (isset($sourceEntityData[$sourceKeyColumn])) {
$dataValue = $sourceEntityData[$sourceKeyColumn];
if ($dataValue !== null) {
$resolvedSourceData = true;
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
$dataValue;
}
}
if (! $resolvedSourceData) {
throw MappingException::joinColumnMustPointToMappedField(
$sourceClass->name,
$sourceKeyColumn,
);
}
} else {
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
}
}
$targetEntity = $this->load($computedIdentifier, null, $assoc);

View File

@@ -210,15 +210,14 @@ EOPHP;
/**
* Creates a closure capable of initializing a proxy
*
* @return Closure(InternalProxy, InternalProxy):void
* @return Closure(InternalProxy, array):void
*
* @throws EntityNotFoundException
*/
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
{
return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
$identifier = $classMetadata->getIdentifierValues($proxy);
$original = $entityPersister->loadById($identifier);
return static function (InternalProxy $proxy, array $identifier) use ($entityPersister, $classMetadata, $identifierFlattener): void {
$original = $entityPersister->loadById($identifier);
if ($original === null) {
throw EntityNotFoundException::fromClassNameAndIdentifier(
@@ -234,7 +233,7 @@ EOPHP;
$class = $entityPersister->getClassMetadata();
foreach ($class->getReflectionProperties() as $property) {
if (! $property || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
if (! $property || isset($identifier[$property->getName()]) || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
continue;
}
@@ -283,7 +282,9 @@ EOPHP;
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);
$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {
$proxy = self::createLazyGhost($initializer, $skippedProperties);
$proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void {
$initializer($object, $identifier);
}, $skippedProperties);
foreach ($identifierFields as $idField => $reflector) {
if (! isset($identifier[$idField])) {
@@ -386,12 +387,18 @@ EOPHP;
$code = substr($code, 7 + (int) strpos($code, "\n{"));
$code = substr($code, 0, (int) strpos($code, "\n}"));
$code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait {
initializeLazyObject as __load;
initializeLazyObject as private;
setLazyObjectAsInitialized as public __setInitialized;
isLazyObjectInitialized as private;
createLazyGhost as private;
resetLazyObject as private;
}'), $code);
}
public function __load(): void
{
$this->initializeLazyObject();
}
'), $code);
return $code;
}

View File

@@ -2563,7 +2563,10 @@ final class Parser
return new AST\ParenthesisExpression($expr);
}
assert($this->lexer->lookahead !== null);
if ($this->lexer->lookahead === null) {
$this->syntaxError('ArithmeticPrimary');
}
switch ($this->lexer->lookahead->type) {
case TokenType::T_COALESCE:
case TokenType::T_NULLIF:

View File

@@ -911,7 +911,9 @@ class SqlWalker
}
}
if ($relation->fetch === ClassMetadata::FETCH_EAGER && $condExpr !== null) {
$fetchMode = $this->query->getHint('fetchMode')[$assoc->sourceEntity][$assoc->fieldName] ?? $relation->fetch;
if ($fetchMode === ClassMetadata::FETCH_EAGER && $condExpr !== null) {
throw QueryException::eagerFetchJoinWithNotAllowed($assoc->sourceEntity, $assoc->fieldName);
}

View File

@@ -2467,10 +2467,7 @@ class UnitOfWork implements PropertyChangedListener
} else {
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
}
} elseif (
$targetClass->containsForeignIdentifier
&& in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifier, true)
) {
} elseif (in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifier, true)) {
// the missing key is part of target's entity primary key
$associatedId = [];
break;

View File

@@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Performance;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\PDO\SQLite\Driver;
use Doctrine\DBAL\Result;
final class ArrayResultFactory
{
public static function createFromArray(array $resultSet): Result
{
return new Result(new ArrayResult($resultSet), new Connection([], new Driver()));
}
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\Performance;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\PDO\SQLite\Driver;
@@ -17,6 +16,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\TestUtil;
use function array_map;
@@ -67,7 +67,7 @@ final class EntityManagerFactory
/** {@inheritDoc} */
public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile|null $qcp = null): Result
{
return new Result(new ArrayResult([]), $this);
return ArrayResultFactory::createWrapperResultFromArray([], $this);
}
};

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ArrayHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsUser;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
@@ -62,7 +62,7 @@ final class MixedQueryFetchJoinArrayHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ArrayHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsUser;
@@ -49,7 +49,7 @@ final class MixedQueryFetchJoinFullObjectHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ObjectHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ArrayHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsUser;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
@@ -53,7 +53,7 @@ final class SimpleQueryArrayHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ArrayHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsUser;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
@@ -44,7 +44,7 @@ final class SimpleQueryFullObjectHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ObjectHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ScalarHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsUser;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
@@ -53,7 +53,7 @@ final class SimpleQueryScalarHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ScalarHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Mocks;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\PDO\SQLite\Driver;
use Doctrine\DBAL\Result;
use ReflectionMethod;
use function array_keys;
use function array_map;
use function array_values;
final class ArrayResultFactory
{
/** @param list<array<string, mixed>> $resultSet */
public static function createDriverResultFromArray(array $resultSet): ArrayResult
{
if ((new ReflectionMethod(ArrayResult::class, '__construct'))->getNumberOfRequiredParameters() < 2) {
// DBAL < 4.2
return new ArrayResult($resultSet);
}
// DBAL 4.2+
return new ArrayResult(
array_keys($resultSet[0] ?? []),
array_map(array_values(...), $resultSet),
);
}
/** @param list<array<string, mixed>> $resultSet */
public static function createWrapperResultFromArray(array $resultSet, Connection|null $connection = null): Result
{
return new Result(
self::createDriverResultFromArray($resultSet),
$connection ?? new Connection([], new Driver()),
);
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\CompositeKeyRelations;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
#[Entity]
class CustomerClass
{
#[Id]
#[Column(type: 'string')]
public string $companyCode;
#[Id]
#[Column(type: 'string')]
public string $code;
#[Column(type: 'string')]
public string $name;
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\CompositeKeyRelations;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;
#[Entity]
class InvoiceClass
{
#[Id]
#[Column(type: 'string')]
public string $companyCode;
#[Id]
#[Column(type: 'string')]
public string $invoiceNumber;
#[ManyToOne(targetEntity: CustomerClass::class)]
#[JoinColumn(name: 'companyCode', referencedColumnName: 'companyCode')]
#[JoinColumn(name: 'customerCode', referencedColumnName: 'code')]
public CustomerClass|null $customer;
#[Column(type: 'string', nullable: true)]
public string|null $customerCode = null;
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\ECommerce;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Index;
use Doctrine\ORM\Mapping\Table;
/**
* ECommerceProduct2
* Resets the id when being cloned.
*/
#[Entity]
#[Table(name: 'ecommerce_products')]
#[Index(name: 'name_idx', columns: ['name'])]
class ECommerceProduct2
{
#[Column]
#[Id]
#[GeneratedValue]
private int|null $id = null;
#[Column(length: 50, nullable: true)]
private string|null $name = null;
public function getId(): int|null
{
return $this->id;
}
public function getName(): string|null
{
return $this->name;
}
public function __clone()
{
$this->id = null;
$this->name = 'Clone of ' . $this->name;
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\OneToOne;
use Doctrine\ORM\Mapping\Table;
#[Entity]
#[Table(name: 'one_to_one_inverse_side_assoc_id_load_inverse')]
class InverseSide
{
/** Associative id (owning identifier) */
#[Id]
#[OneToOne(targetEntity: InverseSideIdTarget::class, inversedBy: 'inverseSide')]
#[JoinColumn(nullable: false, name: 'associativeId')]
public InverseSideIdTarget $associativeId;
#[OneToOne(targetEntity: OwningSide::class, mappedBy: 'inverse')]
public OwningSide $owning;
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToOne;
use Doctrine\ORM\Mapping\Table;
#[Entity]
#[Table(name: 'one_to_one_inverse_side_assoc_id_load_inverse_id_target')]
class InverseSideIdTarget
{
#[Id]
#[Column(type: 'string', length: 255)]
#[GeneratedValue(strategy: 'NONE')]
public string $id;
#[OneToOne(targetEntity: InverseSide::class, mappedBy: 'associativeId')]
public InverseSide $inverseSide;
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\OneToOne;
use Doctrine\ORM\Mapping\Table;
#[Entity]
#[Table(name: 'one_to_one_inverse_side_assoc_id_load_owning')]
class OwningSide
{
#[Id]
#[Column(type: 'string', length: 255)]
#[GeneratedValue(strategy: 'NONE')]
public string $id;
/** Owning side */
#[OneToOne(targetEntity: InverseSide::class, inversedBy: 'owning')]
#[JoinColumn(name: 'inverse', referencedColumnName: 'associativeId')]
public InverseSide $inverse;
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CompositeKeyRelations\CustomerClass;
use Doctrine\Tests\Models\CompositeKeyRelations\InvoiceClass;
use Doctrine\Tests\OrmFunctionalTestCase;
class CompositeKeyRelationsTest extends OrmFunctionalTestCase
{
protected function setUp(): void
{
$this->useModelSet('compositekeyrelations');
parent::setUp();
}
public function testFindEntityWithNotNullRelation(): void
{
$this->_em->getConnection()->insert('CustomerClass', [
'companyCode' => 'AA',
'code' => 'CUST1',
'name' => 'Customer 1',
]);
$this->_em->getConnection()->insert('InvoiceClass', [
'companyCode' => 'AA',
'invoiceNumber' => 'INV1',
'customerCode' => 'CUST1',
]);
$entity = $this->findEntity('AA', 'INV1');
self::assertSame('AA', $entity->companyCode);
self::assertSame('INV1', $entity->invoiceNumber);
self::assertInstanceOf(CustomerClass::class, $entity->customer);
self::assertSame('Customer 1', $entity->customer->name);
}
public function testFindEntityWithNullRelation(): void
{
$this->_em->getConnection()->insert('InvoiceClass', [
'companyCode' => 'BB',
'invoiceNumber' => 'INV1',
]);
$entity = $this->findEntity('BB', 'INV1');
self::assertSame('BB', $entity->companyCode);
self::assertSame('INV1', $entity->invoiceNumber);
self::assertNull($entity->customer);
}
private function findEntity(string $companyCode, string $invoiceNumber): InvoiceClass
{
return $this->_em->find(
InvoiceClass::class,
['companyCode' => $companyCode, 'invoiceNumber' => $invoiceNumber],
);
}
}

View File

@@ -89,6 +89,14 @@ class EagerFetchCollectionTest extends OrmFunctionalTestCase
$query->getResult();
}
public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEager(): void
{
$query = $this->_em->createQuery('SELECT o, c FROM ' . EagerFetchOwner::class . ' o JOIN o.children c WITH c.id = 1');
$query->setFetchMode(EagerFetchChild::class, 'owner', ORM\ClassMetadata::FETCH_LAZY);
$this->assertIsString($query->getSql());
}
public function testEagerFetchWithIterable(): void
{
$this->createOwnerWithChildren(2);

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad\InverseSide;
use Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad\InverseSideIdTarget;
use Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad\OwningSide;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
use function assert;
class OneToOneInverseSideWithAssociativeIdLoadAfterDqlQueryTest extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(OwningSide::class, InverseSideIdTarget::class, InverseSide::class);
}
#[Group('GH-11108')]
public function testInverseSideWithAssociativeIdOneToOneLoadedAfterDqlQuery(): void
{
$owner = new OwningSide();
$inverseId = new InverseSideIdTarget();
$inverse = new InverseSide();
$owner->id = 'owner';
$inverseId->id = 'inverseId';
$inverseId->inverseSide = $inverse;
$inverse->associativeId = $inverseId;
$owner->inverse = $inverse;
$inverse->owning = $owner;
$this->_em->persist($owner);
$this->_em->persist($inverseId);
$this->_em->persist($inverse);
$this->_em->flush();
$this->_em->clear();
$fetchedInverse = $this
->_em
->createQueryBuilder()
->select('inverse')
->from(InverseSide::class, 'inverse')
->andWhere('inverse.associativeId = :associativeId')
->setParameter('associativeId', 'inverseId')
->getQuery()
->getSingleResult();
assert($fetchedInverse instanceof InverseSide);
self::assertInstanceOf(InverseSide::class, $fetchedInverse);
self::assertInstanceOf(InverseSideIdTarget::class, $fetchedInverse->associativeId);
self::assertInstanceOf(OwningSide::class, $fetchedInverse->owning);
$this->assertSQLEquals(
'select o0_.associativeid as associativeid_0 from one_to_one_inverse_side_assoc_id_load_inverse o0_ where o0_.associativeid = ?',
$this->getLastLoggedQuery(1)['sql'],
);
$this->assertSQLEquals(
'select t0.id as id_1, t0.inverse as inverse_2 from one_to_one_inverse_side_assoc_id_load_owning t0 where t0.inverse = ?',
$this->getLastLoggedQuery()['sql'],
);
}
}

View File

@@ -58,7 +58,7 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
public function testPersistUpdate(): void
{
// Considering case (a)
$proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => 123]);
$proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => $this->user->getId()]);
$proxy->id = null;
$proxy->username = 'ocra';

View File

@@ -9,6 +9,7 @@ use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Proxy\InternalProxy;
use Doctrine\Tests\Models\Company\CompanyAuction;
use Doctrine\Tests\Models\ECommerce\ECommerceProduct;
use Doctrine\Tests\Models\ECommerce\ECommerceProduct2;
use Doctrine\Tests\Models\ECommerce\ECommerceShipping;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
@@ -112,6 +113,24 @@ class ReferenceProxyTest extends OrmFunctionalTestCase
self::assertFalse($entity->isCloned);
}
public function testCloneProxyWithResetId(): void
{
$id = $this->createProduct();
$entity = $this->_em->getReference(ECommerceProduct2::class, $id);
assert($entity instanceof ECommerceProduct2);
$clone = clone $entity;
assert($clone instanceof ECommerceProduct2);
self::assertEquals($id, $entity->getId());
self::assertEquals('Doctrine Cookbook', $entity->getName());
self::assertFalse($this->_em->contains($clone));
self::assertNull($clone->getId());
self::assertEquals('Clone of Doctrine Cookbook', $clone->getName());
}
#[Group('DDC-733')]
public function testInitializeProxy(): void
{

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
/** @see https://github.com/doctrine/orm/issues/10889 */
#[Group('GH10889')]
class GH10889Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(
GH10889Person::class,
GH10889Company::class,
GH10889Resume::class,
);
}
public function testIssue(): void
{
$person = new GH10889Person();
$resume = new GH10889Resume($person, null);
$this->_em->persist($person);
$this->_em->persist($resume);
$this->_em->flush();
$this->_em->clear();
/** @var list<GH10889Resume> $resumes */
$resumes = $this->_em
->getRepository(GH10889Resume::class)
->createQueryBuilder('resume')
->leftJoin('resume.currentCompany', 'company')->addSelect('company')
->getQuery()
->getResult();
$this->assertArrayHasKey(0, $resumes);
$this->assertEquals(1, $resumes[0]->person->id);
$this->assertNull($resumes[0]->currentCompany);
}
}
#[ORM\Entity]
class GH10889Person
{
#[ORM\Id]
#[ORM\Column]
#[ORM\GeneratedValue]
public int|null $id = null;
}
#[ORM\Entity]
class GH10889Company
{
#[ORM\Id]
#[ORM\Column]
#[ORM\GeneratedValue]
public int|null $id = null;
}
#[ORM\Entity]
class GH10889Resume
{
public function __construct(
#[ORM\Id]
#[ORM\OneToOne]
public GH10889Person $person,
#[ORM\ManyToOne]
public GH10889Company|null $currentCompany,
) {
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Query\QueryException;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH11487Test extends OrmFunctionalTestCase
{
public function testItThrowsASyntaxErrorOnUnfinishedQuery(): void
{
$this->expectException(QueryException::class);
$this->expectExceptionMessage('Syntax Error');
$this->_em->createQuery('UPDATE Doctrine\Tests\ORM\Functional\Ticket\TaxType t SET t.default =')->execute();
}
}
#[Entity]
class TaxType
{
#[Column]
#[Id]
#[GeneratedValue]
public int|null $id = null;
#[Column]
public bool $default = false;
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Exception\ORMException;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH11500Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH11500AbstractTestEntity::class,
GH11500TestEntityOne::class,
GH11500TestEntityTwo::class,
GH11500TestEntityHolder::class,
]);
}
/** @throws ORMException */
public function testDeleteOneToManyCollectionWithSingleTableInheritance(): void
{
$testEntityOne = new GH11500TestEntityOne();
$testEntityTwo = new GH11500TestEntityTwo();
$testEntityHolder = new GH11500TestEntityHolder();
$testEntityOne->testEntityHolder = $testEntityHolder;
$testEntityHolder->testEntityOnes->add($testEntityOne);
$testEntityTwo->testEntityHolder = $testEntityHolder;
$testEntityHolder->testEntityTwos->add($testEntityTwo);
$em = $this->getEntityManager();
$em->persist($testEntityOne);
$em->persist($testEntityTwo);
$em->persist($testEntityHolder);
$em->flush();
$testEntityTwosBeforeRemovalOfTestEntityOnes = $testEntityHolder->testEntityTwos->toArray();
$testEntityHolder->testEntityOnes = new ArrayCollection();
$em->persist($testEntityHolder);
$em->flush();
$em->refresh($testEntityHolder);
static::assertEmpty($testEntityHolder->testEntityOnes->toArray(), 'All records should have been deleted');
static::assertEquals($testEntityTwosBeforeRemovalOfTestEntityOnes, $testEntityHolder->testEntityTwos->toArray(), 'Different Entity\'s records should not have been deleted');
}
}
#[ORM\Entity]
#[ORM\Table(name: 'one_to_many_single_table_inheritance_test_entities')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap(['test_entity_one' => 'GH11500TestEntityOne', 'test_entity_two' => 'GH11500TestEntityTwo'])]
class GH11500AbstractTestEntity
{
#[ORM\Id]
#[ORM\Column]
#[ORM\GeneratedValue]
public int|null $id = null;
}
#[ORM\Entity]
class GH11500TestEntityOne extends GH11500AbstractTestEntity
{
#[ORM\ManyToOne(inversedBy:'testEntityOnes')]
#[ORM\JoinColumn(name:'test_entity_holder_id', referencedColumnName:'id')]
public GH11500TestEntityHolder|null $testEntityHolder = null;
}
#[ORM\Entity]
class GH11500TestEntityTwo extends GH11500AbstractTestEntity
{
#[ORM\ManyToOne(inversedBy:'testEntityTwos')]
#[ORM\JoinColumn(name:'test_entity_holder_id', referencedColumnName:'id')]
public GH11500TestEntityHolder|null $testEntityHolder = null;
}
#[ORM\Entity]
class GH11500TestEntityHolder
{
#[ORM\Id]
#[ORM\Column]
#[ORM\GeneratedValue]
public int|null $id = null;
#[ORM\OneToMany(targetEntity: 'GH11500TestEntityOne', mappedBy: 'testEntityHolder', orphanRemoval: true)]
public Collection $testEntityOnes;
#[ORM\OneToMany(targetEntity: 'GH11500TestEntityTwo', mappedBy: 'testEntityHolder', orphanRemoval: true)]
public Collection $testEntityTwos;
public function __construct()
{
$this->testEntityOnes = new ArrayCollection();
$this->testEntityTwos = new ArrayCollection();
}
}

View File

@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Exception\ORMException;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH11501Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH11501AbstractTestEntity::class,
GH11501TestEntityOne::class,
GH11501TestEntityTwo::class,
GH11501TestEntityHolder::class,
]);
}
/** @throws ORMException */
public function testDeleteOneToManyCollectionWithSingleTableInheritance(): void
{
$testEntityOne = new GH11501TestEntityOne();
$testEntityTwo = new GH11501TestEntityTwo();
$testEntityHolder = new GH11501TestEntityHolder();
$testEntityOne->testEntityHolder = $testEntityHolder;
$testEntityHolder->testEntities->add($testEntityOne);
$testEntityTwo->testEntityHolder = $testEntityHolder;
$testEntityHolder->testEntities->add($testEntityTwo);
$em = $this->getEntityManager();
$em->persist($testEntityOne);
$em->persist($testEntityTwo);
$em->persist($testEntityHolder);
$em->flush();
$testEntityHolder->testEntities = new ArrayCollection();
$em->persist($testEntityHolder);
$em->flush();
$em->refresh($testEntityHolder);
static::assertEmpty($testEntityHolder->testEntities->toArray(), 'All records should have been deleted');
}
}
#[ORM\Entity]
#[ORM\Table(name: 'one_to_many_single_table_inheritance_test_entities_parent_join')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap([
'test_entity_one' => 'GH11501TestEntityOne',
'test_entity_two' => 'GH11501TestEntityTwo',
])]
class GH11501AbstractTestEntity
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
public int $id;
#[ORM\ManyToOne(targetEntity: 'GH11501TestEntityHolder', inversedBy: 'testEntities')]
#[ORM\JoinColumn(name: 'test_entity_holder_id', referencedColumnName: 'id')]
public GH11501TestEntityHolder $testEntityHolder;
}
#[ORM\Entity]
class GH11501TestEntityOne extends GH11501AbstractTestEntity
{
}
#[ORM\Entity]
class GH11501TestEntityTwo extends GH11501AbstractTestEntity
{
}
#[ORM\Entity]
class GH11501TestEntityHolder
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
public int $id;
#[ORM\OneToMany(
targetEntity: 'GH11501AbstractTestEntity',
mappedBy: 'testEntityHolder',
orphanRemoval: true,
)]
public Collection $testEntities;
public function __construct()
{
$this->testEntities = new ArrayCollection();
}
}

View File

@@ -5,9 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\DiscriminatorColumn;
@@ -19,6 +17,7 @@ use Doctrine\ORM\Mapping\InheritanceType;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
@@ -78,7 +77,7 @@ final class GH6362Test extends OrmFunctionalTestCase
],
];
$stmt = new Result(new ArrayResult($resultSet), $this->createMock(Connection::class));
$stmt = ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->createMock(Connection::class));
$hydrator = new ObjectHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);

View File

@@ -5,9 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Column;
@@ -15,6 +13,7 @@ use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\OrmFunctionalTestCase;
final class GH9807Test extends OrmFunctionalTestCase
@@ -63,7 +62,7 @@ final class GH9807Test extends OrmFunctionalTestCase
],
];
$stmt = new Result(new ArrayResult($resultSet), $this->createMock(Connection::class));
$stmt = ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->createMock(Connection::class));
/** @var GH9807Main[] $result */
$result = $hydrator->hydrateAll($stmt, $rsm);

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Hydration;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Result;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\OrmTestCase;
class HydrationTestCase extends OrmTestCase
@@ -22,6 +22,6 @@ class HydrationTestCase extends OrmTestCase
protected function createResultMock(array $resultSet): Result
{
return new Result(new ArrayResult($resultSet), $this->entityManager->getConnection());
return ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->entityManager->getConnection());
}
}

View File

@@ -4,9 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Hydration;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\HydrationException;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -14,6 +12,7 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\InternalProxy;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsComment;
@@ -1337,7 +1336,7 @@ class ObjectHydratorTest extends HydrationTestCase
$hydrator = new ObjectHydrator($this->entityManager);
$rowNum = 0;
$iterableResult = $hydrator->toIterable(
new Result(new ArrayResult($resultSet), $this->createMock(Connection::class)),
ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->createMock(Connection::class)),
$rsm,
);

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Query;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\OrmTestCase;
class NativeQueryTest extends OrmTestCase
{
/** @var EntityManagerMock */
protected $entityManager;
protected function setUp(): void
{
$this->entityManager = $this->getTestEntityManager();
}
public function testValuesAreNotBeingResolvedForSpecifiedParameterTypes(): void
{
$unitOfWork = $this->createMock(UnitOfWork::class);
$this->entityManager->setUnitOfWork($unitOfWork);
$unitOfWork
->expects(self::never())
->method('getSingleIdentifierValue');
$rsm = new ResultSetMapping();
$query = $this->entityManager->createNativeQuery('SELECT d.* FROM date_time_model d WHERE d.datetime = :value', $rsm);
$query->setParameter('value', new DateTime(), Types::DATETIME_MUTABLE);
self::assertEmpty($query->getResult());
}
}

View File

@@ -8,7 +8,6 @@ use DateTime;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Result;
@@ -22,6 +21,7 @@ use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsGroup;
@@ -361,10 +361,10 @@ class QueryTest extends OrmTestCase
{
$entityManager = $this->createTestEntityManagerWithConnection(
$this->createConnection(
new ArrayResult([
ArrayResultFactory::createDriverResultFromArray([
['id_0' => 1],
]),
new ArrayResult([]),
ArrayResultFactory::createDriverResultFromArray([]),
),
);
@@ -399,14 +399,14 @@ class QueryTest extends OrmTestCase
{
$entityManager = $this->createTestEntityManagerWithConnection(
$this->createConnection(
new ArrayResult([
ArrayResultFactory::createDriverResultFromArray([
['id_0' => 1],
]),
new ArrayResult([
ArrayResultFactory::createDriverResultFromArray([
['id_0' => 1],
['id_0' => 2],
]),
new ArrayResult([
ArrayResultFactory::createDriverResultFromArray([
['id_0' => 1],
]),
),

View File

@@ -349,6 +349,10 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
SingleRootClass::class,
SingleChildClass::class,
],
'compositekeyrelations' => [
Models\CompositeKeyRelations\InvoiceClass::class,
Models\CompositeKeyRelations\CustomerClass::class,
],
'taxi' => [
PaidRide::class,
Ride::class,