Compare commits

..

105 Commits

Author SHA1 Message Date
Luís Cobucci
b52ef5a100 Merge pull request #7322 from dennisenderink/fix/joinedsubclasspersister-pass-identifier-types-on-delete
JoinedSubclassPersister pass identifier types on delete
2019-09-20 16:30:26 +02:00
Luís Cobucci
ef783f7049 Make use of the PersisterHelper to get field type
Removing the unnecessary code duplication.
2019-09-20 16:17:43 +02:00
Luís Cobucci
435d624d33 Centralise functional test classes
Which provides better isolation and makes things a bit more
understandable.
2019-09-20 16:17:43 +02:00
Dennis Enderink
53775fe086 Added correct return types to docblock 2019-09-20 16:17:43 +02:00
Dennis Enderink
59f1679fed Patched spacing 2019-09-20 16:17:43 +02:00
Dennis Enderink
390d081fca Created new DBAL type and updated/reverted tests; also clarified new method return type 2019-09-20 16:17:43 +02:00
Dennis Enderink
37d1d57900 Added unit tests 2019-09-20 16:17:42 +02:00
Dennis Enderink
d7a537c941 Pass ClassMetadata object instead of string in parent classes loop 2019-09-20 16:17:42 +02:00
Dennis Enderink
cfe73cd74f Separated class identifiers types retrieval in a separate method and implemented in JoinedSubclassPersister delete method 2019-09-20 15:40:03 +02:00
Luís Cobucci
d0e1da8c51 Merge pull request #7490 from vladyslavstartsev/patch-2
Fix broken link
2019-09-20 15:32:13 +02:00
vladyslavstartsev
7fbe663ea0 Fix broken link
closing #7489
2019-09-20 15:31:12 +02:00
Luís Cobucci
409f2f5d82 Merge pull request #7672 from jschaedl/patch-5
Added cross-links to relevant documentation
2019-09-20 15:30:14 +02:00
Luís Cobucci
3d8b672771 Merge pull request #7610 from smtchahal/patch-1
Change APC to OPcache in improving-performance.rst
2019-09-20 15:29:07 +02:00
Jan Schädlich
17650a6100 Added cross-links to relevant documentation 2019-09-20 15:19:49 +02:00
Luís Cobucci
1588ca7e1f Merge pull request #7821 from Ocramius/bug/#7820-paginator-ignores-dbal-type-conversions-in-identifiers
Bug: #7820 paginator ignores dbal type conversions in identifiers
2019-09-20 15:13:58 +02:00
Marco Pivetta
0de17319d3 #7820 use PersisterHelper to figure out identifier types
This logic was pre-existing, but I forgot about it while writing
#7820, therefore it was re-implemented inside this unit of
code. Now we just use the `PersisterHelper`, which does all
the nice and shiny identifier type discovery operations we need.
2019-09-20 15:03:22 +02:00
Marco Pivetta
681ff32e76 #7820 documented PersisterHelper#getTypeOfField() array return type
Array values are `string`, and the array is a packed array.
2019-09-20 14:57:44 +02:00
Marco Pivetta
caee6c8685 #7820 restricted return type of Doctrine\ORM\Mapping\ClassMetadataInfo#getTypeOfColumn()
This method will always return `string|null`, so we can safely
remove DBAL types from its possible return types.
2019-09-20 14:51:56 +02:00
Marco Pivetta
c67a515cc2 As per discussion with @lcobucci, it is better to keep dragons where
there be dragons, and this change does indeed rewrite the previous
approach by moving the responsibility of type conversion on a query
object from the `Paginator` to the `WhereInWalker`, which already
has access to class metadata for the root of the selection (and can
reliably detect the root of the selection too)
2019-09-19 20:05:34 +02:00
Luís Cobucci
24892779f7 Merge pull request #7818 from SenseException/simple-annotation-docs
Add note into docs about not using SimpleAnnotationReader
2019-09-18 10:49:43 +02:00
Marco Pivetta
39d2113549 Fixed #7820 - convert identifiers for WHERE IN(?) queries before binding parameters
This patch introduces new internal API on the `ResultSetMapping` class, which is responsible
for finding the type of the single column identifier of a DQL query selection root.
2019-09-17 16:54:25 +02:00
Marco Pivetta
65522d9775 Failing test case for #7820 - paginator doesn't use custom ID types
When using a `Doctrine\ORM\Tools\Pagination\Paginator` to iterate over a query that has entities with a custom DBAL type used in the identifier, then `$id->__toString()` is used implicitly by PDO, instead of being converted by the `Doctrine\DBAL\Types` system.

In order to reproduce this, you must have identifiers implementing `#__toString()` (to allow the `UnitOfWork` to hash them) and other accessors that are used by the custom DBAL type during DB/PHP conversions. If `#__toString()` and the DBAL type conversions are asymmetric, then the paginator will fail to find records.

Tricky situation, but this very much affects `ramsey/uuid-doctrine` and anyone relying on the `uuid_binary`.
2019-09-17 11:37:50 +02:00
Claudio Zizza
50eecf698c Add note into docs about not using SimpleAnnotationReader 2019-09-15 22:50:46 +02:00
Luís Cobucci
20ab78e3c1 Merge pull request #7753 from SenseException/getting-started-annotation
Add ORM annotations in getting-started docs
2019-09-12 17:01:53 +02:00
Luís Cobucci
613ffe9bbd Backport documentation sidebar 2019-09-10 16:31:41 +02:00
Luís Cobucci
61ff45f98e Merge pull request #7785 from mlocati/php74-fixes
Fix "access array offset on value of type null" PHP 7.4 notices
2019-09-10 16:08:49 +02:00
Luís Cobucci
a8aa475d09 Add PHP 7.4 to test matrix 2019-09-10 15:48:05 +02:00
Luís Cobucci
a4215cfa59 Update locked dependencies 2019-09-10 15:48:04 +02:00
Luís Cobucci
a4ac9a721f Upgrade PHPUnit to 7.5 2019-09-10 15:48:03 +02:00
Michele Locati
447183e235 Fix "access array offset on value of type null" PHP 7.4 notices 2019-09-10 15:47:57 +02:00
Luís Cobucci
642e543b4b Merge pull request #7778 from umpirsky/fix/issue-7266
Guard L2C regions against corrupted data
2019-08-14 18:07:58 +02:00
Luís Cobucci
80503c4837 Guard cache regions against corrupted data
For some bizarre reason the underlying cache drivers are returning
unexpected values, which are leaking to the cache objects and causing
them to error.

This makes our cache regions much more strict about the types that are
fetched from the cache provider, ensuring that no invalid information is
ever sent to the hydrators.
2019-08-14 17:42:56 +02:00
Luís Cobucci
3577064f8c Make closure static
To adhere to our coding standard.
2019-08-14 17:42:56 +02:00
Luís Cobucci
b6663733c0 Add type assertion to be more strict about persister type 2019-08-14 17:42:56 +02:00
Luís Cobucci
b9d6834213 Remove unnecessary function calls 2019-08-14 17:42:56 +02:00
Luís Cobucci
eafc4c5a0c Remove unnecessary parentheses 2019-08-14 17:42:56 +02:00
Saša Stamenković
ecf80b47a0 Call to a member function resolveAssociationEntries() on boolean
The following mistakes occur occasionally:

```
Call to a member function resolveAssociationEntries() on boolean {"detail":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Call to a member function resolveAssociationEntries() on boolean at /www/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php:140)"}
```

On cache miss the parameter `$entityEntry` sometimes will be false. This fixes issue #7266.
2019-08-14 17:42:56 +02:00
Luís Cobucci
5499555862 Merge pull request #7791 from Steveb-p/fix-docs
Fix preFlush event documentation stating incorrectly that flush can be called safely
2019-08-14 14:03:13 +02:00
Paweł Niedzielski
70df74f65f Fix preFlush event documentation stating incorrectly that flush can be called safely
Original author: egonolieux
Supersedes #6858
2019-08-14 13:41:31 +02:00
Luís Cobucci
74415becce Merge pull request #7737 from Smartel1/smartel1/patch1
Fix MEMBER_OF comparison when using criteria in query builder
2019-08-14 10:07:11 +02:00
drews
3a56cf8ad9 Add MEMBER_OF comparison to queryExpressionVisitor 2019-08-14 09:47:20 +02:00
Luís Cobucci
6b7f53f0f3 Merge pull request #7766 from stephanschuler/respect-collection-ordering-in-selectable-matching
Respect collection orderBy meta when matching()

Fixes https://github.com/doctrine/orm/issues/7767
2019-08-13 23:42:32 +02:00
Stephan Schuler
e51666e8be Fix PersistentCollection::matching() not respecting collection ordering
The ordering of a Criteria is prefered over the collections default
ordering.

The default collection ordering used as additional sorting attributes.
2019-08-13 23:34:54 +02:00
Luís Cobucci
6e56bcd75f Merge pull request #7750 from AlexSmerw/issue_7735_null_values_in_entities_cache_for_2.6
Fix incorrect return of null values in L2C
2019-08-12 01:28:20 +02:00
Luís Cobucci
48bfef1f7a Merge pull request #7761 from paxal/persistent_collection/deferred_explicit_2.6
Do not modify UOW on PersistentCollection::clear() when owner has DEFFERED_EXPLICIT change tracking policy
2019-08-12 01:21:21 +02:00
A.Kuterev
e8f91434a7 Avoid reusing variable name
The same variable name is used below, and that causes a bug etc.
Fixes https://github.com/doctrine/orm/issues/7735
2019-08-12 01:18:48 +02:00
Luís Cobucci
7e26d82790 Merge pull request #7794 from lcobucci/fix-compatibility-with-dev-dependencies
Fix test compatibility with DBAL 2.10.x-dev
2019-08-12 00:00:45 +02:00
Luís Cobucci
869b70e4db Use Ubuntu Xenial for MySQL 5.7 build
Since July 21st 2019 it's no longer possible to install MySQL 5.7 in
Ubuntu Trusty.

More info: https://docs.travis-ci.com/user/database-setup/#mysql-57
2019-08-11 23:41:32 +02:00
Luís Cobucci
33904cb9c1 Fix test compatibility with DBAL 2.10.x-dev 2019-08-11 23:41:32 +02:00
Cyril PASCAL
a42191eecf Add functional test for ArrayCollection::clear() bug 2019-07-19 16:21:12 +02:00
Cyril PASCAL
3fbf163d34 Do not modify UOW on PersistentCollection::clear() when owner has DEFFERED_EXPLICIT change tracking policy 2019-06-26 16:07:15 +02:00
Claudio Zizza
c777aa62b6 Fix of ORM annotation in examples 2019-06-23 23:39:41 +02:00
Luís Cobucci
6296bd4e1d Merge pull request #7744 from noobshow/patch-1
Fixed a typo-error
2019-06-18 08:30:35 +02:00
Luís Cobucci
5a236c19f5 Merge pull request #7731 from greg0ire/try-mysql-addon
Replace custom install script with add-on
2019-06-17 23:33:04 +02:00
Olumide Samson
4f8a1f92a3 Fixed a typo-error
exploitet changed to exploited
2019-06-17 09:00:45 +01:00
Jonathan H. Wage
0b5be00374 Merge pull request #7732 from lchrusciel/patch-1
[Documentation] Missing comma fix
2019-06-04 11:11:46 -05:00
Łukasz Chruściel
145cc782ff [Documentation] Missing comma fix 2019-06-04 17:38:26 +02:00
Jonathan H. Wage
9712506be8 Merge pull request #7729 from JoppeDC/patch-1
Update DATE_ADD and DATE_SUB docs
2019-06-04 10:23:15 -05:00
Grégoire Paris
bd9ead11c5 Replace custom install script with add-on
Following this documentation:
https://docs.travis-ci.com/user/database-setup/#mysql-57
found via https://stackoverflow.com/a/49542847/353612
2019-06-03 22:12:54 +02:00
Joppe de Cuyper
a98ebf7344 Whitespace fixes 2019-06-03 20:21:19 +02:00
Joppe de Cuyper
c721ab63ee Update DATE_ADD and DATE_SUB docs 2019-06-03 20:20:03 +02:00
Jonathan H. Wage
2820438afc Merge pull request #7694 from darrylhein/patch-1
Change variable name in docs
2019-04-29 11:00:10 -05:00
Darryl Hein
180cfcc3e3 change variable name
to make it consistent throughout document
2019-04-28 21:07:22 -06:00
Marco Pivetta
52d806a34a Merge pull request #7612 from spirlici/patch-1
Update ordered-associations.rst
2019-03-12 00:16:09 +01:00
Marco Pivetta
49a8f2ec96 Merge pull request #7630 from yethee/gh-7629
Fix #7629 - `scheduledForSynchronization` leaks memory when using `@ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")`
2019-03-01 21:24:57 +01:00
yethee
7f5f4629e5 Ensure state is cleanup after empty commit 2019-03-01 23:00:01 +03:00
yethee
d91e0b3867 Failing tests 2019-02-28 17:12:54 +03:00
Sergiu Pirlici
b537758b32 Update ordered-associations.rst
Fixed some typos
2019-02-16 15:04:45 +02:00
Sumit Chahal
2ba6e473de Change APC to OPcache in improving-performance.rst
OPcache is a lot better supported now than APC,
which is apparently not maintained anymore.
2019-02-16 16:44:21 +05:30
Marco Pivetta
de97061d65 Merge pull request #7596 from mbessolov/patch-1
Correct method names and broken link in docs
2019-02-09 15:47:17 +01:00
Michael Bessolov
624ee78081 Correct method names and broken link in docs
This basically applying the same documentation fix as in #7335 (master) to 2.6 branch
2019-02-08 22:29:10 -08:00
Marco Pivetta
e003bb2bb4 Merge pull request #7577 from SenseException/advanced-config-link
Fix of single link to dbal docs in advanced-configuration.rst
2019-01-23 07:48:12 +01:00
Claudio Zizza
5c5f310646 Fix of link to dbal docs 2019-01-22 22:42:13 +01:00
Marco Pivetta
c10433e512 Merge pull request #7572 from SenseException/remove-codeigniter
Remove codeigniter Framework example
2019-01-17 22:35:58 +01:00
Claudio Zizza
580c530041 Remove codeigniter example from docs 2019-01-17 21:54:33 +01:00
Marco Pivetta
4d461afbd6 Merge pull request #7571 from batwolf/patch-1
Fix typo in inheritance mappings docs
2019-01-17 21:42:45 +01:00
Marco Pivetta
536e31f343 Merge pull request #7385 from SenseException/links-and-typos
Update information and links of documentation
2019-01-17 21:11:50 +01:00
Marc Plotz
c6eb4df25e fix typo
`has to allow null values` vs `has to allows null values`
2019-01-17 10:14:56 +01:00
Claudio Zizza
aae00e3987 Fix of links and php version after review 2019-01-07 14:40:26 +01:00
Claudio Zizza
b56800b15c Fix of links and anchors 2019-01-07 14:40:17 +01:00
Claudio Zizza
be461be36b Update getting help section 2019-01-07 14:40:10 +01:00
Claudio Zizza
85171a9490 Fix of reStructuredText format 2019-01-07 14:40:02 +01:00
Claudio Zizza
f5b9f2052a Update MySQL links to current version 2019-01-07 14:39:54 +01:00
Claudio Zizza
3d652997d1 Remove changelog from documentation
This was removed in favour of the UPGRADE.md file, which
contains the changelog information needed.
2019-01-07 14:39:42 +01:00
Marco Pivetta
10393dca68 Merge pull request #7557 from doctrine/malarzm-patch-1
Change Stackoverflow tag to doctrine-orm
2019-01-05 17:48:41 +01:00
Maciej Malarz
597bfaea03 Change Stackoverflow tag to doctrine-orm 2019-01-04 22:20:24 +01:00
Jonathan H. Wage
98b8ced814 Merge pull request #7551 from Majkl578/repo-rename/2.6
[2.6] Migrate repository name doctrine/doctrine2 -> doctrine/orm
2019-01-03 17:18:59 -06:00
Michael Moravec
efaee8ce85 Migrate repository name doctrine/doctrine2 -> doctrine/orm 2019-01-03 09:07:03 +01:00
Luís Cobucci
6e93f5bb72 Merge pull request #7528 from Ocramius/fix/#7527-prevent-unit-of-work-lookup-for-known-value-types
Fix #7527:  prevent `UnitOfWork` lookup for DBAL types specified in `Doctrine\ORM\Query#setParameter()`
2018-12-21 21:54:20 +01:00
Marco Pivetta
a41f5673bc #7527 automated CS checks 2018-12-20 22:59:46 +01:00
Marco Pivetta
ca436f0bae #7527 performance benchmark - verifying performance impact of inferred query parameter types
As an example result:

