Compare commits

...

97 Commits

Author SHA1 Message Date
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
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
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
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
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
Alexander M. Turek
d31aabb40c Psalm 5.24.0 (#11467) 2024-05-21 14:21:50 +02:00
Alexander M. Turek
d66884403f PHPStan 1.11.1 (#11466) 2024-05-21 13:32:25 +02:00
Alexander M. Turek
552eae37a3 Test with actual lock modes (#11465) 2024-05-21 12:30:36 +02:00
Alexander M. Turek
ee4b03aa78 Backport test for Query::setLockMode() (#11463) 2024-05-21 12:30:16 +02:00
dependabot[bot]
c5291b4de8 Bump ramsey/composer-install from 2 to 3 (#11442)
Bumps [ramsey/composer-install](https://github.com/ramsey/composer-install) from 2 to 3.
- [Release notes](https://github.com/ramsey/composer-install/releases)
- [Commits](https://github.com/ramsey/composer-install/compare/v2...v3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-05 23:47:43 +02:00
Grégoire Paris
831d86548c Merge pull request #11441 from doctrine/dependabot/github_actions/2.19.x/doctrine/dot-github-5.0.1
Bump doctrine/.github from 3.0.0 to 5.0.1
2024-05-05 23:23:39 +02:00
dependabot[bot]
f26b3b9cf9 Bump doctrine/.github from 3.0.0 to 5.0.1
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 3.0.0 to 5.0.1.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/3.0.0...5.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-05 21:17:24 +00:00
Grégoire Paris
9ab84f7478 Merge pull request #11440 from greg0ire/update-codecov
Upgrade codecov/codecov-action
2024-05-05 22:56:55 +02:00
Grégoire Paris
e6bb4ef20e Upgrade codecov/codecov-action 2024-05-05 22:43:51 +02:00
Grégoire Paris
94986af284 Merge pull request #11430 from W0rma/fix-deprecation-layer-orm-exception
Fix deprecation layer of Doctrine\ORM\ORMException
2024-04-30 08:49:54 +02:00
W0rma
ad5c8e4bdc Make test compatible with PHP 7.1 2024-04-30 08:35:06 +02:00
W0rma
c363f55ad1 Fix deprecation layer 2024-04-29 14:48:36 +02:00
Grégoire Paris
c973a62272 Merge pull request #11429 from SenseException/unused-test-group
Remove unused test group
2024-04-27 11:42:05 +02:00
Grégoire Paris
8d3446015a Merge pull request #11428 from xificurk/keep-removed-entity-in-identity-map
Prevent creation of new MANAGED entity instance by reloading REMOVED entity from database
2024-04-27 11:40:56 +02:00
Claudio Zizza
4e335f4044 Remove unused test group 2024-04-27 10:46:19 +02:00
Petr Morávek
bb36d49b38 Keep entities in identity map until the scheduled deletions are executed.
If the entity gets reloaded from database before the deletions are
executed UnitOfWork needs to be able to return the original instance in
REMOVED state.
2024-04-26 21:54:02 +02:00
Grégoire Paris
2b81a8e260 Merge pull request #11426 from nasimic/patch-1
Update association-mapping.rst
2024-04-26 21:27:07 +02:00
Nasimi Mammadov
7d3b3f28e9 Update association-mapping.rst
Changed capitalized column names to lowercase for consistency. Other occurances of column names mentioned as lowercase several times at this same page.
2024-04-26 21:24:28 +02:00
Simon Podlipsky
cbec236e8b fix: always cleanup in AbstractHydrator::toIterable() (#11101)
Previously it didn't cleanup anything as long as the iteration hasn't reached the final row.

Co-authored-by: Oleg Andreyev <oleg.andreyev@lampa.lv>
2024-04-25 10:32:40 +02:00
Grégoire Paris
306963fe79 Merge pull request #11422 from tomasz-ryba/bugfix/fetch-eager-order-by
Bugfix: respect orderBy for fetch EAGER mode
2024-04-25 00:09:43 +02:00
Tomasz Ryba
fb4578406f Respect orderBy for EAGER fetch mode
EAGER fetch mode ignores orderBy as of changes introduced with #8391

Fixes #11163
Fixes #11381
2024-04-24 22:44:16 +02:00
Grégoire Paris
bdc41e2b5e Merge pull request #11420 from tyteen4a03/patch-1
fix(docs): typo
2024-04-22 15:40:39 +02:00
Timothy Choi
90376a6431 fix(docs): typo 2024-04-22 15:30:56 +02:00
Alexander M. Turek
b274893486 Fix BIGINT validation (#11414) 2024-04-15 15:11:10 +02:00
Vincent Langlet
8709fb38b0 Fix templated phpdoc return type (#11407)
* Improve getClassMetadata phpdoc

* Update baseline
2024-04-01 12:44:58 +02:00
Grégoire Paris
e9e60f2fbc Merge pull request #11403 from ThomasLandauer/patch-10
[Documentation] Merging "Query Result Formats" with "Hydration Modes"
2024-03-28 07:51:53 +01:00
Thomas Landauer
5f3c1dbab8 [Documentation] Merging "Query Result Formats" with "Hydration Modes"
Page: https://www.doctrine-project.org/projects/doctrine-orm/en/2.19/reference/dql-doctrine-query-language.html#query-result-formats

As announced in https://github.com/doctrine/orm/pull/11372#issue-2190613801, I merged the (mostly) identical sections.

* I changed the `const`s from `Query` to `AbstractQuery`
* I deleted this - mainly cause I didn't find a nice place for it:
    > In parentheses are the constants of the ``Query`` class which you can use with the
general-purpose method ``Query::execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``.
2024-03-27 13:52:50 +01:00
Grégoire Paris
6090141e0b Merge pull request #11389 from tantegerda1/2.19.x
Fix psalm errors: remove override of template type
2024-03-23 11:13:39 +01:00
Grégoire Paris
e4a6c041b5 Merge pull request #11372 from ThomasLandauer/patch-12
[Documentation] Query Result Formats
2024-03-23 10:07:42 +01:00
Ludwig Rafelsberger
c54c557e02 Fix psalm errors: remove override of template type
See https://github.com/doctrine/collections/issues/368 for the same
issue in doctrine/collections which has been fixed there.

The issue happens when using ->contains(). Running psalm emits

  > InvalidArgument - Argument 1 of Doctrine\ORM\PersistentCollection::contains
  > expects
  > TMaybeContained:fn-doctrine\common\collections\readablecollection::contains
  > as mixed, but … provided.

Solution: we should either not define @template TMaybeContained or
re-define the complete psalm docblock from ReadableCollection.

Repairing the docblock necessitates an update to the psalm baseline:
one "known issue" is no longer an issue and thus removed.
2024-03-22 11:05:00 +01:00
Thomas Landauer
46d0865339 Update dql-doctrine-query-language.rst 2024-03-21 17:55:39 +01:00
Alexander M. Turek
1a5a4c674a Set column length explicitly (#11393) 2024-03-21 12:01:42 +01:00
Alexander M. Turek
95795c87a8 Add missing import 2024-03-21 10:38:59 +01:00
Alexander M. Turek
db6e702088 Remove unused variable (#11391) 2024-03-21 10:32:55 +01:00
Thomas Landauer
5ccbc201bf [Documentation] Removing "Doctrine Mapping Types" ... (#11384)
... in favor of https://www.doctrine-project.org/projects/doctrine-dbal/en/3.8/reference/types.html#reference

Page: https://www.doctrine-project.org/projects/doctrine-orm/en/2.19/reference/basic-mapping.html#doctrine-mapping-types

As announced in https://github.com/doctrine/dbal/pull/6336#issuecomment-2003720361 , the goal is to remove this duplicated type information from ORM and replace it with a link to DBAL.

In https://github.com/doctrine/dbal/pull/6341 , I'm adding any detail which I'm deleting here to the DBAL.
2024-03-20 23:34:10 +01:00
Benjamin Eberlei
d15624f72f [GH-11185] Bugfix: do not use collection batch loading for indexBy assocations. (#11380) 2024-03-20 15:45:47 +01:00
Benjamin Eberlei
9d1a4973ae Improve lazy ghost performance by avoiding self-referencing closure. (#11376)
* Improve lazy ghost performance by avoiding self-referencing closure.

Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>

* update baselien

---------

Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
2024-03-19 09:19:25 +01:00
Grégoire Paris
40a0964f06 Merge pull request #11289 from themasch/reproduce-issue-11154-composite-key-eager-fetch-one
Do not use batch loading for collections with composite identifier
2024-03-18 20:12:56 +01:00
Grégoire Paris
08a9e60ed0 Remove outdated git metadata files (#11362)
Some of it seems related to the previous documentation build system,
some of it seems related to IntelliJ.
2024-03-17 23:06:30 +01:00
Benjamin Eberlei
3e3c023c95 Switch join columns around, otherwise index doesnt match 2024-03-17 19:50:56 +01:00
Benjamin Eberlei
5e6d5c06a9 Key on fk 2024-03-17 19:43:26 +01:00
Benjamin Eberlei
1622b7877d Fix entities and mapping. 2024-03-17 18:02:11 +01:00
Benjamin Eberlei
80aae2796d Merge pull request #11373 from kaznovac/patch-3
Minor code style fix in AbstractRemoteControl
2024-03-17 17:20:01 +01:00
Marko Kaznovac
528ef40fc4 Minor code style fix in AbstractRemoteControl 2024-03-17 15:55:54 +01:00
Thomas Landauer
4b4b9b7b6f Adding NonUniqueResultException 2024-03-17 12:25:05 +01:00
Thomas Landauer
ae842259f5 [Documentation] Query Result Formats
Page: https://www.doctrine-project.org/projects/doctrine-orm/en/2.19/reference/dql-doctrine-query-language.html#query-result-formats

Follow-up of https://github.com/doctrine/orm/pull/11359

The table I suggested is probably not working, since the text for each method is too long. And what I really wanted is to make it more *scanable*. So I tried boldfacing - if this doesn't work, I'll try something else.

Questions:

1. This section here is basically the same as https://www.doctrine-project.org/projects/doctrine-orm/en/2.10/reference/dql-doctrine-query-language.html#hydration-modes ! So I'll try to merge them (in another PR), OK? I think the list is a better format (more scanable) - since those methods all work the same, there's no need for a full-blown code sample for each, IMO.

2. `getSingleColumnResult()` is missing.
2024-03-17 12:24:10 +01:00
Benjamin Eberlei
820a0da4c1 Do not schedule batch loading for target classes with composite identifier. 2024-03-16 23:05:28 +01:00
Benjamin Eberlei
fcd02b1ee2 Cleanup tests not to use model sets. 2024-03-16 23:04:57 +01:00
Grégoire Paris
abcad6fa45 Merge pull request #11090 from dbannik/2.17.x-failed-getting-entity-with-fetch-eager
[2.17.x] Failed getting entity with fetch eager property
2024-03-16 21:23:13 +01:00
Benjamin Eberlei
1b6cf58a1a Rename tables to avoid pg related illegal table name 2024-03-16 21:08:30 +01:00
Benjamin Eberlei
6501890ab5 Static analysis enforces the extra isset() even though that just masks no sense. 2024-03-16 20:48:15 +01:00
Benjamin Eberlei
e399d21fb3 Simplify condition, improve comment on this edge case. 2024-03-16 20:41:24 +01:00
Benjamin Eberlei
16f355f0cc Remove tests for already working case as they add no value other than exploration, and we only need the regression test. 2024-03-16 20:31:09 +01:00
Grégoire Paris
94d45a036f Merge pull request #11347 from greg0ire/remove-orphan
Remove guides-specific markup
2024-03-11 21:08:16 +01:00
Grégoire Paris
9acca2252f Remove guides-specific markup
doctrine/rst-parser does not appear to support orphan metadata yet, and
renders it verbatim on the website.

Let's move this to the CI job.
2024-03-11 20:31:22 +01:00
Alexander M. Turek
a809a71aa6 Prepare releases 2.19 and 3.1 (#11335) 2024-03-03 18:43:41 +01:00
Alexander M. Turek
bd4449c462 Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Fix annotation
2024-03-03 16:49:22 +01:00
Alexander M. Turek
12e0cefba1 Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Bump CI workflows (#11336)
  Fix SchemaTool::getSchemaFromMetadata() uniqueConstraint without a predefined name (#11314)
2024-03-03 16:46:50 +01:00
Benjamin Eberlei
e62571c8f4 Refator array_map into simple loop for performance. (#11332) 2024-03-02 23:11:11 +01:00
Alexander M. Turek
98f9de2af6 Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Psalm 5.22.2 (#11326)
2024-03-01 10:48:12 +01:00
Alexander M. Turek
83c81f6c41 Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  PHPStan 1.10.59 (#11320)
2024-02-29 16:48:49 +01:00
Grégoire Paris
791667a9e4 Merge pull request #11317 from doctrine/2.18.x
Merge 2.18.x up into 2.19.x
2024-02-28 23:05:01 +01:00
Grégoire Paris
abd9186d00 Merge pull request #11309 from greg0ire/deprecate-invalid-call
Deprecate invalid method call
2024-02-26 08:45:50 +01:00
Grégoire Paris
08d3f72755 Deprecate invalid method call
`getAssociationMappedByTargetField()` returns `null` when called with
the owning side of an association.
This is undocumented and wrong because the phpdoc advertises a string as
a return type.

Instead, callers should ensure they are calling that method with an
inverse side.

Closes #11250
2024-02-25 22:09:47 +01:00
Grégoire Paris
779781173a Merge pull request #11301 from doctrine/2.18.x
Merge 2.18.x up into 2.19.x
2024-02-24 20:51:45 +01:00
Alexander M. Turek
c9c493b2fe Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Backport QueryParameterTest (#11288)
2024-02-22 13:23:21 +01:00
Mark Schmale
8d4718f875 provides a test case for github issue 11154
After 2.17 (some?) EAGER fetched OneToMany associations stopped working, if they have multiple join columns. Loads for these associations will trigger a `MessingPositionalParameter` exception "Positional parameter at index 1 does not have a bound value".

This test case should reproduce this issue, so it can be fixed.
2024-02-22 10:58:50 +01:00
Alexander M. Turek
c0dfba2ef3 Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Fix Static Analysis folder reference (#11281)
  docs: recommend safer way to disable logging (#11269)
  Remove unused baseline entries
  Treat '0' as a legitimate trim char
2024-02-21 18:52:54 +01:00
Grégoire Paris
b59189ab48 Merge pull request #11267 from doctrine/2.18.x
Merge 2.18.x up into 2.19.x
2024-02-17 20:00:05 +01:00
Dmitry Bannik
e5e3166747 #11090 - Fix obtaining an identifier in cases where the hydration has not yet fully completed on eagerLoadCollections 2024-02-16 12:57:23 +03:00
Grégoire Paris
afbf293c94 Merge pull request #11255 from doctrine/2.18.x
Merge 2.18.x up into 2.19.x
2024-02-13 12:07:19 +01:00
Alexander M. Turek
7baef1e120 Remove references to deprecated constants from Lexer (#11234) 2024-02-07 15:39:20 +01:00
Karoly Gossler
5049b615c5 Add TokenType class (#11228)
* Add TokenType class
Co-authored-by: Alexander M. Turek <me@derrabus.de>

* Deprecated Lexer constants in favour of TokenType

* Replace all Lexer::T_ occurrences with TokenType::T_

* Add upgrade note

* Fixed import Lexer => TokenType

* Fixed deprecation phpdoc

* Replaced int value with matching constant of TokenType

* Update src/Query/Lexer.php

---------

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2024-02-07 13:31:08 +01:00
103 changed files with 2629 additions and 1159 deletions

View File

@@ -5,23 +5,47 @@
"slug": "orm",
"docsSlug": "doctrine-orm",
"versions": [
{
"name": "4.0",
"branchName": "4.0.x",
"slug": "latest",
"upcoming": true
},
{
"name": "3.2",
"branchName": "3.2.x",
"slug": "3.2",
"upcoming": true
},
{
"name": "3.1",
"branchName": "3.1.x",
"slug": "3.1",
"current": true
},
{
"name": "3.0",
"branchName": "3.0.x",
"slug": "latest",
"slug": "3.0",
"maintained": false
},
{
"name": "2.20",
"branchName": "2.20.x",
"slug": "2.20",
"upcoming": true
},
{
"name": "2.19",
"branchName": "2.19.x",
"slug": "2.19",
"upcoming": true
"maintained": true
},
{
"name": "2.18",
"branchName": "2.18.x",
"slug": "2.18",
"current": true
"maintained": false
},
{
"name": "2.17",

View File

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

View File

@@ -80,7 +80,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
@@ -162,7 +162,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
@@ -190,7 +190,7 @@ jobs:
- "default"
- "3@dev"
mariadb-version:
- "10.9"
- "11.4"
extension:
- "mysqli"
- "pdo_mysql"
@@ -204,11 +204,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"
@@ -232,7 +232,7 @@ jobs:
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
@@ -302,7 +302,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
@@ -348,7 +348,7 @@ jobs:
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "${{ matrix.deps }}"
@@ -377,6 +377,8 @@ jobs:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v3"
uses: "codecov/codecov-action@v4"
with:
directory: reports
env:
CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"

View File

@@ -36,9 +36,14 @@ jobs:
run: "composer require --dev phpdocumentor/guides-cli --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
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 )"

View File

@@ -47,15 +47,8 @@ jobs:
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v3"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
- name: "Install dependencies with composer"
run: "composer update --no-interaction --no-progress"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
- name: "Run PHPBench"
run: "vendor/bin/phpbench run --report=default"

View File

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

View File

@@ -58,7 +58,7 @@ jobs:
run: "composer require doctrine/persistence ^$([ ${{ matrix.persistence-version }} = default ] && echo '3.1' || echo ${{ matrix.persistence-version }}) --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "highest"
@@ -95,7 +95,7 @@ jobs:
run: "composer require doctrine/persistence ^3.1 --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "highest"

View File

@@ -1,7 +1,7 @@
| [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] |
| [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] |
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
| [![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] |
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
@@ -22,19 +22,19 @@ 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.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
[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.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
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
[2.19 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.19.x
[2.19]: https://github.com/doctrine/orm/tree/2.19.x
[2.19 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.19.x/graph/badge.svg
[2.19 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.19.x
[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

View File

@@ -1,3 +1,20 @@
# Upgrade to 2.19
## Deprecate calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association
Calling
`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with
the owning side of an association returns `null`, which is 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.
## Deprecate `Doctrine\ORM\Query\Lexer::T_*` constants
Use `Doctrine\ORM\Query\TokenType::T_*` instead.
# Upgrade to 2.17
## Deprecate annotations classes for named queries

View File

@@ -42,14 +42,14 @@
"doctrine/annotations": "^1.13 || ^2",
"doctrine/coding-standard": "^9.0.2 || ^12.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.10.59",
"phpstan/phpstan": "~1.4.10 || 1.11.1",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
"vimeo/psalm": "4.30.0 || 5.22.2"
"vimeo/psalm": "4.30.0 || 5.24.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 3.0"

4
docs/.gitignore vendored
View File

@@ -1,4 +0,0 @@
en/_exts/configurationblock.pyc
build
en/_build
.idea

3
docs/.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "en/_theme"]
path = en/_theme
url = https://github.com/doctrine/doctrine-sphinx-theme.git

View File

@@ -99,12 +99,12 @@ discuss it step by step:
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER); // (2)
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (3)
$parser->match(TokenType::T_IDENTIFIER); // (2)
$parser->match(TokenType::T_OPEN_PARENTHESIS); // (3)
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
$parser->match(Lexer::T_COMMA); // (5)
$parser->match(TokenType::T_COMMA); // (5)
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
$parser->match(TokenType::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
@@ -183,23 +183,23 @@ I'll skip the blah and show the code for this function:
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(TokenType::T_COMMA);
$parser->match(TokenType::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(TokenType::T_IDENTIFIER);
/** @var Lexer $lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)

View File

@@ -11,7 +11,7 @@ What we offer are hooks to execute any kind of validation.
.. note::
You don't need to validate your entities in the lifecycle
events. Its only one of many options. Of course you can also
events. It is only one of many options. Of course you can also
perform validations in value setters or any other method of your
entities that are used in your code.

View File

@@ -1300,8 +1300,8 @@ This is essentially the same as the following, more verbose, mapping:
* @var Collection<int, Group>
*/
#[JoinTable(name: 'User_Group')]
#[JoinColumn(name: 'User_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'Group_id', referencedColumnName: 'id')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
@@ -1333,10 +1333,10 @@ This is essentially the same as the following, more verbose, mapping:
<many-to-many field="groups" target-entity="Group">
<join-table name="User_Group">
<join-columns>
<join-column id="User_id" referenced-column-name="id" />
<join-column id="user_id" referenced-column-name="id" />
</join-columns>
<inverse-join-columns>
<join-column id="Group_id" referenced-column-name="id" />
<join-column id="group_id" referenced-column-name="id" />
</inverse-join-columns>
</join-table>
</many-to-many>

View File

@@ -300,50 +300,12 @@ and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
Doctrine Mapping Types
----------------------
The ``type`` option used in the ``@Column`` accepts any of the existing
Doctrine types or even your own custom types. A Doctrine type defines
The ``type`` option used in the ``@Column`` accepts any of the
`existing Doctrine DBAL types <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/types.html#reference>`_
or :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`. A Doctrine type defines
the conversion between PHP and SQL types, independent from the database vendor
you are using. All Mapping Types that ship with Doctrine are fully portable
between the supported database systems.
As an example, the Doctrine Mapping Type ``string`` defines the
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
depending on the RDBMS brand). Here is a quick overview of the
built-in mapping types:
- ``string``: Type that maps a SQL VARCHAR to a PHP string.
- ``integer``: Type that maps a SQL INT to a PHP integer.
- ``smallint``: Type that maps a database SMALLINT to a PHP
integer.
- ``bigint``: Type that maps a database BIGINT to a PHP string.
- ``boolean``: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
- ``date``: Type that maps a SQL DATETIME to a PHP DateTime
object.
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object.
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object with timezone.
- ``text``: Type that maps a SQL CLOB to a PHP string.
- ``object``: Type that maps a SQL CLOB to a PHP object using
``serialize()`` and ``unserialize()``
- ``array``: Type that maps a SQL CLOB to a PHP array using
``serialize()`` and ``unserialize()``
- ``simple_array``: Type that maps a SQL CLOB to a PHP array using
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
Only use this type if you are sure that your values cannot contain a ",".
- ``json_array``: Type that maps a SQL CLOB to a PHP array using
``json_encode()`` and ``json_decode()``
- ``float``: Type that maps a SQL Float (Double Precision) to a
PHP double. *IMPORTANT*: Works only with locale settings that use
decimal points as separator.
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
varchar but uses a specific type if the platform supports it.
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
A cookbook article shows how to define :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`.
you are using.
.. note::

View File

@@ -812,7 +812,7 @@ classes have to implement the base class :
namespace MyProject\Query\AST;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\TokenType;
class MysqlFloor extends FunctionNode
{
@@ -827,12 +827,12 @@ classes have to implement the base class :
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}
@@ -1003,7 +1003,7 @@ The Query class
---------------
An instance of the ``Doctrine\ORM\Query`` class represents a DQL
query. You create a Query instance be calling
query. You create a Query instance by calling
``EntityManager#createQuery($dql)``, passing the DQL query string.
Alternatively you can create an empty ``Query`` instance and invoke
``Query#setDQL($dql)`` afterwards. Here are some examples:
@@ -1020,58 +1020,146 @@ Alternatively you can create an empty ``Query`` instance and invoke
$q = $em->createQuery();
$q->setDQL('select u from MyProject\Model\User u');
Query Result Formats
~~~~~~~~~~~~~~~~~~~~
Query Result Formats (Hydration Modes)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The format in which the result of a DQL SELECT query is returned
can be influenced by a so-called ``hydration mode``. A hydration
mode specifies a particular way in which a SQL result set is
transformed. Each hydration mode has its own dedicated method on
the Query class. Here they are:
The way in which the SQL result set of a DQL SELECT query is transformed
to PHP is determined by the so-called "hydration mode".
``getResult()``
^^^^^^^^^^^^^^^
- ``Query#getResult()``: Retrieves a collection of objects. The
result is either a plain collection of objects (pure) or an array
where the objects are nested in the result rows (mixed).
- ``Query#getSingleResult()``: Retrieves a single object. If the
result contains more than one object, an ``NonUniqueResultException``
is thrown. If the result contains no objects, an ``NoResultException``
is thrown. The pure/mixed distinction does not apply.
- ``Query#getOneOrNullResult()``: Retrieve a single object. If the
result contains more than one object, a ``NonUniqueResultException``
is thrown. If no object is found null will be returned.
- ``Query#getArrayResult()``: Retrieves an array graph (a nested
array) that is largely interchangeable with the object graph
generated by ``Query#getResult()`` for read-only purposes.
Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array
where the objects are nested in the result rows (mixed):
.. note::
.. code-block:: php
An array graph can differ from the corresponding object
graph in certain scenarios due to the difference of the identity
semantics between arrays and objects.
<?php
use Doctrine\ORM\AbstractQuery;
$query = $em->createQuery('SELECT u FROM User u');
$users = $query->getResult();
// same as:
$users = $query->getResult(AbstractQuery::HYDRATE_OBJECT);
- Objects fetched in a FROM clause are returned as a Set, that means every
object is only ever included in the resulting array once. This is the case
even when using JOIN or GROUP BY in ways that return the same row for an
object multiple times. If the hydrator sees the same object multiple times,
then it makes sure it is only returned once.
- ``Query#getScalarResult()``: Retrieves a flat/rectangular result
set of scalar values that can contain duplicate data. The
pure/mixed distinction does not apply.
- ``Query#getSingleScalarResult()``: Retrieves a single scalar
value from the result returned by the dbms. If the result contains
more than a single scalar value, an exception is thrown. The
pure/mixed distinction does not apply.
- If an object is already in memory from a previous query of any kind, then
then the previous object is used, even if the database may contain more
recent data. This even happens if the previous object is still an unloaded proxy.
Instead of using these methods, you can alternatively use the
general-purpose method
``Query#execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``.
Using this method you can directly supply the hydration mode as the
second parameter via one of the Query constants. In fact, the
methods mentioned earlier are just convenient shortcuts for the
execute method. For example, the method ``Query#getResult()``
internally invokes execute, passing in ``Query::HYDRATE_OBJECT`` as
the hydration mode.
``getArrayResult()``
^^^^^^^^^^^^^^^^^^^^
The use of the methods mentioned earlier is generally preferred as
it leads to more concise code.
Retrieves an array graph (a nested array) for read-only purposes.
.. note::
An array graph can differ from the corresponding object
graph in certain scenarios due to the difference of the identity
semantics between arrays and objects.
.. code-block:: php
<?php
$users = $query->getArrayResult();
// same as:
$users = $query->getResult(AbstractQuery::HYDRATE_ARRAY);
``getScalarResult()``
^^^^^^^^^^^^^^^^^^^^^
Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The
pure/mixed distinction does not apply.
.. code-block:: php
<?php
$users = $query->getScalarResult();
// same as:
$users = $query->getResult(AbstractQuery::HYDRATE_SCALAR);
Fields from classes are prefixed by the DQL alias in the result.
A query of the kind `SELECT u.name ...` returns a key `u_name` in the result rows.
``getSingleScalarResult()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Retrieves a single scalar value from the result returned by the database. If the result contains
more than a single scalar value, a ``NonUniqueResultException`` is thrown. The pure/mixed distinction does not apply.
.. code-block:: php
<?php
$query = $em->createQuery('SELECT COUNT(u.id) FROM User u');
$numUsers = $query->getSingleScalarResult();
// same as:
$numUsers = $query->getResult(AbstractQuery::HYDRATE_SINGLE_SCALAR);
``getSingleColumnResult()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Retrieves an array from a one-dimensional array of scalar values:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT a.id FROM User u');
$ids = $query->getSingleColumnResult();
// same as:
$ids = $query->getResult(AbstractQuery::HYDRATE_SCALAR_COLUMN);
``getSingleResult()``
^^^^^^^^^^^^^^^^^^^^^
Retrieves a single object. If the result contains more than one object, a ``NonUniqueResultException``
is thrown. If the result contains no objects, a ``NoResultException`` is thrown. The pure/mixed distinction does not apply.
``getOneOrNullResult()``
^^^^^^^^^^^^^^^^^^^^^^^^
Retrieves a single object. If the result contains more than one object, a ``NonUniqueResultException``
is thrown. If no object is found, ``null`` will be returned.
Custom Hydration Modes
^^^^^^^^^^^^^^^^^^^^^^
You can easily add your own custom hydration modes by first
creating a class which extends ``AbstractHydrator``:
.. code-block:: php
<?php
namespace MyProject\Hydrators;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
class CustomHydrator extends AbstractHydrator
{
protected function _hydrateAll()
{
return $this->_stmt->fetchAllAssociative();
}
}
Next you just need to add the class to the ORM configuration:
.. code-block:: php
<?php
$em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator');
Now the hydrator is ready to be used in your queries:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM CmsUser u');
$results = $query->getResult('CustomHydrator');
Pure and Mixed Results
~~~~~~~~~~~~~~~~~~~~~~
@@ -1175,165 +1263,6 @@ will return the rows iterating the different top-level entities.
[2] => Object (User)
[3] => Object (Group)
Hydration Modes
~~~~~~~~~~~~~~~
Each of the Hydration Modes makes assumptions about how the result
is returned to user land. You should know about all the details to
make best use of the different result formats:
The constants for the different hydration modes are:
- ``Query::HYDRATE_OBJECT``
- ``Query::HYDRATE_ARRAY``
- ``Query::HYDRATE_SCALAR``
- ``Query::HYDRATE_SINGLE_SCALAR``
- ``Query::HYDRATE_SCALAR_COLUMN``
Object Hydration
^^^^^^^^^^^^^^^^
Object hydration hydrates the result set into the object graph:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM CmsUser u');
$users = $query->getResult(Query::HYDRATE_OBJECT);
Sometimes the behavior in the object hydrator can be confusing, which is
why we are listing as many of the assumptions here for reference:
- Objects fetched in a FROM clause are returned as a Set, that means every
object is only ever included in the resulting array once. This is the case
even when using JOIN or GROUP BY in ways that return the same row for an
object multiple times. If the hydrator sees the same object multiple times,
then it makes sure it is only returned once.
- If an object is already in memory from a previous query of any kind, then
then the previous object is used, even if the database may contain more
recent data. Data from the database is discarded. This even happens if the
previous object is still an unloaded proxy.
This list might be incomplete.
Array Hydration
^^^^^^^^^^^^^^^
You can run the same query with array hydration and the result set
is hydrated into an array that represents the object graph:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM CmsUser u');
$users = $query->getResult(Query::HYDRATE_ARRAY);
You can use the ``getArrayResult()`` shortcut as well:
.. code-block:: php
<?php
$users = $query->getArrayResult();
Scalar Hydration
^^^^^^^^^^^^^^^^
If you want to return a flat rectangular result set instead of an
object graph you can use scalar hydration:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM CmsUser u');
$users = $query->getResult(Query::HYDRATE_SCALAR);
echo $users[0]['u_id'];
The following assumptions are made about selected fields using
Scalar Hydration:
1. Fields from classes are prefixed by the DQL alias in the result.
A query of the kind 'SELECT u.name ..' returns a key 'u_name' in
the result rows.
Single Scalar Hydration
^^^^^^^^^^^^^^^^^^^^^^^
If you have a query which returns just a single scalar value you can use
single scalar hydration:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id');
$query->setParameter(1, 'jwage');
$numArticles = $query->getResult(Query::HYDRATE_SINGLE_SCALAR);
You can use the ``getSingleScalarResult()`` shortcut as well:
.. code-block:: php
<?php
$numArticles = $query->getSingleScalarResult();
Scalar Column Hydration
^^^^^^^^^^^^^^^^^^^^^^^
If you have a query which returns a one-dimensional array of scalar values
you can use scalar column hydration:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT a.id FROM CmsUser u');
$ids = $query->getResult(Query::HYDRATE_SCALAR_COLUMN);
You can use the ``getSingleColumnResult()`` shortcut as well:
.. code-block:: php
<?php
$ids = $query->getSingleColumnResult();
Custom Hydration Modes
^^^^^^^^^^^^^^^^^^^^^^
You can easily add your own custom hydration modes by first
creating a class which extends ``AbstractHydrator``:
.. code-block:: php
<?php
namespace MyProject\Hydrators;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
class CustomHydrator extends AbstractHydrator
{
protected function _hydrateAll()
{
return $this->_stmt->fetchAllAssociative();
}
}
Next you just need to add the class to the ORM configuration:
.. code-block:: php
<?php
$em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator');
Now the hydrator is ready to be used in your queries:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM CmsUser u');
$results = $query->getResult('CustomHydrator');
Iterating Large Result Sets
~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -1,5 +1,3 @@
: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

@@ -1,5 +1,3 @@
:orphan:
.. toc::
.. tocheader:: Tutorials

View File

@@ -188,7 +188,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

@@ -144,7 +144,7 @@ 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 annotation, YAML or XML
@@ -153,7 +153,7 @@ step:
// isDevMode: true,
// );
// $config = ORMSetup::createXMLMetadataConfiguration(
// paths: array(__DIR__."/config/xml"),
// paths: [__DIR__ . '/config/xml'],
// isDevMode: true,
//);
// $config = ORMSetup::createYAMLMetadataConfiguration(

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

@@ -135,11 +135,6 @@ parameters:
count: 2
path: src/EntityManager.php
-
message: "#^Template type T of method Doctrine\\\\ORM\\\\EntityManagerInterface\\:\\:getClassMetadata\\(\\) is not referenced in a parameter\\.$#"
count: 1
path: src/EntityManagerInterface.php
-
message: "#^Method Doctrine\\\\ORM\\\\EntityRepository\\:\\:matching\\(\\) should return Doctrine\\\\Common\\\\Collections\\\\AbstractLazyCollection\\<int, T of object\\>&Doctrine\\\\Common\\\\Collections\\\\Selectable\\<int, T of object\\> but returns Doctrine\\\\ORM\\\\LazyCriteriaCollection\\<\\(int\\|string\\), object\\>\\.$#"
count: 1

View File

@@ -74,3 +74,9 @@ parameters:
-
message: '#^Call to method injectObjectManager\(\) on an unknown class Doctrine\\Persistence\\ObjectManagerAware\.$#'
path: src/UnitOfWork.php
-
message: '#contains generic type.*but class.*is not generic#'
paths:
- src/Mapping/Driver/XmlDriver.php
- src/Mapping/Driver/YamlDriver.php

View File

@@ -64,3 +64,9 @@ parameters:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
-
message: '#contains generic type.*but class.*is not generic#'
paths:
- src/Mapping/Driver/XmlDriver.php
- src/Mapping/Driver/YamlDriver.php

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.22.2@d768d914152dbbf3486c36398802f74e80cfde48">
<files psalm-version="5.24.0@462c80e31c34e58cc4f750c656be3927e80e550e">
<file src="src/AbstractQuery.php">
<DeprecatedClass>
<code><![CDATA[IterableResult]]></code>
@@ -257,12 +257,6 @@
<code><![CDATA[transactional]]></code>
<code><![CDATA[transactional]]></code>
</DeprecatedMethod>
<InvalidReturnStatement>
<code><![CDATA[$this->wrapped->getClassMetadata($className)]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[getClassMetadata]]></code>
</InvalidReturnType>
<MissingParamType>
<code><![CDATA[$entity]]></code>
<code><![CDATA[$lockMode]]></code>
@@ -309,11 +303,9 @@
<code><![CDATA[$entity instanceof $class->name ? $entity : null]]></code>
<code><![CDATA[$persister->load($sortedId, null, null, [], $lockMode)]]></code>
<code><![CDATA[$persister->loadById($sortedId)]]></code>
<code><![CDATA[$this->metadataFactory->getMetadataFor($className)]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[?T]]></code>
<code><![CDATA[getClassMetadata]]></code>
</InvalidReturnType>
<MissingReturnType>
<code><![CDATA[wrapInTransaction]]></code>
@@ -388,6 +380,9 @@
<code><![CDATA[serialize]]></code>
<code><![CDATA[unserialize]]></code>
</MethodSignatureMustProvideReturnType>
<ParamNameMismatch>
<code><![CDATA[$serialized]]></code>
</ParamNameMismatch>
</file>
<file src="src/Id/TableGenerator.php">
<PossiblyFalseOperand>
@@ -462,9 +457,6 @@
</RedundantConditionGivenDocblockType>
</file>
<file src="src/Internal/Hydration/ObjectHydrator.php">
<InvalidArgument>
<code><![CDATA[$element]]></code>
</InvalidArgument>
<PossiblyFalseArgument>
<code><![CDATA[$index]]></code>
</PossiblyFalseArgument>
@@ -940,13 +932,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,
@@ -970,7 +957,6 @@
* options?: array
* }]]></code>
<code><![CDATA[array{usage: int|null, region?: string}]]></code>
<code><![CDATA[loadMappingFile]]></code>
</InvalidReturnType>
<MissingParamType>
<code><![CDATA[$fileExtension]]></code>
@@ -979,15 +965,6 @@
<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/Driver/YamlDriver.php">
<ArgumentTypeCoercion>
@@ -1019,38 +996,6 @@
<MoreSpecificReturnType>
<code><![CDATA[array{usage: int|null, region: string|null}]]></code>
</MoreSpecificReturnType>
<PossiblyUndefinedMethod>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
</PossiblyUndefinedMethod>
<UndefinedInterfaceMethod>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
<code><![CDATA[$element]]></code>
</UndefinedInterfaceMethod>
</file>
<file src="src/Mapping/Embedded.php">
<MissingParamType>
@@ -1492,6 +1437,7 @@
<code><![CDATA[getValue]]></code>
<code><![CDATA[setAccessible]]></code>
<code><![CDATA[setValue]]></code>
<code><![CDATA[setValue]]></code>
</PossiblyNullReference>
<TypeDoesNotContainType>
<code><![CDATA[$autoGenerate < 0]]></code>
@@ -1501,13 +1447,10 @@
<code><![CDATA[__wakeup]]></code>
</UndefinedInterfaceMethod>
<UndefinedMethod>
<code><![CDATA[self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
$initializer($object, $proxy);
<code><![CDATA[self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void {
$initializer($object, $identifier);
}, $skippedProperties)]]></code>
</UndefinedMethod>
<UndefinedVariable>
<code><![CDATA[$proxy]]></code>
</UndefinedVariable>
<UnresolvableInclude>
<code><![CDATA[require $fileName]]></code>
</UnresolvableInclude>
@@ -2108,7 +2051,7 @@
</PossiblyUndefinedVariable>
<RedundantConditionGivenDocblockType>
<code><![CDATA[$AST instanceof AST\SelectStatement]]></code>
<code><![CDATA[$token === Lexer::T_IDENTIFIER]]></code>
<code><![CDATA[$token === TokenType::T_IDENTIFIER]]></code>
</RedundantConditionGivenDocblockType>
</file>
<file src="src/Query/ParserResult.php">
@@ -2356,9 +2299,6 @@
<InvalidReturnType>
<code><![CDATA[ObjectRepository]]></code>
</InvalidReturnType>
<TypeDoesNotContainType>
<code><![CDATA[$repository instanceof EntityRepository]]></code>
</TypeDoesNotContainType>
<UnsafeInstantiation>
<code><![CDATA[new $repositoryClassName($entityManager, $metadata)]]></code>
</UnsafeInstantiation>
@@ -2529,6 +2469,11 @@
<PossiblyNullArgument>
<code><![CDATA[$variableType]]></code>
</PossiblyNullArgument>
<PossiblyUndefinedArrayOffset>
<code><![CDATA[$fieldMapping['declaredField']]]></code>
<code><![CDATA[$fieldMapping['declaredField']]]></code>
<code><![CDATA[$fieldMapping['declaredField']]]></code>
</PossiblyUndefinedArrayOffset>
<PropertyNotSetInConstructor>
<code><![CDATA[$classToExtend]]></code>
</PropertyNotSetInConstructor>

View File

@@ -340,7 +340,7 @@ interface EntityManagerInterface extends ObjectManager
* @psalm-param string|class-string<T> $className
*
* @return Mapping\ClassMetadata
* @psalm-return Mapping\ClassMetadata<T>
* @psalm-return ($className is class-string<T> ? Mapping\ClassMetadata<T> : Mapping\ClassMetadata<object>)
*
* @psalm-template T of object
*/

View File

@@ -182,29 +182,31 @@ abstract class AbstractHydrator
$this->prepare();
while (true) {
$row = $this->statement()->fetchAssociative();
try {
while (true) {
$row = $this->statement()->fetchAssociative();
if ($row === false) {
$this->cleanup();
break;
}
$result = [];
$this->hydrateRowData($row, $result);
$this->cleanupAfterRowIteration();
if (count($result) === 1) {
if (count($resultSetMapping->indexByMap) === 0) {
yield end($result);
} else {
yield from $result;
if ($row === false) {
break;
}
$result = [];
$this->hydrateRowData($row, $result);
$this->cleanupAfterRowIteration();
if (count($result) === 1) {
if (count($resultSetMapping->indexByMap) === 0) {
yield end($result);
} else {
yield from $result;
}
} else {
yield $result;
}
} else {
yield $result;
}
} finally {
$this->cleanup();
}
}

View File

@@ -367,11 +367,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

@@ -76,11 +76,11 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
}
/**
* {@inheritDoc}
*
* Do an optimized search of an element
*
* @template TMaybeContained
* @param mixed $element The element to search for.
*
* @return bool TRUE if the collection contains $element, FALSE otherwise.
*/
public function contains($element)
{

View File

@@ -3681,6 +3681,17 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function getAssociationMappedByTargetField($fieldName)
{
if (! $this->isAssociationInverseSide($fieldName)) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11309',
'Calling %s with owning side field %s is deprecated and will no longer be supported in Doctrine ORM 3.0. Call %s::isAssociationInverseSide() to check first.',
__METHOD__,
$fieldName,
self::class
);
}
return $this->associationMappings[$fieldName]['mappedBy'];
}

View File

@@ -37,6 +37,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
{
@@ -79,7 +81,6 @@ class XmlDriver extends FileDriver
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata)
{
$xmlRoot = $this->getElement($className);
assert($xmlRoot instanceof SimpleXMLElement);
if ($xmlRoot->getName() === 'entity') {
if (isset($xmlRoot['repository-class'])) {
@@ -203,6 +204,7 @@ class XmlDriver extends FileDriver
];
if (isset($discrColumn['options'])) {
assert($discrColumn['options'] instanceof SimpleXMLElement);
$columnDef['options'] = $this->parseOptions($discrColumn['options']->children());
}
@@ -214,6 +216,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

@@ -31,6 +31,8 @@ use function substr;
* The YamlDriver reads the mapping metadata from yaml schema files.
*
* @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
*
* @template-extends FileDriver<array<string, mixed>>
*/
class YamlDriver extends FileDriver
{

View File

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

View File

@@ -5,11 +5,31 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use Doctrine\Common\Cache\Cache as CacheDriver;
use Doctrine\Persistence\ObjectRepository;
use Doctrine\ORM\Cache\Exception\InvalidResultCacheDriver;
use Doctrine\ORM\Cache\Exception\MetadataCacheNotConfigured;
use Doctrine\ORM\Cache\Exception\MetadataCacheUsesNonPersistentCache;
use Doctrine\ORM\Cache\Exception\QueryCacheNotConfigured;
use Doctrine\ORM\Cache\Exception\QueryCacheUsesNonPersistentCache;
use Doctrine\ORM\Exception\EntityManagerClosed;
use Doctrine\ORM\Exception\InvalidEntityRepository;
use Doctrine\ORM\Exception\InvalidHydrationMode;
use Doctrine\ORM\Exception\MismatchedEventManager;
use Doctrine\ORM\Exception\MissingIdentifierField;
use Doctrine\ORM\Exception\MissingMappingDriverImplementation;
use Doctrine\ORM\Exception\NamedNativeQueryNotFound;
use Doctrine\ORM\Exception\NamedQueryNotFound;
use Doctrine\ORM\Exception\ProxyClassesAlwaysRegenerating;
use Doctrine\ORM\Exception\UnexpectedAssociationValue;
use Doctrine\ORM\Exception\UnknownEntityNamespace;
use Doctrine\ORM\Exception\UnrecognizedIdentifierFields;
use Doctrine\ORM\Persisters\Exception\CantUseInOperatorOnCompositeKeys;
use Doctrine\ORM\Persisters\Exception\InvalidOrientation;
use Doctrine\ORM\Persisters\Exception\UnrecognizedField;
use Doctrine\ORM\Repository\Exception\InvalidFindByCall;
use Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall;
use Doctrine\ORM\Tools\Exception\NotSupported;
use Exception;
use function get_debug_type;
use function implode;
use function sprintf;
/**
@@ -26,8 +46,7 @@ class ORMException extends Exception
*/
public static function missingMappingDriverImpl()
{
return new self("It's a requirement to specify a Metadata Driver and pass it " .
'to Doctrine\\ORM\\Configuration::setMetadataDriverImpl().');
return MissingMappingDriverImplementation::create();
}
/**
@@ -39,11 +58,11 @@ class ORMException extends Exception
*/
public static function namedQueryNotFound($queryName)
{
return new self('Could not find a named query by the name "' . $queryName . '"');
return NamedQueryNotFound::fromName($queryName);
}
/**
* @deprecated Use Doctrine\ORM\Exception\NamedQueryNotFound
* @deprecated Use Doctrine\ORM\Exception\NamedNativeQueryNotFound
*
* @param string $nativeQueryName
*
@@ -51,7 +70,7 @@ class ORMException extends Exception
*/
public static function namedNativeQueryNotFound($nativeQueryName)
{
return new self('Could not find a named native query by the name "' . $nativeQueryName . '"');
return NamedNativeQueryNotFound::fromName($nativeQueryName);
}
/**
@@ -63,7 +82,7 @@ class ORMException extends Exception
*/
public static function unrecognizedField($field)
{
return new self(sprintf('Unrecognized field: %s', $field));
return new UnrecognizedField(sprintf('Unrecognized field: %s', $field));
}
/**
@@ -78,7 +97,7 @@ class ORMException extends Exception
*/
public static function unexpectedAssociationValue($class, $association, $given, $expected)
{
return new self(sprintf('Found entity of type %s on association %s#%s, but expecting %s', $given, $class, $association, $expected));
return UnexpectedAssociationValue::create($class, $association, $given, $expected);
}
/**
@@ -91,7 +110,7 @@ class ORMException extends Exception
*/
public static function invalidOrientation($className, $field)
{
return new self('Invalid order by orientation specified for ' . $className . '#' . $field);
return InvalidOrientation::fromClassNameAndField($className, $field);
}
/**
@@ -101,7 +120,7 @@ class ORMException extends Exception
*/
public static function entityManagerClosed()
{
return new self('The EntityManager is closed.');
return EntityManagerClosed::create();
}
/**
@@ -113,7 +132,7 @@ class ORMException extends Exception
*/
public static function invalidHydrationMode($mode)
{
return new self(sprintf("'%s' is an invalid hydration mode.", $mode));
return InvalidHydrationMode::fromMode($mode);
}
/**
@@ -123,7 +142,7 @@ class ORMException extends Exception
*/
public static function mismatchedEventManager()
{
return new self('Cannot use different EventManager instances for EntityManager and Connection.');
return MismatchedEventManager::create();
}
/**
@@ -135,11 +154,11 @@ class ORMException extends Exception
*/
public static function findByRequiresParameter($methodName)
{
return new self("You need to pass a parameter to '" . $methodName . "'");
return InvalidMagicMethodCall::onMissingParameter($methodName);
}
/**
* @deprecated Doctrine\ORM\Repository\Exception\InvalidFindByCall
* @deprecated Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall::becauseFieldNotFoundIn()
*
* @param string $entityName
* @param string $fieldName
@@ -149,10 +168,7 @@ class ORMException extends Exception
*/
public static function invalidMagicCall($entityName, $fieldName, $method)
{
return new self(
"Entity '" . $entityName . "' has no field '" . $fieldName . "'. " .
"You can therefore not call '" . $method . "' on the entities' repository"
);
return InvalidMagicMethodCall::becauseFieldNotFoundIn($entityName, $fieldName, $method);
}
/**
@@ -165,10 +181,7 @@ class ORMException extends Exception
*/
public static function invalidFindByInverseAssociation($entityName, $associationFieldName)
{
return new self(
"You cannot search for the association field '" . $entityName . '#' . $associationFieldName . "', " .
'because it is the inverse side of an association. Find methods only work on owning side associations.'
);
return InvalidFindByCall::fromInverseSideUsage($entityName, $associationFieldName);
}
/**
@@ -178,7 +191,7 @@ class ORMException extends Exception
*/
public static function invalidResultCacheDriver()
{
return new self('Invalid result cache driver; it must implement Doctrine\\Common\\Cache\\Cache.');
return InvalidResultCacheDriver::create();
}
/**
@@ -188,7 +201,7 @@ class ORMException extends Exception
*/
public static function notSupported()
{
return new self('This behaviour is (currently) not supported by Doctrine 2');
return NotSupported::create();
}
/**
@@ -198,7 +211,7 @@ class ORMException extends Exception
*/
public static function queryCacheNotConfigured()
{
return new self('Query Cache is not configured.');
return QueryCacheNotConfigured::create();
}
/**
@@ -208,7 +221,7 @@ class ORMException extends Exception
*/
public static function metadataCacheNotConfigured()
{
return new self('Class Metadata Cache is not configured.');
return MetadataCacheNotConfigured::create();
}
/**
@@ -218,7 +231,7 @@ class ORMException extends Exception
*/
public static function queryCacheUsesNonPersistentCache(CacheDriver $cache)
{
return new self('Query Cache uses a non-persistent cache driver, ' . get_debug_type($cache) . '.');
return QueryCacheUsesNonPersistentCache::fromDriver($cache);
}
/**
@@ -228,7 +241,7 @@ class ORMException extends Exception
*/
public static function metadataCacheUsesNonPersistentCache(CacheDriver $cache)
{
return new self('Metadata Cache uses a non-persistent cache driver, ' . get_debug_type($cache) . '.');
return MetadataCacheUsesNonPersistentCache::fromDriver($cache);
}
/**
@@ -238,7 +251,7 @@ class ORMException extends Exception
*/
public static function proxyClassesAlwaysRegenerating()
{
return new self('Proxy Classes are always regenerating.');
return ProxyClassesAlwaysRegenerating::create();
}
/**
@@ -250,9 +263,7 @@ class ORMException extends Exception
*/
public static function unknownEntityNamespace($entityNamespaceAlias)
{
return new self(
sprintf("Unknown Entity namespace alias '%s'.", $entityNamespaceAlias)
);
return UnknownEntityNamespace::fromNamespaceAlias($entityNamespaceAlias);
}
/**
@@ -264,11 +275,7 @@ class ORMException extends Exception
*/
public static function invalidEntityRepository($className)
{
return new self(sprintf(
"Invalid repository class '%s'. It must be a %s.",
$className,
ObjectRepository::class
));
return InvalidEntityRepository::fromClassName($className);
}
/**
@@ -281,7 +288,7 @@ class ORMException extends Exception
*/
public static function missingIdentifierField($className, $fieldName)
{
return new self(sprintf('The identifier %s is missing for a query of %s', $fieldName, $className));
return MissingIdentifierField::fromFieldAndClass($fieldName, $className);
}
/**
@@ -294,10 +301,7 @@ class ORMException extends Exception
*/
public static function unrecognizedIdentifierFields($className, $fieldNames)
{
return new self(
"Unrecognized identifier fields: '" . implode("', '", $fieldNames) . "' " .
"are not present on class '" . $className . "'."
);
return UnrecognizedIdentifierFields::fromClassAndFieldNames($className, $fieldNames);
}
/**
@@ -307,6 +311,6 @@ class ORMException extends Exception
*/
public static function cantUseInOperatorOnCompositeKeys()
{
return new self("Can't use IN operator on entities that have composite keys.");
return CantUseInOperatorOnCompositeKeys::create();
}
}

View File

@@ -412,11 +412,6 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
return parent::containsKey($key);
}
/**
* {@inheritDoc}
*
* @template TMaybeContained
*/
public function contains($element): bool
{
if (! $this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {

View File

@@ -8,6 +8,8 @@ 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\PersistentCollection;
use Doctrine\ORM\Utility\PersisterHelper;
@@ -166,7 +168,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 = $collection->getMapping();
@@ -186,6 +192,13 @@ class OneToManyPersister extends AbstractCollectionPersister
$statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform)
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
if ($targetClass->isInheritanceTypeSingleTable()) {
$discriminatorColumn = $targetClass->getDiscriminatorColumn();
$statement .= ' AND ' . $discriminatorColumn['name'] . ' = ?';
$parameters[] = $targetClass->discriminatorValue;
$types[] = $discriminatorColumn['type'];
}
$numAffected = $this->conn->executeStatement($statement, $parameters, $types);
assert(is_int($numAffected));

View File

@@ -354,17 +354,16 @@ 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, InternalProxy $original) use ($entityPersister, $classMetadata, $identifierFlattener): void {
$identifier = $classMetadata->getIdentifierValues($original);
$entity = $entityPersister->loadById($identifier, $original);
return static function (InternalProxy $proxy, array $identifier) use ($entityPersister, $classMetadata, $identifierFlattener): void {
$original = $entityPersister->loadById($identifier);
if ($entity === null) {
if ($original === null) {
throw EntityNotFoundException::fromClassNameAndIdentifier(
$classMetadata->getName(),
$identifierFlattener->flattenIdentifier($classMetadata, $identifier)
@@ -378,11 +377,11 @@ EOPHP;
$class = $entityPersister->getClassMetadata();
foreach ($class->getReflectionProperties() as $property) {
if (! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
if (isset($identifier[$property->name]) || ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
continue;
}
$property->setValue($proxy, $property->getValue($entity));
$property->setValue($proxy, $property->getValue($original));
}
};
}
@@ -468,8 +467,8 @@ EOPHP;
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);
$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {
$proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
$initializer($object, $proxy);
$proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void {
$initializer($object, $identifier);
}, $skippedProperties);
foreach ($identifierFields as $idField => $reflector) {

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "ABS" "(" SimpleArithmeticExpression ")"
@@ -30,11 +30,11 @@ class AbsFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
@@ -36,13 +36,13 @@ class BitAndFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstArithmetic = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondArithmetic = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
@@ -36,13 +36,13 @@ class BitOrFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstArithmetic = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondArithmetic = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary }* ")"
@@ -42,22 +42,22 @@ class ConcatFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstStringPrimary = $parser->StringPrimary();
$this->concatExpressions[] = $this->firstStringPrimary;
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondStringPrimary = $parser->StringPrimary();
$this->concatExpressions[] = $this->secondStringPrimary;
while ($parser->getLexer()->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);
while ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) {
$parser->match(TokenType::T_COMMA);
$this->concatExpressions[] = $parser->StringPrimary();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "CURRENT_DATE"
@@ -24,8 +24,8 @@ class CurrentDateFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "CURRENT_TIME"
@@ -24,8 +24,8 @@ class CurrentTimeFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "CURRENT_TIMESTAMP"
@@ -24,8 +24,8 @@ class CurrentTimestampFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,10 +5,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function strtolower;
@@ -84,15 +84,15 @@ class DateAddFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->unit = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
@@ -34,13 +34,13 @@ class DateDiffFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->date1 = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->date2 = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,10 +5,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function assert;
use function reset;
@@ -77,20 +77,20 @@ class IdentityFunction extends FunctionNode
*/
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->pathExpression = $parser->SingleValuedAssociationPathExpression();
if ($parser->getLexer()->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_STRING);
if ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) {
$parser->match(TokenType::T_COMMA);
$parser->match(TokenType::T_STRING);
$token = $parser->getLexer()->token;
assert($token !== null);
$this->fieldMapping = $token->value;
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -8,9 +8,9 @@ use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\AST\TypedExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "LENGTH" "(" StringPrimary ")"
@@ -33,12 +33,12 @@ class LengthFunction extends FunctionNode implements TypedExpression
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getReturnType(): Type

View File

@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")"
@@ -48,22 +48,22 @@ class LocateFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstStringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondStringPrimary = $parser->StringPrimary();
$lexer = $parser->getLexer();
if ($lexer->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);
if ($lexer->isNextToken(TokenType::T_COMMA)) {
$parser->match(TokenType::T_COMMA);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function sprintf;
@@ -33,11 +33,11 @@ class LowerFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")"
@@ -34,15 +34,15 @@ class ModFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function assert;
@@ -105,11 +105,11 @@ class SizeFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->collectionPathExpression = $parser->CollectionValuedPathExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function sprintf;
@@ -33,11 +33,11 @@ class SqrtFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")"
@@ -44,22 +44,22 @@ class SubstringFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$lexer = $parser->getLexer();
if ($lexer->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);
if ($lexer->isNextToken(TokenType::T_COMMA)) {
$parser->match(TokenType::T_COMMA);
$this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\DBAL\Platforms\TrimMode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function assert;
use function strcasecmp;
@@ -62,25 +62,25 @@ class TrimFunction extends FunctionNode
{
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->parseTrimMode($parser);
if ($lexer->isNextToken(Lexer::T_STRING)) {
$parser->match(Lexer::T_STRING);
if ($lexer->isNextToken(TokenType::T_STRING)) {
$parser->match(TokenType::T_STRING);
assert($lexer->token !== null);
$this->trimChar = $lexer->token->value;
}
if ($this->leading || $this->trailing || $this->both || ($this->trimChar !== false)) {
$parser->match(Lexer::T_FROM);
$parser->match(TokenType::T_FROM);
}
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
/** @psalm-return TrimMode::* */
@@ -108,7 +108,7 @@ class TrimFunction extends FunctionNode
$value = $lexer->lookahead->value;
if (strcasecmp('leading', $value) === 0) {
$parser->match(Lexer::T_LEADING);
$parser->match(TokenType::T_LEADING);
$this->leading = true;
@@ -116,7 +116,7 @@ class TrimFunction extends FunctionNode
}
if (strcasecmp('trailing', $value) === 0) {
$parser->match(Lexer::T_TRAILING);
$parser->match(TokenType::T_TRAILING);
$this->trailing = true;
@@ -124,7 +124,7 @@ class TrimFunction extends FunctionNode
}
if (strcasecmp('both', $value) === 0) {
$parser->match(Lexer::T_BOTH);
$parser->match(TokenType::T_BOTH);
$this->both = true;

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function sprintf;
@@ -33,11 +33,11 @@ class UpperFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -21,95 +21,249 @@ use function substr;
/**
* Scans a DQL query for tokens.
*
* @extends AbstractLexer<Lexer::T_*, string>
* @extends AbstractLexer<TokenType::T_*, string>
*/
class Lexer extends AbstractLexer
{
// All tokens that are not valid identifiers must be < 100
public const T_NONE = 1;
public const T_INTEGER = 2;
public const T_STRING = 3;
public const T_INPUT_PARAMETER = 4;
public const T_FLOAT = 5;
public const T_CLOSE_PARENTHESIS = 6;
public const T_OPEN_PARENTHESIS = 7;
public const T_COMMA = 8;
public const T_DIVIDE = 9;
public const T_DOT = 10;
public const T_EQUALS = 11;
public const T_GREATER_THAN = 12;
public const T_LOWER_THAN = 13;
public const T_MINUS = 14;
public const T_MULTIPLY = 15;
public const T_NEGATE = 16;
public const T_PLUS = 17;
public const T_OPEN_CURLY_BRACE = 18;
public const T_CLOSE_CURLY_BRACE = 19;
/** @deprecated use {@see TokenType::T_NONE} */
public const T_NONE = TokenType::T_NONE;
/** @deprecated use {@see TokenType::T_INTEGER} */
public const T_INTEGER = TokenType::T_INTEGER;
/** @deprecated use {@see TokenType::T_STRING} */
public const T_STRING = TokenType::T_STRING;
/** @deprecated use {@see TokenType::T_INPUT_PARAMETER} */
public const T_INPUT_PARAMETER = TokenType::T_INPUT_PARAMETER;
/** @deprecated use {@see TokenType::T_FLOAT} */
public const T_FLOAT = TokenType::T_FLOAT;
/** @deprecated use {@see TokenType::T_CLOSE_PARENTHESIS} */
public const T_CLOSE_PARENTHESIS = TokenType::T_CLOSE_PARENTHESIS;
/** @deprecated use {@see TokenType::T_OPEN_PARENTHESIS} */
public const T_OPEN_PARENTHESIS = TokenType::T_OPEN_PARENTHESIS;
/** @deprecated use {@see TokenType::T_COMMA} */
public const T_COMMA = TokenType::T_COMMA;
/** @deprecated use {@see TokenType::T_DIVIDE} */
public const T_DIVIDE = TokenType::T_DIVIDE;
/** @deprecated use {@see TokenType::T_DOT} */
public const T_DOT = TokenType::T_DOT;
/** @deprecated use {@see TokenType::T_EQUALS} */
public const T_EQUALS = TokenType::T_EQUALS;
/** @deprecated use {@see TokenType::T_GREATER_THAN} */
public const T_GREATER_THAN = TokenType::T_GREATER_THAN;
/** @deprecated use {@see TokenType::T_LOWER_THAN} */
public const T_LOWER_THAN = TokenType::T_LOWER_THAN;
/** @deprecated use {@see TokenType::T_MINUS} */
public const T_MINUS = TokenType::T_MINUS;
/** @deprecated use {@see TokenType::T_MULTIPLY} */
public const T_MULTIPLY = TokenType::T_MULTIPLY;
/** @deprecated use {@see TokenType::T_NEGATE} */
public const T_NEGATE = TokenType::T_NEGATE;
/** @deprecated use {@see TokenType::T_PLUS} */
public const T_PLUS = TokenType::T_PLUS;
/** @deprecated use {@see TokenType::T_OPEN_CURLY_BRACE} */
public const T_OPEN_CURLY_BRACE = TokenType::T_OPEN_CURLY_BRACE;
/** @deprecated use {@see TokenType::T_CLOSE_CURLY_BRACE} */
public const T_CLOSE_CURLY_BRACE = TokenType::T_CLOSE_CURLY_BRACE;
// All tokens that are identifiers or keywords that could be considered as identifiers should be >= 100
/** @deprecated No Replacement planned. */
public const T_ALIASED_NAME = 100;
public const T_FULLY_QUALIFIED_NAME = 101;
public const T_IDENTIFIER = 102;
public const T_ALIASED_NAME = TokenType::T_ALIASED_NAME;
/** @deprecated use {@see TokenType::T_FULLY_QUALIFIED_NAME} */
public const T_FULLY_QUALIFIED_NAME = TokenType::T_FULLY_QUALIFIED_NAME;
/** @deprecated use {@see TokenType::T_IDENTIFIER} */
public const T_IDENTIFIER = TokenType::T_IDENTIFIER;
// All keyword tokens should be >= 200
public const T_ALL = 200;
public const T_AND = 201;
public const T_ANY = 202;
public const T_AS = 203;
public const T_ASC = 204;
public const T_AVG = 205;
public const T_BETWEEN = 206;
public const T_BOTH = 207;
public const T_BY = 208;
public const T_CASE = 209;
public const T_COALESCE = 210;
public const T_COUNT = 211;
public const T_DELETE = 212;
public const T_DESC = 213;
public const T_DISTINCT = 214;
public const T_ELSE = 215;
public const T_EMPTY = 216;
public const T_END = 217;
public const T_ESCAPE = 218;
public const T_EXISTS = 219;
public const T_FALSE = 220;
public const T_FROM = 221;
public const T_GROUP = 222;
public const T_HAVING = 223;
public const T_HIDDEN = 224;
public const T_IN = 225;
public const T_INDEX = 226;
public const T_INNER = 227;
public const T_INSTANCE = 228;
public const T_IS = 229;
public const T_JOIN = 230;
public const T_LEADING = 231;
public const T_LEFT = 232;
public const T_LIKE = 233;
public const T_MAX = 234;
public const T_MEMBER = 235;
public const T_MIN = 236;
public const T_NEW = 237;
public const T_NOT = 238;
public const T_NULL = 239;
public const T_NULLIF = 240;
public const T_OF = 241;
public const T_OR = 242;
public const T_ORDER = 243;
public const T_OUTER = 244;
public const T_PARTIAL = 245;
public const T_SELECT = 246;
public const T_SET = 247;
public const T_SOME = 248;
public const T_SUM = 249;
public const T_THEN = 250;
public const T_TRAILING = 251;
public const T_TRUE = 252;
public const T_UPDATE = 253;
public const T_WHEN = 254;
public const T_WHERE = 255;
public const T_WITH = 256;
/** @deprecated use {@see TokenType::T_ALL} */
public const T_ALL = TokenType::T_ALL;
/** @deprecated use {@see TokenType::T_AND} */
public const T_AND = TokenType::T_AND;
/** @deprecated use {@see TokenType::T_ANY} */
public const T_ANY = TokenType::T_ANY;
/** @deprecated use {@see TokenType::T_AS} */
public const T_AS = TokenType::T_AS;
/** @deprecated use {@see TokenType::T_ASC} */
public const T_ASC = TokenType::T_ASC;
/** @deprecated use {@see TokenType::T_AVG} */
public const T_AVG = TokenType::T_AVG;
/** @deprecated use {@see TokenType::T_BETWEEN} */
public const T_BETWEEN = TokenType::T_BETWEEN;
/** @deprecated use {@see TokenType::T_BOTH} */
public const T_BOTH = TokenType::T_BOTH;
/** @deprecated use {@see TokenType::T_BY} */
public const T_BY = TokenType::T_BY;
/** @deprecated use {@see TokenType::T_CASE} */
public const T_CASE = TokenType::T_CASE;
/** @deprecated use {@see TokenType::T_COALESCE} */
public const T_COALESCE = TokenType::T_COALESCE;
/** @deprecated use {@see TokenType::T_COUNT} */
public const T_COUNT = TokenType::T_COUNT;
/** @deprecated use {@see TokenType::T_DELETE} */
public const T_DELETE = TokenType::T_DELETE;
/** @deprecated use {@see TokenType::T_DESC} */
public const T_DESC = TokenType::T_DESC;
/** @deprecated use {@see TokenType::T_DISTINCT} */
public const T_DISTINCT = TokenType::T_DISTINCT;
/** @deprecated use {@see TokenType::T_ELSE} */
public const T_ELSE = TokenType::T_ELSE;
/** @deprecated use {@see TokenType::T_EMPTY} */
public const T_EMPTY = TokenType::T_EMPTY;
/** @deprecated use {@see TokenType::T_END} */
public const T_END = TokenType::T_END;
/** @deprecated use {@see TokenType::T_ESCAPE} */
public const T_ESCAPE = TokenType::T_ESCAPE;
/** @deprecated use {@see TokenType::T_EXISTS} */
public const T_EXISTS = TokenType::T_EXISTS;
/** @deprecated use {@see TokenType::T_FALSE} */
public const T_FALSE = TokenType::T_FALSE;
/** @deprecated use {@see TokenType::T_FROM} */
public const T_FROM = TokenType::T_FROM;
/** @deprecated use {@see TokenType::T_GROUP} */
public const T_GROUP = TokenType::T_GROUP;
/** @deprecated use {@see TokenType::T_HAVING} */
public const T_HAVING = TokenType::T_HAVING;
/** @deprecated use {@see TokenType::T_HIDDEN} */
public const T_HIDDEN = TokenType::T_HIDDEN;
/** @deprecated use {@see TokenType::T_IN} */
public const T_IN = TokenType::T_IN;
/** @deprecated use {@see TokenType::T_INDEX} */
public const T_INDEX = TokenType::T_INDEX;
/** @deprecated use {@see TokenType::T_INNER} */
public const T_INNER = TokenType::T_INNER;
/** @deprecated use {@see TokenType::T_INSTANCE} */
public const T_INSTANCE = TokenType::T_INSTANCE;
/** @deprecated use {@see TokenType::T_IS} */
public const T_IS = TokenType::T_IS;
/** @deprecated use {@see TokenType::T_JOIN} */
public const T_JOIN = TokenType::T_JOIN;
/** @deprecated use {@see TokenType::T_LEADING} */
public const T_LEADING = TokenType::T_LEADING;
/** @deprecated use {@see TokenType::T_LEFT} */
public const T_LEFT = TokenType::T_LEFT;
/** @deprecated use {@see TokenType::T_LIKE} */
public const T_LIKE = TokenType::T_LIKE;
/** @deprecated use {@see TokenType::T_MAX} */
public const T_MAX = TokenType::T_MAX;
/** @deprecated use {@see TokenType::T_MEMBER} */
public const T_MEMBER = TokenType::T_MEMBER;
/** @deprecated use {@see TokenType::T_MIN} */
public const T_MIN = TokenType::T_MIN;
/** @deprecated use {@see TokenType::T_NEW} */
public const T_NEW = TokenType::T_NEW;
/** @deprecated use {@see TokenType::T_NOT} */
public const T_NOT = TokenType::T_NOT;
/** @deprecated use {@see TokenType::T_NULL} */
public const T_NULL = TokenType::T_NULL;
/** @deprecated use {@see TokenType::T_NULLIF} */
public const T_NULLIF = TokenType::T_NULLIF;
/** @deprecated use {@see TokenType::T_OF} */
public const T_OF = TokenType::T_OF;
/** @deprecated use {@see TokenType::T_OR} */
public const T_OR = TokenType::T_OR;
/** @deprecated use {@see TokenType::T_ORDER} */
public const T_ORDER = TokenType::T_ORDER;
/** @deprecated use {@see TokenType::T_OUTER} */
public const T_OUTER = TokenType::T_OUTER;
/** @deprecated use {@see TokenType::T_PARTIAL} */
public const T_PARTIAL = TokenType::T_PARTIAL;
/** @deprecated use {@see TokenType::T_SELECT} */
public const T_SELECT = TokenType::T_SELECT;
/** @deprecated use {@see TokenType::T_SET} */
public const T_SET = TokenType::T_SET;
/** @deprecated use {@see TokenType::T_SOME} */
public const T_SOME = TokenType::T_SOME;
/** @deprecated use {@see TokenType::T_SUM} */
public const T_SUM = TokenType::T_SUM;
/** @deprecated use {@see TokenType::T_THEN} */
public const T_THEN = TokenType::T_THEN;
/** @deprecated use {@see TokenType::T_TRAILING} */
public const T_TRAILING = TokenType::T_TRAILING;
/** @deprecated use {@see TokenType::T_TRUE} */
public const T_TRUE = TokenType::T_TRUE;
/** @deprecated use {@see TokenType::T_UPDATE} */
public const T_UPDATE = TokenType::T_UPDATE;
/** @deprecated use {@see TokenType::T_WHEN} */
public const T_WHEN = TokenType::T_WHEN;
/** @deprecated use {@see TokenType::T_WHERE} */
public const T_WHERE = TokenType::T_WHERE;
/** @deprecated use {@see TokenType::T_WITH} */
public const T_WITH = TokenType::T_WITH;
/**
* Creates a new query scanner object.
@@ -150,26 +304,26 @@ class Lexer extends AbstractLexer
*/
protected function getType(&$value)
{
$type = self::T_NONE;
$type = TokenType::T_NONE;
switch (true) {
// Recognize numeric values
case is_numeric($value):
if (str_contains($value, '.') || stripos($value, 'e') !== false) {
return self::T_FLOAT;
return TokenType::T_FLOAT;
}
return self::T_INTEGER;
return TokenType::T_INTEGER;
// Recognize quoted strings
case $value[0] === "'":
$value = str_replace("''", "'", substr($value, 1, strlen($value) - 2));
return self::T_STRING;
return TokenType::T_STRING;
// Recognize identifiers, aliased or qualified names
case ctype_alpha($value[0]) || $value[0] === '_' || $value[0] === '\\':
$name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value);
$name = 'Doctrine\ORM\Query\TokenType::T_' . strtoupper($value);
if (defined($name)) {
$type = constant($name);
@@ -187,61 +341,61 @@ class Lexer extends AbstractLexer
$value
);
return self::T_ALIASED_NAME;
return TokenType::T_ALIASED_NAME;
}
if (str_contains($value, '\\')) {
return self::T_FULLY_QUALIFIED_NAME;
return TokenType::T_FULLY_QUALIFIED_NAME;
}
return self::T_IDENTIFIER;
return TokenType::T_IDENTIFIER;
// Recognize input parameters
case $value[0] === '?' || $value[0] === ':':
return self::T_INPUT_PARAMETER;
return TokenType::T_INPUT_PARAMETER;
// Recognize symbols
case $value === '.':
return self::T_DOT;
return TokenType::T_DOT;
case $value === ',':
return self::T_COMMA;
return TokenType::T_COMMA;
case $value === '(':
return self::T_OPEN_PARENTHESIS;
return TokenType::T_OPEN_PARENTHESIS;
case $value === ')':
return self::T_CLOSE_PARENTHESIS;
return TokenType::T_CLOSE_PARENTHESIS;
case $value === '=':
return self::T_EQUALS;
return TokenType::T_EQUALS;
case $value === '>':
return self::T_GREATER_THAN;
return TokenType::T_GREATER_THAN;
case $value === '<':
return self::T_LOWER_THAN;
return TokenType::T_LOWER_THAN;
case $value === '+':
return self::T_PLUS;
return TokenType::T_PLUS;
case $value === '-':
return self::T_MINUS;
return TokenType::T_MINUS;
case $value === '*':
return self::T_MULTIPLY;
return TokenType::T_MULTIPLY;
case $value === '/':
return self::T_DIVIDE;
return TokenType::T_DIVIDE;
case $value === '!':
return self::T_NEGATE;
return TokenType::T_NEGATE;
case $value === '{':
return self::T_OPEN_CURLY_BRACE;
return TokenType::T_OPEN_CURLY_BRACE;
case $value === '}':
return self::T_CLOSE_CURLY_BRACE;
return TokenType::T_CLOSE_CURLY_BRACE;
// Default
default:

File diff suppressed because it is too large Load Diff

View File

@@ -1062,7 +1062,9 @@ class SqlWalker implements TreeWalker
}
}
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']);
}

99
src/Query/TokenType.php Normal file
View File

@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Query;
final class TokenType
{
// All tokens that are not valid identifiers must be < 100
public const T_NONE = 1;
public const T_INTEGER = 2;
public const T_STRING = 3;
public const T_INPUT_PARAMETER = 4;
public const T_FLOAT = 5;
public const T_CLOSE_PARENTHESIS = 6;
public const T_OPEN_PARENTHESIS = 7;
public const T_COMMA = 8;
public const T_DIVIDE = 9;
public const T_DOT = 10;
public const T_EQUALS = 11;
public const T_GREATER_THAN = 12;
public const T_LOWER_THAN = 13;
public const T_MINUS = 14;
public const T_MULTIPLY = 15;
public const T_NEGATE = 16;
public const T_PLUS = 17;
public const T_OPEN_CURLY_BRACE = 18;
public const T_CLOSE_CURLY_BRACE = 19;
// All tokens that are identifiers or keywords that could be considered as identifiers should be >= 100
/** @deprecated No Replacement planned. */
public const T_ALIASED_NAME = 100;
public const T_FULLY_QUALIFIED_NAME = 101;
public const T_IDENTIFIER = 102;
// All keyword tokens should be >= 200
public const T_ALL = 200;
public const T_AND = 201;
public const T_ANY = 202;
public const T_AS = 203;
public const T_ASC = 204;
public const T_AVG = 205;
public const T_BETWEEN = 206;
public const T_BOTH = 207;
public const T_BY = 208;
public const T_CASE = 209;
public const T_COALESCE = 210;
public const T_COUNT = 211;
public const T_DELETE = 212;
public const T_DESC = 213;
public const T_DISTINCT = 214;
public const T_ELSE = 215;
public const T_EMPTY = 216;
public const T_END = 217;
public const T_ESCAPE = 218;
public const T_EXISTS = 219;
public const T_FALSE = 220;
public const T_FROM = 221;
public const T_GROUP = 222;
public const T_HAVING = 223;
public const T_HIDDEN = 224;
public const T_IN = 225;
public const T_INDEX = 226;
public const T_INNER = 227;
public const T_INSTANCE = 228;
public const T_IS = 229;
public const T_JOIN = 230;
public const T_LEADING = 231;
public const T_LEFT = 232;
public const T_LIKE = 233;
public const T_MAX = 234;
public const T_MEMBER = 235;
public const T_MIN = 236;
public const T_NEW = 237;
public const T_NOT = 238;
public const T_NULL = 239;
public const T_NULLIF = 240;
public const T_OF = 241;
public const T_OR = 242;
public const T_ORDER = 243;
public const T_OUTER = 244;
public const T_PARTIAL = 245;
public const T_SELECT = 246;
public const T_SET = 247;
public const T_SOME = 248;
public const T_SUM = 249;
public const T_THEN = 250;
public const T_TRAILING = 251;
public const T_TRUE = 252;
public const T_UPDATE = 253;
public const T_WHEN = 254;
public const T_WHERE = 255;
public const T_WITH = 256;
/** @internal */
private function __construct()
{
}
}

View File

@@ -64,18 +64,18 @@ class SchemaValidator
* It maps built-in Doctrine types to PHP types
*/
private const BUILTIN_TYPES_MAP = [
AsciiStringType::class => 'string',
BigIntType::class => 'string',
BooleanType::class => 'bool',
DecimalType::class => 'string',
FloatType::class => 'float',
GuidType::class => 'string',
IntegerType::class => 'int',
JsonType::class => 'array',
SimpleArrayType::class => 'array',
SmallIntType::class => 'int',
StringType::class => 'string',
TextType::class => 'string',
AsciiStringType::class => ['string'],
BigIntType::class => ['int', 'string'],
BooleanType::class => ['bool'],
DecimalType::class => ['string'],
FloatType::class => ['float'],
GuidType::class => ['string'],
IntegerType::class => ['int'],
JsonType::class => ['array'],
SimpleArrayType::class => ['array'],
SmallIntType::class => ['int'],
StringType::class => ['string'],
TextType::class => ['string'],
];
public function __construct(EntityManagerInterface $em, bool $validatePropertyTypes = true)
@@ -390,21 +390,21 @@ class SchemaValidator
$propertyType = $propertyType->getName();
// If the property type is the same as the metadata field type, we are ok
if ($propertyType === $metadataFieldType) {
if (in_array($propertyType, $metadataFieldType, true)) {
return null;
}
if (is_a($propertyType, BackedEnum::class, true)) {
$backingType = (string) (new ReflectionEnum($propertyType))->getBackingType();
if ($metadataFieldType !== $backingType) {
if (! in_array($backingType, $metadataFieldType, true)) {
return sprintf(
"The field '%s#%s' has the property type '%s' with a backing type of '%s' that differs from the metadata field type '%s'.",
$class->name,
$fieldName,
$propertyType,
$backingType,
$metadataFieldType
implode('|', $metadataFieldType)
);
}
@@ -429,7 +429,7 @@ class SchemaValidator
) {
$backingType = (string) (new ReflectionEnum($fieldMapping['enumType']))->getBackingType();
if ($metadataFieldType === $backingType) {
if (in_array($backingType, $metadataFieldType, true)) {
return null;
}
@@ -439,7 +439,7 @@ class SchemaValidator
$fieldName,
$fieldMapping['enumType'],
$backingType,
$metadataFieldType
implode('|', $metadataFieldType)
);
}
@@ -455,7 +455,7 @@ class SchemaValidator
$class->name,
$fieldName,
$propertyType,
$metadataFieldType,
implode('|', $metadataFieldType),
$fieldMapping['type']
);
},
@@ -468,8 +468,10 @@ class SchemaValidator
/**
* The exact DBAL type must be used (no subclasses), since consumers of doctrine/orm may have their own
* customization around field types.
*
* @return list<string>|null
*/
private function findBuiltInType(Type $type): ?string
private function findBuiltInType(Type $type): ?array
{
$typeName = get_class($type);

View File

@@ -1292,6 +1292,8 @@ class UnitOfWork implements PropertyChangedListener
$eventsToDispatch = [];
foreach ($entities as $entity) {
$this->removeFromIdentityMap($entity);
$oid = spl_object_id($entity);
$class = $this->em->getClassMetadata(get_class($entity));
$persister = $this->getEntityPersister($class->name);
@@ -1667,8 +1669,6 @@ class UnitOfWork implements PropertyChangedListener
return;
}
$this->removeFromIdentityMap($entity);
unset($this->entityUpdates[$oid]);
if (! isset($this->entityDeletions[$oid])) {
@@ -1781,18 +1781,15 @@ EXCEPTION
*/
final public static function getIdHashByIdentifier(array $identifier): string
{
foreach ($identifier as $k => $value) {
if ($value instanceof BackedEnum) {
$identifier[$k] = $value->value;
}
}
return implode(
' ',
array_map(
static function ($value) {
if ($value instanceof BackedEnum) {
return $value->value;
}
return $value;
},
$identifier
)
$identifier
);
}
@@ -3056,10 +3053,7 @@ EXCEPTION
} 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;
@@ -3169,9 +3163,9 @@ EXCEPTION
if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
if (! $isIteration && $assoc['type'] === ClassMetadata::ONE_TO_MANY) {
if ($assoc['type'] === ClassMetadata::ONE_TO_MANY && ! $isIteration && ! $targetClass->isIdentifierComposite && ! isset($assoc['indexBy'])) {
$this->scheduleCollectionForBatchLoading($pColl, $class);
} elseif (($isIteration && $assoc['type'] === ClassMetadata::ONE_TO_MANY) || $assoc['type'] === ClassMetadata::MANY_TO_MANY) {
} else {
$this->loadCollection($pColl);
$pColl->takeSnapshot();
}
@@ -3227,7 +3221,13 @@ EXCEPTION
*
* @param PersistentCollection[] $collections
* @param array<string, mixed> $mapping
* @psalm-param array{targetEntity: class-string, sourceEntity: class-string, mappedBy: string, indexBy: string|null} $mapping
* @psalm-param array{
* targetEntity: class-string,
* sourceEntity: class-string,
* mappedBy: string,
* indexBy: string|null,
* orderBy: array<string, string>|null
* } $mapping
*/
private function eagerLoadCollections(array $collections, array $mapping): void
{
@@ -3244,7 +3244,7 @@ EXCEPTION
$entities[] = $collection->getOwner();
}
$found = $this->getEntityPersister($targetEntity)->loadAll([$mappedBy => $entities]);
$found = $this->getEntityPersister($targetEntity)->loadAll([$mappedBy => $entities], $mapping['orderBy'] ?? null);
$targetClass = $this->em->getClassMetadata($targetEntity);
$targetProperty = $targetClass->getReflectionProperty($mappedBy);
@@ -3252,7 +3252,19 @@ EXCEPTION
foreach ($found as $targetValue) {
$sourceEntity = $targetProperty->getValue($targetValue);
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
if ($sourceEntity === null && isset($targetClass->associationMappings[$mappedBy]['joinColumns'])) {
// case where the hydration $targetValue itself has not yet fully completed, for example
// in case a bi-directional association is being hydrated and deferring eager loading is
// not possible due to subclassing.
$data = $this->getOriginalEntityData($targetValue);
$id = [];
foreach ($targetClass->associationMappings[$mappedBy]['joinColumns'] as $joinColumn) {
$id[] = $data[$joinColumn['name']];
}
} else {
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
}
$idHash = implode(' ', $id);
if (isset($mapping['indexBy'])) {

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="abstract_fetch_eager_remote_control")
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="type", type="string")
* @ORM\DiscriminatorMap({"mobile"="MobileRemoteControl"})
*/
abstract class AbstractRemoteControl
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*
* @var int
*/
public $id;
/**
* @ORM\Column(type="string")
*
* @var string
*/
public $name;
/**
* @ORM\OneToMany(targetEntity="User", mappedBy="remoteControl", fetch="EAGER")
*
* @var Collection<User>
*/
public $users;
public function __construct(string $name)
{
$this->name = $name;
$this->users = new ArrayCollection();
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class MobileRemoteControl extends AbstractRemoteControl
{
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="abstract_fetch_eager_user")
*/
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*
* @var int
*/
public $id;
/**
* @ORM\ManyToOne(targetEntity="AbstractRemoteControl", inversedBy="users")
* @ORM\JoinColumn(nullable=false)
*
* @var AbstractRemoteControl
*/
public $remoteControl;
public function __construct(AbstractRemoteControl $control)
{
$this->remoteControl = $control;
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\BigIntegers;
use Doctrine\ORM\Mapping as ORM;
/** @ORM\Entity */
class BigIntegers
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue
*/
public ?int $id = null;
/** @ORM\Column(type="bigint") */
public int $one = 1;
/** @ORM\Column(type="bigint") */
public string $two = '2';
/** @ORM\Column(type="bigint") */
public float $three = 3.0;
}

View File

@@ -0,0 +1,33 @@
<?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
{
/**
* @var string
* @Id
* @Column(type="string")
*/
public $companyCode;
/**
* @var string
* @Id
* @Column(type="string")
*/
public $code;
/**
* @var string
* @Column(type="string")
*/
public $name;
}

View File

@@ -0,0 +1,46 @@
<?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\JoinColumns;
use Doctrine\ORM\Mapping\ManyToOne;
/** @Entity */
class InvoiceClass
{
/**
* @var string
* @Id
* @Column(type="string")
*/
public $companyCode;
/**
* @var string
* @Id
* @Column(type="string")
*/
public $invoiceNumber;
/**
* @var CustomerClass|null
* @ManyToOne(targetEntity="CustomerClass")
* @JoinColumns({
* @JoinColumn(name="companyCode", referencedColumnName="companyCode"),
* @JoinColumn(name="customerCode", referencedColumnName="code")
* })
*/
public $customer;
/**
* @var string|null
* @Column(type="string", nullable=true)
*/
public $customerCode;
}

View File

@@ -0,0 +1,52 @@
<?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",indexes={@Index(name="name_idx", columns={"name"})})
*/
class ECommerceProduct2
{
/**
* @var int|null
* @Column(type="integer")
* @Id
* @GeneratedValue
*/
private $id;
/**
* @var string
* @Column(type="string", length=50, nullable=true)
*/
private $name;
public function getId(): ?int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function __clone()
{
$this->id = null;
$this->name = 'Clone of ' . $this->name;
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="eager_composite_join_root")
*/
class RootEntity
{
/**
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
*
* @var int|null
*/
private $id = null;
/**
* @ORM\Id
* @ORM\Column(type="string", nullable=false, name="other_key")
*
* @var string
*/
private $otherKey;
/**
* @ORM\OneToMany(mappedBy="root", targetEntity=SecondLevel::class, fetch="EAGER")
*
* @var Collection<int, SecondLevel>
*/
private $secondLevel;
public function __construct(int $id, string $other)
{
$this->otherKey = $other;
$this->secondLevel = new ArrayCollection();
$this->id = $id;
}
public function getId(): ?int
{
return $this->id;
}
public function getOtherKey(): string
{
return $this->otherKey;
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="eager_composite_join_second_level", indexes={
* @ORM\Index(name="root_other_key_idx", columns={"root_other_key", "root_id"})
* })
*/
class SecondLevel
{
/**
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
*
* @var int|null
*/
private $upperId;
/**
* @ORM\Id
* @ORM\Column(type="string", nullable=false, name="other_key")
*
* @var string
*/
private $otherKey;
/**
* @ORM\ManyToOne(targetEntity=RootEntity::class, inversedBy="secondLevel")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="root_id", referencedColumnName="id"),
* @ORM\JoinColumn(name="root_other_key", referencedColumnName="other_key")
* })
*
* @var RootEntity
*/
private $root;
public function __construct(RootEntity $upper)
{
$this->upperId = $upper->getId();
$this->otherKey = $upper->getOtherKey();
$this->root = $upper;
}
public function getId(): ?int
{
return $this->id;
}
}

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Cache\Persister\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Cache\Persister\Entity\AbstractEntityPersister;
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
@@ -99,7 +100,7 @@ abstract class EntityPersisterTestCase extends OrmTestCase
->with(
self::identicalTo(['name' => 'Foo']),
self::identicalTo([0]),
self::identicalTo(1),
self::identicalTo(LockMode::OPTIMISTIC),
self::identicalTo(2),
self::identicalTo(3),
self::identicalTo([4])
@@ -109,7 +110,7 @@ abstract class EntityPersisterTestCase extends OrmTestCase
self::assertSame('SELECT * FROM foo WERE name = ?', $persister->getSelectSQL(
['name' => 'Foo'],
[0],
1,
LockMode::OPTIMISTIC,
2,
3,
[4]
@@ -228,13 +229,21 @@ abstract class EntityPersisterTestCase extends OrmTestCase
self::identicalTo($entity),
self::identicalTo([0]),
self::identicalTo([1]),
self::identicalTo(2),
self::identicalTo(LockMode::PESSIMISTIC_READ),
self::identicalTo(3),
self::identicalTo([4])
)
->willReturn($entity);
self::assertSame($entity, $persister->load(['id' => 1], $entity, [0], [1], 2, 3, [4]));
self::assertSame($entity, $persister->load(
['id' => 1],
$entity,
[0],
[1],
LockMode::PESSIMISTIC_READ,
3,
[4]
));
}
public function testInvokeLoadAll(): void
@@ -391,9 +400,9 @@ abstract class EntityPersisterTestCase extends OrmTestCase
$this->entityPersister->expects(self::once())
->method('lock')
->with(self::identicalTo($identifier), self::identicalTo(1));
->with(self::identicalTo($identifier), self::identicalTo(LockMode::OPTIMISTIC));
$persister->lock($identifier, 1);
$persister->lock($identifier, LockMode::OPTIMISTIC);
}
public function testInvokeExists(): void

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\AbstractFetchEager\AbstractRemoteControl;
use Doctrine\Tests\Models\AbstractFetchEager\MobileRemoteControl;
use Doctrine\Tests\Models\AbstractFetchEager\User;
use Doctrine\Tests\OrmFunctionalTestCase;
final class AbstractFetchEagerTest extends OrmFunctionalTestCase
{
public function testWithAbstractFetchEager(): void
{
$this->createSchemaForModels(
AbstractRemoteControl::class,
User::class
);
$control = new MobileRemoteControl('smart');
$user = new User($control);
$entityManage = $this->getEntityManager();
$entityManage->persist($control);
$entityManage->persist($user);
$entityManage->flush();
$entityManage->clear();
$user = $entityManage->find(User::class, $user->id);
self::assertNotNull($user);
self::assertEquals('smart', $user->remoteControl->name);
self::assertTrue($user->remoteControl->users->contains($user));
}
}

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

@@ -7,9 +7,9 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\OrmFunctionalTestCase;
@@ -75,10 +75,10 @@ class NoOp extends FunctionNode
public function parse(Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->field = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getSql(SqlWalker $sqlWalker): string

View File

@@ -88,6 +88,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,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\RootEntity;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\SecondLevel;
use Doctrine\Tests\OrmFunctionalTestCase;
final class EagerFetchOneToManyWithCompositeKeyTest extends OrmFunctionalTestCase
{
/** @ticket 11154 */
public function testItDoesNotThrowAnExceptionWhenTriggeringALoad(): void
{
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class]);
$a1 = new RootEntity(1, 'A');
$this->_em->persist($a1);
$this->_em->flush();
$this->_em->clear();
self::assertCount(1, $this->_em->getRepository(RootEntity::class)->findAll());
}
}

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

@@ -249,7 +249,6 @@ class QueryDqlFunctionTest extends OrmFunctionalTestCase
self::assertEquals(1600000, $result[3]['op']);
}
/** @group test */
public function testOperatorDiv(): void
{
$result = $this->_em->createQuery('SELECT m, (m.salary/0.5) AS op FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC')

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;
@@ -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,107 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
/**
* @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(type="integer")
* @ORM\GeneratedValue
*
* @var int
*/
public $id;
}
/**
* @ORM\Entity
*/
class GH10889Company
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*
* @var int
*/
public $id;
}
/**
* @ORM\Entity
*/
class GH10889Resume
{
/**
* @ORM\Id
* @ORM\OneToOne(targetEntity="GH10889Person")
*
* @var GH10889Person
*/
public $person;
/**
* @ORM\ManyToOne(targetEntity="GH10889Company")
*
* @var GH10889Company|null
*/
public $currentCompany;
public function __construct(GH10889Person $person, ?GH10889Company $currentCompany)
{
$this->person = $person;
$this->currentCompany = $currentCompany;
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table("gh11149_eager_product")
*/
class EagerProduct
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*
* @var int
*/
public $id;
/**
* @ORM\OneToMany(
* targetEntity=EagerProductTranslation::class,
* mappedBy="product",
* fetch="EAGER",
* indexBy="locale_code"
* )
*
* @var Collection<string, EagerProductTranslation>
*/
public $translations;
public function __construct(int $id)
{
$this->id = $id;
$this->translations = new ArrayCollection();
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table("gh11149_eager_product_translation")
*/
class EagerProductTranslation
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*
* @var int
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=EagerProduct::class, inversedBy="translations")
* @ORM\JoinColumn(nullable=false)
*
* @var EagerProduct
*/
public $product;
/**
* @ORM\ManyToOne(targetEntity=Locale::class)
* @ORM\JoinColumn(name="locale_code", referencedColumnName="code", nullable=false)
*
* @var Locale
*/
public $locale;
public function __construct($id, EagerProduct $product, Locale $locale)
{
$this->id = $id;
$this->product = $product;
$this->locale = $locale;
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Persistence\Proxy;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH11149Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
Locale::class,
EagerProduct::class,
EagerProductTranslation::class,
]);
}
public function testFetchEagerModeWithIndexBy(): void
{
// Load entities into database
$this->_em->persist($product = new EagerProduct(11149));
$this->_em->persist($locale = new Locale('fr_FR'));
$this->_em->persist(new EagerProductTranslation(11149, $product, $locale));
$this->_em->flush();
$this->_em->clear();
// Fetch entity from database
$product = $this->_em->find(EagerProduct::class, 11149);
// Assert associated entity is loaded eagerly
static::assertInstanceOf(EagerProduct::class, $product);
static::assertInstanceOf(PersistentCollection::class, $product->translations);
static::assertTrue($product->translations->isInitialized());
static::assertCount(1, $product->translations);
// Assert associated entity is indexed by given property
$translation = $product->translations->get('fr_FR');
static::assertInstanceOf(EagerProductTranslation::class, $translation);
static::assertNotInstanceOf(Proxy::class, $translation);
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table("gh11149_locale")
*/
class Locale
{
/**
* @ORM\Id
* @ORM\Column(type="string", length=5)
*
* @var string
*/
public $code;
public function __construct(string $code)
{
$this->code = $code;
}
}

View File

@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH11163Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH11163Bucket::class,
GH11163BucketItem::class,
]);
}
public function tearDown(): void
{
parent::tearDown();
$conn = static::$sharedConn;
$conn->executeStatement('DELETE FROM GH11163BucketItem');
$conn->executeStatement('DELETE FROM GH11163Bucket');
}
public function testFetchEagerModeWithOrderBy(): void
{
// Load entities into database
$this->_em->persist($bucket = new GH11163Bucket(11163));
$this->_em->persist(new GH11163BucketItem(1, $bucket, 2));
$this->_em->persist(new GH11163BucketItem(2, $bucket, 3));
$this->_em->persist(new GH11163BucketItem(3, $bucket, 1));
$this->_em->flush();
$this->_em->clear();
// Fetch entity from database
$dql = 'SELECT bucket FROM ' . GH11163Bucket::class . ' bucket WHERE bucket.id = :id';
$bucket = $this->_em->createQuery($dql)
->setParameter('id', 11163)
->getSingleResult();
// Assert associated entity is loaded eagerly
static::assertInstanceOf(GH11163Bucket::class, $bucket);
static::assertInstanceOf(PersistentCollection::class, $bucket->items);
static::assertTrue($bucket->items->isInitialized());
static::assertCount(3, $bucket->items);
// Assert order of entities
static::assertSame(1, $bucket->items[0]->position);
static::assertSame(3, $bucket->items[0]->id);
static::assertSame(2, $bucket->items[1]->position);
static::assertSame(1, $bucket->items[1]->id);
static::assertSame(3, $bucket->items[2]->position);
static::assertSame(2, $bucket->items[2]->id);
}
}
/**
* @ORM\Entity
*/
class GH11163Bucket
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*
* @var int
*/
private $id;
/**
* @ORM\OneToMany(
* targetEntity=GH11163BucketItem::class,
* mappedBy="bucket",
* fetch="EAGER"
* )
* @ORM\OrderBy({"position" = "ASC"})
*
* @var Collection<int, GH11163BucketItem>
*/
public $items;
public function __construct(int $id)
{
$this->id = $id;
$this->items = new ArrayCollection();
}
}
/**
* @ORM\Entity
*/
class GH11163BucketItem
{
/**
* @ORM\ManyToOne(targetEntity=GH11163Bucket::class, inversedBy="items")
* @ORM\JoinColumn(nullable=false)
*
* @var GH11163Bucket
*/
private $bucket;
/**
* @ORM\Id
* @ORM\Column(type="integer")
*
* @var int
*/
public $id;
/**
* @ORM\Column(type="integer", nullable=false)
*
* @var int
*/
public $position;
public function __construct(int $id, GH11163Bucket $bucket, int $position)
{
$this->id = $id;
$this->bucket = $bucket;
$this->position = $position;
}
}

View File

@@ -0,0 +1,40 @@
<?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
{
/**
* @var int|null
* @Column(type="integer")
* @Id
* @GeneratedValue
*/
public $id;
/**
* @var bool
* @Column(type="boolean")
*/
public $default = false;
}

View File

@@ -0,0 +1,137 @@
<?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(type="integer")
* @ORM\GeneratedValue
*
* @var int
*/
public $id;
}
/** @ORM\Entity */
class GH11500TestEntityOne extends GH11500AbstractTestEntity
{
/**
* @ORM\ManyToOne(targetEntity="GH11500TestEntityHolder", inversedBy="testEntityOnes")
* @ORM\JoinColumn(name="test_entity_holder_id", referencedColumnName="id")
*
* @var GH11500TestEntityHolder
*/
public $testEntityHolder;
}
/** @ORM\Entity */
class GH11500TestEntityTwo extends GH11500AbstractTestEntity
{
/**
* @ORM\ManyToOne(targetEntity="GH11500TestEntityHolder", inversedBy="testEntityTwos")
* @ORM\JoinColumn(name="test_entity_holder_id", referencedColumnName="id")
*
* @var GH11500TestEntityHolder
*/
public $testEntityHolder;
}
/** @ORM\Entity */
class GH11500TestEntityHolder
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*
* @var int
*/
public $id;
/**
* @ORM\OneToMany(targetEntity="GH11500TestEntityOne", mappedBy="testEntityHolder", orphanRemoval=true)
*
* @var Collection
*/
public $testEntityOnes;
/**
* @ORM\OneToMany(targetEntity="GH11500TestEntityTwo", mappedBy="testEntityHolder", orphanRemoval=true)
*
* @var Collection
*/
public $testEntityTwos;
public function __construct()
{
$this->testEntityOnes = new ArrayCollection();
$this->testEntityTwos = new ArrayCollection();
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH6123Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(
GH6123Entity::class
);
}
public function testLoadingRemovedEntityFromDatabaseDoesNotCreateNewManagedEntityInstance(): void
{
$entity = new GH6123Entity();
$this->_em->persist($entity);
$this->_em->flush();
self::assertSame(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($entity));
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForDelete($entity));
$this->_em->remove($entity);
$freshEntity = $this->loadEntityFromDatabase($entity->id);
self::assertSame($entity, $freshEntity);
self::assertSame(UnitOfWork::STATE_REMOVED, $this->_em->getUnitOfWork()->getEntityState($freshEntity));
self::assertTrue($this->_em->getUnitOfWork()->isScheduledForDelete($freshEntity));
}
public function testRemovedEntityCanBePersistedAgain(): void
{
$entity = new GH6123Entity();
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->remove($entity);
self::assertSame(UnitOfWork::STATE_REMOVED, $this->_em->getUnitOfWork()->getEntityState($entity));
self::assertTrue($this->_em->getUnitOfWork()->isScheduledForDelete($entity));
$this->loadEntityFromDatabase($entity->id);
$this->_em->persist($entity);
self::assertSame(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($entity));
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForDelete($entity));
$this->_em->flush();
}
private function loadEntityFromDatabase(int $id): ?GH6123Entity
{
return $this->_em->createQueryBuilder()
->select('e')
->from(GH6123Entity::class, 'e')
->where('e.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
}
/**
* @ORM\Entity
*/
#[ORM\Entity]
class GH6123Entity
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @var int
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: Types::INTEGER)]
public $id;
}

View File

@@ -10,9 +10,9 @@ use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\OrmFunctionalTestCase;
final class GH7286Test extends OrmFunctionalTestCase
@@ -114,14 +114,14 @@ class GH7286CustomConcat extends FunctionNode
public function parse(Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->first = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->second = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getSql(SqlWalker $walker): string

View File

@@ -12,6 +12,7 @@ use Doctrine\ORM\Events;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Models\Hydration\SimpleEntity;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\MockObject\MockObject;
@@ -154,4 +155,33 @@ class AbstractHydratorTest extends OrmFunctionalTestCase
$this->expectException(ORMException::class);
$this->hydrator->hydrateAll($this->mockResult, $this->mockResultMapping);
}
public function testToIterableIfYieldAndBreakBeforeFinishAlwaysCleansUp(): void
{
$this->setUpEntitySchema([SimpleEntity::class]);
$entity1 = new SimpleEntity();
$this->_em->persist($entity1);
$entity2 = new SimpleEntity();
$this->_em->persist($entity2);
$this->_em->flush();
$this->_em->clear();
$evm = $this->_em->getEventManager();
$q = $this->_em->createQuery('SELECT e.id FROM ' . SimpleEntity::class . ' e');
// select two entities, but do no iterate
$q->toIterable();
self::assertCount(0, $evm->getListeners(Events::onClear));
// select two entities, but abort after first record
foreach ($q->toIterable() as $result) {
self::assertCount(1, $evm->getListeners(Events::onClear));
break;
}
self::assertCount(0, $evm->getListeners(Events::onClear));
}
}

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Mapping;
use ArrayObject;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ChainTypedFieldMapper;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -54,6 +55,8 @@ require_once __DIR__ . '/../../Models/Global/GlobalNamespaceModel.php';
class ClassMetadataTest extends OrmTestCase
{
use VerifyDeprecations;
public function testClassMetadataInstanceSerialization(): void
{
$cm = new ClassMetadata(CMS\CmsUser::class);
@@ -1379,6 +1382,16 @@ class ClassMetadataTest extends OrmTestCase
'columnPrefix' => false,
]);
}
public function testInvalidCallToGetAssociationMappedByTargetFieldIsDeprecated(): void
{
$metadata = new ClassMetadata(self::class);
$metadata->mapOneToOne(['fieldName' => 'foo', 'targetEntity' => 'bar']);
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11309');
$metadata->getAssociationMappedByTargetField('foo');
}
}
/** @MappedSuperclass */

View File

@@ -64,8 +64,7 @@ class ProxyFactoryTest extends OrmTestCase
public function testReferenceProxyDelegatesLoadingToThePersister(): void
{
$identifier = ['id' => 42];
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$persister = $this->getMockBuilderWithOnlyMethods(BasicEntityPersister::class, ['load'])
$persister = $this->getMockBuilderWithOnlyMethods(BasicEntityPersister::class, ['loadById'])
->disableOriginalConstructor()
->getMock();
@@ -75,8 +74,8 @@ class ProxyFactoryTest extends OrmTestCase
$persister
->expects(self::atLeastOnce())
->method('load')
->with(self::equalTo($identifier), self::isInstanceOf($proxyClass))
->method('loadById')
->with(self::equalTo($identifier))
->will(self::returnValue($proxy));
$proxy->getDescription();

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Query;
use Doctrine\Common\Lexer\Token;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\OrmTestCase;
class LexerTest extends OrmTestCase
@@ -35,7 +36,7 @@ class LexerTest extends OrmTestCase
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_ALL, $token->type);
self::assertEquals(TokenType::T_ALL, $token->type);
}
public function testScannerRecognizesDecimalInteger(): void
@@ -43,7 +44,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('1234');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_INTEGER, $token->type);
self::assertEquals(TokenType::T_INTEGER, $token->type);
self::assertEquals(1234, $token->value);
}
@@ -52,7 +53,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('1.234');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(1.234, $token->value);
}
@@ -61,7 +62,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('1.2e3');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(1.2e3, $token->value);
}
@@ -70,7 +71,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('0.2e3');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(.2e3, $token->value);
}
@@ -79,7 +80,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('7E-10');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(7E-10, $token->value);
}
@@ -88,7 +89,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('123456789.01');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(1.2345678901e8, $token->value);
}
@@ -97,12 +98,12 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('- 1.234e2');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_MINUS, $token->type);
self::assertEquals(TokenType::T_MINUS, $token->type);
self::assertEquals('-', $token->value);
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertNotEquals(-1.234e2, $token->value);
self::assertEquals(1.234e2, $token->value);
}
@@ -112,7 +113,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer("'This is a string.'");
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_STRING, $token->type);
self::assertEquals(TokenType::T_STRING, $token->type);
self::assertEquals('This is a string.', $token->value);
}
@@ -121,7 +122,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer("'abc''defg'''");
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_STRING, $token->type);
self::assertEquals(TokenType::T_STRING, $token->type);
self::assertEquals("abc'defg'", $token->value);
}
@@ -130,7 +131,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('?1');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_INPUT_PARAMETER, $token->type);
self::assertEquals(TokenType::T_INPUT_PARAMETER, $token->type);
self::assertEquals('?1', $token->value);
}
@@ -139,7 +140,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer(':name');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_INPUT_PARAMETER, $token->type);
self::assertEquals(TokenType::T_INPUT_PARAMETER, $token->type);
self::assertEquals(':name', $token->value);
}
@@ -148,7 +149,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer(':_name');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_INPUT_PARAMETER, $token->type);
self::assertEquals(TokenType::T_INPUT_PARAMETER, $token->type);
self::assertEquals(':_name', $token->value);
}
@@ -158,17 +159,17 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer($dql);
$tokens = [
new Token('SELECT', Lexer::T_SELECT, 0),
new Token('u', Lexer::T_IDENTIFIER, 7),
new Token('FROM', Lexer::T_FROM, 9),
new Token('My\Namespace\User', Lexer::T_FULLY_QUALIFIED_NAME, 14),
new Token('u', Lexer::T_IDENTIFIER, 32),
new Token('WHERE', Lexer::T_WHERE, 34),
new Token('u', Lexer::T_IDENTIFIER, 40),
new Token('.', Lexer::T_DOT, 41),
new Token('name', Lexer::T_IDENTIFIER, 42),
new Token('=', Lexer::T_EQUALS, 47),
new Token("Jack O'Neil", Lexer::T_STRING, 49),
new Token('SELECT', TokenType::T_SELECT, 0),
new Token('u', TokenType::T_IDENTIFIER, 7),
new Token('FROM', TokenType::T_FROM, 9),
new Token('My\Namespace\User', TokenType::T_FULLY_QUALIFIED_NAME, 14),
new Token('u', TokenType::T_IDENTIFIER, 32),
new Token('WHERE', TokenType::T_WHERE, 34),
new Token('u', TokenType::T_IDENTIFIER, 40),
new Token('.', TokenType::T_DOT, 41),
new Token('name', TokenType::T_IDENTIFIER, 42),
new Token('=', TokenType::T_EQUALS, 47),
new Token("Jack O'Neil", TokenType::T_STRING, 49),
];
foreach ($tokens as $expected) {
@@ -186,15 +187,15 @@ class LexerTest extends OrmTestCase
public static function provideTokens(): array
{
return [
[Lexer::T_IDENTIFIER, 'u'], // one char
[Lexer::T_IDENTIFIER, 'someIdentifier'],
[Lexer::T_IDENTIFIER, 's0m31d3nt1f13r'], // including digits
[Lexer::T_IDENTIFIER, 'some_identifier'], // including underscore
[Lexer::T_IDENTIFIER, '_some_identifier'], // starts with underscore
[Lexer::T_IDENTIFIER, 'comma'], // name of a token class with value < 100 (whitebox test)
[Lexer::T_FULLY_QUALIFIED_NAME, 'Some\Class'], // DQL class reference
[Lexer::T_ALIASED_NAME, 'Some:Name'],
[Lexer::T_ALIASED_NAME, 'Some:Subclassed\Name'],
[TokenType::T_IDENTIFIER, 'u'], // one char
[TokenType::T_IDENTIFIER, 'someIdentifier'],
[TokenType::T_IDENTIFIER, 's0m31d3nt1f13r'], // including digits
[TokenType::T_IDENTIFIER, 'some_identifier'], // including underscore
[TokenType::T_IDENTIFIER, '_some_identifier'], // starts with underscore
[TokenType::T_IDENTIFIER, 'comma'], // name of a token class with value < 100 (whitebox test)
[TokenType::T_FULLY_QUALIFIED_NAME, 'Some\Class'], // DQL class reference
[TokenType::T_ALIASED_NAME, 'Some:Name'],
[TokenType::T_ALIASED_NAME, 'Some:Subclassed\Name'],
];
}
}

View File

@@ -6,9 +6,9 @@ namespace Doctrine\Tests\ORM\Query;
use Doctrine\Common\Persistence\PersistentObject;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\OrmTestCase;
use stdClass;
@@ -124,11 +124,11 @@ class ParserTest extends OrmTestCase
* but in LexerTest.
*/
return [
[Lexer::T_WHERE, 'where'], // keyword
[Lexer::T_DOT, '.'], // token that cannot be an identifier
[Lexer::T_IDENTIFIER, 'someIdentifier'],
[Lexer::T_IDENTIFIER, 'from'], // also a terminal string (the "FROM" keyword) as in DDC-505
[Lexer::T_IDENTIFIER, 'comma'],
[TokenType::T_WHERE, 'where'], // keyword
[TokenType::T_DOT, '.'], // token that cannot be an identifier
[TokenType::T_IDENTIFIER, 'someIdentifier'],
[TokenType::T_IDENTIFIER, 'from'], // also a terminal string (the "FROM" keyword) as in DDC-505
[TokenType::T_IDENTIFIER, 'comma'],
// not even a terminal string, but the name of a constant in the Lexer (whitebox test)
];
}
@@ -137,15 +137,15 @@ class ParserTest extends OrmTestCase
public static function invalidMatches(): array
{
return [
[Lexer::T_DOT, 'ALL'], // ALL is a terminal string (reserved keyword) and also possibly an identifier
[Lexer::T_DOT, ','], // "," is a token on its own, but cannot be used as identifier
[Lexer::T_WHERE, 'WITH'], // as in DDC-3697
[Lexer::T_WHERE, '.'],
[TokenType::T_DOT, 'ALL'], // ALL is a terminal string (reserved keyword) and also possibly an identifier
[TokenType::T_DOT, ','], // "," is a token on its own, but cannot be used as identifier
[TokenType::T_WHERE, 'WITH'], // as in DDC-3697
[TokenType::T_WHERE, '.'],
// The following are qualified or aliased names and must not be accepted where only an Identifier is expected
[Lexer::T_IDENTIFIER, '\\Some\\Class'],
[Lexer::T_IDENTIFIER, 'Some\\Class'],
[Lexer::T_IDENTIFIER, 'Some:Name'],
[TokenType::T_IDENTIFIER, '\\Some\\Class'],
[TokenType::T_IDENTIFIER, 'Some\\Class'],
[TokenType::T_IDENTIFIER, 'Some:Name'],
];
}
@@ -164,7 +164,7 @@ class ParserTest extends OrmTestCase
$parser = new Parser($query);
$this->expectException(QueryException::class);
$parser->match(Lexer::T_SELECT);
$parser->match(TokenType::T_SELECT);
}
private function createParser(string $dql): Parser

View File

@@ -13,10 +13,13 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Internal\Hydration\IterableResult;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\UnitOfWork;
@@ -88,6 +91,33 @@ class QueryTest extends OrmTestCase
self::assertEquals($parameters, $query->getParameters());
}
/**
* @psalm-param LockMode::* $lockMode
*
* @dataProvider provideLockModes
*/
public function testSetLockMode(int $lockMode): void
{
$query = $this->entityManager->wrapInTransaction(static function (EntityManagerInterface $em) use ($lockMode): Query {
$query = $em->createQuery('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username = ?1');
$query->setLockMode($lockMode);
return $query;
});
self::assertSame($lockMode, $query->getLockMode());
self::assertSame($lockMode, $query->getHint(Query::HINT_LOCK_MODE));
}
/** @psalm-return list<array{LockMode::*}> */
public static function provideLockModes(): array
{
return [
[LockMode::PESSIMISTIC_READ],
[LockMode::PESSIMISTIC_WRITE],
];
}
public function testFree(): void
{
$query = $this->entityManager->createQuery('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username = ?1');

View File

@@ -18,10 +18,10 @@ use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Query as ORMQuery;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\DbalTypes\NegativeToPositiveType;
use Doctrine\Tests\Models\CMS\CmsGroup;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
@@ -2186,12 +2186,12 @@ class MyAbsFunction extends FunctionNode
{
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}
/** @Entity */

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Tests\ORM\Query;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\ParserResult;
use Doctrine\ORM\Query\TokenType;
use Doctrine\ORM\Query\TreeWalkerAdapter;
use PHPUnit\Framework\TestCase;
use stdClass;
@@ -33,7 +33,7 @@ class TreeWalkerAdapterTest extends TestCase
'relation' => null,
'map' => null,
'nestingLevel' => 0,
'token' => ['value' => '', 'type' => Lexer::T_NONE, 'position' => 0],
'token' => ['value' => '', 'type' => TokenType::T_NONE, 'position' => 0],
]);
}
@@ -52,7 +52,7 @@ class TreeWalkerAdapterTest extends TestCase
'relation' => null,
'map' => null,
'nestingLevel' => 0,
'token' => ['value' => '', 'type' => Lexer::T_NONE, 'position' => 0],
'token' => ['value' => '', 'type' => TokenType::T_NONE, 'position' => 0],
]);
}
};

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