mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3cc0fdd8c | ||
|
|
e3e96745cc | ||
|
|
21221f73cc | ||
|
|
ab5e9e393b | ||
|
|
b8d0a85017 | ||
|
|
52a6a21387 | ||
|
|
bf49055a1f | ||
|
|
694413a888 | ||
|
|
20a6efdff6 | ||
|
|
4fc8629414 | ||
|
|
95da667862 | ||
|
|
feb27f00c1 | ||
|
|
b187bc8588 | ||
|
|
719d007a81 | ||
|
|
3f7a3333ad | ||
|
|
2a8802af12 | ||
|
|
9cc11d2541 | ||
|
|
3907872046 | ||
|
|
54cd70002c | ||
|
|
76c4539ffa | ||
|
|
0f8d193512 | ||
|
|
cc314d0fb7 | ||
|
|
2a250b5814 | ||
|
|
e6eef1a97d | ||
|
|
44fa5d340a | ||
|
|
708146bbbc | ||
|
|
a5bf9bb96a | ||
|
|
3eace16e85 | ||
|
|
bc5efd4bfe | ||
|
|
0efac09141 | ||
|
|
b6b4cbcb93 | ||
|
|
efb6cebd41 | ||
|
|
e4769d3191 | ||
|
|
cf408ad9ae | ||
|
|
7c29078051 | ||
|
|
d5ba106803 | ||
|
|
f9a4adc8ab | ||
|
|
401a0c4fe9 | ||
|
|
dba9d72b2d | ||
|
|
fe0647053a | ||
|
|
7b3db4a037 | ||
|
|
6672aaf165 | ||
|
|
aa3b331cae | ||
|
|
3918dcfb42 | ||
|
|
bfb033fe3c | ||
|
|
bf86155dc2 | ||
|
|
1d218bae30 | ||
|
|
9acc70d5b8 | ||
|
|
5a40b99e11 | ||
|
|
94144e1227 | ||
|
|
599dd58fe1 | ||
|
|
aff543a4ff | ||
|
|
1854ce2d32 | ||
|
|
b00f0c258e | ||
|
|
13a79b068c | ||
|
|
27c9e9cab3 | ||
|
|
40fbbf4429 | ||
|
|
00ed2ca991 | ||
|
|
54b7ad2073 | ||
|
|
6f98147d09 | ||
|
|
7527b788de | ||
|
|
cfadb5499d | ||
|
|
e52bc846f0 | ||
|
|
9ce9ae2818 | ||
|
|
f259754b7c | ||
|
|
3bc2cb6b15 | ||
|
|
fdb9d44538 | ||
|
|
a9fcaf1d18 |
@@ -5,23 +5,41 @@
|
||||
"slug": "orm",
|
||||
"docsSlug": "doctrine-orm",
|
||||
"versions": [
|
||||
{
|
||||
"name": "4.0",
|
||||
"branchName": "4.0.x",
|
||||
"slug": "latest",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.1",
|
||||
"branchName": "3.1.x",
|
||||
"slug": "3.1",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.0",
|
||||
"branchName": "3.0.x",
|
||||
"slug": "latest",
|
||||
"slug": "3.0",
|
||||
"current": true
|
||||
},
|
||||
{
|
||||
"name": "2.19",
|
||||
"branchName": "2.19.x",
|
||||
"slug": "2.19",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.18",
|
||||
"branchName": "2.18.x",
|
||||
"slug": "2.18",
|
||||
"upcoming": true
|
||||
"maintained": true
|
||||
},
|
||||
{
|
||||
"name": "2.17",
|
||||
"branchName": "2.17.x",
|
||||
"slug": "2.17",
|
||||
"current": true
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.16",
|
||||
|
||||
14
.github/workflows/continuous-integration.yml
vendored
14
.github/workflows/continuous-integration.yml
vendored
@@ -91,9 +91,9 @@ jobs:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
@@ -164,9 +164,9 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
|
||||
path: "coverage.xml"
|
||||
|
||||
|
||||
@@ -230,7 +230,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@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
@@ -311,7 +311,7 @@ jobs:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage files"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
@@ -332,7 +332,7 @@ jobs:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Download coverage files"
|
||||
uses: "actions/download-artifact@v3"
|
||||
uses: "actions/download-artifact@v4"
|
||||
with:
|
||||
path: "reports"
|
||||
|
||||
|
||||
8
.github/workflows/documentation.yml
vendored
8
.github/workflows/documentation.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
|
||||
- name: "Remove existing composer file"
|
||||
run: "rm composer.json"
|
||||
@@ -40,9 +40,5 @@ jobs:
|
||||
with:
|
||||
dependency-versions: "highest"
|
||||
|
||||
- name: "Add dummy title to the sidebar"
|
||||
run: |
|
||||
printf '%s\n%s\n\n%s\n' "Dummy title" "===========" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
|
||||
|
||||
- name: "Run guides-cli"
|
||||
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'Unknown directive' | ( ! grep WARNING )"
|
||||
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )"
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@3.0.0"
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@4.0.0"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
|
||||
12
.github/workflows/static-analysis.yml
vendored
12
.github/workflows/static-analysis.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
- src/**
|
||||
- phpstan*
|
||||
- psalm*
|
||||
- tests/Doctrine/StaticAnalysis/**
|
||||
- tests/StaticAnalysis/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
@@ -20,7 +20,7 @@ on:
|
||||
- src/**
|
||||
- phpstan*
|
||||
- psalm*
|
||||
- tests/Doctrine/StaticAnalysis/**
|
||||
- tests/StaticAnalysis/**
|
||||
|
||||
jobs:
|
||||
static-analysis-phpstan:
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
include:
|
||||
- dbal-version: default
|
||||
config: phpstan.neon
|
||||
- dbal-version: 3.7
|
||||
- dbal-version: 3.8.2
|
||||
config: phpstan-dbal3.neon
|
||||
|
||||
steps:
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
coverage: none
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
tools: cs2pr
|
||||
|
||||
- name: Require specific DBAL version
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
matrix:
|
||||
dbal-version:
|
||||
- default
|
||||
- 3.7
|
||||
- 3.8.2
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
coverage: none
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
tools: cs2pr
|
||||
|
||||
- name: Require specific DBAL version
|
||||
|
||||
26
README.md
26
README.md
@@ -1,11 +1,11 @@
|
||||
| [3.0.x][3.0] | [2.18.x][2.18] | [2.17.x][2.17] |
|
||||
|:----------------:|:----------------:|:----------:|
|
||||
| [![Build status][3.0 image]][3.0] | [![Build status][2.18 image]][2.18] | [![Build status][2.17 image]][2.17] |
|
||||
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.18 coverage image]][2.18 coverage] | [![Coverage Status][2.17 coverage image]][2.17 coverage] |
|
||||
| [4.0.x][4.0] | [3.1.x][3.1] | [3.0.x][3.0] | [2.19.x][2.19] | [2.18.x][2.18] |
|
||||
|:------------------------------------------------------:|:------------------------------------------------------:|:-------------------------------------------------------:|:--------------------------------------------------------:|:---------------------------------------------------------:|
|
||||
| [![Build status][4.0 image]][4.0] | [![Build status][3.1 image]][3.1] | [![Build status][3.0 image]][3.0] | [![Build status][2.19 image]][2.19] | [![Build status][2.18 image]][2.18] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.1 coverage image]][3.1 coverage] | [![Coverage Status][3.0 coverage image]][3.0 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] | [![Coverage Status][2.18 coverage image]][2.18 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 7.1+ that provides transparent persistence
|
||||
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
|
||||
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
|
||||
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
|
||||
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
|
||||
@@ -18,15 +18,23 @@ without requiring unnecessary code duplication.
|
||||
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html)
|
||||
|
||||
|
||||
[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 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.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
|
||||
[3.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.0.x
|
||||
[3.0]: https://github.com/doctrine/orm/tree/3.0.x
|
||||
[3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg
|
||||
[3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.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
|
||||
[2.18 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.18.x
|
||||
[2.18]: https://github.com/doctrine/orm/tree/2.18.x
|
||||
[2.18 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.18.x/graph/badge.svg
|
||||
[2.18 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.18.x
|
||||
[2.17 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.17.x
|
||||
[2.17]: https://github.com/doctrine/orm/tree/2.17.x
|
||||
[2.17 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.17.x/graph/badge.svg
|
||||
[2.17 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.17.x
|
||||
|
||||
23
UPGRADE.md
23
UPGRADE.md
@@ -1,5 +1,16 @@
|
||||
# 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.
|
||||
@@ -13,9 +24,9 @@ so `$targetEntity` is a first argument now. This change affects only non-named a
|
||||
|
||||
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`. When upgrading from ORM 2.x and preference is on keeping
|
||||
the `SEQUENCE` based identity generation, then configure the ORM this way:
|
||||
|
||||
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;
|
||||
@@ -495,8 +506,8 @@ The methods have been replaced by PSR-6 compatible counterparts
|
||||
|
||||
## BC BREAK: Remove `Doctrine\ORM\Configuration::newDefaultAnnotationDriver`
|
||||
|
||||
This functionality has been moved to the new `DoctrineSetup` class. Call
|
||||
`Doctrine\ORM\Tools\DoctrineSetup::createDefaultAnnotationDriver()` to create
|
||||
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`
|
||||
@@ -504,7 +515,7 @@ a new annotation driver.
|
||||
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\Tools\DoctrineSetup` which accepts a PSR-6
|
||||
The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6
|
||||
cache instead.
|
||||
|
||||
## BC BREAK: Removed named queries
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"composer-runtime-api": "^2",
|
||||
"ext-ctype": "*",
|
||||
"doctrine/collections": "^2.1",
|
||||
"doctrine/dbal": "^3.6 || ^4",
|
||||
"doctrine/dbal": "^3.8.2 || ^4",
|
||||
"doctrine/deprecations": "^0.5.3 || ^1",
|
||||
"doctrine/event-manager": "^1.2 || ^2",
|
||||
"doctrine/inflector": "^1.4 || ^2.0",
|
||||
@@ -38,14 +38,13 @@
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12.0",
|
||||
"phpbench/phpbench": "^1.0",
|
||||
"phpstan/phpstan": "1.10.35",
|
||||
"phpstan/phpstan": "1.10.59",
|
||||
"phpunit/phpunit": "^10.4.0",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"squizlabs/php_codesniffer": "3.7.2",
|
||||
"symfony/cache": "^5.4 || ^6.2 || ^7.0",
|
||||
"vimeo/psalm": "5.16.0"
|
||||
"vimeo/psalm": "5.22.2"
|
||||
},
|
||||
"minimum-stability": "RC",
|
||||
"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"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Implementing ArrayAccess for Domain Objects
|
||||
===========================================
|
||||
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
.. sectionauthor:: Roman Borschel <roman@code-factory.org>
|
||||
|
||||
This recipe will show you how to implement ArrayAccess for your
|
||||
domain objects in order to allow more uniform access, for example
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Welcome to Doctrine 2 ORM's documentation!
|
||||
Welcome to Doctrine ORM's documentation!
|
||||
==========================================
|
||||
|
||||
The Doctrine documentation is comprised of tutorials, a reference section and
|
||||
@@ -93,7 +93,7 @@ Tutorials
|
||||
Changelogs
|
||||
----------
|
||||
|
||||
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
|
||||
* `Upgrade <https://github.com/doctrine/orm/blob/HEAD/UPGRADE.md>`_
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
@@ -377,7 +377,7 @@ Here is the list of possible generation strategies:
|
||||
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. <attrref_customidgenerator>`
|
||||
|
||||
Sequence Generator
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -18,14 +18,20 @@ especially what the strategies presented here provide help with.
|
||||
|
||||
.. note::
|
||||
|
||||
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
|
||||
To avoid that you should remove the corresponding middleware.
|
||||
To remove all middlewares, you can use this line:
|
||||
Having an SQL logger enabled when processing batches can have a
|
||||
serious impact on performance and resource usage.
|
||||
To avoid that, you should use a PSR logger implementation that can be
|
||||
disabled at runtime.
|
||||
For example, with Monolog, you can use ``Logger::pushHandler()``
|
||||
to push a ``NullHandler`` to the logger instance, and then pop it
|
||||
when you need to enable logging again.
|
||||
|
||||
With DBAL 2, you can disable the SQL logger like below:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConnection()->getConfiguration()->setMiddlewares([]); // DBAL 3
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null); // DBAL 2
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null);
|
||||
|
||||
Bulk Inserts
|
||||
------------
|
||||
@@ -188,6 +194,3 @@ problems using the following approach:
|
||||
Iterating results is not possible with queries that
|
||||
fetch-join a collection-valued association. The nature of such SQL
|
||||
result sets is not suitable for incremental hydration.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -131,47 +131,47 @@ There are two ways to set up an event handler:
|
||||
* For *all events* you can create a Lifecycle Event Listener or Subscriber class and register
|
||||
it by calling ``$eventManager->addEventListener()`` or ``eventManager->addEventSubscriber()``,
|
||||
see
|
||||
:ref:`Listening and subscribing to Lifecycle Events<listening-and-subscribing-to-lifecycle-events>`
|
||||
:ref:`Listening and subscribing to Lifecycle Events <listening-and-subscribing-to-lifecycle-events>`
|
||||
* For *some events* (see table below), you can create a *Lifecycle Callback* method in the
|
||||
entity, see :ref:`Lifecycle Callbacks<lifecycle-callbacks>`.
|
||||
entity, see :ref:`Lifecycle Callbacks <lifecycle-callbacks>`.
|
||||
|
||||
.. _reference-events-lifecycle-events:
|
||||
|
||||
Events Overview
|
||||
---------------
|
||||
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| Event | Dispatched by | Lifecycle | Passed |
|
||||
| | | Callback | Argument |
|
||||
+=================================================================+=======================+===========+=====================================+
|
||||
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
|
||||
| | on *initial* persist | | |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preUpdate<reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
|
||||
| | metadata | | |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preFlush<reference-events-pre-flush>` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onFlush<reference-events-on-flush>` | ``$em->flush()`` | No | `OnFlushEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postFlush<reference-events-post-flush>` | ``$em->flush()`` | No | `PostFlushEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onClear<reference-events-on-clear>` | ``$em->clear()`` | No | `OnClearEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| Event | Dispatched by | Lifecycle | Passed |
|
||||
| | | Callback | Argument |
|
||||
+==================================================================+=======================+===========+=====================================+
|
||||
| :ref:`preRemove <reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postRemove <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`prePersist <reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
|
||||
| | on *initial* persist | | |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postPersist <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preUpdate <reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postUpdate <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postLoad <reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`loadClassMetadata <reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
|
||||
| | metadata | | |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preFlush <reference-events-pre-flush>` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onFlush <reference-events-on-flush>` | ``$em->flush()`` | No | `OnFlushEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postFlush <reference-events-post-flush>` | ``$em->flush()`` | No | `PostFlushEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onClear <reference-events-on-clear>` | ``$em->clear()`` | No | `OnClearEventArgs`_ |
|
||||
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -313,7 +313,7 @@ behaviors across different entity classes.
|
||||
|
||||
Note that they require much more detailed knowledge about the inner
|
||||
workings of the ``EntityManager`` and ``UnitOfWork`` classes. Please
|
||||
read the :ref:`Implementing Event Listeners<reference-events-implementing-listeners>` section
|
||||
read the :ref:`Implementing Event Listeners <reference-events-implementing-listeners>` section
|
||||
carefully if you are trying to write your own listener.
|
||||
|
||||
For event subscribers, there are no surprises. They declare the
|
||||
@@ -426,11 +426,11 @@ prePersist
|
||||
There are two ways for the ``prePersist`` event to be triggered:
|
||||
|
||||
- One is when you call ``EntityManager::persist()``. The
|
||||
event is also called for all :ref:`cascaded associations<transitive-persistence>`.
|
||||
event is also called for all :ref:`cascaded associations <transitive-persistence>`.
|
||||
- The other is inside the ``flush()`` method when changes to associations are computed and
|
||||
this association is marked as :ref:`cascade: persist<transitive-persistence>`. Any new entity found
|
||||
this association is marked as :ref:`cascade: persist <transitive-persistence>`. Any new entity found
|
||||
during this operation is also persisted and ``prePersist`` called
|
||||
on it. This is called :ref:`persistence by reachability<persistence-by-reachability>`.
|
||||
on it. This is called :ref:`persistence by reachability <persistence-by-reachability>`.
|
||||
|
||||
In both cases you get passed a ``PrePersistEventArgs`` instance
|
||||
which has access to the entity and the entity manager.
|
||||
@@ -454,7 +454,7 @@ preRemove
|
||||
|
||||
The ``preRemove`` event is called on every entity immediately when it is passed
|
||||
to the ``EntityManager::remove()`` method. It is cascaded for all
|
||||
associations that are marked as :ref:`cascade: remove<transitive-persistence>`
|
||||
associations that are marked as :ref:`cascade: remove <transitive-persistence>`
|
||||
|
||||
It is not called for a DQL ``DELETE`` statement.
|
||||
|
||||
@@ -502,7 +502,7 @@ entities and their associations have been computed. This means, the
|
||||
- Collections scheduled for removal
|
||||
|
||||
To make use of the ``onFlush`` event you have to be familiar with the
|
||||
internal :ref:`UnitOfWork<unit-of-work>` API, which grants you access to the previously
|
||||
internal :ref:`UnitOfWork <unit-of-work>` API, which grants you access to the previously
|
||||
mentioned sets. See this example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -101,7 +101,7 @@ The many-to-many association is only supporting foreign keys in the table defini
|
||||
To work with many-to-many tables containing extra columns you have to use the
|
||||
foreign keys as primary keys feature of Doctrine ORM.
|
||||
|
||||
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
|
||||
See :doc:`the tutorial on composite primary keys for more information <../tutorials/composite-primary-keys>`.
|
||||
|
||||
|
||||
How can i paginate fetch-joined collections?
|
||||
|
||||
@@ -342,7 +342,7 @@ It is not supported to use overrides in entity inheritance scenarios.
|
||||
.. note::
|
||||
|
||||
When using traits, make sure not to miss the warnings given in the
|
||||
:doc:`Limitations and Known Issues</reference/limitations-and-known-issues>` chapter.
|
||||
:doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
|
||||
|
||||
|
||||
Association Override
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
:orphan:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
:orphan:
|
||||
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Tutorials
|
||||
@@ -31,6 +33,7 @@
|
||||
reference/inheritance-mapping
|
||||
reference/working-with-objects
|
||||
reference/working-with-associations
|
||||
reference/typedfieldmapper
|
||||
reference/events
|
||||
reference/unitofwork
|
||||
reference/unitofwork-associations
|
||||
|
||||
@@ -82,9 +82,9 @@ that directory with the following contents:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"doctrine/orm": "^2.11.0",
|
||||
"doctrine/dbal": "^3.2",
|
||||
"symfony/cache": "^5.4"
|
||||
"doctrine/orm": "^3",
|
||||
"doctrine/dbal": "^4",
|
||||
"symfony/cache": "^7"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {"": "src/"}
|
||||
|
||||
@@ -125,11 +125,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/EntityRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata\\:\\:fullyQualifiedClassName\\(\\) should return class\\-string\\|null but returns string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Mapping/ClassMetadata.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always true\\.$#"
|
||||
count: 1
|
||||
|
||||
@@ -30,7 +30,7 @@ parameters:
|
||||
message: '~^Result of method Doctrine\\DBAL\\Connection::commit\(\) \(void\) is used\.$~'
|
||||
path: src/UnitOfWork.php
|
||||
-
|
||||
message: '~^Strict comparison using === between void and false will always evaluate to false\.$~'
|
||||
message: '~^Strict comparison using === between null and false will always evaluate to false\.$~'
|
||||
path: src/UnitOfWork.php
|
||||
-
|
||||
message: '~^Variable \$e on left side of \?\? always exists and is not nullable\.$~'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -170,6 +170,12 @@
|
||||
<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. -->
|
||||
|
||||
@@ -17,6 +17,7 @@ use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\TimestampCacheKey;
|
||||
use Doctrine\ORM\Cache\TimestampRegion;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
@@ -33,6 +34,8 @@ use function sha1;
|
||||
|
||||
abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
protected UnitOfWork $uow;
|
||||
protected ClassMetadataFactory $metadataFactory;
|
||||
|
||||
@@ -426,7 +429,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria): array
|
||||
{
|
||||
$orderBy = $criteria->getOrderings();
|
||||
$orderBy = self::getCriteriaOrderings($criteria);
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->persister->getSelectSQL($criteria);
|
||||
|
||||
@@ -36,8 +36,7 @@ use function method_exists;
|
||||
* The EntityManager is the central access point to ORM functionality.
|
||||
*
|
||||
* It is a facade to all different ORM subsystems such as UnitOfWork,
|
||||
* Query Language and Repository API. Instantiation is done through
|
||||
* the static create() method. The quickest way to obtain a fully
|
||||
* Query Language and Repository API. The quickest way to obtain a fully
|
||||
* configured EntityManager is:
|
||||
*
|
||||
* use Doctrine\ORM\Tools\ORMSetup;
|
||||
|
||||
50
src/Internal/CriteriaOrderings.php
Normal file
50
src/Internal/CriteriaOrderings.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
|
||||
use function array_map;
|
||||
use function enum_exists;
|
||||
use function method_exists;
|
||||
use function strtoupper;
|
||||
|
||||
trait CriteriaOrderings
|
||||
{
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*
|
||||
* @psalm-suppress DeprecatedMethod We need to call the deprecated API if the new one does not exist yet.
|
||||
*/
|
||||
private static function getCriteriaOrderings(Criteria $criteria): array
|
||||
{
|
||||
if (! method_exists(Criteria::class, 'orderings')) {
|
||||
return $criteria->getOrderings();
|
||||
}
|
||||
|
||||
return array_map(
|
||||
static fn (Order $order): string => $order->value,
|
||||
$criteria->orderings(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $orderings
|
||||
*
|
||||
* @return array<string, string>|array<string, Order>
|
||||
*/
|
||||
private static function mapToOrderEnumIfAvailable(array $orderings): array
|
||||
{
|
||||
if (! enum_exists(Order::class)) {
|
||||
return $orderings;
|
||||
}
|
||||
|
||||
return array_map(
|
||||
static fn (string $order): Order => Order::from(strtoupper($order)),
|
||||
$orderings,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ use function is_subclass_of;
|
||||
use function ltrim;
|
||||
use function method_exists;
|
||||
use function spl_object_id;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
use function strtolower;
|
||||
@@ -2108,13 +2109,12 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
* @param DiscriminatorColumnMapping|mixed[]|null $columnDef
|
||||
* @psalm-param DiscriminatorColumnMapping|array{
|
||||
* name: string|null,
|
||||
* fieldName?: string,
|
||||
* type?: string,
|
||||
* length?: int,
|
||||
* fieldName?: string|null,
|
||||
* type?: string|null,
|
||||
* length?: int|null,
|
||||
* columnDefinition?: string|null,
|
||||
* enumType?: class-string<BackedEnum>|null,
|
||||
* options?:array<string,
|
||||
* mixed>|null
|
||||
* options?: array<string, mixed>|null
|
||||
* }|null $columnDef
|
||||
*
|
||||
* @throws MappingException
|
||||
@@ -2136,13 +2136,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
|
||||
}
|
||||
|
||||
if (! isset($columnDef['fieldName'])) {
|
||||
$columnDef['fieldName'] = $columnDef['name'];
|
||||
}
|
||||
|
||||
if (! isset($columnDef['type'])) {
|
||||
$columnDef['type'] = 'string';
|
||||
}
|
||||
$columnDef['fieldName'] ??= $columnDef['name'];
|
||||
$columnDef['type'] ??= 'string';
|
||||
$columnDef['options'] ??= [];
|
||||
|
||||
if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) {
|
||||
throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
|
||||
@@ -2462,17 +2458,25 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
|
||||
public function getAssociationMappedByTargetField(string $assocName): string
|
||||
{
|
||||
$assoc = $this->associationMappings[$assocName];
|
||||
$assoc = $this->getAssociationMapping($assocName);
|
||||
|
||||
assert($assoc instanceof InverseSideMapping);
|
||||
if (! $assoc instanceof InverseSideMapping) {
|
||||
throw new LogicException(sprintf(
|
||||
<<<'EXCEPTION'
|
||||
Context: Calling %s() with "%s", which is the owning side of an association.
|
||||
Problem: The owning side of an association has no "mappedBy" field.
|
||||
Solution: Call %s::isAssociationInverseSide() to check first.
|
||||
EXCEPTION,
|
||||
__METHOD__,
|
||||
$assocName,
|
||||
self::class,
|
||||
));
|
||||
}
|
||||
|
||||
return $assoc->mappedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null null if the input value is null
|
||||
* @psalm-return class-string|null
|
||||
*/
|
||||
/** @return string|null null if the input value is null */
|
||||
public function fullyQualifiedClassName(string|null $className): string|null
|
||||
{
|
||||
if (empty($className)) {
|
||||
|
||||
@@ -642,7 +642,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
configuration.
|
||||
We currently recommend "SEQUENCE" for "%s", when using DBAL 3,
|
||||
and "IDENTITY" when using DBAL 4,
|
||||
so you should use probably use the following configuration before upgrading to DBAL 4,
|
||||
so you should probably use the following configuration before upgrading to DBAL 4,
|
||||
and remove it after deploying that upgrade:
|
||||
|
||||
$configuration->setIdentityGenerationPreferences([
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use BackedEnum;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
@@ -16,6 +17,7 @@ use ReflectionProperty;
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function enum_exists;
|
||||
use function is_a;
|
||||
|
||||
/** @psalm-type ScalarName = 'array'|'bool'|'float'|'int'|'string' */
|
||||
final class DefaultTypedFieldMapper implements TypedFieldMapper
|
||||
@@ -52,18 +54,18 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
) {
|
||||
if (! $type->isBuiltin() && enum_exists($type->getName())) {
|
||||
$mapping['enumType'] = $type->getName();
|
||||
|
||||
$reflection = new ReflectionEnum($type->getName());
|
||||
if (! $reflection->isBacked()) {
|
||||
throw MappingException::backedEnumTypeRequired(
|
||||
$field->class,
|
||||
$mapping['fieldName'],
|
||||
$mapping['enumType'],
|
||||
$type->getName(),
|
||||
);
|
||||
}
|
||||
|
||||
$type = $reflection->getBackingType();
|
||||
assert(is_a($type->getName(), BackedEnum::class, true));
|
||||
$mapping['enumType'] = $type->getName();
|
||||
$type = $reflection->getBackingType();
|
||||
|
||||
assert($type instanceof ReflectionNamedType);
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@ final class DiscriminatorColumnMapping implements ArrayAccess
|
||||
* type: string,
|
||||
* fieldName: string,
|
||||
* name: string,
|
||||
* length?: int,
|
||||
* columnDefinition?: string,
|
||||
* enumType?: class-string<BackedEnum>,
|
||||
* options?: array<string, mixed>,
|
||||
* length?: int|null,
|
||||
* columnDefinition?: string|null,
|
||||
* enumType?: class-string<BackedEnum>|null,
|
||||
* options?: array<string, mixed>|null,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): self
|
||||
@@ -58,7 +58,7 @@ final class DiscriminatorColumnMapping implements ArrayAccess
|
||||
}
|
||||
|
||||
if (property_exists($mapping, $key)) {
|
||||
$mapping->$key = $value;
|
||||
$mapping->$key = $value ?? $mapping->$key;
|
||||
} else {
|
||||
throw new Exception('Unknown property ' . $key . ' on class ' . static::class);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
@@ -20,6 +21,7 @@ use function assert;
|
||||
use function constant;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function enum_exists;
|
||||
use function explode;
|
||||
use function extension_loaded;
|
||||
use function file_get_contents;
|
||||
@@ -403,9 +405,10 @@ class XmlDriver extends FileDriver
|
||||
if (isset($oneToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
|
||||
/** @psalm-suppress DeprecatedConstant */
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
: Criteria::ASC;
|
||||
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
|
||||
}
|
||||
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
@@ -531,9 +534,10 @@ class XmlDriver extends FileDriver
|
||||
if (isset($manyToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
|
||||
/** @psalm-suppress DeprecatedConstant */
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
: Criteria::ASC;
|
||||
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
|
||||
}
|
||||
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
|
||||
@@ -9,6 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ToManyAssociationMapping;
|
||||
@@ -40,6 +41,8 @@ use function spl_object_id;
|
||||
*/
|
||||
final class PersistentCollection extends AbstractLazyCollection implements Selectable
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
/**
|
||||
* A snapshot of the collection at the moment it was fetched from the database.
|
||||
* This is used to create a diff of the collection at commit time.
|
||||
@@ -585,7 +588,9 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
|
||||
$criteria = clone $criteria;
|
||||
$criteria->where($expression);
|
||||
$criteria->orderBy($criteria->getOrderings() ?: $association->orderBy());
|
||||
$criteria->orderBy(self::mapToOrderEnumIfAvailable(
|
||||
self::getCriteriaOrderings($criteria) ?: $association->orderBy(),
|
||||
));
|
||||
|
||||
$persister = $this->getUnitOfWork()->getEntityPersister($association->targetEntity);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Expr\Comparison;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\InverseSideMapping;
|
||||
@@ -32,6 +33,8 @@ use function sprintf;
|
||||
*/
|
||||
class ManyToManyPersister extends AbstractCollectionPersister
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
public function delete(PersistentCollection $collection): void
|
||||
{
|
||||
$mapping = $this->getMapping($collection);
|
||||
@@ -732,7 +735,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
|
||||
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string
|
||||
{
|
||||
$orderings = $criteria->getOrderings();
|
||||
$orderings = self::getCriteriaOrderings($criteria);
|
||||
if ($orderings) {
|
||||
$orderBy = [];
|
||||
foreach ($orderings as $name => $direction) {
|
||||
|
||||
@@ -16,6 +16,7 @@ use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\JoinColumnMapping;
|
||||
@@ -97,6 +98,7 @@ use function trim;
|
||||
*/
|
||||
class BasicEntityPersister implements EntityPersister
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
use LockSqlHelper;
|
||||
|
||||
/** @var array<string,string> */
|
||||
@@ -842,7 +844,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria): array
|
||||
{
|
||||
$orderBy = $criteria->getOrderings();
|
||||
$orderBy = self::getCriteriaOrderings($criteria);
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
|
||||
|
||||
@@ -11,8 +11,6 @@ use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
use Doctrine\ORM\Query\TokenType;
|
||||
|
||||
use function assert;
|
||||
use function is_numeric;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
@@ -63,17 +61,10 @@ class DateAddFunction extends FunctionNode
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return numeric-string
|
||||
*
|
||||
* @throws ASTException
|
||||
*/
|
||||
/** @throws ASTException */
|
||||
private function dispatchIntervalExpression(SqlWalker $sqlWalker): string
|
||||
{
|
||||
$sql = $this->intervalExpression->dispatch($sqlWalker);
|
||||
assert(is_numeric($sql));
|
||||
|
||||
return $sql;
|
||||
return $this->intervalExpression->dispatch($sqlWalker);
|
||||
}
|
||||
|
||||
public function parse(Parser $parser): void
|
||||
|
||||
@@ -8,8 +8,6 @@ use Doctrine\ORM\Query\AST\ASTException;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
use function assert;
|
||||
use function is_numeric;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
@@ -56,16 +54,9 @@ class DateSubFunction extends DateAddFunction
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return numeric-string
|
||||
*
|
||||
* @throws ASTException
|
||||
*/
|
||||
/** @throws ASTException */
|
||||
private function dispatchIntervalExpression(SqlWalker $sqlWalker): string
|
||||
{
|
||||
$sql = $this->intervalExpression->dispatch($sqlWalker);
|
||||
assert(is_numeric($sql));
|
||||
|
||||
return $sql;
|
||||
return $this->intervalExpression->dispatch($sqlWalker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class TrimFunction extends FunctionNode
|
||||
$this->trimChar = $lexer->token->value;
|
||||
}
|
||||
|
||||
if ($this->leading || $this->trailing || $this->both || $this->trimChar) {
|
||||
if ($this->leading || $this->trailing || $this->both || ($this->trimChar !== false)) {
|
||||
$parser->match(TokenType::T_FROM);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ namespace Doctrine\ORM\Query\Expr;
|
||||
use InvalidArgumentException;
|
||||
use Stringable;
|
||||
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function get_debug_type;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
@@ -33,6 +35,10 @@ abstract class Base implements Stringable
|
||||
|
||||
public function __construct(mixed $args = [])
|
||||
{
|
||||
if (is_array($args) && array_key_exists(0, $args) && is_array($args[0])) {
|
||||
$args = $args[0];
|
||||
}
|
||||
|
||||
$this->addMultiple($args);
|
||||
}
|
||||
|
||||
|
||||
@@ -376,6 +376,10 @@ class SqlWalker
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqlTableAlias = $this->useSqlTableAliases
|
||||
? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
|
||||
: '';
|
||||
|
||||
$conn = $this->em->getConnection();
|
||||
$values = [];
|
||||
|
||||
@@ -384,14 +388,22 @@ class SqlWalker
|
||||
}
|
||||
|
||||
foreach ($class->subClasses as $subclassName) {
|
||||
$values[] = $conn->quote((string) $this->em->getClassMetadata($subclassName)->discriminatorValue);
|
||||
$subclassMetadata = $this->em->getClassMetadata($subclassName);
|
||||
|
||||
// Abstract entity classes show up in the list of subClasses, but may be omitted
|
||||
// from the discriminator map. In that case, they have a null discriminator value.
|
||||
if ($subclassMetadata->discriminatorValue === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$values[] = $conn->quote((string) $subclassMetadata->discriminatorValue);
|
||||
}
|
||||
|
||||
$sqlTableAlias = $this->useSqlTableAliases
|
||||
? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
|
||||
: '';
|
||||
|
||||
$sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()->name . ' IN (' . implode(', ', $values) . ')';
|
||||
if ($values !== []) {
|
||||
$sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()->name . ' IN (' . implode(', ', $values) . ')';
|
||||
} else {
|
||||
$sqlParts[] = '1=0'; // impossible condition
|
||||
}
|
||||
}
|
||||
|
||||
$sql = implode(' AND ', $sqlParts);
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\ORM\Internal\CriteriaOrderings;
|
||||
use Doctrine\ORM\Internal\QueryType;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
@@ -38,6 +41,8 @@ use function substr;
|
||||
*/
|
||||
class QueryBuilder implements Stringable
|
||||
{
|
||||
use CriteriaOrderings;
|
||||
|
||||
/**
|
||||
* The array of DQL parts collected.
|
||||
*
|
||||
@@ -428,12 +433,12 @@ class QueryBuilder implements Stringable
|
||||
* ->setParameter('user_id', 1);
|
||||
* </code>
|
||||
*
|
||||
* @param string|int $key The parameter position or name.
|
||||
* @param string|int|null $type ParameterType::* or \Doctrine\DBAL\Types\Type::* constant
|
||||
* @param string|int $key The parameter position or name.
|
||||
* @param ParameterType|ArrayParameterType|string|int|null $type ParameterType::*, ArrayParameterType::* or \Doctrine\DBAL\Types\Type::* constant
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParameter(string|int $key, mixed $value, string|int|null $type = null): static
|
||||
public function setParameter(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|int|null $type = null): static
|
||||
{
|
||||
$existingParameter = $this->getParameter($key);
|
||||
|
||||
@@ -1162,22 +1167,20 @@ class QueryBuilder implements Stringable
|
||||
}
|
||||
}
|
||||
|
||||
if ($criteria->getOrderings()) {
|
||||
foreach ($criteria->getOrderings() as $sort => $order) {
|
||||
$hasValidAlias = false;
|
||||
foreach ($allAliases as $alias) {
|
||||
if (str_starts_with($sort . '.', $alias . '.')) {
|
||||
$hasValidAlias = true;
|
||||
break;
|
||||
}
|
||||
foreach (self::getCriteriaOrderings($criteria) as $sort => $order) {
|
||||
$hasValidAlias = false;
|
||||
foreach ($allAliases as $alias) {
|
||||
if (str_starts_with($sort . '.', $alias . '.')) {
|
||||
$hasValidAlias = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $hasValidAlias) {
|
||||
$sort = $allAliases[0] . '.' . $sort;
|
||||
}
|
||||
|
||||
$this->addOrderBy($sort, $order);
|
||||
}
|
||||
|
||||
if (! $hasValidAlias) {
|
||||
$sort = $allAliases[0] . '.' . $sort;
|
||||
}
|
||||
|
||||
$this->addOrderBy($sort, $order);
|
||||
}
|
||||
|
||||
// Overwrite limits only if they was set in criteria
|
||||
|
||||
@@ -337,7 +337,7 @@ class SchemaTool
|
||||
|
||||
if (isset($class->table['uniqueConstraints'])) {
|
||||
foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) {
|
||||
$uniqIndex = new Index($indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []);
|
||||
$uniqIndex = new Index('tmp__' . $indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []);
|
||||
|
||||
foreach ($table->getIndexes() as $tableIndexName => $tableIndex) {
|
||||
if ($tableIndex->isFulfilledBy($uniqIndex)) {
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
@@ -26,8 +25,6 @@ use Psr\Cache\CacheItemPoolInterface;
|
||||
*/
|
||||
class ConfigurationTest extends TestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
private Configuration $configuration;
|
||||
|
||||
protected function setUp(): void
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Exception\EntityManagerClosed;
|
||||
@@ -27,8 +26,6 @@ use TypeError;
|
||||
|
||||
class EntityManagerTest extends OrmTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
private EntityManagerMock $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
@@ -16,6 +17,7 @@ use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
|
||||
/**
|
||||
* Basic many-to-many association tests.
|
||||
@@ -435,7 +437,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$user = $this->_em->find($user::class, $user->id);
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->orderBy(['name' => Criteria::ASC]);
|
||||
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
|
||||
|
||||
self::assertEquals(
|
||||
['A', 'B', 'C', 'Developers_0'],
|
||||
@@ -475,7 +477,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$user = $this->_em->find($user::class, $user->id);
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->orderBy(['name' => Criteria::ASC]);
|
||||
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
|
||||
|
||||
self::assertEquals(
|
||||
['A', 'B', 'C'],
|
||||
|
||||
@@ -8,7 +8,6 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type as DBALType;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Internal\Hydration\HydrationException;
|
||||
use Doctrine\ORM\Internal\SQLResultCasing;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
@@ -33,7 +32,6 @@ use PHPUnit\Framework\Attributes\Group;
|
||||
class NativeQueryTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use SQLResultCasing;
|
||||
use VerifyDeprecations;
|
||||
|
||||
private AbstractPlatform|null $platform = null;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform;
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\Tests\Models\Company\CompanyEmployee;
|
||||
use Doctrine\Tests\Models\Company\CompanyManager;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
@@ -487,4 +488,34 @@ SQL;
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
#[Group('GH-11240')]
|
||||
public function testDateAddWithColumnInterval(): void
|
||||
{
|
||||
$query = sprintf(
|
||||
'SELECT DATE_ADD(CURRENT_TIMESTAMP(), m.salary, \'day\') AS add FROM %s m',
|
||||
CompanyEmployee::class,
|
||||
);
|
||||
|
||||
$result = $this->_em->createQuery($query)
|
||||
->setMaxResults(1)
|
||||
->getSingleResult(AbstractQuery::HYDRATE_ARRAY);
|
||||
|
||||
self::assertArrayHasKey('add', $result);
|
||||
}
|
||||
|
||||
#[Group('GH-11240')]
|
||||
public function testDateSubWithColumnInterval(): void
|
||||
{
|
||||
$query = sprintf(
|
||||
'SELECT DATE_SUB(CURRENT_TIMESTAMP(), m.salary, \'day\') AS add FROM %s m',
|
||||
CompanyEmployee::class,
|
||||
);
|
||||
|
||||
$result = $this->_em->createQuery($query)
|
||||
->setMaxResults(1)
|
||||
->getSingleResult(AbstractQuery::HYDRATE_ARRAY);
|
||||
|
||||
self::assertArrayHasKey('add', $result);
|
||||
}
|
||||
}
|
||||
|
||||
124
tests/Tests/ORM/Functional/QueryParameterTest.php
Normal file
124
tests/Tests/ORM/Functional/QueryParameterTest.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
#[Group('GH-11278')]
|
||||
final class QueryParameterTest extends OrmFunctionalTestCase
|
||||
{
|
||||
private int $userId;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->useModelSet('cms');
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$user = new CmsUser();
|
||||
$user->name = 'John Doe';
|
||||
$user->username = 'john';
|
||||
$user2 = new CmsUser();
|
||||
$user2->name = 'Jane Doe';
|
||||
$user2->username = 'jane';
|
||||
$user3 = new CmsUser();
|
||||
$user3->name = 'Just Bill';
|
||||
$user3->username = 'bill';
|
||||
|
||||
$this->_em->persist($user);
|
||||
$this->_em->persist($user2);
|
||||
$this->_em->persist($user3);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->userId = $user->id;
|
||||
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
public function testParameterTypeInBuilder(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.id = :id')
|
||||
->setParameter('id', $this->userId, ParameterType::INTEGER)
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testParameterTypeInQuery(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.id = :id')
|
||||
->getQuery()
|
||||
->setParameter('id', $this->userId, ParameterType::INTEGER)
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testDbalTypeStringInBuilder(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.id = :id')
|
||||
->setParameter('id', $this->userId, Types::INTEGER)
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testDbalTypeStringInQuery(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.id = :id')
|
||||
->getQuery()
|
||||
->setParameter('id', $this->userId, Types::INTEGER)
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testArrayParameterTypeInBuilder(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.username IN (:usernames)')
|
||||
->orderBy('u.username')
|
||||
->setParameter('usernames', ['john', 'jane'], ArrayParameterType::STRING)
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'Jane Doe'], ['name' => 'John Doe']], $result);
|
||||
}
|
||||
|
||||
public function testArrayParameterTypeInQuery(): void
|
||||
{
|
||||
$result = $this->_em->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select('u.name')
|
||||
->where('u.username IN (:usernames)')
|
||||
->orderBy('u.username')
|
||||
->getQuery()
|
||||
->setParameter('usernames', ['john', 'jane'], ArrayParameterType::STRING)
|
||||
->getArrayResult();
|
||||
|
||||
self::assertSame([['name' => 'Jane Doe'], ['name' => 'John Doe']], $result);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\Models\DDC117\DDC117ApproveChanges;
|
||||
use Doctrine\Tests\Models\DDC117\DDC117Article;
|
||||
@@ -23,8 +22,6 @@ use function count;
|
||||
#[Group('DDC-117')]
|
||||
class DDC117Test extends OrmFunctionalTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
private DDC117Article|null $article1;
|
||||
|
||||
private DDC117Article|null $article2;
|
||||
|
||||
81
tests/Tests/ORM/Functional/Ticket/GH11199Test.php
Normal file
81
tests/Tests/ORM/Functional/Ticket/GH11199Test.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Generator;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
class GH11199Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
GH11199Root::class,
|
||||
GH11199Parent::class,
|
||||
GH11199Foo::class,
|
||||
GH11199Baz::class,
|
||||
GH11199AbstractLeaf::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function dqlStatements(): Generator
|
||||
{
|
||||
yield ['SELECT e FROM ' . GH11199Root::class . ' e', "/WHERE g0_.asset_type IN \('root', 'foo', 'baz'\)$/"];
|
||||
yield ['SELECT e FROM ' . GH11199Parent::class . ' e', "/WHERE g0_.asset_type IN \('foo'\)$/"];
|
||||
yield ['SELECT e FROM ' . GH11199Foo::class . ' e', "/WHERE g0_.asset_type IN \('foo'\)$/"];
|
||||
yield ['SELECT e FROM ' . GH11199Baz::class . ' e', "/WHERE g0_.asset_type IN \('baz'\)$/"];
|
||||
yield ['SELECT e FROM ' . GH11199AbstractLeaf::class . ' e', '/WHERE 1=0/'];
|
||||
}
|
||||
|
||||
#[DataProvider('dqlStatements')]
|
||||
public function testGH11199(string $dql, string $expectedDiscriminatorValues): void
|
||||
{
|
||||
$query = $this->_em->createQuery($dql);
|
||||
$sql = $query->getSQL();
|
||||
|
||||
self::assertMatchesRegularExpression($expectedDiscriminatorValues, $sql);
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'gh11199')]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorColumn(name: 'asset_type', type: 'string')]
|
||||
#[ORM\DiscriminatorMap([
|
||||
'root' => GH11199Root::class,
|
||||
'foo' => GH11199Foo::class,
|
||||
'baz' => GH11199Baz::class,
|
||||
])]
|
||||
class GH11199Root
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
abstract class GH11199Parent extends GH11199Root
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11199Foo extends GH11199Parent
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH11199Baz extends GH11199Root
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
abstract class GH11199AbstractLeaf extends GH11199Root
|
||||
{
|
||||
}
|
||||
@@ -6,6 +6,8 @@ namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
@@ -17,6 +19,7 @@ use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
|
||||
#[Group('GH7767')]
|
||||
class GH7767Test extends OrmFunctionalTestCase
|
||||
@@ -54,7 +57,9 @@ class GH7767Test extends OrmFunctionalTestCase
|
||||
$parent = $this->_em->find(GH7767ParentEntity::class, 1);
|
||||
assert($parent instanceof GH7767ParentEntity);
|
||||
|
||||
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['position' => 'DESC']));
|
||||
$children = $parent->getChildren()->matching(
|
||||
Criteria::create()->orderBy(['position' => class_exists(Order::class) ? Order::Descending : 'DESC']),
|
||||
);
|
||||
|
||||
self::assertEquals(300, $children[0]->position);
|
||||
self::assertEquals(200, $children[1]->position);
|
||||
@@ -70,7 +75,7 @@ class GH7767ParentEntity
|
||||
#[GeneratedValue]
|
||||
private int $id;
|
||||
|
||||
/** @psalm-var Collection<int, GH7767ChildEntity> */
|
||||
/** @psalm-var Collection<int, GH7767ChildEntity>&Selectable<int, GH7767ChildEntity> */
|
||||
#[OneToMany(targetEntity: GH7767ChildEntity::class, mappedBy: 'parent', fetch: 'EXTRA_LAZY', cascade: ['persist'])]
|
||||
#[OrderBy(['position' => 'ASC'])]
|
||||
private $children;
|
||||
@@ -80,7 +85,7 @@ class GH7767ParentEntity
|
||||
$this->children[] = new GH7767ChildEntity($this, $position);
|
||||
}
|
||||
|
||||
/** @psalm-return Collection<int, GH7767ChildEntity> */
|
||||
/** @psalm-return Collection<int, GH7767ChildEntity>&Selectable<int, GH7767ChildEntity> */
|
||||
public function getChildren(): Collection
|
||||
{
|
||||
return $this->children;
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
@@ -17,6 +19,7 @@ use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
|
||||
#[Group('GH7836')]
|
||||
class GH7836Test extends OrmFunctionalTestCase
|
||||
@@ -57,7 +60,13 @@ class GH7836Test extends OrmFunctionalTestCase
|
||||
$parent = $this->_em->find(GH7836ParentEntity::class, 1);
|
||||
assert($parent instanceof GH7836ParentEntity);
|
||||
|
||||
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['position' => 'DESC', 'name' => 'ASC']));
|
||||
$children = $parent->getChildren()->matching(
|
||||
Criteria::create()->orderBy(
|
||||
class_exists(Order::class)
|
||||
? ['position' => Order::Descending, 'name' => Order::Ascending]
|
||||
: ['position' => 'DESC', 'name' => 'ASC'],
|
||||
),
|
||||
);
|
||||
|
||||
self::assertSame(200, $children[0]->position);
|
||||
self::assertSame('baz', $children[0]->name);
|
||||
@@ -72,7 +81,13 @@ class GH7836Test extends OrmFunctionalTestCase
|
||||
$parent = $this->_em->find(GH7836ParentEntity::class, 1);
|
||||
assert($parent instanceof GH7836ParentEntity);
|
||||
|
||||
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['name' => 'ASC', 'position' => 'ASC']));
|
||||
$children = $parent->getChildren()->matching(
|
||||
Criteria::create()->orderBy(
|
||||
class_exists(Order::class)
|
||||
? ['name' => Order::Ascending, 'position' => Order::Ascending]
|
||||
: ['name' => 'ASC', 'position' => 'ASC'],
|
||||
),
|
||||
);
|
||||
|
||||
self::assertSame(100, $children[0]->position);
|
||||
self::assertSame('bar', $children[0]->name);
|
||||
@@ -91,7 +106,7 @@ class GH7836ParentEntity
|
||||
#[GeneratedValue]
|
||||
private int $id;
|
||||
|
||||
/** @var Collection<int, GH7836ChildEntity> */
|
||||
/** @var Collection<int, GH7836ChildEntity>&Selectable<int, GH7836ChildEntity> */
|
||||
#[OneToMany(targetEntity: GH7836ChildEntity::class, mappedBy: 'parent', fetch: 'EXTRA_LAZY', cascade: ['persist'])]
|
||||
#[OrderBy(['position' => 'ASC', 'name' => 'ASC'])]
|
||||
private $children;
|
||||
@@ -101,7 +116,7 @@ class GH7836ParentEntity
|
||||
$this->children[] = new GH7836ChildEntity($this, $position, $name);
|
||||
}
|
||||
|
||||
/** @psalm-return Collection<int, GH7836ChildEntity> */
|
||||
/** @psalm-return Collection<int, GH7836ChildEntity>&Selectable<int, GH7836ChildEntity> */
|
||||
public function getChildren(): Collection
|
||||
{
|
||||
return $this->children;
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Id\SequenceGenerator as IdSequenceGenerator;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -42,8 +41,6 @@ use function unserialize;
|
||||
|
||||
class BasicInheritanceMappingTest extends OrmTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
private ClassMetadataFactory $cmf;
|
||||
|
||||
protected function setUp(): void
|
||||
|
||||
@@ -9,7 +9,6 @@ use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
|
||||
@@ -55,8 +54,6 @@ use function sprintf;
|
||||
|
||||
class ClassMetadataFactoryTest extends OrmTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
public function testGetMetadataForSingleClass(): void
|
||||
{
|
||||
$platform = $this->createMock(AbstractPlatform::class);
|
||||
|
||||
@@ -47,6 +47,7 @@ use Doctrine\Tests\Models\TypedProperties\UserTypedWithCustomTypedField;
|
||||
use Doctrine\Tests\ORM\Mapping\TypedFieldMapper\CustomIntAsStringTypedFieldMapper;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use DoctrineGlobalArticle;
|
||||
use LogicException;
|
||||
use PHPUnit\Framework\Attributes\Group as TestGroup;
|
||||
use ReflectionClass;
|
||||
use stdClass;
|
||||
@@ -1054,6 +1055,21 @@ class ClassMetadataTest extends OrmTestCase
|
||||
|
||||
$metadata->addLifecycleCallback('foo', 'bar');
|
||||
}
|
||||
|
||||
public function testItThrowsOnInvalidCallToGetAssociationMappedByTargetField(): void
|
||||
{
|
||||
$metadata = new ClassMetadata(self::class);
|
||||
$metadata->mapOneToOne(['fieldName' => 'foo', 'targetEntity' => 'bar']);
|
||||
|
||||
$this->expectException(LogicException::class);
|
||||
$this->expectExceptionMessage(<<<'EXCEPTION'
|
||||
Context: Calling Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField() with "foo", which is the owning side of an association.
|
||||
Problem: The owning side of an association has no "mappedBy" field.
|
||||
Solution: Call Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide() to check first.
|
||||
EXCEPTION);
|
||||
|
||||
$metadata->getAssociationMappedByTargetField('foo');
|
||||
}
|
||||
}
|
||||
|
||||
#[MappedSuperclass]
|
||||
|
||||
@@ -162,6 +162,11 @@ class LanguageRecognitionTest extends OrmTestCase
|
||||
$this->assertValidDQL("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE TRIM(u.name) = 'someone'");
|
||||
}
|
||||
|
||||
public function testTrimFalsyString(): void
|
||||
{
|
||||
$this->assertValidDQL("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE TRIM('0' FROM u.name) = 'someone'");
|
||||
}
|
||||
|
||||
public function testArithmeticExpressionsSupportedInWherePart(): void
|
||||
{
|
||||
$this->assertValidDQL('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000');
|
||||
|
||||
@@ -15,7 +15,6 @@ use Doctrine\DBAL\Driver\Result;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
@@ -40,8 +39,6 @@ use function array_map;
|
||||
|
||||
class QueryTest extends OrmTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
/** @var EntityManagerMock */
|
||||
protected $entityManager;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
@@ -22,6 +23,7 @@ use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function array_filter;
|
||||
use function class_exists;
|
||||
|
||||
/**
|
||||
* Test case for the QueryBuilder class used to build DQL query string in a
|
||||
@@ -80,6 +82,15 @@ class QueryBuilderTest extends OrmTestCase
|
||||
$this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u');
|
||||
}
|
||||
|
||||
public function testSimpleSelectArray(): void
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
->from(CmsUser::class, 'u')
|
||||
->select(['u.id', 'u.username']);
|
||||
|
||||
$this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u');
|
||||
}
|
||||
|
||||
public function testSimpleDelete(): void
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
@@ -563,7 +574,7 @@ class QueryBuilderTest extends OrmTestCase
|
||||
->from(CmsUser::class, 'u');
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria->orderBy(['field' => Criteria::DESC]);
|
||||
$criteria->orderBy(['field' => class_exists(Order::class) ? Order::Descending : Criteria::DESC]);
|
||||
|
||||
$qb->addCriteria($criteria);
|
||||
|
||||
@@ -580,7 +591,7 @@ class QueryBuilderTest extends OrmTestCase
|
||||
->join('u.article', 'a');
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria->orderBy(['a.field' => Criteria::DESC]);
|
||||
$criteria->orderBy(['a.field' => class_exists(Order::class) ? Order::Descending : Criteria::DESC]);
|
||||
|
||||
$qb->addCriteria($criteria);
|
||||
|
||||
|
||||
@@ -370,6 +370,27 @@ class SchemaToolTest extends OrmTestCase
|
||||
self::assertTrue($schema->hasTable('first_entity'), 'Table first_entity should exist.');
|
||||
self::assertFalse($schema->hasTable('second_entity'), 'Table second_entity should not exist.');
|
||||
}
|
||||
|
||||
#[Group('GH-11314')]
|
||||
public function testLoadUniqueConstraintWithoutName(): void
|
||||
{
|
||||
$em = $this->getTestEntityManager();
|
||||
$entity = $em->getClassMetadata(GH11314Entity::class);
|
||||
|
||||
$schemaTool = new SchemaTool($em);
|
||||
$schema = $schemaTool->getSchemaFromMetadata([$entity]);
|
||||
|
||||
self::assertTrue($schema->hasTable('GH11314Entity'));
|
||||
|
||||
$tableEntity = $schema->getTable('GH11314Entity');
|
||||
|
||||
self::assertTrue($tableEntity->hasIndex('uniq_2d81a3ed5bf54558875f7fd5'));
|
||||
|
||||
$tableIndex = $tableEntity->getIndex('uniq_2d81a3ed5bf54558875f7fd5');
|
||||
|
||||
self::assertTrue($tableIndex->isUnique());
|
||||
self::assertSame(['field', 'anotherField'], $tableIndex->getColumns());
|
||||
}
|
||||
}
|
||||
|
||||
#[Table(options: ['foo' => 'bar', 'baz' => ['key' => 'val']])]
|
||||
@@ -500,6 +521,21 @@ class IndexByFieldEntity
|
||||
public $fieldName;
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
#[UniqueConstraint(columns: ['field', 'anotherField'])]
|
||||
class GH11314Entity
|
||||
{
|
||||
#[Column]
|
||||
#[Id]
|
||||
private int $id;
|
||||
|
||||
#[Column(name: 'field')]
|
||||
private string $field;
|
||||
|
||||
#[Column(name: 'anotherField')]
|
||||
private string $anotherField;
|
||||
}
|
||||
|
||||
class IncorrectIndexByFieldEntity
|
||||
{
|
||||
/** @var int */
|
||||
|
||||
@@ -11,7 +11,6 @@ use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Statement;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use Doctrine\ORM\Exception\EntityIdentityCollisionException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -46,8 +45,6 @@ use function uniqid;
|
||||
*/
|
||||
class UnitOfWorkTest extends OrmTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
/**
|
||||
* SUT
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user