```
./phpbench.phar run tests/Doctrine/Performance/Query --iterations=50 --revs=50 --report=aggregate
PhpBench 0.15-dev (dcbe193). Running benchmarks.
Using configuration file: /home/ocramius/Documents/doctrine/doctrine2/phpbench.json

\Doctrine\Performance\Query\QueryBoundParameterProcessingBench

    benchExecuteParsedQueryWithInferredParameterTypeI49 P0 	[μ Mo]/r: 643.684 634.664 (μs) 	[μSD μRSD]/r: 17.700μs 2.75%
    benchExecuteParsedQueryWithDeclaredParameterTypeI49 P0 	[μ Mo]/r: 97.673 94.251 (μs) 	[μSD μRSD]/r: 8.259μs 8.46%

2 subjects, 100 iterations, 100 revs, 0 rejects, 0 failures, 0 warnings
(best [mean mode] worst) = 88.460 [370.679 364.458] 127.400 (μs)
⅀T: 37,067.880μs μSD/r 12.980μs μRSD/r: 5.603%
suite: 133f0e30090f815142331ebec6af18241694e7c0, date: 2018-12-19, stime: 10:47:10
+------------------------------------+--------------------------------------------------+--------+--------+------+-----+------------+-----------+-----------+-----------+-----------+----------+--------+-------+
| benchmark                          | subject                                          | groups | params | revs | its | mem_peak   | best      | mean      | mode      | worst     | stdev    | rstdev | diff  |
+------------------------------------+--------------------------------------------------+--------+--------+------+-----+------------+-----------+-----------+-----------+-----------+----------+--------+-------+
| QueryBoundParameterProcessingBench | benchExecuteParsedQueryWithInferredParameterType |        | []     | 50   | 50  | 5,970,568b | 604.680μs | 643.684μs | 634.664μs | 677.640μs | 17.700μs | 2.75%  | 6.59x |
| QueryBoundParameterProcessingBench | benchExecuteParsedQueryWithDeclaredParameterType |        | []     | 50   | 50  | 5,922,424b | 88.460μs  | 97.673μs  | 94.251μs  | 127.400μs | 8.259μs  | 8.46%  | 1.00x |
+------------------------------------+--------------------------------------------------+--------+--------+------+-----+------------+-----------+-----------+-----------+-----------+----------+--------+-------+
```

This indicates that the performance impact for NOT declaring parameter types
explicitly is *MASSIVE*.
2018-12-19 10:52:11 +01:00
Marco Pivetta
d8212e8dd6 Merge pull request #7530 from vladyslavstartsev/patch-3
Documentation error fix
2018-12-17 16:00:44 +01:00
vladyslavstartsev
12eb9f42dc Documentation error fix 2018-12-16 20:33:21 +02:00
Marco Pivetta
23af164d7a Note: this will still lead to the UnitOfWork#getSingleIdentifierValue() still being
called when not specifying the type of a DQL parameter being bound via
`Doctrine\ORM\Query#setParameter()`:

```php
$query->setParameter('foo', $theValue, $theType);
```

A full parameter bind is required in order to gain back performance:

```php
$query->setParameter('foo', $theValue, $theType);
```

This is up for discussion with patch reviewers.
2018-12-16 18:05:02 +01:00
Marco Pivetta
960a437d46 #7527 failing test case: UnitOfWork#getSingleIdentifierValue() should not be called for a well specified parameter type
As previously reported by @flaushi in https://github.com/doctrine/doctrine2/pull/7471#discussion_r241949045, we discovered
that binding a parameter causes a `ClassMetadataFactory#getClassMetadata()` call, which in turn leads to large performance
regression when using any `object` type as parameter.

Following two snippets lead to an internal `ClassMetadataFactory#getClassMetadata()` call, which in turn leads to an
exception being thrown and garbage collected, plus multiple associated performance implications:

```php
$query->setParameter('foo', new DateTime());
$query->getResult();
```

```php
$query->setParameter('foo', new DateTime(), DateTimeType::NAME);
$query->getResult();
```

This is due to following portion of code:

434820973c/lib/Doctrine/ORM/Query.php (L406-L409)

Notice how `$value = $this->processParameterValue($value);` happens before attempting to infer the type for the parameter value.

That call leads to this segment being reached, which leads to the regression:

434820973c/lib/Doctrine/ORM/AbstractQuery.php (L423-L433)

Assuming the bound parameter type is provided, we can completely skip attempting to introspect the given object:

```php
$query->setParameter('foo', new DateTime(), DateTimeType::NAME);
$query->getResult();
```

Processing the parameter value is not needed in this case, so we can safely skip that logic for all known parameters.
In order to not introduce a BC break or change the `AbstractQuery#processParameterValue()` implementation, we could filter
out all parameters for which the type is given upfront, and later on merge them back in instead.

The test expectation to be set is for `UnitOfWork#getSingleIdentifierValue()` to never be called.
2018-12-16 15:37:45 +01:00
Marco Pivetta
237bebe2ed Merge pull request #7519 from koftikes/fix/#7518-phpdoc-error
#7518 Fixed type mismatch between `EntityRepository#__construct()` and its documented constructor arguments
2018-12-13 08:14:30 +01:00
Jonathan H. Wage
fc3dca772e Merge pull request #7521 from doctrine/update-chat-link
Update chat link from Gitter to Slack.
2018-12-12 20:07:31 +00:00
Konstantin Litvinov
ee64d31f48 7518 Fixed PHPDoc Error. 2018-12-12 17:08:35 +03:00
Michael Moravec
493ff74a0d Merge pull request #7473 from Majkl578/incremental-cs-2.x
Incremental CS checks in 2.x branches
2018-12-10 14:43:55 +01:00
Michael Moravec
78c7000962 Lock dependencies for Code Quality stage 2018-12-10 13:58:51 +01:00
Michael Moravec
6a05e01298 Perform incremental coding standard checks for pull requests 2018-12-10 13:58:51 +01:00
Gabriel Ostrolucký
7de3434733 Update doctrine/coding-standard in 2.x branch
Co-Authored-By: Michael Moravec <me@majkl.me>
2018-12-10 13:58:51 +01:00
Luís Cobucci
74e6189f3e Merge pull request #7483 from javiereguiluz/patch-9
Fixed a minor syntax issue
2018-11-21 10:48:33 +01:00
Javier Eguiluz
2e7a3affba Fixed a minor syntax issue 2018-11-21 09:06:54 +01:00
Luís Cobucci
505ec21f97 Bump up development version 2018-11-21 01:24:06 +01:00
94 changed files with 4681 additions and 1248 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,5 @@ lib/Doctrine/DBAL
.idea
*.iml
vendor/
composer.lock
/tests/Doctrine/Performance/history.db
/.phpcs-cache

View File

@@ -6,6 +6,7 @@ php:
- 7.1
- 7.2
- 7.3
- 7.4snapshot
- nightly
env:
@@ -32,22 +33,31 @@ jobs:
mariadb: 10.1
- stage: Test
dist: xenial
env: DB=mysql MYSQL_VERSION=5.7
php: 7.1
services:
- mysql
before_script:
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
sudo: required
- stage: Test
dist: xenial
env: DB=mysql MYSQL_VERSION=5.7
php: 7.2
services:
- mysql
before_script:
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
sudo: required
- stage: Test
dist: xenial
env: DB=mysql MYSQL_VERSION=5.7
php: nightly
services:
- mysql
before_script:
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
sudo: required
@@ -77,7 +87,7 @@ jobs:
- stage: Code Quality
env: DB=none STATIC_ANALYSIS
install: travis_retry composer update --prefer-dist --prefer-stable
install: travis_retry composer install --prefer-dist
before_script:
- echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- echo "extension=redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
@@ -90,13 +100,32 @@ jobs:
script: php phpbench.phar run -l dots --report=default
- stage: Code Quality
if: NOT type = pull_request
env: DB=none CODING_STANDARDS
php: nightly
php: 7.1
install: travis_retry composer install --prefer-dist
script:
- ./vendor/bin/phpcs
- stage: Code Quality
if: type = pull_request
env: DB=none PULL_REQUEST_CODING_STANDARDS
php: 7.1
install: travis_retry composer install --prefer-dist
script:
- |
if [ $TRAVIS_BRANCH != "master" ]; then
git remote set-branches --add origin $TRAVIS_BRANCH;
git fetch origin $TRAVIS_BRANCH;
fi
- git merge-base origin/$TRAVIS_BRANCH $TRAVIS_PULL_REQUEST_SHA || git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge --unshallow
- wget https://github.com/diff-sniffer/git/releases/download/0.2.0/git-phpcs.phar
- php git-phpcs.phar origin/$TRAVIS_BRANCH...$TRAVIS_PULL_REQUEST_SHA
allow_failures:
- php: nightly
- stage: Code Quality
env: DB=none CODING_STANDARDS
cache:
directories:

View File

@@ -44,12 +44,12 @@ Please try to add a test for your pull-request.
You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project.
It will run all the tests with an in memory SQLite database.
In order to do that, you will need a fresh copy of doctrine2, and you
In order to do that, you will need a fresh copy of the ORM, and you
will have to run a composer installation in the project:
```sh
git clone git@github.com:doctrine/doctrine2.git
cd doctrine2
git clone git@github.com:doctrine/orm.git
cd orm
curl -sS https://getcomposer.org/installer | php --
./composer.phar install
```
@@ -66,7 +66,7 @@ sqlite database.
Tips for creating unit tests:
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
See `https://github.com/doctrine/doctrine2/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
See `https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
example.
## Travis

View File

@@ -16,11 +16,11 @@ without requiring unnecessary code duplication.
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
[Master image]: https://img.shields.io/travis/doctrine/doctrine2/master.svg?style=flat-square
[Master]: https://travis-ci.org/doctrine/doctrine2
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/master.svg?style=flat-square
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=master
[2.5 image]: https://img.shields.io/travis/doctrine/doctrine2/2.5.svg?style=flat-square
[2.5]: https://github.com/doctrine/doctrine2/tree/2.5
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/2.5.svg?style=flat-square
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=2.5
[Master image]: https://img.shields.io/travis/doctrine/orm/master.svg?style=flat-square
[Master]: https://travis-ci.org/doctrine/orm
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/master.svg?style=flat-square
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=master
[2.5 image]: https://img.shields.io/travis/doctrine/orm/2.5.svg?style=flat-square
[2.5]: https://github.com/doctrine/orm/tree/2.5
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.5.svg?style=flat-square
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.5

View File

@@ -11,7 +11,7 @@ Please read the documentation chapter on Security in Doctrine DBAL and ORM to
understand the assumptions we make.
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
- [ORM Security Page](https://github.com/doctrine/doctrine2/blob/master/docs/en/reference/security.rst)
- [ORM Security Page](https://github.com/doctrine/orm/blob/master/docs/en/reference/security.rst)
If you find a Security bug in Doctrine, please report it on Jira and change the
Security Level to "Security Issues". It will be visible to Doctrine Core

View File

@@ -20,13 +20,13 @@ now has a required parameter `$pathExpr`.
Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because
the distinction between internal function and user defined DQL was removed.
[#6500](https://github.com/doctrine/doctrine2/pull/6500)
[#6500](https://github.com/doctrine/orm/pull/6500)
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
removed because of the choice to allow users to overwrite internal functions, ie
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/doctrine2/pull/6500)
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/orm/pull/6500)
## PHP 7.1 is now required
@@ -42,7 +42,7 @@ As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Conf
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`
Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/doctrine2/pull/5600).
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/orm/pull/5600).
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`

View File

@@ -27,9 +27,8 @@
"symfony/console": "~3.0|~4.0"
},
"require-dev": {
"doctrine/coding-standard": "^1.0",
"phpunit/phpunit": "^6.5",
"squizlabs/php_codesniffer": "^3.2",
"doctrine/coding-standard": "^5.0",
"phpunit/phpunit": "^7.5",
"symfony/yaml": "~3.4|~4.0"
},
"suggest": {

2804
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,711 +0,0 @@
What is new in Doctrine ORM 2.5?
================================
This document describes changes between Doctrine ORM 2.4 and 2.5.
It contains a description of all the new features and sections about
behavioral changes and potential backwards compatibility breaks.
Please review this document carefully when updating to Doctrine 2.5.
First note, that with the ORM 2.5 release we are dropping support
for PHP 5.3. We are enforcing this with Composer, servers without
at least PHP 5.4 will not allow installing Doctrine 2.5.
New Features and Improvements
-----------------------------
Events: PostLoad now triggered after associations are loaded
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before Doctrine 2.5 if you had an entity with a ``@PostLoad`` event
defined then Doctrine would trigger listeners after the fields were
loaded, but before assocations are available.
- `DDC-54 <http://doctrine-project.org/jira/browse/DDC-54>`_
- `Commit #a90629 <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
Events: Add API to programatically add event listeners to Entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When developing third party libraries or decoupled applications
it can be interesting to develop an entity listener without knowing
the entities that require this listener.
You can now attach entity listeners to entities using the
``AttachEntityListenersListener`` class, which is listening to the
``loadMetadata`` event that is fired once for every entity during
metadata generation:
.. code-block:: php
<?php
use Doctrine\ORM\Tools\AttachEntityListenersListener;
use Doctrine\ORM\Events;
$listener = new AttachEntityListenersListener();
$listener->addEntityListener(
'MyProject\Entity\User', 'MyProject\Listener\TimestampableListener',
Events::prePersist, 'onPrePersist'
);
$evm->addEventListener(Events::loadClassMetadata, $listener);
class TimestampableListener
{
public function onPrePersist($event)
{
$entity = $event->getEntity();
$entity->setCreated(new \DateTime('now'));
}
}
Embeddable Objects
~~~~~~~~~~~~~~~~~~
Doctrine now supports creating multiple PHP objects from one database table
implementing a feature called "Embeddable Objects". Next to an ``@Entity``
class you can now define a class that is embeddable into a database table of an
entity using the ``@Embeddable`` annotation. Embeddable objects can never be
saved, updated or deleted on their own, only as part of an entity (called
"root-entity" or "aggregate"). Consequently embeddables don't have a primary
key, they are identified only by their values.
Example of defining and using embeddables classes:
.. code-block:: php
<?php
/** @Entity */
class Product
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Embedded(class = "Money") */
private $price;
}
/** @Embeddable */
class Money
{
/** @Column(type = "decimal") */
private $value;
/** @Column(type = "string") */
private $currency = 'EUR';
}
You can read more on the features of Embeddables objects `in the documentation
<http://docs.doctrine-project.org/en/latest/tutorials/embeddables.html>`_.
This feature was developed by external contributor `Johannes Schmitt
<https://twitter.com/schmittjoh>`_
- `DDC-93 <http://doctrine-project.org/jira/browse/DDC-93>`_
- `Pull Request #835 <https://github.com/doctrine/doctrine2/pull/835>`_
Second-Level-Cache
~~~~~~~~~~~~~~~~~~
Since version 2.0 of Doctrine, fetching the same object twice by primary key
would result in just one query. This was achieved by the identity map pattern
(first-level-cache) that kept entities in memory.
The newly introduced second-level-cache works a bit differently. Instead
of saving objects in memory, it saves them in a fast in-memory cache such
as Memcache, Redis, Riak or MongoDB. Additionally it allows saving the result
of more complex queries than by primary key. Summarized this feature works
like the existing Query result cache, but it is much more powerful.
As an example lets cache an entity Country that is a relation to the User
entity. We always want to display the country, but avoid the additional
query to this table.
.. code-block:: php
<?php
/**
* @Entity
* @Cache(usage="READ_ONLY", region="country_region")
*/
class Country
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
/**
* @Column(unique=true)
*/
protected $name;
}
In this example we have specified a caching region name called
``country_region``, which we have to configure now on the EntityManager:
.. code-block:: php
$config = new \Doctrine\ORM\Configuration();
$config->setSecondLevelCacheEnabled();
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$regionConfig = $cacheConfig->getRegionsConfiguration();
$regionConfig->setLifetime('country_region', 3600);
Now Doctrine will first check for the data of any country in the cache
instead of the database.
- `Documentation
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
- `Pull Request #808 <https://github.com/doctrine/doctrine2/pull/808>`_
Criteria API: Support for ManyToMany assocations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We introduced support for querying collections using the `Criteria API
<http://docs.doctrine-project.org/en/latest/reference/working-with-associations.html#filtering-collections>`_
in 2.4. This only worked efficently for One-To-Many assocations, not for
Many-To-Many. With the start of 2.5 also Many-To-Many associations get queried
instead of loading them into memory.
Criteria API: Add new contains() expression
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to use the Criteria API to check for string contains needle
using ``contains()``. This translates to using a ``column LIKE '%needle%'`` SQL
condition.
.. code-block:: php
<?php
use \Doctrine\Common\Collections\Criteria;
$criteria = Criteria::create()
->where(Criteria::expr()->contains('name', 'Benjamin'));
$users = $repository->matching($criteria);
Criteria API: Support for EXTRA_LAZY
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A collection that is marked as ``fetch="EXTRA_LAZY"`` will now return another
lazy collection when using ``Collection::matching($criteria)``:
.. code-block:: php
<?php
class Post
{
/** @OneToMany(targetEntity="Comment", fetch="EXTRA_LAZY") */
private $comments;
}
$criteria = Criteria::create()
->where(Criteria->expr()->eq("published", 1));
$publishedComments = $post->getComments()->matching($criteria);
echo count($publishedComments);
The lazy criteria currently supports the ``count()`` and ``contains()``
functionality lazily. All other operations of the ``Collection`` interface
trigger a full load of the collection.
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
- `Pull Request #882 <https://github.com/doctrine/doctrine2/pull/882>`_
- `Pull Request #1032 <https://github.com/doctrine/doctrine2/pull/1032>`_
Mapping: Allow configuring Index flags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to control the index flags in the DBAL
schema abstraction from the ORM using metadata. This was possible
only with a schema event listener before.
.. code-block:: php
<?php
/**
* @Table(name="product", indexes={@Index(columns={"description"},flags={"fulltext"})})
*/
class Product
{
private $description;
}
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
- `Pull Request #973 <https://github.com/doctrine/doctrine2/pull/973>`_
SQLFilter API: Check if a parameter is set
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can now check in your SQLFilter if a parameter was set. This allows
to more easily control which features of a filter to enable or disable.
Extending on the locale example of the documentation:
.. code-block:: php
<?php
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
return "";
}
if (!$this->hasParameter('locale')) {
return "";
}
return $targetTableAlias.'.locale = ' . $this->getParameter('locale');
}
}
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
- `Pull Request #963 <https://github.com/doctrine/doctrine2/pull/963>`_
EXTRA_LAZY Improvements
~~~~~~~~~~~~~~~~~~~~~~~
1. Efficient query when using EXTRA_LAZY and containsKey
When calling ``Collection::containsKey($key)`` on one-to-many and many-to-many
collections using ``indexBy`` and ``EXTRA_LAZY`` a query is now executed to check
for the existance for the item. Prevoiusly this operation was performed in memory
by loading all entities of the collection.
.. code-block:: php
<?php
class User
{
/** @OneToMany(targetEntity="Group", indexBy="id") */
private $groups;
}
if ($user->getGroups()->containsKey($groupId)) {
echo "User is in group $groupId\n";
}
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
- `Pull Request #937 <https://github.com/doctrine/doctrine2/pull/937>`_
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
This was contributed by `Sander Marechal <https://github.com/sandermarechal>`_.
Improve efficiency of One-To-Many EAGER
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When marking a one-to-many association with ``fetch="EAGER"`` it will now
execute one query less than before and work correctly in combination with
``indexBy``.
Better support for EntityManagerInterface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Many of the locations where previously only the ``Doctrine\ORM\EntityManager``
was allowed are now changed to accept the ``EntityManagerInterface`` that was
introduced in 2.4. This allows you to more easily use the decorator pattern
to extend the EntityManager if you need. It's still not replaced everywhere,
so you still have to be careful.
DQL Improvements
~~~~~~~~~~~~~~~~
1. It is now possible to add functions to the ``ORDER BY`` clause in DQL statements:
.. code-block:: php
<?php
$dql = "SELECT u FROM User u ORDER BY CONCAT(u.username, u.name)";
2. Support for functions in ``IS NULL`` expressions:
.. code-block:: php
<?php
$dql = "SELECT u.name FROM User u WHERE MAX(u.name) IS NULL";
3. A ``LIKE`` expression is now suported in ``HAVING`` clause.
4. Subselects are now supported inside a ``NEW()`` expression:
.. code-block:: php
<?php
$dql = "SELECT new UserDTO(u.name, SELECT count(g.id) FROM Group g WHERE g.id = u.id) FROM User u";
5. ``MEMBER OF`` expression now allows to filter for more than one result:
.. code-block:: php
<?php
$dql = "SELECT u FROM User u WHERE :groups MEMBER OF u.groups";
$query = $entityManager->createQuery($dql);
$query->setParameter('groups', array(1, 2, 3));
$users = $query->getResult();
6. Expressions inside ``COUNT()`` now allowed
.. code-block:: php
<?php
$dql = "SELECT COUNT(DISTINCT CONCAT(u.name, u.lastname)) FROM User u";
7. Add support for ``HOUR`` in ``DATE_ADD()``/``DATE_SUB()`` functions
Custom DQL Functions: Add support for factories
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously custom DQL functions could only be provided with their
full-qualified class-name, preventing runtime configuration through
dependency injection.
A simplistic approach has been contributed by `Matthieu Napoli
<https://github.com/mnapoli>`_ to pass a callback instead that resolves
the function:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->addCustomNumericFunction(
'IS_PUBLISHED', function($funcName) use ($currentSiteId) {
return new IsPublishedFunction($currentSiteId);
}
);
Query API: WHERE IN Query using a Collection as parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When performing a ``WHERE IN`` query for a collection of entities you can
now pass the array collection of entities as a parameter value to the query
object:
.. code-block:: php
<?php
$categories = $rootCategory->getChildren();
$queryBuilder
->select('p')
->from('Product', 'p')
->where('p.category IN (:categories)')
->setParameter('categories', $categories)
;
This feature was contributed by `Michael Perrin
<https://github.com/michaelperrin>`_.
- `Pull Request #590 <https://github.com/doctrine/doctrine2/pull/590>`_
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
Query API: Add support for default Query Hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To configure multiple different features such as custom AST Walker, fetch modes,
locking and other features affecting DQL generation we have had a feature
called "query hints" since version 2.0.
It is now possible to add query hints that are always enabled for every Query:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->setDefaultQueryHints(
'doctrine.customOutputWalker' => 'MyProject\CustomOutputWalker'
);
This feature was contributed by `Artur Eshenbrener
<https://github.com/Strate>`_.
- `Pull Request #863 <https://github.com/doctrine/doctrine2/pull/863>`_
ResultSetMappingBuilder: Add support for Single-Table Inheritance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before 2.5 the ResultSetMappingBuilder did not work with entities
that are using Single-Table-Inheritance. This restriction was lifted
by adding the missing support.
YAML Mapping: Many-To-Many doesnt require join column definition
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In Annotations and XML it was not necessary using conventions for naming
the many-to-many join column names, in YAML it was not possible however.
A many-to-many definition in YAML is now possible using this minimal
definition:
.. code-block:: yaml
manyToMany:
groups:
targetEntity: Group
joinTable:
name: users_groups
Schema Validator Command: Allow to skip sub-checks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Schema Validator command executes two independent checks
for validity of the mappings and if the schema is synchronized
correctly. It is now possible to skip any of the two steps
when executing the command:
::
$ php vendor/bin/doctrine orm:validate-schema --skip-mapping
$ php vendor/bin/doctrine orm:validate-schema --skip-sync
This allows you to write more specialized continuous integration and automation
checks. When no changes are found the command returns the exit code 0
and 1, 2 or 3 when failing because of mapping, sync or both.
EntityGenerator Command: Avoid backups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When calling the EntityGenerator for an existing entity, Doctrine would
create a backup file every time to avoid losing changes to the code. You
can now skip generating the backup file by passing the ``--no-backup``
flag:
::
$ php vendor/bin/doctrine orm:generate-entities src/ --no-backup
Support for Objects as Identifiers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to use Objects as identifiers for Entities
as long as they implement the magic method ``__toString()``.
.. code-block:: php
<?php
class UserId
{
private $value;
public function __construct($value)
{
$this->value = $value;
}
public function __toString()
{
return (string)$this->value;
}
}
class User
{
/** @Id @Column(type="userid") */
private $id;
public function __construct(UserId $id)
{
$this->id = $id;
}
}
class UserIdType extends \Doctrine\DBAL\Types\Type
{
// ...
}
Doctrine\DBAL\Types\Type::addType('userid', 'MyProject\UserIdType');
Behavioral Changes (BC Breaks)
------------------------------
NamingStrategy interface changed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``Doctrine\ORM\Mapping\NamingStrategyInterface`` changed slightly
to pass the Class Name of the entity into the join column name generation:
::
- function joinColumnName($propertyName);
+ function joinColumnName($propertyName, $className = null);
It also received a new method for supporting embeddables:
::
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName);
Minor BC BREAK: EntityManagerInterface instead of EntityManager in type-hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
an ``EntityManagerInterface`` instead.
If you are extending any of the following classes, then you need to check following
signatures:
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
Minor BC BREAK: Custom Hydrators API change
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 2.5, ``AbstractHydrator`` does not enforce the usage of cache as part of
API, and now provides you a clean API for column information through the method
``hydrateColumnInfo($column)``.
Cache variable being passed around by reference is no longer needed since
Hydrators are per query instantiated since Doctrine 2.4.
- `DDC-3060 <http://doctrine-project.org/jira/browse/DDC-3060>`_
Minor BC BREAK: All non-transient classes in an inheritance must be part of the inheritance map
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 2.5, classes, if you define an inheritance map for an inheritance tree, you are required
to map all non-transient classes in that inheritance, including the root of the inheritance.
So far, the root of the inheritance was allowed to be skipped in the inheritance map: this is
not possible anymore, and if you don't plan to persist instances of that class, then you should
either:
- make that class as ``abstract``
- add that class to your inheritance map
If you fail to do so, then a ``Doctrine\ORM\Mapping\MappingException`` will be thrown.
- `DDC-3300 <http://doctrine-project.org/jira/browse/DDC-3300>`_
- `DDC-3503 <http://doctrine-project.org/jira/browse/DDC-3503>`_
Minor BC BREAK: Entity based EntityManager#clear() calls follow cascade detach
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Whenever ``EntityManager#clear()`` method gets called with a given entity class
name, until 2.4, it was only detaching the specific requested entity.
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
memory management since associations will be garbage collected, optimizing
resources consumption on long running jobs.
Updates on entities scheduled for deletion are no longer processed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
produce an ``UPDATE`` statement to be executed right before the ``DELETE`` statement. The entity in question
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
calculation logic is optimized away.
Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
instead of the default READ COMMITTED transaction isolation level.
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
- ``Doctrine\ORM\EntityManager#find()``
- ``Doctrine\ORM\EntityRepository#find()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
You should update signatures for these methods if you have subclassed one of the above classes.
Please also check the calling code of these methods in your application and update if necessary.
.. note::
This in fact is really a minor BC BREAK and should not have any affect on database vendors
other than SQL Server because it is the only one that supports and therefore cares about
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
Minor BC BREAK: __clone method not called anymore when entities are instantiated via metadata API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of PHP 5.6, instantiation of new entities is deferred to the
`doctrine/instantiator <https://github.com/doctrine/instantiator>`_ library, which will avoid calling ``__clone``
or any public API on instantiated objects.
BC BREAK: DefaultRepositoryFactory is now final
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Please implement the ``Doctrine\ORM\Repository\RepositoryFactory`` interface instead of extending
the ``Doctrine\ORM\Repository\DefaultRepositoryFactory``.
BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When executing DQL queries with new object expressions, instead of returning
DTOs numerically indexes, it will now respect user provided aliases. Consider
the following query:
::
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId
FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
Previously, your result would be similar to this:
::
array(
0=>array(
0=>{UserDTO object},
1=>{AddressDTO object},
2=>{u.id scalar},
3=>{u.name scalar},
4=>{a.street scalar},
5=>{a.postalCode scalar},
'addressId'=>{a.id scalar},
),
...
)
From now on, the resultset will look like this:
::
array(
0=>array(
'user'=>{UserDTO object},
'address'=>{AddressDTO object},
'addressId'=>{a.id scalar}
),
...
)

View File

@@ -11,9 +11,9 @@ There are several ways to achieve this: converting the value inside the Type
class, converting the value on the database-level or a combination of both.
This article describes the third way by implementing the MySQL specific column
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.
type `Point <https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html>`_.
The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
The ``Point`` type is part of the `Spatial extension <https://dev.mysql.com/doc/refman/8.0/en/spatial-extensions.html>`_
of MySQL and enables you to store a single location in a coordinate space by
using x and y coordinates. You can use the Point type to store a
longitude/latitude pair to represent a geographic location.
@@ -192,9 +192,9 @@ object into a string representation before saving to the database (in the
``convertToDatabaseValue`` method) and back into an object after fetching the
value from the database (in the ``convertToPHPValue`` method).
The format of the string representation format is called `Well-known text (WKT)
<http://en.wikipedia.org/wiki/Well-known_text>`_. The advantage of this format
is, that it is both human readable and parsable by MySQL.
The format of the string representation format is called
`Well-known text (WKT) <http://en.wikipedia.org/wiki/Well-known_text>`_.
The advantage of this format is, that it is both human readable and parsable by MySQL.
Internally, MySQL stores geometry values in a binary format that is not
identical to the WKT format. So, we need to let MySQL transform the WKT
@@ -204,8 +204,8 @@ This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL``
methods come into play.
This methods wrap a sql expression (the WKT representation of the Point) into
MySQL functions `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
MySQL functions `ST_PointFromText <https://dev.mysql.com/doc/refman/8.0/en/gis-wkt-functions.html#function_st-pointfromtext>`_
and `ST_AsText <https://dev.mysql.com/doc/refman/8.0/en/gis-format-conversion-functions.html#function_st-astext>`_
which convert WKT strings to and from the internal format of MySQL.
.. note::

View File

@@ -21,7 +21,7 @@ the :doc:`Native Query <../reference/native-sql>` chapter.
The DQL Parser has hooks to register functions that can then be
used in your DQL queries and transformed into SQL, allowing to
extend Doctrines Query capabilities to the vendors strength. This
post explains the Used-Defined Functions API (UDF) of the Dql
post explains the User-Defined Functions API (UDF) of the Dql
Parser and shows some examples to give you some hints how you would
extend DQL.
@@ -70,7 +70,7 @@ methods, which are quite handy in my opinion:
Date Diff
---------
`Mysql's DateDiff function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
`Mysql's DateDiff function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff>`_
takes two dates as argument and calculates the difference in days
with ``date1-date2``.
@@ -164,7 +164,7 @@ Date Add
Often useful it the ability to do some simple date calculations in
your DQL query using
`MySql's DATE\_ADD function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
`MySql's DATE_ADD function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-add>`_.
I'll skip the blah and show the code for this function:
@@ -246,6 +246,6 @@ vendor sql functions and extend the DQL languages scope.
Code for this Extension to DQL and other Doctrine Extensions can be
found
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
`in the GitHub DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.

View File

@@ -11,8 +11,8 @@ However, it is quite easy to make use of these methods in a safe
way by guarding the custom wakeup or clone code with an entity
identity check, as demonstrated in the following sections.
Safely implementing \_\_wakeup
------------------------------
Safely implementing __wakeup
----------------------------
To safely implement ``__wakeup``, simply enclose your
implementation code in an identity check as follows:
@@ -37,8 +37,8 @@ implementation code in an identity check as follows:
//...
}
Safely implementing \_\_clone
-----------------------------
Safely implementing __clone
---------------------------
Safely implementing ``__clone`` is pretty much the same:

View File

@@ -1,140 +0,0 @@
Integrating with CodeIgniter
============================
This is recipe for using Doctrine 2 in your
`CodeIgniter <http://www.codeigniter.com>`_ framework.
.. note::
This might not work for all CodeIgniter versions and may require
slight adjustments.
Here is how to set it up:
Make a CodeIgniter library that is both a wrapper and a bootstrap
for Doctrine 2.
Setting up the file structure
-----------------------------
Here are the steps:
- Add a php file to your system/application/libraries folder
called Doctrine.php. This is going to be your wrapper/bootstrap for
the D2 entity manager.
- Put the Doctrine folder (the one that contains Common, DBAL, and
ORM) inside that same libraries folder.
- Your system/application/libraries folder now looks like this:
system/applications/libraries -Doctrine -Doctrine.php -index.html
- If you want, open your config/autoload.php file and autoload
your Doctrine library.
<?php $autoload['libraries'] = array('doctrine');
Creating your Doctrine CodeIgniter library
------------------------------------------
Now, here is what your Doctrine.php file should look like.
Customize it to your needs.
.. code-block:: php
<?php
use Doctrine\Common\ClassLoader,
Doctrine\ORM\Configuration,
Doctrine\ORM\EntityManager,
Doctrine\Common\Cache\ArrayCache,
Doctrine\DBAL\Logging\EchoSQLLogger;
class Doctrine {
public $em = null;
public function __construct()
{
// load database configuration from CodeIgniter
require_once APPPATH.'config/database.php';
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
// if you want to.
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
$doctrineClassLoader->register();
$entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" ));
$entitiesClassLoader->register();
$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
$proxiesClassLoader->register();
// Set up caches
$config = new Configuration;
$cache = new ArrayCache;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// Proxy configuration
$config->setProxyDir(APPPATH.'/models/proxies');
$config->setProxyNamespace('Proxies');
// Set up logger
$logger = new EchoSQLLogger;
$config->setSQLLogger($logger);
$config->setAutoGenerateProxyClasses( TRUE );
// Database connection information
$connectionOptions = array(
'driver' => 'pdo_mysql',
'user' => $db['default']['username'],
'password' => $db['default']['password'],
'host' => $db['default']['hostname'],
'dbname' => $db['default']['database']
);
// Create EntityManager
$this->em = EntityManager::create($connectionOptions, $config);
}
}
Please note that this is a development configuration; for a
production system you'll want to use a real caching system like
APC, get rid of EchoSqlLogger, and turn off
autoGenerateProxyClasses.
For more details, consult the
`Doctrine 2 Configuration documentation <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html>`_.
Now to use it
-------------
Whenever you need a reference to the entity manager inside one of
your controllers, views, or models you can do this:
.. code-block:: php
<?php
$em = $this->doctrine->em;
That's all there is to it. Once you get the reference to your
EntityManager do your Doctrine 2.0 voodoo as normal.
Note: If you do not choose to autoload the Doctrine library, you
will need to put this line before you get a reference to it:
.. code-block:: php
<?php
$this->load->library('doctrine');
Good luck!

View File

@@ -3,7 +3,7 @@ Strategy-Pattern
This recipe will give you a short introduction on how to design
similar entities without using expensive (i.e. slow) inheritance
but with not more than \* the well-known strategy pattern \* event
but with not more than *the well-known strategy pattern* event
listeners
Scenario / Problem

View File

@@ -134,4 +134,4 @@ instances. This was already discussed in the previous blog post on
the Versionable extension, which requires another type of event
called "onFlush".
Further readings: :doc:`Lifecycle Events <../reference/events>`
Further readings: :ref:`reference-events-lifecycle-events`

View File

@@ -13,11 +13,11 @@ If this documentation is not helping to answer questions you have about
Doctrine ORM don't panic. You can get help from different sources:
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
- Internet Relay Chat (IRC) in #doctrine on Freenode
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
If you need more structure over the different topics you can browse the :doc:`table
of contents <toc>`.
@@ -72,9 +72,9 @@ Advanced Topics
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
* :doc:`Best Practices <reference/best-practices>`
* :doc:`Metadata Drivers <reference/metadata-drivers>`
@@ -95,7 +95,7 @@ Tutorials
Changelogs
----------
* :doc:`Migration to 2.5 <changelog/migration_2_5>`
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
Cookbook
--------
@@ -103,7 +103,7 @@ Cookbook
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
* **DQL Extension Points**:
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
@@ -118,9 +118,6 @@ Cookbook
:doc:`Entities in the Session <cookbook/entities-in-session>` |
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
* **Integration into Frameworks/Libraries**
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
* **Hidden Gems**
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`

View File

@@ -292,7 +292,7 @@ instance of ``Doctrine\DBAL\Connection``. If an array is passed it
is directly passed along to the DBAL Factory
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
configuration is explained in the
`DBAL section <./../../../../../projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
Proxy Objects
-------------

View File

@@ -181,7 +181,7 @@ Examples:
protected $initials;
/**
* @Column(type="integer", name="login_count" nullable=false, options={"unsigned":true, "default":0})
* @Column(type="integer", name="login_count", nullable=false, options={"unsigned":true, "default":0})
*/
protected $loginCount;

View File

@@ -18,7 +18,7 @@ well.
Requirements
------------
Doctrine 2 requires a minimum of PHP 5.4. For greatly improved
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine 2 Packages

View File

@@ -270,7 +270,7 @@ A cookbook article shows how to define :doc:`your own custom mapping types
.. warning::
All Date types assume that you are exclusively using the default timezone
set by `date_default_timezone_set() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
set by `date_default_timezone_set() <http://php.net/manual/en/function.date-default-timezone-set.php>`_
or by the php.ini configuration ``date.timezone``. Working with
different timezones will cause troubles and unexpected behavior.

View File

@@ -29,10 +29,10 @@ abstract protected methods that each of the drivers must
implement:
- \_doFetch($id)
- \_doContains($id)
- \_doSave($id, $data, $lifeTime = false)
- \_doDelete($id)
- doFetch($id)
- doContains($id)
- doSave($id, $data, $lifeTime = false)
- doDelete($id)
The public methods ``fetch()``, ``contains()`` etc. use the
above protected methods which are implemented by the drivers. The
@@ -43,7 +43,7 @@ these methods.
This documentation does not cover every single cache driver included
with Doctrine. For an up-to-date-list, see the
`cache directory on GitHub <https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache>`.
`cache directory on GitHub <https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache>`_.
APC
~~~
@@ -282,6 +282,8 @@ You can set the namespace a cache driver should use by using the
<?php
$cacheDriver->setNamespace('my_namespace_');
.. _integrating-with-the-orm:
Integrating with the ORM
------------------------

View File

@@ -1,9 +1,7 @@
Installation and Configuration
==============================
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
older versions we still have `PEAR packages
<http://pear.doctrine-project.org>`_.
Doctrine can be installed with `Composer <https://getcomposer.org>`_.
Define the following requirement in your ``composer.json`` file:
@@ -16,8 +14,7 @@ Define the following requirement in your ``composer.json`` file:
}
Then call ``composer install`` from your command line. If you don't know
how Composer works, check out their `Getting Started
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
how Composer works, check out their `Getting Started <https://getcomposer.org/doc/00-intro.md>`_ to set up.
Class loading
-------------
@@ -93,8 +90,7 @@ Inside the ``Setup`` methods several assumptions are made:
- If `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line.
- If third argument `$proxyDir` is not set, use the systems temporary directory.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced
Configuration <reference/advanced-configuration>` section.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
.. note::

View File

@@ -103,15 +103,15 @@ their inclusion in the SELECT clause.
In this case, the result will be an array of arrays. In the example
above, each element of the result array would be an array of the
scalar name and address values.
scalar name and address values.
You can select scalars from any entity in the query.
You can select scalars from any entity in the query.
**Mixed**
.. code-block:: sql
``SELECT u, p.quantity FROM Users u...``
SELECT u, p.quantity FROM Users u...
Here, the result will again be an array of arrays, with each element
being an array made up of a User object and the scalar value
@@ -691,8 +691,8 @@ clauses:
- TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim
the string by the given trim char, defaults to whitespaces.
- UPPER(str) - Return the upper-case of the given string.
- DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH)
- DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are DAY, MONTH)
- DATE_ADD(date, value, unit) - Add the given time to a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
- DATE_SUB(date, value, unit) - Subtract the given time from a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
- DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
Arithmetic operators
@@ -1172,7 +1172,7 @@ why we are listing as many of the assumptions here for reference:
- 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.
previous object is still an unloaded proxy.
This list might be incomplete.
@@ -1452,10 +1452,10 @@ Given that there are 10 users and corresponding addresses in the database the ex
SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
.. note::
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
all the necessary IDs are available after the root entity (``user`` in the above example) has been loaded. So, one
query per association can be executed to fetch all the referred-to entities (``address``).
For one-to-many relations, changing the fetch mode to eager will cause to execute one query **for every root entity
loaded**. This gives no improvement over the ``lazy`` fetch mode which will also initialize the associations on
a one-by-one basis once they are accessed.

View File

@@ -548,8 +548,9 @@ preFlush
~~~~~~~~
``preFlush`` is called at ``EntityManager#flush()`` before
anything else. ``EntityManager#flush()`` can be called safely
inside its listeners.
anything else. ``EntityManager#flush()`` should not be called inside
its listeners, since `preFlush` event is dispatched in it, which would
result in infinite loop.
.. code-block:: php

View File

@@ -4,7 +4,7 @@ Improving Performance
Bytecode Cache
--------------
It is highly recommended to make use of a bytecode cache like APC.
It is highly recommended to make use of a bytecode cache like OPcache.
A bytecode cache removes the need for parsing PHP code on every
request and can greatly improve performance.
@@ -26,6 +26,8 @@ Doctrine will need to load your mapping information on every single
request and has to parse each DQL query on every single request.
This is a waste of resources.
See :ref:`integrating-with-the-orm`
Alternative Query Result Formats
--------------------------------
@@ -42,6 +44,8 @@ for updates, which means when you call flush on the EntityManager these entities
even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
ones, they are just not considered for updates.
See :ref:`annref_entity`
Extra-Lazy Collections
----------------------
@@ -52,7 +56,7 @@ for more information on how this fetch mode works.
Temporarily change fetch mode in DQL
------------------------------------
See :ref:`Doctrine Query Language chapter <dql-temporarily-change-fetch-mode>`
See :ref:`dql-temporarily-change-fetch-mode`
Apply Best Practices
@@ -61,6 +65,7 @@ Apply Best Practices
A lot of the points mentioned in the Best Practices chapter will
also positively affect the performance of Doctrine.
See :doc:`Best Practices <reference/best-practices>`
Change Tracking policies
------------------------

View File

@@ -174,7 +174,7 @@ SQL Schema considerations
For Single-Table-Inheritance to work in scenarios where you are
using either a legacy database schema or a self-written database
schema you have to make sure that all columns that are not in the
root entity but in any of the different sub-entities has to allows
root entity but in any of the different sub-entities has to allow
null values. Columns that have NOT NULL constraints have to be on
the root entity of the single-table inheritance hierarchy.

View File

@@ -1,5 +1,4 @@
Installation
============
The installation chapter has moved to `Installation and Configuration
<reference/configuration>`_.
The installation chapter has moved to :doc:`Installation and Configuration <reference/configuration>`_.

View File

@@ -63,7 +63,7 @@ Where the ``attribute_name`` column contains the key and
``$attributes``.
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket <https://github.com/doctrine/doctrine2/issues/3743>`_.
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.
Cascade Merge with Bi-directional Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -71,8 +71,8 @@ Cascade Merge with Bi-directional Associations
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
Make sure to study the behavior of cascade merge if you are using it:
- `DDC-875 <https://github.com/doctrine/doctrine2/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <https://github.com/doctrine/doctrine2/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
- `DDC-875 <https://github.com/doctrine/orm/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <https://github.com/doctrine/orm/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
Custom Persisters
~~~~~~~~~~~~~~~~~
@@ -83,8 +83,8 @@ Currently there is no way to overwrite the persister implementation
for a given entity, however there are several use-cases that can
benefit from custom persister implementations:
- `Add Upsert Support <https://github.com/doctrine/doctrine2/issues/5178>`_
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/doctrine2/issues/4946>`_
- `Add Upsert Support <https://github.com/doctrine/orm/issues/5178>`_
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/orm/issues/4946>`_
Persist Keys of Collections
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -94,7 +94,7 @@ PHP Arrays are ordered hash-maps and so should be the
evaluate a feature that optionally persists and hydrates the keys
of a Collection instance.
`Ticket DDC-213 <https://github.com/doctrine/doctrine2/issues/2817>`_
`Ticket DDC-213 <https://github.com/doctrine/orm/issues/2817>`_
Mapping many tables to one entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -116,7 +116,6 @@ blog posts we have written on this topics:
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
- `Doctrator <https://github.com/pablodip/doctrator`>_
Doctrine 2 has enough hooks and extension points so that **you** can
add whatever you want on top of it. None of this will ever become
@@ -144,8 +143,7 @@ backwards compatibility issues or where no simple fix exists (yet).
We don't plan to add every bug in the tracker there, just those
issues that can potentially cause nightmares or pain of any sort.
See bugs, improvement and feature requests on `Github issues
<https://github.com/doctrine/doctrine2/issues>`_.
See bugs, improvement and feature requests on `Github issues <https://github.com/doctrine/orm/issues>`_.
Identifier Quoting and Legacy Databases
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -92,7 +92,7 @@ a mapping from DQL alias (key) to SQL alias (value)
<?php
$selectClause = $builder->generateSelectClause(array(
$selectClause = $rsm->generateSelectClause(array(
'u' => 't1',
'g' => 't2'
));

View File

@@ -502,7 +502,7 @@ complete list of supported helper methods available:
Adding a Criteria to a Query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can also add a :ref:`Criteria <filtering-collections>` to a QueryBuilder by
You can also add a :ref:`filtering-collections` to a QueryBuilder by
using ``addCriteria``:
.. code-block:: php

View File

@@ -97,7 +97,7 @@ Defines a contract for accessing a particular region.
Defines a contract for accessing a particular cache region.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html>`_.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Region.html>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
@@ -111,7 +111,7 @@ If you want to use an ``READ_WRITE`` cache, you should consider providing your o
Defines contract for concurrently managed data region.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html>`_.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
Timestamp region
~~~~~~~~~~~~~~~~
@@ -120,7 +120,7 @@ Timestamp region
Tracks the timestamps of the most recent updates to particular entity.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
.. _reference-second-level-cache-mode:
@@ -209,7 +209,7 @@ It allows you to provide a specific implementation of the following components :
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/DefaultCacheFactory.html>`_.
Region Lifetime
~~~~~~~~~~~~~~~
@@ -270,7 +270,7 @@ By providing a cache logger you should be able to get information about all cach
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
and collect all information you want.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Logging/CacheLogger.html>`_.
Entity cache definition

View File

@@ -10,7 +10,7 @@ we cannot protect you from SQL injection.
Please also read the documentation chapter on Security in Doctrine DBAL. This
page only handles Security issues in the ORM.
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
- `DBAL Security Page <http://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
If you find a Security bug in Doctrine, please report it on Jira and change the
Security Level to "Security Issues". It will be visible to Doctrine Core
@@ -119,7 +119,7 @@ entity might look like this:
}
Now the possiblity of mass-asignment exists on this entity and can
be exploitet by attackers to set the "isAdmin" flag to true on any
be exploited by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:
.. code-block:: php

View File

@@ -716,6 +716,7 @@ methods:
* ``in($field, array $values)``
* ``notIn($field, array $values)``
* ``contains($field, $value)``
* ``memberOf($value, $field)``
* ``startsWith($field, $value)``
* ``endsWith($field, $value)``

View File

@@ -245,7 +245,7 @@ as follows:
persist operation. However, the persist operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities are mapped with cascade=PERSIST or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`").
":ref:`transitive-persistence`").
- If X is a removed entity, it becomes managed.
- If X is a detached entity, an exception will be thrown on
flush.
@@ -286,12 +286,12 @@ as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
with cascade=REMOVE or cascade=ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
with cascade=REMOVE or cascade=ALL (see ":ref:`transitive-persistence`").
- If X is a managed entity, the remove operation causes it to
become removed. The remove operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=REMOVE or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`").
":ref:`transitive-persistence`").
- If X is a detached entity, an InvalidArgumentException will be
thrown.
- If X is a removed entity, it is ignored by the remove operation.
@@ -357,14 +357,14 @@ as follows:
become detached. The detach operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
":ref:`transitive-persistence`"). Entities which previously referenced X
will continue to reference X.
- If X is a new or detached entity, it is ignored by the detach
operation.
- If X is a removed entity, the detach operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
":ref:`transitive-persistence`"). Entities which previously referenced X
will continue to reference X.
There are several situations in which an entity is detached
@@ -423,7 +423,7 @@ as follows:
- If X is a managed entity, it is ignored by the merge operation,
however, the merge operation is cascaded to entities referenced by
relationships from X if these relationships have been mapped with
the cascade element value MERGE or ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
the cascade element value MERGE or ALL (see ":ref:`transitive-persistence`").
- For all entities Y referenced by relationships from X having the
cascade element value MERGE or ALL, Y is merged recursively as Y'.
For all such Y referenced by X, X' is set to reference Y'. (Note

83
docs/en/sidebar.rst Normal file
View File

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

View File

@@ -75,7 +75,6 @@ Cookbook
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/integrating-with-codeigniter
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction

View File

@@ -7,7 +7,7 @@ Getting Started: Database First
start with developing Objects and then map them onto your database. When
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
example UML) and generate database schema and PHP code from this model.
When you have a :doc:`Database First <getting-started-database>`, you already have a database schema
When you have a Database First, you already have a database schema
and generate the corresponding PHP code from it.
.. note::

View File

@@ -5,7 +5,7 @@ Getting Started: Model First
When you :doc:`Code First <getting-started>`, you
start with developing Objects and then map them onto your database. When
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
you Model First, you are modelling your application using tools (for
example UML) and generate database schema and PHP code from this model.
When you have a :doc:`Database First <getting-started-database>`, then you already have a database schema
and generate the corresponding PHP code from it.

View File

@@ -19,7 +19,7 @@ installed:
- PHP (latest stable version)
- Composer Package Manager (`Install Composer
<http://getcomposer.org/doc/00-intro.md>`_)
<https://getcomposer.org/doc/00-intro.md>`_)
The code of this tutorial is `available on Github <https://github.com/doctrine/doctrine2-orm-tutorial>`_.
@@ -31,9 +31,8 @@ The code of this tutorial is `available on Github <https://github.com/doctrine/d
What is Doctrine?
-----------------
Doctrine 2 is an `object-relational mapper (ORM)
<http://en.wikipedia.org/wiki/Object-relational_mapping>`_ for PHP 5.4+ that
provides transparent persistence for PHP objects. It uses the Data Mapper
Doctrine 2 is an `object-relational mapper (ORM) <https://en.wikipedia.org/wiki/Object-relational_mapping>`_
for PHP 7.1+ that provides transparent persistence for PHP objects. It uses the Data Mapper
pattern at the heart, aiming for a complete separation of your domain/business
logic from the persistence in a relational database management system.
@@ -62,7 +61,7 @@ An Example Model: Bug Tracker
For this Getting Started Guide for Doctrine we will implement the
Bug Tracker domain model from the
`Zend\_Db\_Table <http://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
`Zend_Db_Table <https://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
documentation. Reading their documentation we can extract the
requirements:
@@ -139,7 +138,10 @@ step:
// Create a simple "default" Doctrine ORM configuration for Annotations
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
$proxyDir = null;
$cache = null;
$useSimpleAnnotationReader = false;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
// or if you prefer yaml or XML
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
@@ -157,6 +159,10 @@ step:
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
.. note::
It is recommended not to use the SimpleAnnotationReader because its
usage will be removed for version 3.0.
The ``require_once`` statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
@@ -169,7 +175,7 @@ read up on the configuration details in the
The third block shows the configuration options required to connect to
a database. In this case, we'll use a file-based SQLite database. All the
configuration options for all the shipped drivers are given in the
`DBAL Configuration section of the manual <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/>`_.
`DBAL Configuration section of the manual <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/>`_.
The last block shows how the ``EntityManager`` is obtained from a
factory method.
@@ -284,14 +290,24 @@ but you only need to choose one.
<?php
// src/Product.php
use Doctrine\ORM\Mapping as ORM;
/**
* @Entity @Table(name="products")
**/
* @ORM\Entity
* @ORM\Table(name="products")
*/
class Product
{
/** @Id @Column(type="integer") @GeneratedValue **/
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
protected $id;
/** @Column(type="string") **/
/**
* @ORM\Column(type="string")
*/
protected $name;
// .. (other code)
@@ -468,28 +484,37 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
<?php
// src/Bug.php
use Doctrine\ORM\Mapping as ORM;
/**
* @Entity(repositoryClass="BugRepository") @Table(name="bugs")
* @ORM\Entity(repositoryClass="BugRepository")
* @ORM\Table(name="bugs")
*/
class Bug
{
/**
* @Id @Column(type="integer") @GeneratedValue
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
* @var int
*/
protected $id;
/**
* @Column(type="string")
* @ORM\Column(type="string")
* @var string
*/
protected $description;
/**
* @Column(type="datetime")
* @ORM\Column(type="datetime")
* @var DateTime
*/
protected $created;
/**
* @Column(type="string")
* @ORM\Column(type="string")
* @var string
*/
protected $status;
@@ -534,18 +559,25 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
/**
* @Entity @Table(name="users")
* @ORM\Entity
* @ORM\Table(name="users")
*/
class User
{
/**
* @Id @GeneratedValue @Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @var int
*/
protected $id;
/**
* @Column(type="string")
* @ORM\Column(type="string")
* @var string
*/
protected $name;
@@ -775,7 +807,7 @@ the database that points from Bugs to Products.
{
// ... (previous code)
protected $products = null;
protected $products;
public function assignToProduct(Product $product)
{
@@ -797,41 +829,50 @@ the ``Product`` before:
<?php
// src/Bug.php
use Doctrine\ORM\Mapping as ORM;
/**
* @Entity @Table(name="bugs")
**/
* @ORM\Entity
* @ORM\Table(name="bugs")
*/
class Bug
{
/**
* @Id @Column(type="integer") @GeneratedValue
**/
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
protected $id;
/**
* @Column(type="string")
**/
* @ORM\Column(type="string")
*/
protected $description;
/**
* @Column(type="datetime")
**/
* @ORM\Column(type="datetime")
*/
protected $created;
/**
* @Column(type="string")
**/
* @ORM\Column(type="string")
*/
protected $status;
/**
* @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
**/
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
*/
protected $engineer;
/**
* @ManyToOne(targetEntity="User", inversedBy="reportedBugs")
**/
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
*/
protected $reporter;
/**
* @ManyToMany(targetEntity="Product")
**/
* @ORM\ManyToMany(targetEntity="Product")
*/
protected $products;
// ... (other code)
@@ -925,34 +966,40 @@ Finally, we'll add metadata mappings for the ``User`` entity.
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
/**
* @Entity @Table(name="users")
**/
* @ORM\Entity
* @ORM\Table(name="users")
*/
class User
{
/**
* @Id @GeneratedValue @Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @var int
**/
*/
protected $id;
/**
* @Column(type="string")
* @ORM\Column(type="string")
* @var string
**/
*/
protected $name;
/**
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Bug[] An ArrayCollection of Bug objects.
**/
protected $reportedBugs = null;
*/
protected $reportedBugs;
/**
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Bug[] An ArrayCollection of Bug objects.
**/
protected $assignedBugs = null;
*/
protected $assignedBugs;
// .. (other code)
}
@@ -1490,9 +1537,12 @@ we have to adjust the metadata slightly.
.. code-block:: php
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* @Entity(repositoryClass="BugRepository")
* @Table(name="bugs")
* @ORM\Entity(repositoryClass="BugRepository")
* @ORM\Table(name="bugs")
**/
class Bug
{

View File

@@ -5,8 +5,8 @@ There are use-cases when you'll want to sort collections when they are
retrieved from the database. In userland you do this as long as you
haven't initially saved an entity with its associations into the
database. To retrieve a sorted collection from the database you can
use the ``@OrderBy`` annotation with an collection that specifies
an DQL snippet that is appended to all queries with this
use the ``@OrderBy`` annotation with a collection that specifies
a DQL snippet that is appended to all queries with this
collection.
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
@@ -64,7 +64,7 @@ positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
The semantics of this feature can be described as follows.
The semantics of this feature can be described as follows:
- ``@OrderBy`` acts as an implicit ORDER BY clause for the given
@@ -73,7 +73,7 @@ The semantics of this feature can be described as follows.
- All collections of the ordered type are always retrieved in an
ordered fashion.
- To keep the database impact low, these implicit ORDER BY items
are only added to an DQL Query if the collection is fetch joined in
are only added to a DQL Query if the collection is fetch joined in
the DQL query.
Given our previously defined example, the following would not add

View File

@@ -72,7 +72,7 @@ abstract class AbstractQuery
/**
* The parameter map of this query.
*
* @var \Doctrine\Common\Collections\ArrayCollection
* @var ArrayCollection|Parameter[]
*/
protected $parameters;
@@ -306,7 +306,7 @@ abstract class AbstractQuery
/**
* Get all defined parameters.
*
* @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
* @return ArrayCollection The defined query parameters.
*/
public function getParameters()
{
@@ -336,7 +336,7 @@ abstract class AbstractQuery
/**
* Sets a collection of query parameters.
*
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
* @param ArrayCollection|mixed[] $parameters
*
* @return static This query instance.
*/

View File

@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Cache;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -29,6 +30,7 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
use function assert;
/**
* Default query cache implementation.
@@ -106,25 +108,27 @@ class DefaultQueryCache implements QueryCache
$result = [];
$entityName = reset($rsm->aliasMap);
$hasRelation = ( ! empty($rsm->relationMap));
$hasRelation = ! empty($rsm->relationMap);
$persister = $this->uow->getEntityPersister($entityName);
$region = $persister->getCacheRegion();
$regionName = $region->getName();
assert($persister instanceof CachedEntityPersister);
$region = $persister->getCacheRegion();
$regionName = $region->getName();
$cm = $this->em->getClassMetadata($entityName);
$generateKeys = function (array $entry) use ($cm): EntityCacheKey {
$generateKeys = static function (array $entry) use ($cm) : EntityCacheKey {
return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
};
$cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
$entries = $region->getMultiple($cacheKeys);
$entries = $region->getMultiple($cacheKeys) ?? [];
// @TODO - move to cache hydration component
foreach ($cacheEntry->result as $index => $entry) {
$entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null;
$entityEntry = $entries[$index] ?? null;
if ($entityEntry === null) {
if (! $entityEntry instanceof EntityCacheEntry) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
}
@@ -145,9 +149,11 @@ class DefaultQueryCache implements QueryCache
$data = $entityEntry->data;
foreach ($entry['associations'] as $name => $assoc) {
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
assert($assocPersister instanceof CachedEntityPersister);
$assocRegion = $assocPersister->getCacheRegion();
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
if ($assoc['type'] & ClassMetadata::TO_ONE) {
@@ -267,7 +273,7 @@ class DefaultQueryCache implements QueryCache
$rootAlias = key($rsm->aliasMap);
$persister = $this->uow->getEntityPersister($entityName);
if ( ! ($persister instanceof CachedPersister)) {
if (! $persister instanceof CachedEntityPersister) {
throw CacheException::nonCacheableEntity($entityName);
}

View File

@@ -452,12 +452,14 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$class = $this->metadataFactory->getMetadataFor($cacheEntry->class);
}
if (($entity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) {
$cachedEntity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity);
if ($cachedEntity !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->entityCacheHit($this->regionName, $cacheKey);
}
return $entity;
return $cachedEntity;
}
}

View File

@@ -21,6 +21,7 @@
namespace Doctrine\ORM\Cache\Region;
use Doctrine\Common\Cache\MultiGetCache;
use Doctrine\ORM\Cache\CacheEntry;
use Doctrine\ORM\Cache\CollectionCacheEntry;
/**
@@ -67,7 +68,12 @@ class DefaultMultiGetRegion extends DefaultRegion
}
$returnableItems = [];
foreach ($keysToRetrieve as $index => $key) {
if (! $items[$key] instanceof CacheEntry) {
return null;
}
$returnableItems[$index] = $items[$key];
}

View File

@@ -94,7 +94,13 @@ class DefaultRegion implements Region
*/
public function get(CacheKey $key)
{
return $this->cache->fetch($this->getCacheEntryKey($key)) ?: null;
$entry = $this->cache->fetch($this->getCacheEntryKey($key));
if (! $entry instanceof CacheEntry) {
return null;
}
return $entry;
}
/**
@@ -108,7 +114,7 @@ class DefaultRegion implements Region
$entryKey = $this->getCacheEntryKey($key);
$entryValue = $this->cache->fetch($entryKey);
if ($entryValue === false) {
if (! $entryValue instanceof CacheEntry) {
return null;
}

View File

@@ -57,9 +57,6 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Initializes a new <tt>EntityRepository</tt>.
*
* @param EntityManager $em The EntityManager to use.
* @param Mapping\ClassMetadata $class The class descriptor.
*/
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
{

View File

@@ -703,6 +703,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
case ClassMetadata::GENERATOR_TYPE_CUSTOM:
$definition = $class->customGeneratorDefinition;
if ($definition === null) {
throw new ORMException("Can't instantiate custom generator : no custom generator definition");
}
if ( ! class_exists($definition['class'])) {
throw new ORMException("Can't instantiate custom generator : " .
$definition['class']);

View File

@@ -241,9 +241,9 @@ class ClassMetadataInfo implements ClassMetadata
* )
* </code>
*
* @var array
*
* @todo Merge with tableGeneratorDefinition into generic generatorDefinition
*
* @var array<string, string>|null
*/
public $customGeneratorDefinition;
@@ -2017,7 +2017,7 @@ class ClassMetadataInfo implements ClassMetadata
*
* @param string $fieldName
*
* @return \Doctrine\DBAL\Types\Type|string|null
* @return string|null
*
* @todo 3.0 Remove this. PersisterHelper should fix it somehow
*/
@@ -2033,7 +2033,7 @@ class ClassMetadataInfo implements ClassMetadata
*
* @param string $columnName
*
* @return \Doctrine\DBAL\Types\Type|string|null
* @return string|null
*
* @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
* that is derived by a referenced field on a different entity.

View File

@@ -25,6 +25,8 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Selectable;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping\ClassMetadata;
use function array_merge;
use function get_class;
/**
* A PersistentCollection represents a collection of elements that have persistent state.
@@ -447,7 +449,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
*/
public function count()
{
if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
if (! $this->initialized && $this->association !== null && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
return $persister->count($this) + ($this->isDirty ? $this->collection->count() : 0);
@@ -565,7 +567,9 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
if ($this->association['isOwningSide'] && $this->owner) {
$this->changed();
$uow->scheduleCollectionDeletion($this);
if (! $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingDeferredExplicit()) {
$uow->scheduleCollectionDeletion($this);
}
$this->takeSnapshot();
}
@@ -668,6 +672,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
$criteria = clone $criteria;
$criteria->where($expression);
$criteria->orderBy(array_merge($this->association['orderBy'] ?? [], $criteria->getOrderings()));
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);

View File

@@ -37,7 +37,9 @@ use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Doctrine\ORM\Utility\IdentifierFlattener;
use Doctrine\ORM\Utility\PersisterHelper;
use function array_map;
use function array_merge;
use function assert;
use function reset;
/**
@@ -569,29 +571,12 @@ class BasicEntityPersister implements EntityPersister
*/
public function delete($entity)
{
$self = $this;
$class = $this->class;
$identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
$tableName = $this->quoteStrategy->getTableName($class, $this->platform);
$idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform);
$id = array_combine($idColumns, $identifier);
$types = array_map(function ($identifier) use ($class, $self) {
if (isset($class->fieldMappings[$identifier])) {
return $class->fieldMappings[$identifier]['type'];
}
$targetMapping = $self->em->getClassMetadata($class->associationMappings[$identifier]['targetEntity']);
if (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])) {
return $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type'];
}
if (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])) {
return $targetMapping->associationMappings[$targetMapping->identifier[0]]['type'];
}
throw ORMException::unrecognizedField($targetMapping->identifier[0]);
}, $class->identifier);
$types = $this->getClassIdentifiersTypes($class);
$this->deleteJoinTableRecords($identifier);
@@ -2089,4 +2074,22 @@ class BasicEntityPersister implements EntityPersister
$this->currentPersisterContext = $this->limitsHandlingContext;
}
/**
* @return string[]
*/
protected function getClassIdentifiersTypes(ClassMetadata $class) : array
{
$entityManager = $this->em;
return array_map(
static function ($fieldName) use ($class, $entityManager) : string {
$types = PersisterHelper::getTypeOfField($fieldName, $class, $entityManager);
assert(isset($types[0]));
return $types[0];
},
$class->identifier
);
}
}

View File

@@ -278,22 +278,25 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// If the database platform supports FKs, just
// delete the row from the root table. Cascades do the rest.
if ($this->platform->supportsForeignKeyConstraints()) {
$rootClass = $this->em->getClassMetadata($this->class->rootEntityName);
$rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform);
$rootClass = $this->em->getClassMetadata($this->class->rootEntityName);
$rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform);
$rootTypes = $this->getClassIdentifiersTypes($rootClass);
return (bool) $this->conn->delete($rootTable, $id);
return (bool) $this->conn->delete($rootTable, $id, $rootTypes);
}
// Delete from all tables individually, starting from this class' table up to the root table.
$rootTable = $this->quoteStrategy->getTableName($this->class, $this->platform);
$rootTypes = $this->getClassIdentifiersTypes($this->class);
$affectedRows = $this->conn->delete($rootTable, $id);
$affectedRows = $this->conn->delete($rootTable, $id, $rootTypes);
foreach ($this->class->parentClasses as $parentClass) {
$parentMetadata = $this->em->getClassMetadata($parentClass);
$parentTable = $this->quoteStrategy->getTableName($parentMetadata, $this->platform);
$parentTypes = $this->getClassIdentifiersTypes($parentMetadata);
$this->conn->delete($parentTable, $id);
$this->conn->delete($parentTable, $id, $parentTypes);
}
return (bool) $affectedRows;

View File

@@ -19,15 +19,18 @@
namespace Doctrine\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\ParameterTypeInferer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\ParserResult;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ParameterTypeInferer;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
use function array_keys;
use function assert;
/**
* A Query object represents a DQL query.
@@ -387,26 +390,13 @@ final class Query extends AbstractQuery
$types = [];
foreach ($this->parameters as $parameter) {
$key = $parameter->getName();
$value = $parameter->getValue();
$rsm = $this->getResultSetMapping();
$key = $parameter->getName();
if ( ! isset($paramMappings[$key])) {
throw QueryException::unknownParameter($key);
}
if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) {
$value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
}
if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
$value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em));
}
$value = $this->processParameterValue($value);
$type = ($parameter->getValue() === $value)
? $parameter->getType()
: ParameterTypeInferer::inferType($value);
[$value, $type] = $this->resolveParameterValue($parameter);
foreach ($paramMappings[$key] as $position) {
$types[$position] = $type;
@@ -439,6 +429,38 @@ final class Query extends AbstractQuery
return [$sqlParams, $types];
}
/** @return mixed[] tuple of (value, type) */
private function resolveParameterValue(Parameter $parameter) : array
{
if ($parameter->typeWasSpecified()) {
return [$parameter->getValue(), $parameter->getType()];
}
$key = $parameter->getName();
$originalValue = $parameter->getValue();
$value = $originalValue;
$rsm = $this->getResultSetMapping();
assert($rsm !== null);
if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) {
$value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
}
if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) {
$value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em));
}
$processedValue = $this->processParameterValue($value);
return [
$processedValue,
$originalValue === $processedValue
? $parameter->getType()
: ParameterTypeInferer::inferType($processedValue),
];
}
/**
* Defines a cache driver to be used for caching queries.
*

View File

@@ -19,6 +19,8 @@
namespace Doctrine\ORM\Query;
use function trim;
/**
* Defines a Query Parameter.
*
@@ -49,6 +51,13 @@ class Parameter
*/
private $type;
/**
* Whether the parameter type was explicitly specified or not
*
* @var bool
*/
private $typeSpecified;
/**
* Constructor.
*
@@ -58,7 +67,8 @@ class Parameter
*/
public function __construct($name, $value, $type = null)
{
$this->name = trim($name, ':');
$this->name = trim($name, ':');
$this->typeSpecified = $type !== null;
$this->setValue($value, $type);
}
@@ -104,4 +114,9 @@ class Parameter
$this->value = $value;
$this->type = $type ?: ParameterTypeInferer::inferType($value);
}
public function typeWasSpecified() : bool
{
return $this->typeSpecified;
}
}

View File

@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Query;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\AST\Functions;
use function in_array;
use function strpos;
/**
@@ -469,7 +470,7 @@ class Parser
public function semanticalError($message = '', $token = null)
{
if ($token === null) {
$token = $this->lexer->lookahead;
$token = $this->lexer->lookahead ?? ['position' => null];
}
// Minimum exposed chars ahead of token
@@ -536,7 +537,7 @@ class Parser
*/
private function isMathOperator($token)
{
return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
return $token !== null && in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
}
/**
@@ -551,7 +552,7 @@ class Parser
$this->lexer->resetPeek();
return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS);
return $lookaheadType >= Lexer::T_IDENTIFIER && $peek !== null && $peek['type'] === Lexer::T_OPEN_PARENTHESIS;
}
/**
@@ -842,7 +843,7 @@ class Parser
$this->lexer->moveNext();
switch ($this->lexer->lookahead['type']) {
switch ($this->lexer->lookahead['type'] ?? null) {
case Lexer::T_SELECT:
$statement = $this->SelectStatement();
break;
@@ -1464,7 +1465,7 @@ class Parser
// We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
$glimpse = $this->lexer->glimpse();
if ($glimpse['type'] === Lexer::T_DOT) {
if ($glimpse !== null && $glimpse['type'] === Lexer::T_DOT) {
return $this->SingleValuedPathExpression();
}
@@ -1508,7 +1509,7 @@ class Parser
$expr = $this->SimpleArithmeticExpression();
break;
case ($glimpse['type'] === Lexer::T_DOT):
case $glimpse !== null && $glimpse['type'] === Lexer::T_DOT:
$expr = $this->SingleValuedPathExpression();
break;
@@ -2479,9 +2480,11 @@ class Parser
// Peek beyond the matching closing parenthesis ')'
$peek = $this->peekBeyondClosingParenthesis();
if (in_array($peek['value'], ["=", "<", "<=", "<>", ">", ">=", "!="]) ||
if ($peek !== null && (
in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!=']) ||
in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
$this->isMathOperator($peek)) {
$this->isMathOperator($peek)
)) {
$condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
return $condPrimary;
@@ -2833,11 +2836,11 @@ class Parser
case Lexer::T_IDENTIFIER:
$peek = $this->lexer->glimpse();
if ($peek['value'] == '(') {
if ($peek !== null && $peek['value'] === '(') {
return $this->FunctionDeclaration();
}
if ($peek['value'] == '.') {
if ($peek !== null && $peek['value'] === '.') {
return $this->SingleValuedPathExpression();
}
@@ -2853,7 +2856,7 @@ class Parser
default:
$peek = $this->lexer->glimpse();
if ($peek['value'] == '(') {
if ($peek !== null && $peek['value'] === '(') {
return $this->FunctionDeclaration();
}
@@ -3192,7 +3195,7 @@ class Parser
$escapeChar = null;
if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
if ($this->lexer->lookahead !== null && $this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
$this->match(Lexer::T_ESCAPE);
$this->match(Lexer::T_STRING);

View File

@@ -186,6 +186,8 @@ class QueryExpressionVisitor extends ExpressionVisitor
$this->parameters[] = $parameter;
return $this->expr->like($field, $placeholder);
case Comparison::MEMBER_OF:
return $this->expr->isMemberOf($comparison->getField(), $comparison->getValue()->getValue());
case Comparison::STARTS_WITH:
$parameter->setValue($parameter->getValue() . '%', $parameter->getType());
$this->parameters[] = $parameter;

View File

@@ -584,4 +584,3 @@ class ResultSetMapping
return $this;
}
}

View File

@@ -48,11 +48,12 @@ class YamlExporter extends AbstractExporter
} else {
$array['type'] = 'entity';
}
$metadataTable = $metadata->table ?? ['name' => null];
$array['table'] = $metadata->table['name'];
$array['table'] = $metadataTable['name'];
if (isset($metadata->table['schema'])) {
$array['schema'] = $metadata->table['schema'];
if (isset($metadataTable['schema'])) {
$array['schema'] = $metadataTable['schema'];
}
$inheritanceType = $metadata->inheritanceType;
@@ -73,20 +74,20 @@ class YamlExporter extends AbstractExporter
$array['changeTrackingPolicy'] = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy);
}
if (isset($metadata->table['indexes'])) {
$array['indexes'] = $metadata->table['indexes'];
if (isset($metadataTable['indexes'])) {
$array['indexes'] = $metadataTable['indexes'];
}
if ($metadata->customRepositoryClassName) {
$array['repositoryClass'] = $metadata->customRepositoryClassName;
}
if (isset($metadata->table['uniqueConstraints'])) {
$array['uniqueConstraints'] = $metadata->table['uniqueConstraints'];
if (isset($metadataTable['uniqueConstraints'])) {
$array['uniqueConstraints'] = $metadataTable['uniqueConstraints'];
}
if (isset($metadata->table['options'])) {
$array['options'] = $metadata->table['options'];
if (isset($metadataTable['options'])) {
$array['options'] = $metadataTable['options'];
}
$fieldMappings = $metadata->fieldMappings;

View File

@@ -19,11 +19,12 @@
namespace Doctrine\ORM\Tools\Pagination;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\QueryBuilder;
use function array_map;
/**
* The paginator can handle various complex scenarios with DQL.
@@ -150,14 +151,16 @@ class Paginator implements \Countable, \IteratorAggregate
$subQuery->setFirstResult($offset)->setMaxResults($length);
$ids = array_map('current', $subQuery->getScalarResult());
$foundIdRows = $subQuery->getScalarResult();
$whereInQuery = $this->cloneQuery($this->query);
// don't do this for an empty id array
if (count($ids) === 0) {
if ($foundIdRows === []) {
return new \ArrayIterator([]);
}
$whereInQuery = $this->cloneQuery($this->query);
$ids = array_map('current', $foundIdRows);
$this->appendTreeWalker($whereInQuery, WhereInWalker::class);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
$whereInQuery->setFirstResult(null)->setMaxResults(null);

View File

@@ -19,19 +19,24 @@
namespace Doctrine\ORM\Tools\Pagination;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\AST\ArithmeticExpression;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\TreeWalkerAdapter;
use Doctrine\ORM\Query\AST\SelectStatement;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\AST\InExpression;
use Doctrine\ORM\Query\AST\NullComparisonExpression;
use Doctrine\ORM\Query\AST\InputParameter;
use Doctrine\ORM\Query\AST\ConditionalPrimary;
use Doctrine\ORM\Query\AST\ConditionalTerm;
use Doctrine\ORM\Query\AST\ConditionalExpression;
use Doctrine\ORM\Query\AST\ConditionalFactor;
use Doctrine\ORM\Query\AST\ConditionalPrimary;
use Doctrine\ORM\Query\AST\ConditionalTerm;
use Doctrine\ORM\Query\AST\InExpression;
use Doctrine\ORM\Query\AST\InputParameter;
use Doctrine\ORM\Query\AST\NullComparisonExpression;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\AST\SelectStatement;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\AST\WhereClause;
use Doctrine\ORM\Query\TreeWalkerAdapter;
use Doctrine\ORM\Utility\PersisterHelper;
use function array_map;
use function assert;
use function is_array;
/**
* Replaces the whereClause of the AST with a WHERE id IN (:foo_1, :foo_2) equivalent.
@@ -83,6 +88,7 @@ class WhereInWalker extends TreeWalkerAdapter
$fromRoot = reset($from);
$rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
/** @var ClassMetadata $rootClass */
$rootClass = $queryComponents[$rootAlias]['metadata'];
$identifierFieldName = $rootClass->getSingleIdentifierFieldName();
@@ -104,6 +110,14 @@ class WhereInWalker extends TreeWalkerAdapter
$expression = new InExpression($arithmeticExpression);
$expression->literals[] = new InputParameter(":" . self::PAGINATOR_ID_ALIAS);
$this->convertWhereInIdentifiersToDatabaseValue(
PersisterHelper::getTypeOfField(
$identifierFieldName,
$rootClass,
$this->_getQuery()
->getEntityManager()
)[0]
);
} else {
$expression = new NullComparisonExpression($pathExpression);
$expression->not = false;
@@ -147,4 +161,24 @@ class WhereInWalker extends TreeWalkerAdapter
);
}
}
private function convertWhereInIdentifiersToDatabaseValue(string $type) : void
{
$query = $this->_getQuery();
$identifiersParameter = $query->getParameter(self::PAGINATOR_ID_ALIAS);
assert($identifiersParameter !== null);
$identifiers = $identifiersParameter->getValue();
assert(is_array($identifiers));
$connection = $this->_getQuery()
->getEntityManager()
->getConnection();
$query->setParameter(self::PAGINATOR_ID_ALIAS, array_map(static function ($id) use ($connection, $type) {
return $connection->convertToDatabaseValue($id, $type);
}, $identifiers));
}
}

View File

@@ -90,7 +90,7 @@ class UnitOfWork implements PropertyChangedListener
/**
* Hint used to collect all primary keys of associated entities during hydration
* and execute it in a dedicated query afterwards
* @see https://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html?highlight=eager#temporarily-change-fetch-mode-in-dql
* @see https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql
*/
const HINT_DEFEREAGERLOAD = 'deferEagerLoad';
@@ -356,6 +356,8 @@ class UnitOfWork implements PropertyChangedListener
$this->dispatchOnFlushEvent();
$this->dispatchPostFlushEvent();
$this->postCommitCleanup($entity);
return; // Nothing to do.
}

View File

@@ -39,7 +39,7 @@ class PersisterHelper
* @param ClassMetadata $class
* @param EntityManagerInterface $em
*
* @return array
* @return array<int, string>
*
* @throws QueryException
*/

View File

@@ -35,7 +35,7 @@ class Version
/**
* Current Doctrine Version
*/
const VERSION = '2.6.3';
const VERSION = '2.6.4-DEV';
/**
* Compares a Doctrine version with the current one.

View File

@@ -4,10 +4,10 @@
<arg name="extensions" value="php"/>
<arg name="parallel" value="80"/>
<arg name="cache" value=".phpcs-cache"/>
<arg name="colors" />
<arg name="colors"/>
<!-- Ignore warnings and show progress of the run -->
<arg value="np"/>
<!-- Ignore warnings, show progress of the run and show sniff names -->
<arg value="nps"/>
<file>lib</file>
<file>tests</file>
@@ -15,10 +15,144 @@
<exclude-pattern>*/tests/Doctrine/Tests/Proxies/__CG__/*</exclude-pattern>
<rule ref="Doctrine" />
<rule ref="Doctrine">
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint"/>
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint"/>
<exclude name="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException"/>
<exclude name="SlevomatCodingStandard.ControlStructures.EarlyExit"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming"/>
</rule>
<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
</ruleset>
<rule ref="Squiz.Classes.ClassFileName.NoMatch">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
<rule ref="Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase">
<exclude-pattern>lib/Doctrine/ORM/Events.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Tools/ToolEvents.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversablePropertyTypeHintSpecification">
<exclude-pattern>lib/Doctrine/ORM/Annotation/*</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedNotEqualOperator">
<exclude-pattern>lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php</exclude-pattern>
</rule>
<rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
<exclude-pattern>lib/Doctrine/ORM/Query/Parser.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName">
<exclude-pattern>lib/Doctrine/ORM/Mapping/AssociationOverride.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/AssociationOverrides.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/AttributeOverride.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/AttributeOverrides.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/Cache.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/Column.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/ColumnResult.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/CustomIdGenerator.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/DiscriminatorMap.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/Embeddable.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/Embedded.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/Entity.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/EntityListeners.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/EntityResult.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/FieldResult.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/GeneratedValue.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/Id.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/Index.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/InheritanceType.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/JoinColumn.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/JoinColumns.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/JoinTable.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/ManyToMany.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/ManyToOne.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/MappedSuperclass.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/NamedNativeQueries.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/NamedNativeQuery.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/NamedQueries.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/NamedQuery.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/OneToMany.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/OneToOne.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/OrderBy.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/PostLoad.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/PostPersist.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/PostRemove.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/PostUpdate.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/PreFlush.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/PrePersist.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/PreRemove.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/PreUpdate.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/SequenceGenerator.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/Table.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/UniqueConstraint.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Mapping/Version.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Commenting.EmptyComment">
<exclude-pattern>lib/Doctrine/ORM/Cache/DefaultQueryCache.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming">
<exclude-pattern>lib/Doctrine/ORM/EntityManagerInterface.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingPropertyTypeHint">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedProperty">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversableParameterTypeHintSpecification">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Classes.UnusedPrivateElements.WriteOnlyProperty">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversablePropertyTypeHintSpecification">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversableReturnTypeHintSpecification">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
<!-- intentionally without namespace -->
<rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
<exclude-pattern>tests/Doctrine/Tests/Models/Global/GlobalNamespaceModel.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/Models/DDC3231/DDC3231User1NoNamespace.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/Models/DDC3231/DDC3231User2NoNamespace.php</exclude-pattern>
</rule>
<!-- file with multiple namespaces confuses the sniff -->
<rule ref="PSR2.Namespaces.UseDeclaration.UseAfterNamespace">
<exclude-pattern>tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2084Test.php</exclude-pattern>
</rule>
<!-- file with multiple namespaces confuses the sniff -->
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses.IncorrectlyOrderedUses">
<exclude-pattern>tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2084Test.php</exclude-pattern>
</rule>
<!-- intentionally empty blocks -->
<rule ref="Generic.CodeAnalysis.EmptyStatement.DetectedForeach">
<exclude-pattern>tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php</exclude-pattern>
</rule>
</ruleset>

View File

@@ -18,11 +18,6 @@
failOnRisky="true"
bootstrap="./tests/Doctrine/Tests/TestInit.php"
>
<php>
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine ORM Test Suite">
<directory>./tests/Doctrine/Tests/ORM</directory>
@@ -54,6 +49,8 @@
<var name="tmpdb_password" value="" />
<var name="tmpdb_name" value="doctrine_tests_tmp" />
<var name="tmpdb_port" value="3306"/>
<env name="COLUMNS" value="120"/>
</php>
</phpunit>

View File

@@ -1,13 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Performance;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Cache\ArrayStatement;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\PDOSqlite\Driver;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Tools\SchemaTool;
use function array_map;
use function realpath;
final class EntityManagerFactory
{
@@ -20,7 +28,7 @@ final class EntityManagerFactory
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver([
realpath(__DIR__ . '/Models/Cache'),
realpath(__DIR__ . '/Models/GeoNames')
realpath(__DIR__ . '/Models/GeoNames'),
], true));
$entityManager = EntityManager::create(
@@ -36,4 +44,30 @@ final class EntityManagerFactory
return $entityManager;
}
public static function makeEntityManagerWithNoResultsConnection() : EntityManagerInterface
{
$config = new Configuration();
$config->setProxyDir(__DIR__ . '/../Tests/Proxies');
$config->setProxyNamespace('Doctrine\Tests\Proxies');
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver([
realpath(__DIR__ . '/Models/Cache'),
realpath(__DIR__ . '/Models/Generic'),
realpath(__DIR__ . '/Models/GeoNames'),
], true));
// A connection that doesn't really do anything
$connection = new class ([], new Driver(), null, new EventManager()) extends Connection
{
/** {@inheritdoc} */
public function executeQuery($query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
{
return new ArrayStatement([]);
}
};
return EntityManager::create($connection, $config);
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Doctrine\Performance\Query;
use DateTime;
use Doctrine\DBAL\Types\DateTimeType;
use Doctrine\ORM\Query;
use Doctrine\Performance\EntityManagerFactory;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
use function range;
/**
* @BeforeMethods({"init"})
*/
final class QueryBoundParameterProcessingBench
{
/** @var Query */
private $parsedQueryWithInferredParameterType;
/** @var Query */
private $parsedQueryWithDeclaredParameterType;
public function init() : void
{
$entityManager = EntityManagerFactory::makeEntityManagerWithNoResultsConnection();
// Note: binding a lot of parameters because DQL operations are noisy due to hydrators and other components
// kicking in, so we make the parameter operations more noticeable.
$dql = <<<'DQL'
SELECT e
FROM Doctrine\Tests\Models\Generic\DateTimeModel e
WHERE
e.datetime = :parameter1
OR
e.datetime = :parameter2
OR
e.datetime = :parameter3
OR
e.datetime = :parameter4
OR
e.datetime = :parameter5
OR
e.datetime = :parameter6
OR
e.datetime = :parameter7
OR
e.datetime = :parameter8
OR
e.datetime = :parameter9
OR
e.datetime = :parameter10
DQL;
$this->parsedQueryWithInferredParameterType = $entityManager->createQuery($dql);
$this->parsedQueryWithDeclaredParameterType = $entityManager->createQuery($dql);
foreach (range(1, 10) as $index) {
$this->parsedQueryWithInferredParameterType->setParameter('parameter' . $index, new DateTime());
$this->parsedQueryWithDeclaredParameterType->setParameter('parameter' . $index, new DateTime(), DateTimeType::DATETIME);
}
// Force parsing upfront - we don't benchmark that bit in this scenario
$this->parsedQueryWithInferredParameterType->getSQL();
$this->parsedQueryWithDeclaredParameterType->getSQL();
}
public function benchExecuteParsedQueryWithInferredParameterType() : void
{
$this->parsedQueryWithInferredParameterType->execute();
}
public function benchExecuteParsedQueryWithDeclaredParameterType() : void
{
$this->parsedQueryWithDeclaredParameterType->execute();
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\ValueConversionType;
/**

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\ValueConversionType;
/**
* @Entity
* @Table(name="vct_owning_manytoone_foreignkey")
*/
class OwningManyToOneIdForeignKeyEntity
{
/**
* @Id
* @ManyToOne(targetEntity=AuxiliaryEntity::class, inversedBy="associatedEntities")
* @JoinColumn(name="associated_id", referencedColumnName="id4")
*/
public $associatedEntity;
}

View File

@@ -354,6 +354,34 @@ class DefaultQueryCacheTest extends OrmTestCase
$this->assertEquals('Bar', $result[1]->getName());
}
public function testGetWithAssociationCacheMiss() : void
{
$rsm = new ResultSetMappingBuilder($this->em);
$key = new QueryCacheKey('query.key1', 0);
$entry = new QueryCacheEntry(
[
['identifier' => ['id' => 1]],
['identifier' => ['id' => 2]],
]
);
$this->region->addReturn('get', $entry);
$this->region->addReturn(
'getMultiple',
[
new EntityCacheEntry(Country::class, ['id' => 1, 'name' => 'Foo']),
false,
]
);
$rsm->addRootEntityFromClassMetadata(Country::class, 'c');
$result = $this->queryCache->get($key, $rsm);
self::assertNull($result);
}
public function testCancelPutResultIfEntityPutFails()
{
$result = [];

View File

@@ -95,6 +95,32 @@ class DefaultRegionTest extends AbstractRegionTest
$this->assertEquals($value1, $actual[0]);
$this->assertEquals($value2, $actual[1]);
}
/**
* @test
* @group GH7266
*/
public function corruptedDataDoesNotLeakIntoApplicationWhenGettingSingleEntry() : void
{
$key1 = new CacheKeyMock('key.1');
$this->cache->save($this->region->getName() . '_' . $key1->hash, 'a-very-invalid-value');
self::assertTrue($this->region->contains($key1));
self::assertNull($this->region->get($key1));
}
/**
* @test
* @group GH7266
*/
public function corruptedDataDoesNotLeakIntoApplicationWhenGettingMultipleEntries() : void
{
$key1 = new CacheKeyMock('key.1');
$this->cache->save($this->region->getName() . '_' . $key1->hash, 'a-very-invalid-value');
self::assertTrue($this->region->contains($key1));
self::assertNull($this->region->getMultiple(new CollectionCacheEntry([$key1])));
}
}
/**

View File

@@ -39,4 +39,17 @@ class MultiGetRegionTest extends AbstractRegionTest
$this->assertEquals($value1, $actual[0]);
$this->assertEquals($value2, $actual[1]);
}
/**
* @test
* @group GH7266
*/
public function corruptedDataDoesNotLeakIntoApplication() : void
{
$key1 = new CacheKeyMock('key.1');
$this->cache->save($this->region->getName() . '_' . $key1->hash, 'a-very-invalid-value');
self::assertTrue($this->region->contains($key1));
self::assertNull($this->region->getMultiple(new CollectionCacheEntry([$key1])));
}
}

View File

@@ -35,52 +35,6 @@ abstract class AbstractEntityPersisterTest extends OrmTestCase
*/
protected $em;
/**
* @var array
*/
protected $regionMockMethods = [
'getName',
'contains',
'get',
'getMultiple',
'put',
'evict',
'evictAll'
];
/**
* @var array
*/
protected $entityPersisterMockMethods = [
'getClassMetadata',
'getResultSetMapping',
'getInserts',
'getInsertSQL',
'getSelectSQL',
'getCountSQL',
'expandParameters',
'expandCriteriaParameters',
'getSelectConditionStatementSQL',
'addInsert',
'executeInserts',
'update',
'delete',
'getOwningTable',
'load',
'loadById',
'loadOneToOneEntity',
'count',
'refresh',
'loadCriteria',
'loadAll',
'getManyToManyCollection',
'loadManyToManyCollection',
'loadOneToManyCollection',
'lock',
'getOneToManyCollection',
'exists'
];
/**
* @param \Doctrine\ORM\EntityManager $em
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $persister
@@ -97,11 +51,9 @@ abstract class AbstractEntityPersisterTest extends OrmTestCase
$this->enableSecondLevelCache();
parent::setUp();
$this->em = $this->_getTestEntityManager();
$this->region = $this->createRegion();
$this->entityPersister = $this->getMockBuilder(EntityPersister::class)
->setMethods($this->entityPersisterMockMethods)
->getMock();
$this->em = $this->_getTestEntityManager();
$this->region = $this->createRegion();
$this->entityPersister = $this->createMock(EntityPersister::class);
}
/**
@@ -109,9 +61,7 @@ abstract class AbstractEntityPersisterTest extends OrmTestCase
*/
protected function createRegion()
{
return $this->getMockBuilder(Region::class)
->setMethods($this->regionMockMethods)
->getMock();
return $this->createMock(Region::class);
}
/**

View File

@@ -17,18 +17,6 @@ use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister;
*/
class ReadWriteCachedEntityPersisterTest extends AbstractEntityPersisterTest
{
protected $regionMockMethods = [
'getName',
'contains',
'get',
'getMultiple',
'put',
'evict',
'evictAll',
'lock',
'unlock',
];
/**
* {@inheritdoc}
*/
@@ -42,9 +30,7 @@ class ReadWriteCachedEntityPersisterTest extends AbstractEntityPersisterTest
*/
protected function createRegion()
{
return $this->getMockBuilder(ConcurrentRegion::class)
->setConstructorArgs($this->regionMockMethods)
->getMock();
return $this->createMock(ConcurrentRegion::class);
}
public function testDeleteShouldLockItem()

View File

@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\Tests\DbalTypes\CustomIdObject;
use Doctrine\Tests\OrmFunctionalTestCase;
use function str_replace;
/**
* Functional tests for the Class Table Inheritance mapping strategy with custom id object types.
*
* @group GH5988
*/
final class GH5988Test extends OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
if (! DBALType::hasType(GH5988CustomIdObjectHashType::class)) {
DBALType::addType(GH5988CustomIdObjectHashType::class, GH5988CustomIdObjectHashType::class);
}
$this->setUpEntitySchema([GH5988CustomIdObjectTypeParent::class, GH5988CustomIdObjectTypeChild::class]);
}
public function testDelete()
{
$object = new GH5988CustomIdObjectTypeChild(new CustomIdObject('foo'), 'Test');
$this->_em->persist($object);
$this->_em->flush();
$id = $object->id;
$object2 = $this->_em->find(GH5988CustomIdObjectTypeChild::class, $id);
$this->_em->remove($object2);
$this->_em->flush();
self::assertNull($this->_em->find(GH5988CustomIdObjectTypeChild::class, $id));
}
}
class GH5988CustomIdObjectHashType extends DBALType
{
/**
* {@inheritdoc}
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return $value->id . '_test';
}
/**
* {@inheritdoc}
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return new CustomIdObject(str_replace('_test', '', $value));
}
/**
* {@inheritdoc}
*/
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return self::class;
}
}
/**
* @Entity
* @Table
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="type", type="string")
* @DiscriminatorMap({"child" = GH5988CustomIdObjectTypeChild::class})
*/
abstract class GH5988CustomIdObjectTypeParent
{
/**
* @Id
* @Column(type="Doctrine\Tests\ORM\Functional\GH5988CustomIdObjectHashType")
* @var CustomIdObject
*/
public $id;
}
/**
* @Entity
* @Table
*/
class GH5988CustomIdObjectTypeChild extends GH5988CustomIdObjectTypeParent
{
/** @var string */
public $name;
public function __construct(CustomIdObject $id, string $name)
{
$this->id = $id;
$this->name = $name;
}
}

View File

@@ -270,7 +270,7 @@ DQL;
}
/**
* https://github.com/doctrine/doctrine2/issues/6568
* https://github.com/doctrine/orm/issues/6568
*/
public function testPostLoadIsInvokedOnFetchJoinedEntities()
{

View File

@@ -0,0 +1,48 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH7629Test extends OrmFunctionalTestCase
{
/**
* {@inheritDoc}
*/
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH7629Entity::class,
]);
$this->_em->persist(new GH7629Entity());
$this->_em->flush();
$this->_em->clear();
}
public function testClearScheduledForSynchronizationWhenCommitEmpty(): void
{
$entity = $this->_em->find(GH7629Entity::class, 1);
$this->_em->persist($entity);
$this->_em->flush();
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForDirtyCheck($entity));
}
}
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class GH7629Entity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
public $id;
}

View File

@@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\OrmFunctionalTestCase;
use function assert;
final class GH7735Test extends OrmFunctionalTestCase
{
public function setUp() : void
{
$this->enableSecondLevelCache();
parent::setUp();
$this->_schemaTool->createSchema(
[
$this->_em->getClassMetadata(GH7735Car::class),
$this->_em->getClassMetadata(GH7735Power::class),
$this->_em->getClassMetadata(GH7735Engine::class),
]
);
$this->_em->persist(new GH7735Car(1, new GH7735Engine(1, 'turbo', new GH7735Power(1))));
$this->_em->flush();
$this->_em->clear();
}
/**
* @test
* @group GH7735
*/
public function findByReturnsCachedEntity() : void
{
$this->_em->getCache()->evictEntityRegion(GH7735Power::class);
$car = $this->_em->find(GH7735Car::class, 1);
assert($car instanceof GH7735Car);
self::assertSame('turbo', $car->getEngine()->getModel());
self::assertSame(1, $car->getEngine()->getPower()->getId());
}
}
/**
* @Entity @Cache(usage="READ_ONLY")
*/
class GH7735Car
{
/**
* @Id
* @Column(type="integer")
* @var int
*/
private $id;
/**
* @ManyToOne(targetEntity=GH7735Engine::class, cascade={"all"})
* @JoinColumn(nullable=false)
* @Cache("READ_ONLY")
* @var GH7735Engine
*/
private $engine;
public function __construct(int $id, GH7735Engine $engine)
{
$this->id = $id;
$this->engine = $engine;
}
public function getId() : int
{
return $this->id;
}
public function getEngine() : GH7735Engine
{
return $this->engine;
}
}
/**
* @Entity
* @Cache(usage="READ_ONLY")
*/
class GH7735Engine
{
/**
* @Id
* @Column(type="integer")
* @var int
*/
private $id;
/**
* @OneToOne(targetEntity=GH7735Power::class, mappedBy="engine", cascade={"all"})
* @Cache("READ_ONLY")
* @var GH7735Power
*/
private $power;
/**
* @Column
* @var string
*/
private $model;
public function __construct(int $id, string $model, GH7735Power $power)
{
$this->id = $id;
$this->model = $model;
$this->power = $power;
$power->setEngine($this);
}
public function getId() : int
{
return $this->id;
}
public function getPower() : GH7735Power
{
return $this->power;
}
public function getModel() : string
{
return $this->model;
}
}
/**
* @Entity
* @Cache(usage="READ_ONLY")
*/
class GH7735Power
{
/**
* @Id
* @Column(type="integer")
*/
private $id;
/**
* @OneToOne(targetEntity=GH7735Engine::class, inversedBy="power")
* @Cache("READ_ONLY")
* @var GH7735Engine
*/
private $engine;
public function __construct(int $id)
{
$this->id = $id;
}
public function getId() : int
{
return $this->id;
}
public function setEngine(GH7735Engine $engine) : void
{
$this->engine = $engine;
}
public function getEngine() : GH7735Engine
{
return $this->engine;
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Tests\OrmFunctionalTestCase;
/**
* @group GH7737
*/
class GH7737Test extends OrmFunctionalTestCase
{
protected function setUp() : void
{
parent::setUp();
$this->setUpEntitySchema([GH7737Group::class, GH7737Person::class]);
$group1 = new GH7737Group(1, 'Test 1');
$person = new GH7737Person(1);
$person->groups->add($group1);
$this->_em->persist($person);
$this->_em->persist($group1);
$this->_em->persist(new GH7737Group(2, 'Test 2'));
$this->_em->flush();
$this->_em->clear();
}
/**
* @test
*/
public function memberOfCriteriaShouldBeCompatibleWithQueryBuilder() : void
{
$query = $this->_em->createQueryBuilder()
->select('person')
->from(GH7737Person::class, 'person')
->addCriteria(Criteria::create()->where(Criteria::expr()->memberOf(':group', 'person.groups')))
->getQuery();
$group1 = $this->_em->find(GH7737Group::class, 1);
$matching = $query->setParameter('group', $group1)->getOneOrNullResult();
self::assertInstanceOf(GH7737Person::class, $matching);
self::assertSame(1, $matching->id);
$group2 = $this->_em->find(GH7737Group::class, 2);
$notMatching = $query->setParameter('group', $group2)->getOneOrNullResult();
self::assertNull($notMatching);
}
}
/**
* @Entity
*/
class GH7737Group
{
/**
* @Id
* @Column(type="integer")
*/
public $id;
/** @Column */
public $name;
public function __construct(int $id, string $name)
{
$this->id = $id;
$this->name = $name;
}
}
/**
* @Entity
*/
class GH7737Person
{
/**
* @Id
* @Column(type="integer")
*/
public $id;
/**
* @ManyToMany(targetEntity=GH7737Group::class)
* @JoinTable(inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id", unique=true)})
*/
public $groups;
public function __construct(int $id)
{
$this->id = $id;
$this->groups = new ArrayCollection();
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Tests\OrmFunctionalTestCase;
final class GH7761Test extends OrmFunctionalTestCase
{
/**
* {@inheritDoc}
*/
protected function setUp() : void
{
parent::setUp();
$this->setUpEntitySchema([
GH7761Entity::class,
GH7761ChildEntity::class,
]);
$parent = new GH7761Entity();
$child = new GH7761ChildEntity();
$parent->children->add($child);
$this->_em->persist($parent);
$this->_em->persist($child);
$this->_em->flush();
$this->_em->clear();
}
public function testCollectionClearDoesNotClearIfNotPersisted() : void
{
/** @var GH7761Entity $entity */
$entity = $this->_em->find(GH7761Entity::class, 1);
$entity->children->clear();
$this->_em->persist(new GH7761Entity());
$this->_em->flush();
$this->_em->clear();
$entity = $this->_em->find(GH7761Entity::class, 1);
self::assertCount(1, $entity->children);
$this->_em->clear();
}
}
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class GH7761Entity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
public $id;
/**
* @ManyToMany(targetEntity="Doctrine\Tests\ORM\Functional\Ticket\GH7761ChildEntity", cascade={"all"})
* @JoinTable(name="gh7761_to_child",
* joinColumns={@JoinColumn(name="entity_id")},
* inverseJoinColumns={@JoinColumn(name="child_id")}
* )
*/
public $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
}
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class GH7761ChildEntity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
public $id;
}

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Tests\OrmFunctionalTestCase;
use function assert;
/**
* @group GH7767
*/
class GH7767Test extends OrmFunctionalTestCase
{
protected function setUp() : void
{
parent::setUp();
$this->setUpEntitySchema([GH7767ParentEntity::class, GH7767ChildEntity::class]);
$parent = new GH7767ParentEntity();
$parent->addChild(200);
$parent->addChild(100);
$parent->addChild(300);
$this->_em->persist($parent);
$this->_em->flush();
$this->_em->clear();
}
public function testMatchingRespectsCollectionOrdering() : void
{
$parent = $this->_em->find(GH7767ParentEntity::class, 1);
assert($parent instanceof GH7767ParentEntity);
$children = $parent->getChildren()->matching(Criteria::create());
self::assertEquals(100, $children[0]->position);
self::assertEquals(200, $children[1]->position);
self::assertEquals(300, $children[2]->position);
}
public function testMatchingOverrulesCollectionOrdering() : void
{
$parent = $this->_em->find(GH7767ParentEntity::class, 1);
assert($parent instanceof GH7767ParentEntity);
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['position' => 'DESC']));
self::assertEquals(300, $children[0]->position);
self::assertEquals(200, $children[1]->position);
self::assertEquals(100, $children[2]->position);
}
}
/**
* @Entity
*/
class GH7767ParentEntity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
/**
* @OneToMany(targetEntity=GH7767ChildEntity::class, mappedBy="parent", fetch="EXTRA_LAZY", cascade={"persist"})
* @OrderBy({"position" = "ASC"})
*/
private $children;
public function addChild(int $position) : void
{
$this->children[] = new GH7767ChildEntity($this, $position);
}
public function getChildren() : PersistentCollection
{
return $this->children;
}
}
/**
* @Entity
*/
class GH7767ChildEntity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
/** @Column(type="integer") */
public $position;
/** @ManyToOne(targetEntity=GH7767ParentEntity::class, inversedBy="children") */
private $parent;
public function __construct(GH7767ParentEntity $parent, int $position)
{
$this->parent = $parent;
$this->position = $position;
}
}

View File

@@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\Tests\OrmFunctionalTestCase;
use function array_map;
use function is_string;
use function iterator_to_array;
/**
* @group GH7820
*
* When using a {@see \Doctrine\ORM\Tools\Pagination\Paginator} to iterate over a query
* that has entities with a custom DBAL type used in the identifier, then `$id->__toString()`
* is used implicitly by {@see \PDOStatement::bindValue()}, instead of being converted by the
* expected {@see \Doctrine\DBAL\Types\Type::convertToDatabaseValue()}.
*
* In order to reproduce this, you must have identifiers implementing
* `#__toString()` (to allow {@see \Doctrine\ORM\UnitOfWork} to hash them) and other accessors
* that are used by the custom DBAL type during DB/PHP conversions.
*
* If `#__toString()` and the DBAL type conversions are asymmetric, then the paginator will fail
* to find records.
*
* Tricky situation, but this very much affects `ramsey/uuid-doctrine` and anyone relying on (for
* example) the {@see \Ramsey\Uuid\Doctrine\UuidBinaryType} type.
*/
class GH7820Test extends OrmFunctionalTestCase
{
private const SONG = [
'What is this song all about?',
'Can\'t figure any lyrics out',
'How do the words to it go?',
'I wish you\'d tell me, I don\'t know',
'Don\'t know, don\'t know, don\'t know, I don\'t know!',
'Don\'t know, don\'t know, don\'t know...',
];
protected function setUp() : void
{
parent::setUp();
if (! Type::hasType(GH7820LineTextType::class)) {
Type::addType(GH7820LineTextType::class, GH7820LineTextType::class);
}
$this->setUpEntitySchema([GH7820Line::class]);
foreach (self::SONG as $index => $line) {
$this->_em->persist(new GH7820Line(GH7820LineText::fromText($line), $index));
}
$this->_em->flush();
}
public function testWillFindSongsInPaginator() : void
{
$query = $this->_em->getRepository(GH7820Line::class)
->createQueryBuilder('l')
->orderBy('l.lineNumber', Criteria::ASC);
self::assertSame(
self::SONG,
array_map(static function (GH7820Line $line) : string {
return $line->toString();
}, iterator_to_array(new Paginator($query)))
);
}
}
/** @Entity */
class GH7820Line
{
/**
* @var GH7820LineText
* @Id()
* @Column(type="Doctrine\Tests\ORM\Functional\Ticket\GH7820LineTextType")
*/
private $text;
/**
* @var int
* @Column(type="integer")
*/
private $lineNumber;
public function __construct(GH7820LineText $text, int $index)
{
$this->text = $text;
$this->lineNumber = $index;
}
public function toString() : string
{
return $this->text->getText();
}
}
final class GH7820LineText
{
/** @var string */
private $text;
private function __construct(string $text)
{
$this->text = $text;
}
public static function fromText(string $text) : self
{
return new self($text);
}
public function getText() : string
{
return $this->text;
}
public function __toString() : string
{
return 'Line: ' . $this->text;
}
}
final class GH7820LineTextType extends StringType
{
public function convertToPHPValue($value, AbstractPlatform $platform)
{
$text = parent::convertToPHPValue($value, $platform);
if (! is_string($text)) {
return $text;
}
return GH7820LineText::fromText($text);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (! $value instanceof GH7820LineText) {
return parent::convertToDatabaseValue($value, $platform);
}
return parent::convertToDatabaseValue($value->getText(), $platform);
}
/** {@inheritdoc} */
public function getName() : string
{
return self::class;
}
}

View File

@@ -269,6 +269,7 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase
$this->assertEquals(CmsUser::class, $rsm->getDeclaringClass('status'));
$this->assertEquals(CmsUser::class, $rsm->getDeclaringClass('username'));
}
/**
* @group DDC-117
*/

View File

@@ -170,6 +170,8 @@ class ClassMetadataFactoryTest extends OrmTestCase
public function testAddDefaultDiscriminatorMap()
{
self::markTestSkipped('This test is just incorrect and must be fixed');
$cmf = new ClassMetadataFactory();
$driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/JoinedInheritanceType/']);
$em = $this->_createEntityManager($driver);

View File

@@ -3,6 +3,7 @@
namespace Doctrine\Tests\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Mocks\ConnectionMock;
@@ -264,4 +265,25 @@ class PersistentCollectionTest extends OrmTestCase
self::assertTrue($this->collection->isInitialized());
self::assertFalse($this->collection->isDirty());
}
public function testModifyUOWForDeferredImplicitOwnerOnClear() : void
{
$unitOfWork = $this->createMock(UnitOfWork::class);
$unitOfWork->expects(self::once())->method('scheduleCollectionDeletion');
$this->_emMock->setUnitOfWork($unitOfWork);
$this->collection->clear();
}
public function testDoNotModifyUOWForDeferredExplicitOwnerOnClear() : void
{
$unitOfWork = $this->createMock(UnitOfWork::class);
$unitOfWork->expects(self::never())->method('scheduleCollectionDeletion');
$this->_emMock->setUnitOfWork($unitOfWork);
$classMetaData = $this->_emMock->getClassMetadata(ECommerceCart::class);
$classMetaData->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT);
$this->collection->clear();
}
}

View File

@@ -67,6 +67,7 @@ class QueryExpressionVisitorTest extends TestCase
[$cb->notIn('field', ['value']), $qb->notIn('o.field', ':field'), new Parameter('field', ['value'])],
[$cb->contains('field', 'value'), $qb->like('o.field', ':field'), new Parameter('field', '%value%')],
[$cb->memberOf(':field', 'o.field'), $qb->isMemberOf(':field', 'o.field')],
[$cb->startsWith('field', 'value'), $qb->like('o.field', ':field'), new Parameter('field', 'value%')],
[$cb->endsWith('field', 'value'), $qb->like('o.field', ':field'), new Parameter('field', '%value')],

View File

@@ -2,24 +2,26 @@
namespace Doctrine\Tests\ORM\Query;
use DateTime;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\ORM\EntityManager;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Internal\Hydration\IterableResult;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Mocks\DriverConnectionMock;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\Mocks\StatementArrayMock;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\Generic\DateTimeModel;
use Doctrine\Tests\OrmTestCase;
class QueryTest extends OrmTestCase
{
/** @var EntityManager */
protected $_em = null;
/** @var EntityManagerMock */
protected $_em;
protected function setUp()
{
@@ -400,4 +402,22 @@ class QueryTest extends OrmTestCase
self::assertAttributeSame(null, '_queryCacheProfile', $query);
}
/** @group 7527 */
public function testValuesAreNotBeingResolvedForSpecifiedParameterTypes() : void
{
$unitOfWork = $this->createMock(UnitOfWork::class);
$this->_em->setUnitOfWork($unitOfWork);
$unitOfWork
->expects(self::never())
->method('getSingleIdentifierValue');
$query = $this->_em->createQuery('SELECT d FROM ' . DateTimeModel::class . ' d WHERE d.datetime = :value');
$query->setParameter('value', new DateTime(), Type::DATETIME);
self::assertEmpty($query->getResult());
}
}

View File

@@ -2302,7 +2302,7 @@ class SelectSqlGenerationTest extends OrmTestCase
}
/**
* GitHub issue #4764: https://github.com/doctrine/doctrine2/issues/4764
* GitHub issue #4764: https://github.com/doctrine/orm/issues/4764
* @group DDC-3907
* @dataProvider mathematicOperatorsProvider
*/

View File

@@ -609,8 +609,11 @@ class QueryBuilderTest extends OrmTestCase
->setParameter('id', 1);
$parameter = new Parameter('id', 1, ParameterTypeInferer::inferType(1));
$inferred = $qb->getParameter('id');
$this->assertEquals($parameter, $qb->getParameter('id'));
self::assertSame($parameter->getValue(), $inferred->getValue());
self::assertSame($parameter->getType(), $inferred->getType());
self::assertFalse($inferred->typeWasSpecified());
}
public function testSetParameters()

View File

@@ -11,7 +11,7 @@ abstract class PaginationTestCase extends OrmTestCase
*/
public $entityManager;
public function setUp()
protected function setUp()
{
$this->entityManager = $this->_getTestEntityManager();
}

View File

@@ -2,14 +2,27 @@
namespace Doctrine\Tests\ORM\Tools\Pagination;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Query;
use Doctrine\ORM\Tools\Pagination\WhereInWalker;
use Doctrine\Tests\DbalTypes\Rot13Type;
use Doctrine\Tests\Models\ValueConversionType\AuxiliaryEntity;
use Doctrine\Tests\Models\ValueConversionType\OwningManyToOneIdForeignKeyEntity;
/**
* @group DDC-1613
*/
class WhereInWalkerTest extends PaginationTestCase
{
protected function setUp() : void
{
parent::setUp();
if (! Type::hasType('rot13')) {
Type::addType('rot13', Rot13Type::class);
}
}
public function testWhereInQuery_NoWhere()
{
$query = $this->entityManager->createQuery(
@@ -18,10 +31,16 @@ class WhereInWalkerTest extends PaginationTestCase
$whereInQuery = clone $query;
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE u0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
public function testCountQuery_MixedResultsWithName()
@@ -32,10 +51,16 @@ class WhereInWalkerTest extends PaginationTestCase
$whereInQuery = clone $query;
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT a0_.id AS id_0, a0_.name AS name_1, sum(a0_.name) AS sclr_2 FROM Author a0_ WHERE a0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
public function testWhereInQuery_SingleWhere()
@@ -46,10 +71,16 @@ class WhereInWalkerTest extends PaginationTestCase
$whereInQuery = clone $query;
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE 1 = 1 AND u0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
public function testWhereInQuery_MultipleWhereWithAnd()
@@ -60,10 +91,16 @@ class WhereInWalkerTest extends PaginationTestCase
$whereInQuery = clone $query;
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE 1 = 1 AND 2 = 2 AND u0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
public function testWhereInQuery_MultipleWhereWithOr()
@@ -74,10 +111,16 @@ class WhereInWalkerTest extends PaginationTestCase
$whereInQuery = clone $query;
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 OR 2 = 2) AND u0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
public function testWhereInQuery_MultipleWhereWithMixed_1()
@@ -88,10 +131,16 @@ class WhereInWalkerTest extends PaginationTestCase
$whereInQuery = clone $query;
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 OR 2 = 2) AND 3 = 3 AND u0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
public function testWhereInQuery_MultipleWhereWithMixed_2()
@@ -102,10 +151,16 @@ class WhereInWalkerTest extends PaginationTestCase
$whereInQuery = clone $query;
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 AND 2 = 2 OR 3 = 3) AND u0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
public function testWhereInQuery_WhereNot()
@@ -116,10 +171,16 @@ class WhereInWalkerTest extends PaginationTestCase
$whereInQuery = clone $query;
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (NOT 1 = 2) AND u0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
/**
@@ -132,10 +193,16 @@ class WhereInWalkerTest extends PaginationTestCase
);
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT b0_.id AS id_0, b0_.author_id AS author_id_1, b0_.category_id AS category_id_2 FROM BlogPost b0_ INNER JOIN Category c1_ ON (b0_.category_id = c1_.id) WHERE b0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
public function testWhereInQueryWithArbitraryJoin_SingleWhere()
@@ -145,10 +212,57 @@ class WhereInWalkerTest extends PaginationTestCase
);
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$this->assertEquals(
"SELECT b0_.id AS id_0, b0_.author_id AS author_id_1, b0_.category_id AS category_id_2 FROM BlogPost b0_ INNER JOIN Category c1_ ON (b0_.category_id = c1_.id) WHERE 1 = 1 AND b0_.id IN (?)", $whereInQuery->getSQL()
);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
);
}
public function testWillReplaceBoundQueryIdentifiersWithConvertedTypesAsPerIdentifierMapping() : void
{
$whereInQuery = $this->entityManager->createQuery(
'SELECT e.id4 FROM ' . AuxiliaryEntity::class . ' e'
);
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 3);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, ['foo', 'bar', 'baz']);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
['sbb', 'one', 'onm']
);
}
public function testWillReplaceBoundQueryIdentifiersWithConvertedTypesAsPerAssociatedEntityIdentifierMapping() : void
{
$whereInQuery = $this->entityManager->createQuery(
'SELECT e FROM ' . OwningManyToOneIdForeignKeyEntity::class . ' e'
);
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 3);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, ['foo', 'bar', 'baz']);
$this->assertPaginatorWhereInParameterToBe(
$whereInQuery,
['sbb', 'one', 'onm']
);
}
/** @param mixed $parameter */
private function assertPaginatorWhereInParameterToBe(Query $query, $parameter) : void
{
$query->getSQL(); // forces walker to process the query
$boundParameter = $query->getParameter(WhereInWalker::PAGINATOR_ID_ALIAS);
self::assertNotNull($boundParameter);
self::assertSame($parameter, $boundParameter->getValue());
}
}

View File

@@ -47,21 +47,22 @@ class SchemaToolTest extends OrmTestCase
$this->assertTrue($schema->getTable('cms_users')->columnsAreIndexed(['username']), "username column should be indexed.");
}
public function testAnnotationOptionsAttribute()
public function testAnnotationOptionsAttribute() : void
{
$em = $this->_getTestEntityManager();
$schemaTool = new SchemaTool($em);
$classes = [
$em->getClassMetadata(TestEntityWithAnnotationOptionsAttribute::class),
];
$schema = $schemaTool->getSchemaFromMetadata(
[$em->getClassMetadata(TestEntityWithAnnotationOptionsAttribute::class)]
);
$table = $schema->getTable('TestEntityWithAnnotationOptionsAttribute');
$schema = $schemaTool->getSchemaFromMetadata($classes);
$expected = ['foo' => 'bar', 'baz' => ['key' => 'val']];
$this->assertEquals($expected, $schema->getTable('TestEntityWithAnnotationOptionsAttribute')->getOptions(), "options annotation are passed to the tables options");
$this->assertEquals($expected, $schema->getTable('TestEntityWithAnnotationOptionsAttribute')->getColumn('test')->getCustomSchemaOptions(), "options annotation are passed to the columns customSchemaOptions");
foreach ([$table->getOptions(), $table->getColumn('test')->getCustomSchemaOptions()] as $options) {
self::assertArrayHasKey('foo', $options);
self::assertSame('bar', $options['foo']);
self::assertArrayHasKey('baz', $options);
self::assertSame(['key' => 'val'], $options['baz']);
}
}
/**

View File

@@ -11,6 +11,7 @@ use Doctrine\ORM\Cache\DefaultCacheFactory;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\Tests\Mocks;
use Doctrine\Tests\Mocks\EntityManagerMock;
/**
* Base testcase class for all ORM testcases.
@@ -113,10 +114,8 @@ abstract class OrmTestCase extends DoctrineTestCase
* @param mixed $conf
* @param \Doctrine\Common\EventManager|null $eventManager
* @param bool $withSharedMetadata
*
* @return \Doctrine\ORM\EntityManager
*/
protected function _getTestEntityManager($conn = null, $conf = null, $eventManager = null, $withSharedMetadata = true)
protected function _getTestEntityManager($conn = null, $conf = null, $eventManager = null, $withSharedMetadata = true) : EntityManagerMock
{
$metadataCache = $withSharedMetadata
? self::getSharedMetadataCacheImpl()
@@ -160,7 +159,7 @@ abstract class OrmTestCase extends DoctrineTestCase
$conn = DriverManager::getConnection($conn, $config, $eventManager);
}
return Mocks\EntityManagerMock::create($conn, $config, $eventManager);
return EntityManagerMock::create($conn, $config, $eventManager);
}
protected function enableSecondLevelCache($log = true)

View File

@@ -1,16 +1,16 @@
# Running the Doctrine 2 Testsuite
# Running the Doctrine ORM Testsuite
To execute the Doctrine2 testsuite, you just need to execute this simple steps:
To execute the ORM testsuite, you just need to execute this simple steps:
* Clone the project from GitHub
* Enter the Doctrine2 folder
* Enter the ORM folder
* Install the dependencies
* Execute the tests
All this is (normally) done with:
git clone git@github.com:doctrine/doctrine2.git
cd doctrine2
git clone git@github.com:doctrine/orm.git
cd orm
composer install
./vendor/bin/phpunit

View File

@@ -2,21 +2,7 @@
set -ex
echo "Installing MySQL 5.7..."
sudo service mysql stop
sudo apt-get remove "^mysql.*"
sudo apt-get autoremove
sudo apt-get autoclean
echo mysql-apt-config mysql-apt-config/select-server select mysql-5.7 | sudo debconf-set-selections
wget http://dev.mysql.com/get/mysql-apt-config_0.8.6-1_all.deb
sudo DEBIAN_FRONTEND=noninteractive dpkg -i mysql-apt-config_0.8.6-1_all.deb
sudo rm -rf /var/lib/apt/lists/*
sudo apt-get clean
sudo apt-get update -q
sudo apt-get install -q -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" mysql-server libmysqlclient-dev
sudo mysql_upgrade
echo "Restart mysql..."
sudo mysql -e "use mysql; update user set authentication_string=PASSWORD('') where User='root'; update user set plugin='mysql_native_password';FLUSH PRIVILEGES;"
sudo mysql_upgrade -u root
sudo service mysql restart