Compare commits

...

166 Commits
2.8.0 ... 2.8.3

Author SHA1 Message Date
Benjamin Eberlei
657a30f8ce [GH-6394] Bugfix: IdentifierFlattener support for association non-object values. (#8384)
* [GH-6394] Bugfix: IdentifierFlattener support for association non-object values

* [GH-6394] Bugfix: BasicEntityPersister::update used wrong identifiers for version assignment.

* Exclude MissingNativeTypeHint phpcs rule as 7.4 is not lowest version.
2021-04-01 23:16:53 +02:00
Grégoire Paris
0b25d4d8b0 Merge pull request #8573 from greg0ire/fix-build
Fix build issues
2021-04-01 07:49:27 +02:00
Grégoire Paris
a88242ee6c Adapt test logic to PHP and SQLite
There seems to be at least 2 camps in the software world when it comes
to the question "What's today minus one month", today being at the end
of march.

While PHP and SQLite agree that that would be the 2nd of March, other
RDBMS than SQLite and humans will tell you that it's the last day of
February.

This patch ensures that we check one logic for SQLite, and the other
logic for other platforms.
2021-03-30 21:08:29 +02:00
Grégoire Paris
fe4964008d Accommodate 2 behaviors of symfony/console in test
Decorated text used to be wrapped too early in SymfonyStyle->block()
See https://github.com/symfony/symfony/pull/40348
The fix was not contributed to version 3, which means we have to rewrite
the test so that it passes for both the correct and the buggy version.
2021-03-30 08:41:10 +02:00
Grégoire Paris
3f3de70c3e Merge pull request #8564 from cybercitizen7/featureIncludeDirectory
Adding DIR to include statement to fix issue with pathing
2021-03-26 19:46:40 +01:00
darkw1z
eb4e317144 Adding DIR to include statement to fix issue with pathing 2021-03-26 14:04:46 +01:00
Grégoire Paris
c8f2f61ea1 Merge pull request #8556 from VincentLanglet/patch-2
Fix fieldMapping phpdoc
2021-03-26 08:26:40 +01:00
Vincent Langlet
c9502d3d0b Fix fieldMapping phpdoc 2021-03-24 15:07:08 +01:00
Grégoire Paris
3358ccde39 Merge pull request #8547 from greg0ire/psalm-lv6-phpdoc
Make phpdoc types correct
2021-03-21 22:11:51 +01:00
Grégoire Paris
47475f3a67 Merge pull request #8532 from acoulton/bug-fix-ci-db-connection
All CI runs are using the sqlite fallback connection instead of the expected driver
2021-03-17 19:49:56 +01:00
acoulton
61c4a5da0a Rename tmpdb_ to privileged_db in test config and TestUtil
To avoid confusion, the `tmpdb_` test config values are now named
`privileged_db_` and better documented in the phpunit.xml.dist.

The TestUtil class has been refactored to more closely mirror the
structure and method / variable naming of the equivalent in
doctrine/dbal. This does not introduce any significant functional
changes. The only real difference is that the test output now prints
the selected database driver the first time it is referenced,
rather than repeating this through the test run.
2021-03-17 10:31:22 +00:00
acoulton
dd34bca4eb Upgrade previously-skipped tests to phpunit 9
These tests had not been running in CI so missed the previous
phpunit upgrade.

Note that assertions in decimal/floaty values in GH7941Test have
been changed to compare numerically with a reasonable level of
precision, instead of using regex. This is because the types
returned by the different drivers are inconsistent, and phpunit
now only allows regex comparisons on strings.
2021-03-17 10:31:22 +00:00
acoulton
3e21c50f61 Fix unit test and CI database driver / credential configuration
Builds using the github actions phpunit.xml files were not properly
recognising driver-specific configuration values, so were all
falling back to use the in-memory sqlite database instead of the
expected driver. This also meant a number of tests were skipped
as they rely on functionality not available in sqlite.

This commit addresses that by:

* REMOVING the automatic fallback to the sqlite memory database -
  phpunit.xml must now always specify explicit parameters for the
  desired connection.

* Displaying the active driver in the build output for visibility
  and debugging.

* Changing the way TestUtil loads the database config in line
  with the equivalent logic in doctrine/dbal, and to support the
  way that the config is/was specified in the phpunit.xml files
  for CI.

Note that this means a couple of the expected config variable names
have changed. Developers that are using customised phpunit.xml files
locally will need to update them to provide:

* Database config variables if they want to use the sqlite/memory
  driver - see phpunit.xml.dist for details.
* `db_driver` instead of `db_type`
* `db_user` instead of `db_username`
* `db_dbname` instead of `db_name`
* And, if in use, the equivalent changes to the `tmpdb_` values

The other change is that now if you provide any value for
`db_driver` we will attempt to create that connection type and
that will throw if other details (username / password / etc as
required by the driver) are not provided. Previously providing
partial configuration would cause TestUtil to silently fall back
to the in-memory sqlite driver.
2021-03-17 10:31:22 +00:00
Grégoire Paris
bc3592bcc8 Make phpdoc type correct 2021-03-16 19:20:11 +01:00
Grégoire Paris
5247c56fce Merge pull request #8539 from greg0ire/cs-20210311
🎉 Final CS batch 🎉
2021-03-14 18:47:03 +01:00
Grégoire Paris
cc37c490c2 Synchronize coding standard workflow with upstream
Now that there no longer are cs issues, we can thank diff-sniffer and
kiss it goodbye!
2021-03-13 09:55:38 +01:00
Grégoire Paris
95824efd61 Manually fix cs 2021-03-13 09:46:38 +01:00
Grégoire Paris
44d4712e64 Ignore rule about annotation phpdoc
These phpdoc is parsed by doctrine/annotations, and that package does
not understand things like array<string, mixed> yet.
2021-03-13 09:46:38 +01:00
Grégoire Paris
930f44c02f Ignore rule for externally-defined property 2021-03-13 00:08:03 +01:00
Grégoire Paris
6e3c011e65 Ignore rule about superflous comment
We can fix it with a breaking change.
2021-03-13 00:08:03 +01:00
Grégoire Paris
a82de0d422 Ignore rule about unused method
It cannot work when you call the private method like a callable.
2021-03-13 00:08:03 +01:00
Grégoire Paris
9917488179 Ignore rule about empty statements
This should be implemented in a separate pull request.
2021-03-12 08:20:46 +01:00
Grégoire Paris
93f31d2c33 Merge pull request #8533 from acoulton/test-string-lock-version
Add test coverage for passing optimistic lock version as string
2021-03-11 21:41:32 +01:00
acoulton
77356b954f Add test coverage for passing optimistic lock version as string
As discussed in #8527, when using optimistic locking with integer
version columns, Doctrine has always supported passing the lock
version as a string. For example when passing in a version
received in POST / GET.

Technically speaking this does not comply with the docs and phdoc
(which show the app explicitly casting to int before passing).

Nonetheless the maintainers decided it should continue to be valid
for now and reinstated the old soft-equals logic with #8531.

This modified test just avoids accidental changes in future.
2021-03-11 13:24:53 +00:00
Grégoire Paris
92f764206e Ignore broken rule 2021-03-11 09:11:24 +01:00
Grégoire Paris
141539673e Merge pull request #8530 from doctrine/cs-20210310
CS-batch 25/26 🤩
2021-03-11 00:05:31 +01:00
Grégoire Paris
23dc804c9b Merge pull request #8531 from beberlei/GH-8527-RevertLockEquals
[GH-8527] Revert cs fixes for entity version compares in lock+merge
2021-03-10 23:30:25 +01:00
Benjamin Eberlei
9e3baa7baa [GH-8527] Revert cs fixes for entity version compares in lock+merge 2021-03-10 22:46:07 +01:00
Grégoire Paris
322ea51ecf Manually fix cs 2021-03-10 22:17:31 +01:00
Grégoire Paris
21b046452b Merge pull request #8529 from greg0ire/cs-20210308
CS batch 24/26 🤞
2021-03-10 19:53:52 +01:00
Grégoire Paris
c57b81ada4 Manually fix cs 2021-03-09 21:15:33 +01:00
Grégoire Paris
4fa7c9c6de Merge pull request #8521 from greg0ire/cs-20210228
CS batch 23/an estimated 26
2021-03-08 19:57:18 +01:00
Grégoire Paris
3580517aac Manually fix cs 2021-03-01 21:49:10 +01:00
Grégoire Paris
f9e7c3c2d8 Merge pull request #8516 from greg0ire/cs-20210227
CS batch 22/an estimated 26
2021-02-28 21:27:52 +01:00
Grégoire Paris
3600c0fbca Manually fix cs 2021-02-28 18:31:38 +01:00
Grégoire Paris
f779513042 Remove unused properties
They should have been removed as part of a6b43b93ac
2021-02-28 18:31:38 +01:00
Diego Rin Martín
07d426edf5 Changed lock function to compare timestamps instead of DateTimeInterface objects directly. (#8508)
When using optimistic lock with DateTimeInterface based version field a bug appears due to the use of the === operator for comparing the lock version and the entity version. This comparison always resolves to false because the === operator when comparing objects is only true when both sides are the exact same instance of the object.

To fix the issue I have decided to compare timestamps instead the DateTimeInterface based objects directly, calling getTimestamp() method and doing a strict comparison.

Modified OptimisticLockException to use DateTimeInterface instead of DateTime class.

Added test suite to cover case.

Fixes #8499
2021-02-27 18:40:48 +01:00
Grégoire Paris
4afd4069be Merge pull request #8515 from greg0ire/cs-20210226
CS batch 21/an estimated 26
2021-02-27 14:30:25 +01:00
Grégoire Paris
239215c2e5 Merge pull request #8502 from greg0ire/rework-contributing-md
Rework CONTRIBUTING.md
2021-02-27 13:03:20 +01:00
Grégoire Paris
e6f11652d2 Add section for 2.9.x branch 2021-02-27 12:16:49 +01:00
Grégoire Paris
2910a73927 Rework badges urls
Some .x were missing, and ugly urlencoding can be avoided.
2021-02-27 12:10:47 +01:00
Grégoire Paris
3959b2743c Remove references to Travis 2021-02-27 12:03:35 +01:00
Grégoire Paris
658e54027e Remove trailing whitespace 2021-02-27 12:01:06 +01:00
Grégoire Paris
ba882451b0 Refer to our actual coding/standard
We do much more than just PSR 1 and 2
2021-02-27 12:01:05 +01:00
Grégoire Paris
1ed9840123 Refer to global workflow policy
We are doing things differently now, and the how is already documented.
2021-02-27 12:01:05 +01:00
Grégoire Paris
71044894a1 Manually fix cs 2021-02-26 21:30:49 +01:00
Grégoire Paris
1a41d6b87c Fix configuration mix up 2021-02-26 08:25:44 +01:00
Grégoire Paris
5dfcb08999 Merge pull request #8512 from greg0ire/cs-20210225
CS batch 20/an estimated 26
2021-02-25 23:23:24 +01:00
Grégoire Paris
284bd6fd03 Manually fix cs 2021-02-25 21:22:50 +01:00
Grégoire Paris
e40ac3e1d0 Merge pull request #8510 from greg0ire/cs-20210224
CS batch 19/an estimated 26
2021-02-24 22:41:28 +01:00
Grégoire Paris
0bce2472f2 Manually fix cs 2021-02-24 18:51:02 +01:00
Grégoire Paris
89f57de884 Merge pull request #8504 from greg0ire/cs-20210223
CS batch 18/an estimated 26
2021-02-23 23:18:42 +01:00
Grégoire Paris
ae19f40958 Merge pull request #8495 from Warxcell/fix_to_iterable_with_cache
Fix bug when using Result Cache with Query::toIterable
2021-02-23 20:16:39 +01:00
Grégoire Paris
c2d69a3c48 Merge pull request #8507 from greg0ire/address-move-away-from-master
Address move away from master
2021-02-23 18:19:47 +01:00
Grégoire Paris
6ce91dd37b Address move away from master 2021-02-23 09:24:40 +01:00
Grégoire Paris
57e6ba25c9 Merge pull request #8505 from dbu/patch-1
fix typo in changelog
2021-02-23 08:56:31 +01:00
David Buchmann
9d2e67bbb4 fix typo in changelog 2021-02-23 08:17:31 +01:00
Grégoire Paris
2dce5b20ad Manually fix cs 2021-02-22 23:50:09 +01:00
Warxcell
930859f803 Fix bug when using ResultCache with Query::toIterable.
Signed-off-by: Warxcell <warxcell@gmail.com>
2021-02-22 23:35:22 +02:00
Yup
a70c73ae3a Use RegEx to match if queryPart contains OR/AND (#8453)
This allows fixes cases of queries that contain line feeds or tabs but
do not benefit from automatic wrapping of parenthesis.
2021-02-22 20:58:06 +01:00
David Buchmann
074346b8d5 Note deprecation of AbstractQuery::iterator (#8497) 2021-02-22 20:30:05 +01:00
Grégoire Paris
9ed4a8c043 Merge pull request #8498 from greg0ire/cs-20210222
CS batch 17/an estimated 26
2021-02-22 20:25:06 +01:00
Grégoire Paris
9c917811e5 Manually fix cs 2021-02-22 09:12:04 +01:00
Grégoire Paris
f883820257 Ignore rule about wrong comment style 2021-02-22 08:48:46 +01:00
Grégoire Paris
a32045dd51 Ignore rule violated by external package 2021-02-22 07:52:57 +01:00
Grégoire Paris
672b04a55d Merge pull request #8483 from olsavmic/fix-single-scalar-hydrator-memory-leak-on-exception
Fix single scalar hydrator memory leak on exception
2021-02-21 21:12:50 +01:00
Grégoire Paris
261334aca2 Merge pull request #8494 from greg0ire/cs-20210221
CS batch 16/an estimated 26
2021-02-21 20:29:47 +01:00
Warxcell
7f6ed094cd Add test to verify that using ResultCache with Query::toIterable is failing. 2021-02-21 21:12:52 +02:00
Grégoire Paris
a792655813 Manually fix cs 2021-02-21 12:20:19 +01:00
Grégoire Paris
fb71204910 Relax assertion (#8493)
EntityManager is too restrictive, any implementation can actually be
returned here.

Closes #8488
2021-02-21 07:43:56 +01:00
Grégoire Paris
b918661cf1 Merge pull request #8492 from greg0ire/cs-20210220
CS batch 15/an estimated 26
2021-02-20 20:49:25 +01:00
Michael Olšavský
7971a53164 Method hydrateAll() does not take into account possible exception
from hydrateAllData() which in turn does not call cleanup()
2021-02-20 18:58:50 +01:00
Grégoire Paris
a175f96ae8 Manually fix cs 2021-02-20 15:37:15 +01:00
Grégoire Paris
7c1cde6471 Ignore rule that triggers on external property 2021-02-20 15:32:26 +01:00
Grégoire Paris
1ffc0cacf4 Merge pull request #8491 from greg0ire/cs-20210219
CS batch 14/an estimated 26
2021-02-20 11:10:27 +01:00
Grégoire Paris
e979d0d50f Manually fix cs 2021-02-20 00:01:41 +01:00
Grégoire Paris
553ea03079 Ignore rule about case mismatch
@group does not have to do with the Group entity at all.
2021-02-19 23:33:25 +01:00
Grégoire Paris
149014879d Ignore rule about property defined externally 2021-02-19 23:11:43 +01:00
Grégoire Paris
b991c58988 Merge pull request #8490 from greg0ire/cs-20210218
CS batch 13/an estimated 26
2021-02-19 23:08:25 +01:00
Aleksandr Frolov
ee9627b82e Update QueryBuilder::setParameters docs (#8487)
Use `ArrayCollection` instead of plain array (which is supported only for bc)
2021-02-19 01:45:14 +01:00
Grégoire Paris
e3f03414f9 Manually fix cs 2021-02-18 23:17:03 +01:00
Grégoire Paris
1f406fd3df Merge pull request #8484 from greg0ire/cs-20210217
CS batch 12/an estimated 26
2021-02-18 21:54:43 +01:00
Grégoire Paris
b747bf15ff Manually fix cs 2021-02-17 16:32:39 +01:00
Grégoire Paris
5fe85bfc03 Ignore rule about underscore in method name
We inherit from a class defined in another package.
2021-02-17 15:55:21 +01:00
Grégoire Paris
0dccf05ca8 Automatically fix cs 2021-02-17 15:55:21 +01:00
Grégoire Paris
ebae57eb96 Merge pull request #8480 from greg0ire/cs-20210216
CS batch 11/an estimated 26
2021-02-16 23:10:18 +01:00
Benjamin Eberlei
30a7c2aa67 [GH-8410] Fix memory leak in new toIterable and state bug. (#8467)
* [GH-8410] Fix memory leak in new toIterable and state bug.

The new AbstractQuery::toIterable() had a memory leak that
AbstractQuery::iterable() did not have. This leak is now fixed.

After fixing the leak, one test failed where the identity map in
ObjectHydrator triggered and lead to a notice. Introduced a new
AbstractHydrator::cleanupAfterRowIteration() that the ObjectHydrator
uses to cleanup the state.

* [GH-8413] Bugfix: Iterating with multiple, mixed results

When multiple entity results are part of a row, the result handling
must be different. In addition mixed results with scalars are broken
and now throw an exception as illegal operation.

* Housekeeping: phpcs

* [GH-8413] Add assertions for entity alias iteration.

* [GH-8387] Missing @deprecated on Query::iterate
2021-02-16 17:52:20 +01:00
Benjamin Eberlei
3a9b8fde9b Housekeeping: Fix cs 2021-02-16 16:24:19 +01:00
Yosh
4f864bc178 Identifier type is not set when many2many relations are deleted (#8401)
* Ensure identifier type is set on deleteJoinTableRecords

* Housekeeping: phpcs

* Housekeeping: phpcs

Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
2021-02-16 16:12:39 +01:00
Grégoire Paris
d76cbd755f Manually fix cs 2021-02-16 09:07:26 +01:00
Grégoire Paris
4aece04ae7 Automatically fix cs 2021-02-16 08:48:48 +01:00
Grégoire Paris
f31dbf8d4e Merge pull request #8479 from greg0ire/cs-20210215
CS batch 10/an estimated 27
2021-02-15 23:44:27 +01:00
Grégoire Paris
416f35dba9 Manually fix cs 2021-02-14 21:00:58 +01:00
Grégoire Paris
c29370e061 Automatically fix cs 2021-02-14 14:39:35 +01:00
Grégoire Paris
4e0f6837d0 Merge pull request #8478 from beberlei/CsFixes2
CS Fixes 2 string interpolation to sprintf
2021-02-14 14:39:00 +01:00
Benjamin Eberlei
e45d212f02 Housekeeping: CS fixes Doctrine\ORM\Mapping\MappingException 2021-02-14 14:28:42 +01:00
Benjamin Eberlei
8f62bd39b5 Housekeeping: CS fixes Doctrine\ORM\ORMException 2021-02-14 09:53:19 +01:00
Benjamin Eberlei
5e11afcdf1 Housekeeping: CS fixes Doctrine\ORM\Mapping\MappingException 2021-02-14 09:49:39 +01:00
Benjamin Eberlei
f833222017 Cs fixes1 (#8475)
* Housekeeping: CS Query, AbstractQuery, NativeQuery.

* Housekeeping: phpcs TreeWalker

* Housekeeping: CS Doctrine\ORM\EntityManager

* Housekeeping: CS Doctrine\ORM\Cache

* Upgrade git-phpcs

* Drop unused method parameter

* Describe types more precisely

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-02-14 09:23:15 +01:00
Grégoire Paris
c7f39ebbde Merge pull request #8477 from greg0ire/cs-20210214
CS batch 9/an estimated 28
2021-02-14 09:20:11 +01:00
Grégoire Paris
15f08ed006 Manually fix cs 2021-02-14 00:04:18 +01:00
Grégoire Paris
b6fd4b5ef3 Automatically fix cs 2021-02-14 00:04:11 +01:00
Grégoire Paris
5e4dae88f3 Merge pull request #8476 from greg0ire/cs-20210213
CS batch 8/an estimated 30
2021-02-13 17:26:58 +01:00
Grégoire Paris
7312ddeda7 Manually fix cs 2021-02-13 13:46:01 +01:00
Grégoire Paris
9a67b6f699 Merge pull request #8474 from greg0ire/cs-20210212
CS batch 7/an estimated 30
2021-02-12 23:03:28 +01:00
Grégoire Paris
f59a0c349b Manually fix cs 2021-02-12 20:43:17 +01:00
Grégoire Paris
4c8831f716 Remove unused helper method 2021-02-12 20:04:56 +01:00
Grégoire Paris
01ca442be7 Ignore error about no assignment
These files must be require'd
2021-02-12 08:45:07 +01:00
Grégoire Paris
01374ca2ab Ignore rule about lone comment
That comment is mistaken for a method comment because it precedes one.
The issue was reported but will most likely not be fixed.
2021-02-12 07:39:56 +01:00
Grégoire Paris
94e8b1d43c Merge pull request #8470 from greg0ire/cs-20210211
CS batch 6/an estimated 30
2021-02-12 07:25:43 +01:00
Grégoire Paris
61d0f96c17 Manually fix cs 2021-02-11 21:02:10 +01:00
Grégoire Paris
41729be80a Spell "first" properly 2021-02-11 18:24:56 +01:00
Grégoire Paris
6dbaa39016 Merge pull request #8469 from greg0ire/cs-20210209
CS batch 5/an estimated 30
2021-02-10 21:11:11 +01:00
Grégoire Paris
58c95a92d1 Manually fix cs 2021-02-09 22:58:05 +01:00
Grégoire Paris
4958180b02 Merge pull request #8468 from greg0ire/cs-20210208
CS batch 4/an estimated 30
2021-02-09 22:22:41 +01:00
Grégoire Paris
8312ff0cb5 Merge pull request #8353 from fezfez/patch-1
Add docs to export-ignore
2021-02-08 21:23:35 +01:00
Grégoire Paris
fb9b9b276e Manually fix cs 2021-02-08 13:53:04 +01:00
Grégoire Paris
792a9a9149 Merge pull request #8464 from greg0ire/cs-20210207
CS batch 3/many
2021-02-07 14:21:09 +01:00
Grégoire Paris
0f655f9fb6 Manually fix cs 2021-02-07 12:21:40 +01:00
Grégoire Paris
2d7acbd07f Merge pull request #8457 from greg0ire/cs-20210206
CS Batch 2/many
2021-02-06 12:22:10 +01:00
Grégoire Paris
b06679cc14 Manually fix cs 2021-02-06 11:44:32 +01:00
Julian Ullrich
2693a93aed fixed entity generation for numeric values (#8434)
* fixed entity generation for numeric values

* fixed entity generation for numeric values

Co-authored-by: julian <julian@ullrichmail.net>
2021-02-06 00:35:45 +01:00
Benjamin Eberlei
8724589c6e Housekeeping: Fix wrong typehint - Closes #8421 2021-02-06 00:23:45 +01:00
Grégoire Paris
424305ef38 Merge pull request #8455 from greg0ire/cs
Cs
2021-02-05 23:40:01 +01:00
Grégoire Paris
9d01f6a45c Run phpcbf after running git-phpcs
git-phpcs can publish comments to Github, that will be helpful.
phpcbf will still be helpful to ensure no one upgrades the coding
standard without also fixing issues that can be autofixed.
2021-02-05 21:39:13 +01:00
Grégoire Paris
7ed487b534 Manually fix CS 2021-02-05 21:38:33 +01:00
Grégoire Paris
40f3925589 Merge pull request #8144 from greg0ire/cs
Automated fixes with phpcbf + manual fixes
2021-02-05 08:44:58 +01:00
Alexander Schranz
f92c3dba32 Fix --complete flag in orm:ensure-production-settings command (#8426) 2021-02-01 22:08:04 +01:00
Grégoire Paris
bcbd4401b8 Ignore export directory 2021-01-30 23:50:15 +01:00
Grégoire Paris
d6aca8e146 Fix proxy file exclude pattern
__CG__ is a file prefix, not a directory
2021-01-30 23:45:46 +01:00
Grégoire Paris
8f1911a4fe Fix cs by hand 2021-01-30 23:29:40 +01:00
Grégoire Paris
7f30cd3102 Require doctrine/common ^3.0.3
That release comes with a fix for a bug that affects us since we are
using return type declarations for wakeUp() in proxyfied classes in on
of our tests.
2021-01-30 18:55:32 +01:00
Grégoire Paris
f01fe3e050 Fix or remove wrong assertions 2021-01-30 18:55:32 +01:00
Grégoire Paris
210c2ee6a4 Remove strict types 2021-01-30 18:55:32 +01:00
Grégoire Paris
497dfd1a84 Avoid covariant return types
It's not supported by PHP 7.2/7.3
2021-01-30 18:55:31 +01:00
Grégoire Paris
d9f0e2a27f Add exclude rules for tricky cases 2021-01-30 18:55:31 +01:00
Grégoire Paris
9a40ac6e2a Restore weird phpdoc
Tests do not pass when I format that phpdoc nicely.
2021-01-30 18:55:31 +01:00
Grégoire Paris
1687d9c479 Restore version annotation, but capitalized
@version is commonly used for svn ids and is forbidden
2021-01-30 18:55:31 +01:00
Gabriel Ostrolucký
1a46ed8901 Relax contract of EntityListenerResolver so it doesn't require class name (#8448)
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-01-30 13:37:26 +01:00
Grégoire Paris
36d0352c01 Add missing use statements 2021-01-30 11:21:44 +01:00
Grégoire Paris
15eacd787b Remove weird extra argument 2021-01-30 11:21:44 +01:00
Grégoire Paris
5b3f9bdd7b Fix type declarations 2021-01-30 11:21:44 +01:00
Grégoire Paris
e00dba94f4 Remove strict types 2021-01-30 11:21:44 +01:00
Grégoire Paris
ab0e4007a5 Use interface instead of concretion 2021-01-30 11:17:40 +01:00
Grégoire Paris
32266c54f9 Fix compatibility with parent signature 2021-01-30 11:17:39 +01:00
Grégoire Paris
dd2120cd41 Make default value compatible with phpdoc 2021-01-30 11:17:39 +01:00
Grégoire Paris
8991df0785 Add missing return types 2021-01-30 11:17:39 +01:00
Grégoire Paris
ca31923a39 Run phpcbf in the CI
There are still many CS issues to fix, but with this, we now know people
can run vendor/bin/phpcbf to fix their issues and only their issues.
2021-01-30 11:17:39 +01:00
Grégoire Paris
68bc00b6c6 Automatically fix CS 2021-01-30 10:58:42 +01:00
Grégoire Paris
5b55b8c6cf Disable rules that would result in BC-breaks 2021-01-29 23:21:12 +01:00
Grégoire Paris
10f381bc95 Restrict fixes to be compatible with PHP 7.2 2021-01-29 21:16:18 +01:00
Nikolay Gagarinov
40aa8fe5db update getting-started fix type, improve doc (#8440)
* update getting-started fix type, improve doc

* Update getting-started.rst
2021-01-26 21:40:41 +01:00
Simon Podlipsky
5801474ba3 Catch doctrine/persistence MappingException (#8264)
When driver chain is used doctrine/persistence MappingException is thrown instead of doctrine/orm MappingException
2021-01-11 09:07:06 +01:00
Claudio Zizza
9dbd960631 Update website config to current repository branches (#8420) 2021-01-11 09:06:06 +01:00
Grégoire Paris
544df89055 Use proper workflow name (#8418)
This must have been a copy/paste/adapt mistake.
2021-01-11 09:05:18 +01:00
azjezz
378944dd27 remove T from class metadata (#8398) 2020-12-19 20:48:16 +01:00
Benjamin Eberlei
8b749642cd [GH-8231] Bugfix: Missed dirty check synchronization check. (#8392)
When an entity with change tracking policy "deferred explicit" gets
removed, then persisted again, it is not schedulded for a dirty check
synchronization. This is not the case for entities that are persisted
and are already in the managed state.
2020-12-14 11:00:07 +01:00
Nic Wortel
277b53a970 Use the ramsey/composer-install action to install dependencies (#8388)
Follow-up of https://github.com/doctrine/.github/pull/16.
2020-12-10 20:03:47 +01:00
Benjamin Morel
2febb4509a Fix Psalm param (#8386) 2020-12-09 11:03:01 +01:00
Benjamin Eberlei
21d2c88013 [GH-7486] Bugfix: failing the command when clearing empty cache is wrong semantics. (#8376) 2020-12-06 22:00:00 +01:00
Vašek Henzl
e7d33eb1a9 Infer datetime_immutable DBAL type for \DateTimeImmutable instance parameters (#8328)
The support for passing \DateTimeImmutable instance as a query parameter has
been added to ORM in #1333 (the year 2015), a long time before immutable date
types (datetime_immutable etc) were introduced to DBAL in doctrine/dbal#2450
(2017).

Back then, it made sense to treat \DateTimeImmutable (or any
\DateTimeInterface) in the same way as \DateTime and infer parameter type as
datetime. However, when immutable date types were later added to DBAL, it
wasn't reflected anyhow in type inference in ORM and \DateTimeImmmutable
instances are still inferred as datetime DBAL type.

This PR fixes this IMO incorrect behaviour of
ParameterTypeInferer::inferType(): for a \DateTimeImmmutable parameter, it now
returns datetime_immutable DBAL type; for \DateTime or any other types
implementing \DateTimeInterface, it returns datetime DBAL type as it did
before.

This behaviour is in line with DateTimeImmutableType handling only
\DateTimeImmutable and DateTimeType handling any \DateTimeInterface.

Why? In most cases, it doesn't matter and datetime works for \DateTimeImmutable
parameters just fine. But it does matter if using custom implementation of
datetime_immutable type like UTCDateTimeImmutableType from
simpod/doctrine-utcdatetime. Then the broken type inference is revealed.

This is partially related to #6443, however, this PR isn't about custom DBAL
types but about correct type inference for build-in types.
2020-12-05 23:36:33 +01:00
Michel Hunziker
cab7a4558d Fix invalid psalm annotation (#8374) 2020-12-05 14:47:49 +01:00
Benjamin Eberlei
242cf1a33d Fix ambiguous case where an entity is also a Traversable (#8371)
* Fix ambiguous case where an entity is also a Traversable

* Address phpcs violations.

* Address phpcs violations.

* Address phpcs violations.

Co-authored-by: Laurent VOULLEMIER <laurent.voullemier@gmail.com>
2020-12-04 20:53:07 +01:00
Grégoire Paris
da225a0db8 Drop step that switches the release branch (#8372)
ORM is a repository where we use the stable branch as the default
branch, that step is not appropriate here.
2020-12-04 20:46:09 +01:00
Benjamin Eberlei
3ef5a30102 [GH-8366] Catch additional Persistence MappingException (#8370) 2020-12-04 20:16:50 +01:00
Stéphane
35cf4810c1 Add docs to export-ignore 2020-11-27 18:50:57 +01:00
1215 changed files with 31806 additions and 27310 deletions

View File

@@ -7,26 +7,32 @@
"versions": [
{
"name": "3.0",
"branchName": "master",
"branchName": "3.0.x",
"slug": "latest",
"upcoming": true
},
{
"name": "2.9",
"branchName": "2.9.x",
"slug": "2.9",
"upcoming": true
},
{
"name": "2.8",
"branchName": "2.8.x",
"slug": "2.8",
"upcoming": true
},
{
"name": "2.7",
"branchName": "2.7",
"slug": "2.7",
"current": true,
"aliases": [
"current",
"stable"
]
},
{
"name": "2.7",
"branchName": "2.7",
"slug": "2.7",
"maintained": false
},
{
"name": "2.6",
"branchName": "2.6",

1
.gitattributes vendored
View File

@@ -1,5 +1,6 @@
/tests export-ignore
/tools export-ignore
/docs export-ignore
/.github export-ignore
.gitattributes export-ignore
.gitignore export-ignore

View File

@@ -1,12 +1,17 @@
name: Static Analysis
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
push:
branches:
- "*.x"
jobs:
coding-standards:
name: "Coding Standards"
runs-on: "ubuntu-latest"
runs-on: "ubuntu-20.04"
strategy:
matrix:
@@ -16,8 +21,6 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
with:
fetch-depth: 10
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -26,21 +29,11 @@ jobs:
php-version: "${{ matrix.php-version }}"
tools: "cs2pr"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
dependency-versions: "highest"
- name: "Install dependencies with composer"
run: "composer update --no-interaction --no-progress --no-suggest"
- name: "Install git-phpcs"
run: "wget https://github.com/diff-sniffer/git/releases/download/0.3.2/git-phpcs.phar"
- name: "Fetch head branch"
run: "git remote set-branches --add origin $GITHUB_BASE_REF && git fetch origin $GITHUB_BASE_REF"
- name: "Run git-phpcs"
run: "php git-phpcs.phar origin/$GITHUB_BASE_REF...$GITHUB_SHA --report=checkstyle | cs2pr"
# https://github.com/doctrine/.github/issues/3
- name: "Run PHP_CodeSniffer"
run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr"

View File

@@ -20,9 +20,9 @@ jobs:
- "7.4"
- "8.0"
deps:
- "normal"
- "highest"
include:
- deps: "low"
- deps: "lowest"
php-version: "7.3"
steps:
@@ -39,20 +39,10 @@ jobs:
coverage: "pcov"
ini-values: "zend.assertions=1"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v2"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
- name: "Install dependencies with composer"
run: "composer update --no-interaction --no-progress --no-suggest"
if: "${{ matrix.deps == 'normal' }}"
- name: "Install lowest possible dependencies with composer"
run: "composer update --no-interaction --no-progress --no-suggest --prefer-dist --prefer-lowest"
if: "${{ matrix.deps == 'low' }}"
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --coverage-clover=coverage-no-cache.xml"
@@ -109,15 +99,8 @@ jobs:
coverage: "pcov"
ini-values: "zend.assertions=1"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v2"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
- name: "Install dependencies with composer"
run: "composer update --no-interaction --no-progress --no-suggest"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
@@ -171,15 +154,8 @@ jobs:
ini-values: "zend.assertions=1"
extensions: "${{ matrix.extension }}"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v2"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
- name: "Install dependencies with composer"
run: "composer update --no-interaction --no-progress --no-suggest"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
@@ -233,15 +209,8 @@ jobs:
ini-values: "zend.assertions=1"
extensions: "${{ matrix.extension }}"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v2"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
- name: "Install dependencies with composer"
run: "composer update --no-interaction --no-progress --no-suggest"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"

View File

@@ -35,16 +35,6 @@ jobs:
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
- name: "Create and/or Switch to new Release Branch"
uses: "laminas/automatic-releases@v1"
with:
command-name: "laminas:automatic-releases:switch-default-branch-to-next-minor"
env:
"GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
- name: "Create new milestones"
uses: "laminas/automatic-releases@v1"
with:

View File

@@ -24,15 +24,8 @@ jobs:
php-version: "${{ matrix.php-version }}"
tools: cs2pr
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v1"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
- name: "Install dependencies with composer"
run: "composer update --no-progress --no-suggest --no-interaction --prefer-dist"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
- name: "Run a static analysis with phpstan/phpstan"
run: "php vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr"
@@ -56,15 +49,8 @@ jobs:
coverage: "none"
php-version: "${{ matrix.php-version }}"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v1"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
- name: "Install dependencies with composer"
run: "composer update --no-interaction --no-progress --no-suggest"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
- name: "Run a static analysis with vimeo/psalm"
run: "vendor/bin/psalm --show-info=false --stats --output-format=github --threads=$(nproc)"

View File

@@ -6,30 +6,17 @@ Before we can merge your Pull-Request here are some guidelines that you need to
These guidelines exist not to annoy you, but to keep the code base clean,
unified and future proof.
## We only accept PRs to "master"
Doctrine has [general contributing guidelines][contributor workflow], make
sure you follow them.
Our branching strategy is "everything to master first", even
bugfixes and we then merge them into the stable branches. You should only
open pull requests against the master branch. Otherwise we cannot accept the PR.
There is one exception to the rule, when we merged a bug into some stable branches
we do occasionally accept pull requests that merge the same bug fix into earlier
branches.
[contributor workflow]: https://www.doctrine-project.org/contribute/index.html
## Coding Standard
We use PSR-1 and PSR-2:
This project follows [`doctrine/coding-standard`][coding standard homepage].
You may fix many some of the issues with `vendor/bin/phpcbf`.
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
with some exceptions/differences:
* Keep the nesting of control structures per method as small as possible
* Align equals (=) signs
* Add spaces between assignment, control and return statements
* Prefer early exit over nesting conditions
* Add spaces around a negation if condition ``if ( ! $cond)``
[coding standard homepage]: https://github.com/doctrine/coding-standard
## Unit-Tests
@@ -56,25 +43,19 @@ curl -sS https://getcomposer.org/installer | php --
To run the testsuite against another database, copy the ``phpunit.xml.dist``
to for example ``mysql.phpunit.xml`` and edit the parameters. You can
take a look at the ``tests/travis`` folder for some examples. Then run:
take a look at the ``ci/github/phpunit`` directory for some examples. Then run:
vendor/bin/phpunit -c mysql.phpunit.xml
If you do not provide these parameters, the test suite will use an in-memory
sqlite database.
Tips for creating unit tests:
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
See `https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
See `https://github.com/doctrine/orm/tree/2.8.x/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
example.
## Travis
We automatically run your pull request through [Travis CI](http://www.travis-ci.org)
against SQLite, MySQL and PostgreSQL. If you break the tests, we cannot merge your code,
so please make sure that your code is working before opening up a Pull-Request.
## Getting merged
Please allow us time to review your pull requests. We will give our best to review

View File

@@ -1,7 +1,7 @@
| [Master][Master] | [2.8.x][2.8] |
|:----------------:|:----------:|
| [![Build status][Master image]][Master] | [![Build status][2.8 image]][2.8] |
| [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][2.8 coverage image]][2.8 coverage] |
| [3.0.x][3.0] | [2.9.x][2.9] | [2.8.x][2.8] |
|:----------------:|:----------------:|:----------:|
| [![Build status][3.0 image]][3.0] | [![Build status][2.9 image]][2.9] | [![Build status][2.8 image]][2.8] |
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.9 coverage image]][2.9 coverage] | [![Coverage Status][2.8 coverage image]][2.8 coverage] |
Doctrine 2 is an object-relational mapper (ORM) for PHP 7.1+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
@@ -16,11 +16,15 @@ without requiring unnecessary code duplication.
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
[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://codecov.io/gh/doctrine/orm/branch/master/graph/badge.svg
[Master coverage]: https://codecov.io/gh/doctrine/orm/branch/master
[2.8 image]: https://img.shields.io/travis/doctrine/orm/2.8.svg?style=flat-square
[3.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.0.x
[3.0]: https://github.com/doctrine/orm/tree/3.0.x
[3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg
[3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x
[2.9 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.9.x
[2.9]: https://github.com/doctrine/orm/tree/2.9.x
[2.9 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.9.x/graph/badge.svg
[2.9 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.9.x
[2.8 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg
[2.8]: https://github.com/doctrine/orm/tree/2.8
[2.8 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.8/graph/badge.svg
[2.8 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.8
[2.8 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.8.x/graph/badge.svg
[2.8 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.8.x

View File

@@ -10,8 +10,8 @@ we cannot protect you from SQL injection.
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/orm/blob/master/docs/en/reference/security.rst)
- [DBAL Security Page](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/security.html)
- [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/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

View File

@@ -5,6 +5,11 @@
Method `Doctrine\ORM\UnitOfWork#commit()` can throw an OptimisticLockException when a commit silently fails and returns false
since `Doctrine\DBAL\Connection#commit()` signature changed from returning void to boolean
## Deprecated: `Doctrine\ORM\AbstractQuery#iterator()`
The method `Doctrine\ORM\AbstractQuery#iterator()` is deprecated in favor of `Doctrine\ORM\AbstractQuery#toIterable()`.
Note that `toIterable()` yields results of the query, unlike `iterator()` which yielded each result wrapped into an array.
# Upgrade to 2.7
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods

View File

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

View File

@@ -7,6 +7,10 @@
failOnRisky="true"
>
<php>
<!-- use an in-memory sqlite database -->
<var name="db_driver" value="pdo_sqlite"/>
<var name="db_memory" value="true"/>
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>

View File

@@ -22,7 +22,7 @@
"doctrine/annotations": "^1.11.1",
"doctrine/cache": "^1.9.1",
"doctrine/collections": "^1.5",
"doctrine/common": "^3.0",
"doctrine/common": "^3.0.3",
"doctrine/dbal": "^2.10.0",
"doctrine/event-manager": "^1.1",
"doctrine/inflector": "^1.4|^2.0",
@@ -51,12 +51,7 @@
}
},
"bin": ["bin/doctrine"],
"extra": {
"branch-alias": {
"dev-master": "2.7.x-dev"
}
},
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
"exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
}
}

View File

@@ -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/2.8.x/lib/Doctrine/Common/Cache>`_.
PhpFileCache
~~~~~~~~~~~~

View File

@@ -277,10 +277,17 @@ following syntax:
.. code-block:: php
<?php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Query\Parameter;
// $qb instanceof QueryBuilder
// Query here...
$qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2'));
$qb->setParameters(new ArrayCollection([
new Parameter('1', 'value for ?1'),
new Parameter('2', 'value for ?2')
]));
Getting already bound parameters is easy - simply use the above
mentioned syntax with "getParameter()" or "getParameters()":

View File

@@ -483,6 +483,7 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="bugs")
*/
class Bug
@@ -661,12 +662,12 @@ domain model to match the requirements:
Lazy load proxies always contain an instance of
Doctrine's EntityManager and all its dependencies. Therefore a
var\_dump() will possibly dump a very large recursive structure
``var_dump()`` will possibly dump a very large recursive structure
which is impossible to render and read. You have to use
``Doctrine\Common\Util\Debug::dump()`` to restrict the dumping to a
human readable level. Additionally you should be aware that dumping
the EntityManager to a Browser may take several minutes, and the
Debug::dump() method just ignores any occurrences of it in Proxy
``Debug::dump()`` method just ignores any occurrences of it in Proxy
instances.
Because we only work with collections for the references we must be

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -21,11 +22,20 @@ namespace Doctrine\ORM;
use Countable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\ORM\Cache\Logging\CacheLogger;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Internal\Hydration\IterableResult;
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\Mapping\MappingException;
use Traversable;
use function array_map;
use function array_shift;
use function count;
@@ -40,18 +50,13 @@ use function reset;
use function serialize;
use function sha1;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
*/
abstract class AbstractQuery
{
@@ -60,27 +65,27 @@ abstract class AbstractQuery
/**
* Hydrates an object graph. This is the default behavior.
*/
const HYDRATE_OBJECT = 1;
public const HYDRATE_OBJECT = 1;
/**
* Hydrates an array graph.
*/
const HYDRATE_ARRAY = 2;
public const HYDRATE_ARRAY = 2;
/**
* Hydrates a flat, rectangular result set with scalar values.
*/
const HYDRATE_SCALAR = 3;
public const HYDRATE_SCALAR = 3;
/**
* Hydrates a single scalar value.
*/
const HYDRATE_SINGLE_SCALAR = 4;
public const HYDRATE_SINGLE_SCALAR = 4;
/**
* Very simple object hydrator (optimized for performance).
*/
const HYDRATE_SIMPLEOBJECT = 5;
public const HYDRATE_SIMPLEOBJECT = 5;
/**
* The parameter map of this query.
@@ -93,7 +98,7 @@ abstract class AbstractQuery
/**
* The user-specified ResultSetMapping to use.
*
* @var \Doctrine\ORM\Query\ResultSetMapping
* @var ResultSetMapping
*/
protected $_resultSetMapping;
@@ -107,7 +112,7 @@ abstract class AbstractQuery
/**
* The map of query hints.
*
* @var array
* @psalm-var array<string, mixed>
*/
protected $_hints = [];
@@ -118,33 +123,27 @@ abstract class AbstractQuery
*/
protected $_hydrationMode = self::HYDRATE_OBJECT;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
*/
/** @var QueryCacheProfile */
protected $_queryCacheProfile;
/**
* Whether or not expire the result cache.
*
* @var boolean
* @var bool
*/
protected $_expireResultCache = false;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
*/
/** @var QueryCacheProfile */
protected $_hydrationCacheProfile;
/**
* Whether to use second level cache, if available.
*
* @var boolean
* @var bool
*/
protected $cacheable = false;
/**
* @var boolean
*/
/** @var bool */
protected $hasCache = false;
/**
@@ -157,31 +156,25 @@ abstract class AbstractQuery
/**
* Second level query cache mode.
*
* @var integer|null
* @var int|null
*/
protected $cacheMode;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
/** @var CacheLogger|null */
protected $cacheLogger;
/**
* @var integer
*/
/** @var int */
protected $lifetime = 0;
/**
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
*
* @param \Doctrine\ORM\EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->_em = $em;
$this->parameters = new ArrayCollection();
$this->_hints = $em->getConfiguration()->getDefaultQueryHints();
$this->hasCache = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
$this->_em = $em;
$this->parameters = new ArrayCollection();
$this->_hints = $em->getConfiguration()->getDefaultQueryHints();
$this->hasCache = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
if ($this->hasCache) {
$this->cacheLogger = $em->getConfiguration()
@@ -193,19 +186,19 @@ abstract class AbstractQuery
/**
* Enable/disable second level query (result) caching for this query.
*
* @param boolean $cacheable
* @param bool $cacheable
*
* @return static This query instance.
*/
public function setCacheable($cacheable)
{
$this->cacheable = (boolean) $cacheable;
$this->cacheable = (bool) $cacheable;
return $this;
}
/**
* @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
* @return bool TRUE if the query results are enable for second level cache, FALSE otherwise.
*/
public function isCacheable()
{
@@ -225,17 +218,17 @@ abstract class AbstractQuery
}
/**
* Obtain the name of the second level query cache region in which query results will be stored
*
* @return string|null The cache region name; NULL indicates the default region.
*/
* Obtain the name of the second level query cache region in which query results will be stored
*
* @return string|null The cache region name; NULL indicates the default region.
*/
public function getCacheRegion()
{
return $this->cacheRegion;
}
/**
* @return boolean TRUE if the query cache and second level cache are enabled, FALSE otherwise.
* @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise.
*/
protected function isCacheEnabled()
{
@@ -243,7 +236,7 @@ abstract class AbstractQuery
}
/**
* @return integer
* @return int
*/
public function getLifetime()
{
@@ -253,19 +246,19 @@ abstract class AbstractQuery
/**
* Sets the life-time for this query into second level cache.
*
* @param integer $lifetime
* @param int $lifetime
*
* @return static This query instance.
*/
public function setLifetime($lifetime)
{
$this->lifetime = (integer) $lifetime;
$this->lifetime = (int) $lifetime;
return $this;
}
/**
* @return integer
* @return int
*/
public function getCacheMode()
{
@@ -273,13 +266,13 @@ abstract class AbstractQuery
}
/**
* @param integer $cacheMode
* @param int $cacheMode
*
* @return static This query instance.
*/
public function setCacheMode($cacheMode)
{
$this->cacheMode = (integer) $cacheMode;
$this->cacheMode = (int) $cacheMode;
return $this;
}
@@ -296,7 +289,7 @@ abstract class AbstractQuery
/**
* Retrieves the associated EntityManager of this Query instance.
*
* @return \Doctrine\ORM\EntityManager
* @return EntityManager
*/
public function getEntityManager()
{
@@ -339,7 +332,7 @@ abstract class AbstractQuery
$key = Query\Parameter::normalizeName($key);
$filteredParameters = $this->parameters->filter(
function (Query\Parameter $parameter) use ($key) : bool {
static function (Query\Parameter $parameter) use ($key): bool {
$parameterName = $parameter->getName();
return $key === $parameterName;
@@ -410,7 +403,7 @@ abstract class AbstractQuery
*
* @return mixed[]|string|int|float|bool
*
* @throws \Doctrine\ORM\ORMInvalidArgumentException
* @throws ORMInvalidArgumentException
*
* @psalm-return array|scalar
*/
@@ -420,15 +413,12 @@ abstract class AbstractQuery
return $value;
}
if ($value instanceof Traversable) {
if ($value instanceof Collection) {
$value = iterator_to_array($value);
}
if (is_array($value)) {
foreach ($value as $key => $paramValue) {
$paramValue = $this->processParameterValue($paramValue);
$value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
}
$value = $this->processArrayParameterValue($value);
return $value;
}
@@ -447,10 +437,48 @@ abstract class AbstractQuery
if ($value === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
} catch (ORMMappingException $e) {
// Silence any mapping exceptions. These can occur if the object in
// question is not a mapped entity, in which case we just don't do
// any preparation on the value.
} catch (MappingException | ORMMappingException $e) {
/* Silence any mapping exceptions. These can occur if the object in
question is not a mapped entity, in which case we just don't do
any preparation on the value.
Depending on MappingDriver, either MappingException or
ORMMappingException is thrown. */
$value = $this->potentiallyProcessIterable($value);
}
return $value;
}
/**
* If no mapping is detected, trying to resolve the value as a Traversable
*
* @param mixed $value
*
* @return mixed
*/
private function potentiallyProcessIterable($value)
{
if ($value instanceof Traversable) {
$value = iterator_to_array($value);
$value = $this->processArrayParameterValue($value);
}
return $value;
}
/**
* Process a parameter value which was previously identified as an array
*
* @param mixed[] $value
*
* @return mixed[]
*/
private function processArrayParameterValue(array $value): array
{
foreach ($value as $key => $paramValue) {
$paramValue = $this->processParameterValue($paramValue);
$value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
}
return $value;
@@ -459,8 +487,6 @@ abstract class AbstractQuery
/**
* Sets the ResultSetMapping that should be used for hydration.
*
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
*
* @return static This query instance.
*/
public function setResultSetMapping(Query\ResultSetMapping $rsm)
@@ -474,7 +500,7 @@ abstract class AbstractQuery
/**
* Gets the ResultSetMapping used for hydration.
*
* @return \Doctrine\ORM\Query\ResultSetMapping
* @return ResultSetMapping
*/
protected function getResultSetMapping()
{
@@ -484,17 +510,15 @@ abstract class AbstractQuery
/**
* Allows to translate entity namespaces to full qualified names.
*
* @param Query\ResultSetMapping $rsm
*
* @return void
*/
private function translateNamespaces(Query\ResultSetMapping $rsm)
{
$translate = function ($alias) : string {
$translate = function ($alias): string {
return $this->_em->getClassMetadata($alias)->getName();
};
$rsm->aliasMap = array_map($translate, $rsm->aliasMap);
$rsm->aliasMap = array_map($translate, $rsm->aliasMap);
$rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
}
@@ -510,21 +534,19 @@ abstract class AbstractQuery
* some form of caching with UnitOfWork registration you should use
* {@see AbstractQuery::setResultCacheProfile()}.
*
* @return static This query instance.
*
* @example
* $lifetime = 100;
* $resultKey = "abc";
* $query->setHydrationCacheProfile(new QueryCacheProfile());
* $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
*
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
*
* @return static This query instance.
*/
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
public function setHydrationCacheProfile(?QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
$this->_hydrationCacheProfile = $profile;
@@ -533,7 +555,7 @@ abstract class AbstractQuery
}
/**
* @return \Doctrine\DBAL\Cache\QueryCacheProfile
* @return QueryCacheProfile
*/
public function getHydrationCacheProfile()
{
@@ -546,15 +568,13 @@ abstract class AbstractQuery
* If no result cache driver is set in the QueryCacheProfile, the default
* result cache driver is used from the configuration.
*
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
*
* @return static This query instance.
*/
public function setResultCacheProfile(QueryCacheProfile $profile = null)
public function setResultCacheProfile(?QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
$this->_queryCacheProfile = $profile;
@@ -628,7 +648,7 @@ abstract class AbstractQuery
*
* @return static This query instance.
*/
public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null) : self
public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null): self
{
$this->setResultCacheLifetime($lifetime);
$this->setResultCacheId($resultCacheId);
@@ -641,7 +661,7 @@ abstract class AbstractQuery
*
* @return static This query instance.
*/
public function disableResultCache() : self
public function disableResultCache(): self
{
$this->_queryCacheProfile = null;
@@ -657,7 +677,7 @@ abstract class AbstractQuery
*/
public function setResultCacheLifetime($lifetime)
{
$lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
$lifetime = $lifetime !== null ? (int) $lifetime : 0;
$this->_queryCacheProfile = $this->_queryCacheProfile
? $this->_queryCacheProfile->setLifetime($lifetime)
@@ -671,7 +691,7 @@ abstract class AbstractQuery
*
* @deprecated
*
* @return integer
* @return int
*/
public function getResultCacheLifetime()
{
@@ -681,7 +701,7 @@ abstract class AbstractQuery
/**
* Defines if the result cache is active or not.
*
* @param boolean $expire Whether or not to force resultset cache expiration.
* @param bool $expire Whether or not to force resultset cache expiration.
*
* @return static This query instance.
*/
@@ -695,7 +715,7 @@ abstract class AbstractQuery
/**
* Retrieves if the resultset cache is active or not.
*
* @return boolean
* @return bool
*/
public function getExpireResultCache()
{
@@ -776,7 +796,7 @@ abstract class AbstractQuery
*
* Alias for execute(null, HYDRATE_ARRAY).
*
* @return array
* @return mixed[]
*/
public function getArrayResult()
{
@@ -788,7 +808,7 @@ abstract class AbstractQuery
*
* Alias for execute(null, HYDRATE_SCALAR).
*
* @return array
* @return mixed[]
*/
public function getScalarResult()
{
@@ -812,17 +832,16 @@ abstract class AbstractQuery
return null;
}
if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
return null;
}
if ( ! is_array($result)) {
if (! is_array($result)) {
return $result;
}
if (count($result) > 1) {
throw new NonUniqueResultException;
throw new NonUniqueResultException();
}
return array_shift($result);
@@ -848,15 +867,15 @@ abstract class AbstractQuery
$result = $this->execute(null, $hydrationMode);
if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
throw new NoResultException;
throw new NoResultException();
}
if ( ! is_array($result)) {
if (! is_array($result)) {
return $result;
}
if (count($result) > 1) {
throw new NonUniqueResultException;
throw new NonUniqueResultException();
}
return array_shift($result);
@@ -901,7 +920,7 @@ abstract class AbstractQuery
*/
public function getHint($name)
{
return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
return $this->_hints[$name] ?? false;
}
/**
@@ -919,7 +938,7 @@ abstract class AbstractQuery
/**
* Return the key value map of query hints that are currently set.
*
* @return array
* @return array<string,mixed>
*/
public function getHints()
{
@@ -932,10 +951,10 @@ abstract class AbstractQuery
*
* @deprecated
*
* @param ArrayCollection|array|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @param ArrayCollection|mixed[]|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
*
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
* @return IterableResult
*/
public function iterate($parameters = null, $hydrationMode = null)
{
@@ -948,7 +967,7 @@ abstract class AbstractQuery
$this->setHydrationMode($hydrationMode);
}
if ( ! empty($parameters)) {
if (! empty($parameters)) {
$this->setParameters($parameters);
}
@@ -967,19 +986,25 @@ abstract class AbstractQuery
*
* @return iterable<mixed>
*/
public function toIterable(iterable $parameters = [], $hydrationMode = null) : iterable
public function toIterable(iterable $parameters = [], $hydrationMode = null): iterable
{
if ($hydrationMode !== null) {
$this->setHydrationMode($hydrationMode);
}
if (($this->isCountable($parameters) && count($parameters) !== 0)
if (
($this->isCountable($parameters) && count($parameters) !== 0)
|| ($parameters instanceof Traversable && iterator_count($parameters) !== 0)
) {
$this->setParameters($parameters);
}
$rsm = $this->getResultSetMapping();
$rsm = $this->getResultSetMapping();
if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {
throw QueryException::iterateWithMixedResultNotAllowed();
}
$stmt = $this->_doExecute();
return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt, $rsm, $this->_hints);
@@ -988,8 +1013,8 @@ abstract class AbstractQuery
/**
* Executes the query.
*
* @param ArrayCollection|array|null $parameters Query parameters.
* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
* @param ArrayCollection|mixed[]|null $parameters Query parameters.
* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
*
* @return mixed
*/
@@ -1005,8 +1030,8 @@ abstract class AbstractQuery
/**
* Execute query ignoring second level cache.
*
* @param ArrayCollection|array|null $parameters
* @param string|int|null $hydrationMode
* @param ArrayCollection|mixed[]|null $parameters
* @param string|int|null $hydrationMode
*
* @return mixed
*/
@@ -1016,11 +1041,11 @@ abstract class AbstractQuery
$this->setHydrationMode($hydrationMode);
}
if ( ! empty($parameters)) {
if (! empty($parameters)) {
$this->setParameters($parameters);
}
$setCacheEntry = static function () : void {
$setCacheEntry = static function (): void {
};
if ($this->_hydrationCacheProfile !== null) {
@@ -1034,11 +1059,11 @@ abstract class AbstractQuery
return $result[$realCacheKey];
}
if ( ! $result) {
if (! $result) {
$result = [];
}
$setCacheEntry = static function ($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) : void {
$setCacheEntry = static function ($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile): void {
$result[$realCacheKey] = $data;
$cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
@@ -1064,8 +1089,8 @@ abstract class AbstractQuery
/**
* Load from second level cache or executes the query and put into cache.
*
* @param ArrayCollection|array|null $parameters
* @param string|int|null $hydrationMode
* @param ArrayCollection|mixed[]|null $parameters
* @param string|int|null $hydrationMode
*
* @return mixed
*/
@@ -1080,7 +1105,7 @@ abstract class AbstractQuery
$this->getTimestampKey()
);
$result = $queryCache->get($queryKey, $rsm, $this->_hints);
$result = $queryCache->get($queryKey, $rsm, $this->_hints);
if ($result !== null) {
if ($this->cacheLogger) {
@@ -1105,7 +1130,7 @@ abstract class AbstractQuery
}
/**
* @return \Doctrine\ORM\Cache\TimestampCacheKey|null
* @return TimestampCacheKey|null
*/
private function getTimestampKey()
{
@@ -1178,7 +1203,7 @@ abstract class AbstractQuery
/**
* Executes the query and returns a the resulting Statement object.
*
* @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
* @return Statement The executed database statement that holds the results.
*/
abstract protected function _doExecute();
@@ -1204,10 +1229,12 @@ abstract class AbstractQuery
{
$query = $this->getSQL();
$hints = $this->getHints();
$params = array_map(function(Parameter $parameter) {
$params = array_map(function (Parameter $parameter) {
$value = $parameter->getValue();
// Small optimization
// Does not invoke processParameterValue for scalar values
if (is_scalar($value = $parameter->getValue())) {
// Does not invoke processParameterValue for scalar value
if (is_scalar($value)) {
return $value;
}
@@ -1220,7 +1247,7 @@ abstract class AbstractQuery
}
/** @param iterable<mixed> $subject */
private function isCountable(iterable $subject) : bool
private function isCountable(iterable $subject): bool
{
return $subject instanceof Countable || is_array($subject);
}

View File

@@ -20,44 +20,44 @@
namespace Doctrine\ORM;
use Doctrine\ORM\Cache\QueryCache;
use Doctrine\ORM\Cache\Region;
/**
* Provides an API for querying/managing the second level cache regions.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface Cache
{
const DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
public const DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
const DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
public const DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
/**
* May read items from the cache, but will not add items.
*/
const MODE_GET = 1;
public const MODE_GET = 1;
/**
* Will never read items from the cache,
* but will add items to the cache as it reads them from the database.
*/
const MODE_PUT = 2;
public const MODE_PUT = 2;
/**
* May read items from the cache, and add items to the cache.
*/
const MODE_NORMAL = 3;
public const MODE_NORMAL = 3;
/**
* The query will never read items from the cache,
* but will refresh items to the cache as it reads them from the database.
*/
const MODE_REFRESH = 4;
public const MODE_REFRESH = 4;
/**
* @param string $className The entity class.
*
* @return \Doctrine\ORM\Cache\Region|null
* @return Region|null
*/
public function getEntityCacheRegion($className);
@@ -65,7 +65,7 @@ interface Cache
* @param string $className The entity class.
* @param string $association The field name that represents the association.
*
* @return \Doctrine\ORM\Cache\Region|null
* @return Region|null
*/
public function getCollectionCacheRegion($className, $association);
@@ -75,7 +75,7 @@ interface Cache
* @param string $className The entity class.
* @param mixed $identifier The entity identifier
*
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
* @return bool true if the underlying cache contains corresponding data; false otherwise.
*/
public function containsEntity($className, $identifier);
@@ -112,7 +112,7 @@ interface Cache
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
*
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
* @return bool true if the underlying cache contains corresponding data; false otherwise.
*/
public function containsCollection($className, $association, $ownerIdentifier);
@@ -149,7 +149,7 @@ interface Cache
*
* @param string $regionName The cache name given to the query.
*
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
* @return bool true if the underlying cache contains corresponding data; false otherwise.
*/
public function containsQuery($regionName);
@@ -172,7 +172,7 @@ interface Cache
*
* @param string|null $regionName Query cache region name, or default query cache if the region name is NULL.
*
* @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name.
* @return QueryCache The Query Cache associated with the region name.
*/
public function getQueryCache($regionName = null);
}

View File

@@ -22,16 +22,13 @@ namespace Doctrine\ORM\Cache;
/**
* Association cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class AssociationCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The entity identifier
* @var array<string, mixed> The entity identifier
*/
public $identifier;
@@ -43,13 +40,13 @@ class AssociationCacheEntry implements CacheEntry
public $class;
/**
* @param string $class The entity class.
* @param array $identifier The entity identifier.
* @param string $class The entity class.
* @param array<string, mixed> $identifier The entity identifier.
*/
public function __construct($class, array $identifier)
{
$this->class = $class;
$this->identifier = $identifier;
$this->class = $class;
$this->identifier = $identifier;
}
/**
@@ -57,7 +54,7 @@ class AssociationCacheEntry implements CacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
* @param array<string, mixed> $values array containing property values
*
* @return AssociationCacheEntry
*/

View File

@@ -24,34 +24,23 @@ use Doctrine\ORM\Cache\Logging\CacheLogger;
/**
* Configuration container for second-level cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CacheConfiguration
{
/**
* @var \Doctrine\ORM\Cache\CacheFactory|null
*/
/** @var CacheFactory|null */
private $cacheFactory;
/**
* @var \Doctrine\ORM\Cache\RegionsConfiguration|null
*/
/** @var RegionsConfiguration|null */
private $regionsConfig;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
/** @var CacheLogger|null */
private $cacheLogger;
/**
* @var \Doctrine\ORM\Cache\QueryCacheValidator|null
*/
/** @var QueryCacheValidator|null */
private $queryValidator;
/**
* @return \Doctrine\ORM\Cache\CacheFactory|null
* @return CacheFactory|null
*/
public function getCacheFactory()
{
@@ -59,8 +48,6 @@ class CacheConfiguration
}
/**
* @param \Doctrine\ORM\Cache\CacheFactory $factory
*
* @return void
*/
public function setCacheFactory(CacheFactory $factory)
@@ -69,23 +56,20 @@ class CacheConfiguration
}
/**
* @return \Doctrine\ORM\Cache\Logging\CacheLogger|null
* @return CacheLogger|null
*/
public function getCacheLogger()
{
return $this->cacheLogger;
}
/**
* @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger
*/
public function setCacheLogger(CacheLogger $logger)
{
$this->cacheLogger = $logger;
}
/**
* @return \Doctrine\ORM\Cache\RegionsConfiguration
* @return RegionsConfiguration
*/
public function getRegionsConfiguration()
{
@@ -96,16 +80,13 @@ class CacheConfiguration
return $this->regionsConfig;
}
/**
* @param \Doctrine\ORM\Cache\RegionsConfiguration $regionsConfig
*/
public function setRegionsConfiguration(RegionsConfiguration $regionsConfig)
{
$this->regionsConfig = $regionsConfig;
}
/**
* @return \Doctrine\ORM\Cache\QueryCacheValidator
* @return QueryCacheValidator
*/
public function getQueryValidator()
{
@@ -118,9 +99,6 @@ class CacheConfiguration
return $this->queryValidator;
}
/**
* @param \Doctrine\ORM\Cache\QueryCacheValidator $validator
*/
public function setQueryValidator(QueryCacheValidator $validator)
{
$this->queryValidator = $validator;

View File

@@ -26,11 +26,7 @@ namespace Doctrine\ORM\Cache;
* <b>IMPORTANT NOTE:</b>
*
* Fields of classes that implement CacheEntry are public for performance reason.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CacheEntry
{
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -21,11 +22,10 @@ namespace Doctrine\ORM\Cache;
use Doctrine\ORM\ORMException;
use function sprintf;
/**
* Exception for cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CacheException extends ORMException
{
@@ -33,7 +33,7 @@ class CacheException extends ORMException
* @param string $sourceEntity
* @param string $fieldName
*
* @return \Doctrine\ORM\Cache\CacheException
* @return CacheException
*/
public static function updateReadOnlyCollection($sourceEntity, $fieldName)
{
@@ -43,7 +43,7 @@ class CacheException extends ORMException
/**
* @param string $entityName
*
* @return \Doctrine\ORM\Cache\CacheException
* @return CacheException
*/
public static function updateReadOnlyEntity($entityName)
{
@@ -53,7 +53,7 @@ class CacheException extends ORMException
/**
* @param string $entityName
*
* @return \Doctrine\ORM\Cache\CacheException
* @return CacheException
*/
public static function nonCacheableEntity($entityName)
{

View File

@@ -20,93 +20,91 @@
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Persister\Collection\CachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
/**
* Contract for building second level cache regions components.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CacheFactory
{
/**
* Build an entity persister for the given entity metadata.
*
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $persister The entity persister that will be cached.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param EntityManagerInterface $em The entity manager.
* @param EntityPersister $persister The entity persister that will be cached.
* @param ClassMetadata $metadata The entity metadata.
*
* @return \Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister
* @return CachedEntityPersister
*/
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata);
/**
* Build a collection persister for the given relation mapping.
*
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Persisters\Collection\CollectionPersister $persister The collection persister that will be cached.
* @param array $mapping The association mapping.
* @param EntityManagerInterface $em The entity manager.
* @param CollectionPersister $persister The collection persister that will be cached.
* @param mixed[] $mapping The association mapping.
*
* @return \Doctrine\ORM\Cache\Persister\Collection\CachedCollectionPersister
* @return CachedCollectionPersister
*/
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping);
/**
* Build a query cache based on the given region name
*
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
* @param string $regionName The region name.
* @param EntityManagerInterface $em The Entity manager.
* @param string $regionName The region name.
*
* @return \Doctrine\ORM\Cache\QueryCache The built query cache.
* @return QueryCache The built query cache.
*/
public function buildQueryCache(EntityManagerInterface $em, $regionName = null);
/**
* Build an entity hydrator
*
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param EntityManagerInterface $em The Entity manager.
* @param ClassMetadata $metadata The entity metadata.
*
* @return \Doctrine\ORM\Cache\EntityHydrator The built entity hydrator.
* @return EntityHydrator The built entity hydrator.
*/
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata);
/**
* Build a collection hydrator
*
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
* @param array $mapping The association mapping.
* @param EntityManagerInterface $em The Entity manager.
* @param mixed[] $mapping The association mapping.
*
* @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hydrator.
* @return CollectionHydrator The built collection hydrator.
*/
public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping);
/**
* Build a cache region
*
* @param array $cache The cache configuration.
* @param array<string,mixed> $cache The cache configuration.
*
* @return \Doctrine\ORM\Cache\Region The cache region.
* @return Region The cache region.
*/
public function getRegion(array $cache);
/**
* Build timestamp cache region
*
* @return \Doctrine\ORM\Cache\TimestampRegion The timestamp region.
* @return TimestampRegion The timestamp region.
*/
public function getTimestampRegion();
/**
* Build \Doctrine\ORM\Cache
*
* @param EntityManagerInterface $entityManager
*
* @return \Doctrine\ORM\Cache
* @return Cache
*/
public function createCache(EntityManagerInterface $entityManager);
}

View File

@@ -23,9 +23,6 @@ namespace Doctrine\ORM\Cache;
/**
* Defines entity / collection / query key to be stored in the cache region.
* Allows multiple roles to be stored in the same cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
abstract class CacheKey
{

View File

@@ -22,9 +22,6 @@ namespace Doctrine\ORM\Cache;
/**
* Collection cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CollectionCacheEntry implements CacheEntry
{
@@ -48,7 +45,7 @@ class CollectionCacheEntry implements CacheEntry
*
* This method allows for Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
* @param array<string, mixed> $values array containing property values
*
* @return CollectionCacheEntry
*/

View File

@@ -20,18 +20,20 @@
namespace Doctrine\ORM\Cache;
use function implode;
use function ksort;
use function str_replace;
use function strtolower;
/**
* Defines entity collection roles to be stored in the cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CollectionCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The owner entity identifier
* @var array<string, mixed> The owner entity identifier
*/
public $ownerIdentifier;
@@ -50,17 +52,17 @@ class CollectionCacheKey extends CacheKey
public $association;
/**
* @param string $entityClass The entity class.
* @param string $association The field name that represents the association.
* @param array $ownerIdentifier The identifier of the owning entity.
* @param string $entityClass The entity class.
* @param string $association The field name that represents the association.
* @param array<string, mixed> $ownerIdentifier The identifier of the owning entity.
*/
public function __construct($entityClass, $association, array $ownerIdentifier)
{
ksort($ownerIdentifier);
$this->ownerIdentifier = $ownerIdentifier;
$this->entityClass = (string) $entityClass;
$this->association = (string) $association;
$this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association;
$this->ownerIdentifier = $ownerIdentifier;
$this->entityClass = (string) $entityClass;
$this->association = (string) $association;
$this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association;
}
}

View File

@@ -20,33 +20,31 @@
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
/**
* Hydrator cache entry for collections
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CollectionHydrator
{
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key.
* @param array|\Doctrine\Common\Collections\Collection $collection The collection.
* @param ClassMetadata $metadata The entity metadata.
* @param CollectionCacheKey $key The cached collection key.
* @param mixed[]|Collection $collection The collection.
*
* @return \Doctrine\ORM\Cache\CollectionCacheEntry
* @return CollectionCacheEntry
*/
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection);
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The owning entity metadata.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key.
* @param \Doctrine\ORM\Cache\CollectionCacheEntry $entry The cached collection entry.
* @param \Doctrine\ORM\PersistentCollection $collection The collection to load the cache into.
* @param ClassMetadata $metadata The owning entity metadata.
* @param CollectionCacheKey $key The cached collection key.
* @param CollectionCacheEntry $entry The cached collection entry.
* @param PersistentCollection $collection The collection to load the cache into.
*
* @return array
* @return mixed[]
*/
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection);
}

View File

@@ -26,32 +26,29 @@ namespace Doctrine\ORM\Cache;
*
* When a entry is locked another process should not be able to read or write the entry.
* All evict operation should not consider locks, even though an entry is locked evict should be able to delete the entry and its lock.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface ConcurrentRegion extends Region
{
/**
* Attempts to read lock the mapping for the given key.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock.
* @param CacheKey $key The key of the item to lock.
*
* @return \Doctrine\ORM\Cache\Lock A lock instance or NULL if the lock already exists.
* @return Lock A lock instance or NULL if the lock already exists.
*
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
* @throws LockException Indicates a problem accessing the region.
*/
public function lock(CacheKey $key);
/**
* Attempts to read unlock the mapping for the given key.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock.
* @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link readLock}
* @param CacheKey $key The key of the item to unlock.
* @param Lock $lock The lock previously obtained from {@link readLock}
*
* @return void
*
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
* @throws LockException Indicates a problem accessing the region.
*/
public function unlock(CacheKey $key, Lock $lock);
}

View File

@@ -20,44 +20,35 @@
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Cache;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\ORMInvalidArgumentException;
use Doctrine\ORM\UnitOfWork;
use function is_array;
use function is_object;
/**
* Provides an API for querying/managing the second level cache regions.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultCache implements Cache
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
/** @var EntityManagerInterface */
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
/** @var UnitOfWork */
private $uow;
/**
* @var \Doctrine\ORM\Cache\CacheFactory
*/
/** @var CacheFactory */
private $cacheFactory;
/**
* @var \Doctrine\ORM\Cache\QueryCache[]
*/
/** @var QueryCache[] */
private $queryCaches = [];
/**
* @var \Doctrine\ORM\Cache\QueryCache
*/
/** @var QueryCache */
private $defaultQueryCache;
public function __construct(EntityManagerInterface $em)
@@ -77,7 +68,7 @@ class DefaultCache implements Cache
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
return null;
}
@@ -92,7 +83,7 @@ class DefaultCache implements Cache
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
return null;
}
@@ -104,10 +95,10 @@ class DefaultCache implements Cache
*/
public function containsEntity($className, $identifier)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
return false;
}
@@ -122,7 +113,7 @@ class DefaultCache implements Cache
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
return;
}
@@ -137,7 +128,7 @@ class DefaultCache implements Cache
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
return;
}
@@ -154,7 +145,7 @@ class DefaultCache implements Cache
foreach ($metadatas as $metadata) {
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
continue;
}
@@ -170,7 +161,7 @@ class DefaultCache implements Cache
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
return false;
}
@@ -185,7 +176,7 @@ class DefaultCache implements Cache
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
return;
}
@@ -200,7 +191,7 @@ class DefaultCache implements Cache
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
return;
}
@@ -215,16 +206,14 @@ class DefaultCache implements Cache
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
foreach ($metadatas as $metadata) {
foreach ($metadata->associationMappings as $association) {
if ( ! $association['type'] & ClassMetadata::TO_MANY) {
if (! $association['type'] & ClassMetadata::TO_MANY) {
continue;
}
$persister = $this->uow->getCollectionPersister($association);
if ( ! ($persister instanceof CachedPersister)) {
if (! ($persister instanceof CachedPersister)) {
continue;
}
@@ -279,7 +268,7 @@ class DefaultCache implements Cache
$this->defaultQueryCache = $this->cacheFactory->buildQueryCache($this->em);
}
if ( ! isset($this->queryCaches[$regionName])) {
if (! isset($this->queryCaches[$regionName])) {
$this->queryCaches[$regionName] = $this->cacheFactory->buildQueryCache($this->em, $regionName);
}
@@ -287,14 +276,14 @@ class DefaultCache implements Cache
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
*
* @return \Doctrine\ORM\Cache\EntityCacheKey
*/
* @param ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
*
* @return EntityCacheKey
*/
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier)
{
if ( ! is_array($identifier)) {
if (! is_array($identifier)) {
$identifier = $this->toIdentifierArray($metadata, $identifier);
}
@@ -302,15 +291,15 @@ class DefaultCache implements Cache
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
* @param ClassMetadata $metadata The entity metadata.
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
*
* @return \Doctrine\ORM\Cache\CollectionCacheKey
* @return CollectionCacheKey
*/
private function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier)
{
if ( ! is_array($ownerIdentifier)) {
if (! is_array($ownerIdentifier)) {
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);
}
@@ -318,10 +307,10 @@ class DefaultCache implements Cache
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
* @param ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
*
* @return array
* @return array<string, mixed>
*/
private function toIdentifierArray(ClassMetadata $metadata, $identifier)
{
@@ -335,5 +324,4 @@ class DefaultCache implements Cache
return [$metadata->identifier[0] => $identifier];
}
}

View File

@@ -38,42 +38,30 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use InvalidArgumentException;
use LogicException;
use function sprintf;
use const DIRECTORY_SEPARATOR;
/**
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultCacheFactory implements CacheFactory
{
/**
* @var CacheAdapter
*/
/** @var CacheAdapter */
private $cache;
/**
* @var \Doctrine\ORM\Cache\RegionsConfiguration
*/
/** @var RegionsConfiguration */
private $regionsConfig;
/**
* @var \Doctrine\ORM\Cache\TimestampRegion|null
*/
/** @var TimestampRegion|null */
private $timestampRegion;
/**
* @var \Doctrine\ORM\Cache\Region[]
*/
/** @var Region[] */
private $regions = [];
/**
* @var string|null
*/
/** @var string|null */
private $fileLockRegionDirectory;
/**
* @param RegionsConfiguration $cacheConfig
* @param CacheAdapter $cache
*/
public function __construct(RegionsConfiguration $cacheConfig, CacheAdapter $cache)
{
$this->cache = $cache;
@@ -96,17 +84,11 @@ class DefaultCacheFactory implements CacheFactory
return $this->fileLockRegionDirectory;
}
/**
* @param \Doctrine\ORM\Cache\Region $region
*/
public function setRegion(Region $region)
{
$this->regions[$region->getName()] = $region;
}
/**
* @param \Doctrine\ORM\Cache\TimestampRegion $region
*/
public function setTimestampRegion(TimestampRegion $region)
{
$this->timestampRegion = $region;
@@ -117,8 +99,8 @@ class DefaultCacheFactory implements CacheFactory
*/
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata)
{
$region = $this->getRegion($metadata->cache);
$usage = $metadata->cache['usage'];
$region = $this->getRegion($metadata->cache);
$usage = $metadata->cache['usage'];
if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata);
@@ -132,7 +114,7 @@ class DefaultCacheFactory implements CacheFactory
return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
}
throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage));
throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage));
}
/**
@@ -140,8 +122,8 @@ class DefaultCacheFactory implements CacheFactory
*/
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping)
{
$usage = $mapping['cache']['usage'];
$region = $this->getRegion($mapping['cache']);
$usage = $mapping['cache']['usage'];
$region = $this->getRegion($mapping['cache']);
if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping);
@@ -155,7 +137,7 @@ class DefaultCacheFactory implements CacheFactory
return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
}
throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage));
throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage));
}
/**
@@ -168,7 +150,7 @@ class DefaultCacheFactory implements CacheFactory
$this->getRegion(
[
'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME,
'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE
'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE,
]
)
);
@@ -203,17 +185,16 @@ class DefaultCacheFactory implements CacheFactory
$cacheAdapter = $this->createRegionCache($name);
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
$region = ($cacheAdapter instanceof MultiGetCache)
$region = $cacheAdapter instanceof MultiGetCache
? new DefaultMultiGetRegion($name, $cacheAdapter, $lifetime)
: new DefaultRegion($name, $cacheAdapter, $lifetime);
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
if (
'' === $this->fileLockRegionDirectory ||
null === $this->fileLockRegionDirectory
$this->fileLockRegionDirectory === '' ||
$this->fileLockRegionDirectory === null
) {
throw new \LogicException(
throw new LogicException(
'If you want to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you want to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). '
);
@@ -235,13 +216,13 @@ class DefaultCacheFactory implements CacheFactory
{
$cacheAdapter = clone $this->cache;
if (!$cacheAdapter instanceof CacheProvider) {
if (! $cacheAdapter instanceof CacheProvider) {
return $cacheAdapter;
}
$namespace = $cacheAdapter->getNamespace();
if ('' !== $namespace) {
if ($namespace !== '') {
$namespace .= ':';
}

View File

@@ -20,36 +20,32 @@
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use function array_walk;
use function assert;
/**
* Default hydrator cache for collections
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultCollectionHydrator implements CollectionHydrator
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
/** @var EntityManagerInterface */
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
/** @var UnitOfWork */
private $uow;
/**
* @var array
*/
/** @var array<string,mixed> */
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param EntityManagerInterface $em The entity manager.
*/
public function __construct(EntityManagerInterface $em)
{
@@ -77,23 +73,23 @@ class DefaultCollectionHydrator implements CollectionHydrator
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection)
{
$assoc = $metadata->associationMappings[$key->association];
/* @var $targetPersister \Doctrine\ORM\Cache\Persister\CachedPersister */
$targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$targetRegion = $targetPersister->getCacheRegion();
$list = [];
assert($targetPersister instanceof CachedPersister);
$targetRegion = $targetPersister->getCacheRegion();
$list = [];
/** @var EntityCacheEntry[]|null $entityEntries */
$entityEntries = $targetRegion->getMultiple($entry);
if ($entityEntries === null) {
return null;
}
/* @var $entityEntries \Doctrine\ORM\Cache\EntityCacheEntry[] */
foreach ($entityEntries as $index => $entityEntry) {
$list[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
}
array_walk($list, function($entity, $index) use ($collection) {
array_walk($list, static function ($entity, $index) use ($collection) {
$collection->hydrateSet($index, $entity);
});

View File

@@ -21,49 +21,45 @@
namespace Doctrine\ORM\Cache;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Query;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Doctrine\ORM\Utility\IdentifierFlattener;
use function array_merge;
use function is_array;
use function is_object;
use function reset;
/**
* Default hydrator cache for entities
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultEntityHydrator implements EntityHydrator
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
/** @var EntityManagerInterface */
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
/** @var UnitOfWork */
private $uow;
/**
* The IdentifierFlattener used for manipulating identifiers
*
* @var \Doctrine\ORM\Utility\IdentifierFlattener
* @var IdentifierFlattener
*/
private $identifierFlattener;
/**
* @var array
*/
/** @var array<string,mixed> */
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param EntityManagerInterface $em The entity manager.
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->uow = $em->getUnitOfWork();
$this->em = $em;
$this->uow = $em->getUnitOfWork();
$this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory());
}
@@ -80,19 +76,19 @@ class DefaultEntityHydrator implements EntityHydrator
}
foreach ($metadata->associationMappings as $name => $assoc) {
if ( ! isset($data[$name])) {
if (! isset($data[$name])) {
continue;
}
if ( ! ($assoc['type'] & ClassMetadata::TO_ONE)) {
if (! ($assoc['type'] & ClassMetadata::TO_ONE)) {
unset($data[$name]);
continue;
}
if ( ! isset($assoc['cache'])) {
if (! isset($assoc['cache'])) {
$targetClassMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
$owningAssociation = ( ! $assoc['isOwningSide'])
$owningAssociation = ! $assoc['isOwningSide']
? $targetClassMetadata->associationMappings[$assoc['mappedBy']]
: $assoc;
$associationIds = $this->identifierFlattener->flattenIdentifier(
@@ -113,7 +109,7 @@ class DefaultEntityHydrator implements EntityHydrator
$targetAssoc = $targetClassMetadata->associationMappings[$fieldName];
foreach($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) {
foreach ($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) {
if (isset($targetAssoc['sourceToTargetKeyColumns'][$referencedColumn])) {
$data[$localColumn] = $fieldValue;
}
@@ -123,7 +119,7 @@ class DefaultEntityHydrator implements EntityHydrator
continue;
}
if ( ! isset($assoc['id'])) {
if (! isset($assoc['id'])) {
$targetClass = ClassUtils::getClass($data[$name]);
$targetId = $this->uow->getEntityIdentifier($data[$name]);
$data[$name] = new AssociationCacheEntry($targetClass, $targetId);
@@ -138,7 +134,7 @@ class DefaultEntityHydrator implements EntityHydrator
// @TODO - fix it !
// handle UnitOfWork#createEntity hash generation
if ( ! is_array($targetId)) {
if (! is_array($targetId)) {
$data[reset($assoc['joinColumnFieldNames'])] = $targetId;
$targetEntity = $this->em->getClassMetadata($assoc['targetEntity']);
@@ -160,20 +156,20 @@ class DefaultEntityHydrator implements EntityHydrator
$hints = self::$hints;
if ($entity !== null) {
$hints[Query::HINT_REFRESH] = true;
$hints[Query::HINT_REFRESH_ENTITY] = $entity;
$hints[Query::HINT_REFRESH] = true;
$hints[Query::HINT_REFRESH_ENTITY] = $entity;
}
foreach ($metadata->associationMappings as $name => $assoc) {
if ( ! isset($assoc['cache']) || ! isset($data[$name])) {
if (! isset($assoc['cache']) || ! isset($data[$name])) {
continue;
}
$assocClass = $data[$name]->class;
$assocId = $data[$name]->identifier;
$isEagerLoad = ($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ($assoc['type'] === ClassMetadata::ONE_TO_ONE && ! $assoc['isOwningSide']));
$assocClass = $data[$name]->class;
$assocId = $data[$name]->identifier;
$isEagerLoad = ($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ($assoc['type'] === ClassMetadata::ONE_TO_ONE && ! $assoc['isOwningSide']));
if ( ! $isEagerLoad) {
if (! $isEagerLoad) {
$data[$name] = $this->em->getReference($assocClass, $assocId);
continue;

View File

@@ -21,68 +21,63 @@
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;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Logging\CacheLogger;
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use function array_key_exists;
use function array_map;
use function array_shift;
use function array_unshift;
use function assert;
use function count;
use function is_array;
use function key;
use function reset;
/**
* Default query cache implementation.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultQueryCache implements QueryCache
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
/** @var EntityManagerInterface */
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
/** @var UnitOfWork */
private $uow;
/**
* @var \Doctrine\ORM\Cache\Region
*/
/** @var Region */
private $region;
/**
* @var \Doctrine\ORM\Cache\QueryCacheValidator
*/
/** @var QueryCacheValidator */
private $validator;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
*/
/** @var CacheLogger */
protected $cacheLogger;
/**
* @var array
*/
/** @var array<string,mixed> */
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Cache\Region $region The query region.
* @param EntityManagerInterface $em The entity manager.
* @param Region $region The query region.
*/
public function __construct(EntityManagerInterface $em, Region $region)
{
$cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration();
$this->em = $em;
$this->region = $region;
$this->uow = $em->getUnitOfWork();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->validator = $cacheConfig->getQueryValidator();
$this->em = $em;
$this->region = $region;
$this->uow = $em->getUnitOfWork();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->validator = $cacheConfig->getQueryValidator();
}
/**
@@ -90,17 +85,17 @@ class DefaultQueryCache implements QueryCache
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
{
if ( ! ($key->cacheMode & Cache::MODE_GET)) {
if (! ($key->cacheMode & Cache::MODE_GET)) {
return null;
}
$cacheEntry = $this->region->get($key);
if ( ! $cacheEntry instanceof QueryCacheEntry) {
if (! $cacheEntry instanceof QueryCacheEntry) {
return null;
}
if ( ! $this->validator->isValid($key, $cacheEntry)) {
if (! $this->validator->isValid($key, $cacheEntry)) {
$this->region->evict($key);
return null;
@@ -117,7 +112,7 @@ class DefaultQueryCache implements QueryCache
$cm = $this->em->getClassMetadata($entityName);
$generateKeys = static function (array $entry) use ($cm) : EntityCacheKey {
$generateKeys = static function (array $entry) use ($cm): EntityCacheKey {
return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
};
@@ -140,8 +135,8 @@ class DefaultQueryCache implements QueryCache
$this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]);
}
if ( ! $hasRelation) {
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
if (! $hasRelation) {
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
continue;
}
@@ -156,9 +151,10 @@ class DefaultQueryCache implements QueryCache
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
if ($assoc['type'] & ClassMetadata::TO_ONE) {
$assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']);
$assocEntry = $assocRegion->get($assocKey);
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']))) === null) {
if ($assocEntry === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
}
@@ -177,11 +173,11 @@ class DefaultQueryCache implements QueryCache
continue;
}
if ( ! isset($assoc['list']) || empty($assoc['list'])) {
if (! isset($assoc['list']) || empty($assoc['list'])) {
continue;
}
$generateKeys = function ($id) use ($assocMetadata): EntityCacheKey {
$generateKeys = static function ($id) use ($assocMetadata): EntityCacheKey {
return new EntityCacheKey($assocMetadata->rootEntityName, $id);
};
@@ -249,29 +245,29 @@ class DefaultQueryCache implements QueryCache
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
{
if ($rsm->scalarMappings) {
throw new CacheException("Second level cache does not support scalar results.");
throw new CacheException('Second level cache does not support scalar results.');
}
if (count($rsm->entityMappings) > 1) {
throw new CacheException("Second level cache does not support multiple root entities.");
throw new CacheException('Second level cache does not support multiple root entities.');
}
if ( ! $rsm->isSelect) {
throw new CacheException("Second-level cache query supports only select statements.");
if (! $rsm->isSelect) {
throw new CacheException('Second-level cache query supports only select statements.');
}
if (($hints[Query\SqlWalker::HINT_PARTIAL] ?? false) === true || ($hints[Query::HINT_FORCE_PARTIAL_LOAD] ?? false) === true) {
throw new CacheException("Second level cache does not support partial entities.");
throw new CacheException('Second level cache does not support partial entities.');
}
if ( ! ($key->cacheMode & Cache::MODE_PUT)) {
if (! ($key->cacheMode & Cache::MODE_PUT)) {
return false;
}
$data = [];
$entityName = reset($rsm->aliasMap);
$rootAlias = key($rsm->aliasMap);
$persister = $this->uow->getEntityPersister($entityName);
$data = [];
$entityName = reset($rsm->aliasMap);
$rootAlias = key($rsm->aliasMap);
$persister = $this->uow->getEntityPersister($entityName);
if (! $persister instanceof CachedEntityPersister) {
throw CacheException::nonCacheableEntity($entityName);
@@ -288,7 +284,7 @@ class DefaultQueryCache implements QueryCache
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
// Cancel put result if entity put fail
if ( ! $persister->storeEntityCache($entity, $entityKey)) {
if (! $persister->storeEntityCache($entity, $entityKey)) {
return false;
}
}
@@ -298,11 +294,11 @@ class DefaultQueryCache implements QueryCache
// @TODO - move to cache hydration components
foreach ($rsm->relationMap as $alias => $name) {
$parentAlias = $rsm->parentAliasMap[$alias];
$parentClass = $rsm->aliasMap[$parentAlias];
$metadata = $this->em->getClassMetadata($parentClass);
$assoc = $metadata->associationMappings[$name];
$assocValue = $this->getAssociationValue($rsm, $alias, $entity);
$parentAlias = $rsm->parentAliasMap[$alias];
$parentClass = $rsm->aliasMap[$parentAlias];
$metadata = $this->em->getClassMetadata($parentClass);
$assoc = $metadata->associationMappings[$name];
$assocValue = $this->getAssociationValue($rsm, $alias, $entity);
if ($assocValue === null) {
continue;
@@ -311,7 +307,8 @@ class DefaultQueryCache implements QueryCache
// root entity association
if ($rootAlias === $parentAlias) {
// Cancel put result if association put fail
if ( ($assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue)) === null) {
$assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue);
if ($assocInfo === null) {
return false;
}
@@ -321,7 +318,7 @@ class DefaultQueryCache implements QueryCache
}
// store single nested association
if ( ! is_array($assocValue)) {
if (! is_array($assocValue)) {
// Cancel put result if association put fail
if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) {
return false;
@@ -344,9 +341,8 @@ class DefaultQueryCache implements QueryCache
}
/**
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param array $assoc
* @param mixed $assocValue
* @param array<string,mixed> $assoc
* @param mixed $assocValue
*
* @return mixed[]|null
*
@@ -363,9 +359,9 @@ class DefaultQueryCache implements QueryCache
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
if ( ! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
if (! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
// Entity put fail
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
if (! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
return null;
}
}
@@ -373,7 +369,7 @@ class DefaultQueryCache implements QueryCache
return [
'targetEntity' => $assocMetadata->rootEntityName,
'identifier' => $assocIdentifier,
'type' => $assoc['type']
'type' => $assoc['type'],
];
}
@@ -386,7 +382,7 @@ class DefaultQueryCache implements QueryCache
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
// Entity put fail
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
if (! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
return null;
}
}
@@ -402,11 +398,10 @@ class DefaultQueryCache implements QueryCache
}
/**
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
* @param string $assocAlias
* @param object $entity
* @param string $assocAlias
* @param object $entity
*
* @return array|object
* @return array<object>|object
*/
private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity)
{
@@ -420,9 +415,8 @@ class DefaultQueryCache implements QueryCache
array_unshift($path, [
'field' => $field,
'class' => $class
]
);
'class' => $class,
]);
$alias = $parent;
}
@@ -431,10 +425,10 @@ class DefaultQueryCache implements QueryCache
}
/**
* @param mixed $value
* @param array $path
* @param mixed $value
* @param array<mixed> $path
*
* @return array|object|null
* @return mixed
*/
private function getAssociationPathValue($value, array $path)
{

View File

@@ -22,18 +22,17 @@ namespace Doctrine\ORM\Cache;
use Doctrine\ORM\EntityManagerInterface;
use function array_map;
/**
* Entity cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class EntityCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The entity map data
* @var array<string,mixed> The entity map data
*/
public $data;
@@ -45,8 +44,8 @@ class EntityCacheEntry implements CacheEntry
public $class;
/**
* @param string $class The entity class.
* @param array $data The entity data.
* @param string $class The entity class.
* @param array<string,mixed> $data The entity data.
*/
public function __construct($class, array $data)
{
@@ -59,7 +58,7 @@ class EntityCacheEntry implements CacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
* @param array<string,mixed> $values array containing property values
*
* @return EntityCacheEntry
*/
@@ -71,14 +70,12 @@ class EntityCacheEntry implements CacheEntry
/**
* Retrieves the entity data resolving cache entries
*
* @param \Doctrine\ORM\EntityManagerInterface $em
*
* @return array
* @return array<string, mixed>
*/
public function resolveAssociationEntries(EntityManagerInterface $em)
{
return array_map(function($value) use ($em) {
if ( ! ($value instanceof AssociationCacheEntry)) {
return array_map(static function ($value) use ($em) {
if (! ($value instanceof AssociationCacheEntry)) {
return $value;
}

View File

@@ -20,18 +20,20 @@
namespace Doctrine\ORM\Cache;
use function implode;
use function ksort;
use function str_replace;
use function strtolower;
/**
* Defines entity classes roles to be stored in the cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class EntityCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The entity identifier
* @var array<string, mixed> The entity identifier
*/
public $identifier;
@@ -43,8 +45,8 @@ class EntityCacheKey extends CacheKey
public $entityClass;
/**
* @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class.
* @param array $identifier The entity identifier
* @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class.
* @param array<string, mixed> $identifier The entity identifier
*/
public function __construct($entityClass, array $identifier)
{

View File

@@ -24,26 +24,23 @@ use Doctrine\ORM\Mapping\ClassMetadata;
/**
* Hydrator cache entry for entities
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface EntityHydrator
{
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key.
* @param object $entity The entity.
* @param ClassMetadata $metadata The entity metadata.
* @param EntityCacheKey $key The entity cache key.
* @param object $entity The entity.
*
* @return \Doctrine\ORM\Cache\EntityCacheEntry
* @return EntityCacheEntry
*/
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity);
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key.
* @param \Doctrine\ORM\Cache\EntityCacheEntry $entry The entity cache entry.
* @param object $entity The entity to load the cache into. If not specified, a new entity is created.
* @param ClassMetadata $metadata The entity metadata.
* @param EntityCacheKey $key The entity cache key.
* @param EntityCacheEntry $entry The entity cache entry.
* @param object $entity The entity to load the cache into. If not specified, a new entity is created.
*/
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null);
}

View File

@@ -20,27 +20,23 @@
namespace Doctrine\ORM\Cache;
use function time;
use function uniqid;
/**
* Cache Lock
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class Lock
{
/**
* @var string
*/
/** @var string */
public $value;
/**
* @var integer
*/
/** @var int */
public $time;
/**
* @param string $value
* @param integer $time
* @param string $value
* @param int $time
*/
public function __construct($value, $time = null)
{
@@ -49,7 +45,7 @@ class Lock
}
/**
* @return \Doctrine\ORM\Cache\Lock
* @return Lock
*/
public static function createLockRead()
{

View File

@@ -22,11 +22,7 @@ namespace Doctrine\ORM\Cache;
/**
* Lock exception for cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class LockException extends CacheException
{
}

View File

@@ -26,81 +26,78 @@ use Doctrine\ORM\Cache\QueryCacheKey;
/**
* Interface for logging.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CacheLogger
{
/**
* Log an entity put into second level cache.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
* @param string $regionName The name of the cache region.
* @param EntityCacheKey $key The cache key of the entity.
*/
public function entityCachePut($regionName, EntityCacheKey $key);
/**
* Log an entity get from second level cache resulted in a hit.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
* @param string $regionName The name of the cache region.
* @param EntityCacheKey $key The cache key of the entity.
*/
public function entityCacheHit($regionName, EntityCacheKey $key);
/**
* Log an entity get from second level cache resulted in a miss.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
* @param string $regionName The name of the cache region.
* @param EntityCacheKey $key The cache key of the entity.
*/
public function entityCacheMiss($regionName, EntityCacheKey $key);
/**
* Log an entity put into second level cache.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
*/
* Log an entity put into second level cache.
*
* @param string $regionName The name of the cache region.
* @param CollectionCacheKey $key The cache key of the collection.
*/
public function collectionCachePut($regionName, CollectionCacheKey $key);
/**
* Log an entity get from second level cache resulted in a hit.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
* @param string $regionName The name of the cache region.
* @param CollectionCacheKey $key The cache key of the collection.
*/
public function collectionCacheHit($regionName, CollectionCacheKey $key);
/**
* Log an entity get from second level cache resulted in a miss.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
* @param string $regionName The name of the cache region.
* @param CollectionCacheKey $key The cache key of the collection.
*/
public function collectionCacheMiss($regionName, CollectionCacheKey $key);
/**
* Log a query put into the query cache.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
* @param string $regionName The name of the cache region.
* @param QueryCacheKey $key The cache key of the query.
*/
public function queryCachePut($regionName, QueryCacheKey $key);
/**
* Log a query get from the query cache resulted in a hit.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
* @param string $regionName The name of the cache region.
* @param QueryCacheKey $key The cache key of the query.
*/
public function queryCacheHit($regionName, QueryCacheKey $key);
/**
* Log a query get from the query cache resulted in a miss.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
* @param string $regionName The name of the cache region.
* @param QueryCacheKey $key The cache key of the query.
*/
public function queryCacheMiss($regionName, QueryCacheKey $key);
}

View File

@@ -26,20 +26,14 @@ use Doctrine\ORM\Cache\QueryCacheKey;
/**
* Cache logger chain
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CacheLoggerChain implements CacheLogger
{
/**
* @var array<\Doctrine\ORM\Cache\Logging\CacheLogger>
*/
/** @var array<CacheLogger> */
private $loggers = [];
/**
* @param string $name
* @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger
* @param string $name
*/
public function setLogger($name, CacheLogger $logger)
{
@@ -49,15 +43,15 @@ class CacheLoggerChain implements CacheLogger
/**
* @param string $name
*
* @return \Doctrine\ORM\Cache\Logging\CacheLogger|null
* @return CacheLogger|null
*/
public function getLogger($name)
{
return isset($this->loggers[$name]) ? $this->loggers[$name] : null;
return $this->loggers[$name] ?? null;
}
/**
* @return array<\Doctrine\ORM\Cache\Logging\CacheLogger>
* @return array<CacheLogger>
*/
public function getLoggers()
{

View File

@@ -24,27 +24,20 @@ use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use function array_sum;
/**
* Provide basic second level cache statistics.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class StatisticsCacheLogger implements CacheLogger
{
/**
* @var int[]
*/
/** @var int[] */
private $cacheMissCountMap = [];
/**
* @var int[]
*/
/** @var int[] */
private $cacheHitCountMap = [];
/**
* @var int[]
*/
/** @var int[] */
private $cachePutCountMap = [];
/**
@@ -146,7 +139,7 @@ class StatisticsCacheLogger implements CacheLogger
*/
public function getRegionHitCount($regionName)
{
return isset($this->cacheHitCountMap[$regionName]) ? $this->cacheHitCountMap[$regionName] : 0;
return $this->cacheHitCountMap[$regionName] ?? 0;
}
/**
@@ -158,7 +151,7 @@ class StatisticsCacheLogger implements CacheLogger
*/
public function getRegionMissCount($regionName)
{
return isset($this->cacheMissCountMap[$regionName]) ? $this->cacheMissCountMap[$regionName] : 0;
return $this->cacheMissCountMap[$regionName] ?? 0;
}
/**
@@ -170,11 +163,11 @@ class StatisticsCacheLogger implements CacheLogger
*/
public function getRegionPutCount($regionName)
{
return isset($this->cachePutCountMap[$regionName]) ? $this->cachePutCountMap[$regionName] : 0;
return $this->cachePutCountMap[$regionName] ?? 0;
}
/**
* @return array
* @return array<string, int>
*/
public function getRegionsMiss()
{
@@ -182,7 +175,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* @return array
* @return array<string, int>
*/
public function getRegionsHit()
{
@@ -190,7 +183,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* @return array
* @return array<string, int>
*/
public function getRegionsPut()
{

View File

@@ -24,9 +24,6 @@ namespace Doctrine\ORM\Cache;
* Defines a region that supports multi-get reading.
*
* With one method call we can get multiple items.
*
* @since 2.5
* @author Asmir Mustafic
*/
interface MultiGetRegion
{

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,11 +20,10 @@
namespace Doctrine\ORM\Cache\Persister;
use Doctrine\ORM\Cache\Region;
/**
* Interface for persister that support second level cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CachedPersister
{
@@ -40,7 +40,7 @@ interface CachedPersister
/**
* Gets the The region access.
*
* @return \Doctrine\ORM\Cache\Region
* @return Region
*/
public function getCacheRegion();
}

View File

@@ -21,99 +21,82 @@
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\Region;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\CollectionHydrator;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\Logging\CacheLogger;
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\UnitOfWork;
use function array_values;
use function assert;
use function count;
use function is_array;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5
*/
abstract class AbstractCollectionPersister implements CachedCollectionPersister
{
/**
* @var \Doctrine\ORM\UnitOfWork
*/
/** @var UnitOfWork */
protected $uow;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadataFactory
*/
/** @var ClassMetadataFactory */
protected $metadataFactory;
/**
* @var \Doctrine\ORM\Persisters\Collection\CollectionPersister
*/
/** @var CollectionPersister */
protected $persister;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
/** @var ClassMetadata */
protected $sourceEntity;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
/** @var ClassMetadata */
protected $targetEntity;
/**
* @var array
*/
/** @var mixed[] */
protected $association;
/**
* @var array
*/
/** @var mixed[] */
protected $queuedCache = [];
/**
* @var \Doctrine\ORM\Cache\Region
*/
/** @var Region */
protected $region;
/**
* @var string
*/
/** @var string */
protected $regionName;
/**
* @var \Doctrine\ORM\Cache\CollectionHydrator
*/
/** @var CollectionHydrator */
protected $hydrator;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
*/
/** @var CacheLogger */
protected $cacheLogger;
/**
* @param \Doctrine\ORM\Persisters\Collection\CollectionPersister $persister The collection persister that will be cached.
* @param \Doctrine\ORM\Cache\Region $region The collection region.
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param array $association The association mapping.
* @param CollectionPersister $persister The collection persister that will be cached.
* @param Region $region The collection region.
* @param EntityManagerInterface $em The entity manager.
* @param mixed[] $association The association mapping.
*/
public function __construct(CollectionPersister $persister, Region $region, EntityManagerInterface $em, array $association)
{
$configuration = $em->getConfiguration();
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$configuration = $em->getConfiguration();
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$this->region = $region;
$this->persister = $persister;
$this->association = $association;
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->hydrator = $cacheFactory->buildCollectionHydrator($em, $association);
$this->sourceEntity = $em->getClassMetadata($association['sourceEntity']);
$this->targetEntity = $em->getClassMetadata($association['targetEntity']);
$this->region = $region;
$this->persister = $persister;
$this->association = $association;
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->hydrator = $cacheFactory->buildCollectionHydrator($em, $association);
$this->sourceEntity = $em->getClassMetadata($association['sourceEntity']);
$this->targetEntity = $em->getClassMetadata($association['targetEntity']);
}
/**
@@ -141,22 +124,17 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* @param \Doctrine\ORM\PersistentCollection $collection
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key
*
* @return object[]|null
*/
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key)
{
if (($cache = $this->region->get($key)) === null) {
$cache = $this->region->get($key);
if ($cache === null) {
return null;
}
if (($cache = $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection)) === null) {
return null;
}
return $cache;
return $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection);
}
/**
@@ -164,14 +142,14 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
*/
public function storeCollectionCache(CollectionCacheKey $key, $elements)
{
/* @var $targetPersister CachedEntityPersister */
$associationMapping = $this->sourceEntity->associationMappings[$key->association];
$targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
$targetRegion = $targetPersister->getCacheRegion();
$targetHydrator = $targetPersister->getEntityHydrator();
assert($targetPersister instanceof CachedEntityPersister);
$targetRegion = $targetPersister->getCacheRegion();
$targetHydrator = $targetPersister->getEntityHydrator();
// Only preserve ordering if association configured it
if ( ! (isset($associationMapping['indexBy']) && $associationMapping['indexBy'])) {
if (! (isset($associationMapping['indexBy']) && $associationMapping['indexBy'])) {
// Elements may be an array or a Collection
$elements = array_values(is_array($elements) ? $elements : $elements->getValues());
}
@@ -183,15 +161,15 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
continue;
}
$class = $this->targetEntity;
$className = ClassUtils::getClass($elements[$index]);
$class = $this->targetEntity;
$className = ClassUtils::getClass($elements[$index]);
if ($className !== $this->targetEntity->name) {
$class = $this->metadataFactory->getMetadataFor($className);
}
$entity = $elements[$index];
$entityEntry = $targetHydrator->buildCacheEntry($class, $entityKey, $entity);
$entity = $elements[$index];
$entityEntry = $targetHydrator->buildCacheEntry($class, $entityKey, $entity);
$targetRegion->put($entityKey, $entityEntry);
}
@@ -261,8 +239,6 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
/**
* Clears cache entries related to the current collection
*
* @param PersistentCollection $collection
*/
protected function evictCollectionCache(PersistentCollection $collection)
{
@@ -285,10 +261,10 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
*/
protected function evictElementCache($targetEntity, $element)
{
/* @var $targetPersister CachedEntityPersister */
$targetPersister = $this->uow->getEntityPersister($targetEntity);
$targetRegion = $targetPersister->getCacheRegion();
$key = new EntityCacheKey($targetEntity, $this->uow->getEntityIdentifier($element));
assert($targetPersister instanceof CachedEntityPersister);
$targetRegion = $targetPersister->getCacheRegion();
$key = new EntityCacheKey($targetEntity, $this->uow->getEntityIdentifier($element));
$targetRegion->evict($key);

View File

@@ -20,44 +20,39 @@
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
/**
* Interface for second level cache collection persisters.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
interface CachedCollectionPersister extends CachedPersister, CollectionPersister
{
/**
* @return \Doctrine\ORM\Mapping\ClassMetadata
* @return ClassMetadata
*/
public function getSourceEntityMetadata();
/**
* @return \Doctrine\ORM\Mapping\ClassMetadata
* @return ClassMetadata
*/
public function getTargetEntityMetadata();
/**
* Loads a collection from cache
*
* @param \Doctrine\ORM\PersistentCollection $collection
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key
*
* @return \Doctrine\ORM\PersistentCollection|null
* @return PersistentCollection|null
*/
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key);
/**
* Stores a collection into cache
*
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key
* @param array|\Doctrine\Common\Collections\Collection $elements
* @param mixed[]|Collection $elements
*
* @return void
*/

View File

@@ -23,10 +23,8 @@ namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\PersistentCollection;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
use function spl_object_hash;
class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/**
@@ -78,7 +76,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
$isInitialized = $collection->isInitialized();
$isDirty = $collection->isDirty();
if ( ! $isInitialized && ! $isDirty) {
if (! $isInitialized && ! $isDirty) {
return;
}
@@ -98,7 +96,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
$this->queuedCache['update'][spl_object_hash($collection)] = [
'key' => $key,
'list' => $collection
'list' => $collection,
];
}
}

View File

@@ -20,19 +20,15 @@
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\ORM\PersistentCollection;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister
{
/**
* {@inheritdoc}
*/
* {@inheritdoc}
*/
public function update(PersistentCollection $collection)
{
if ($collection->isDirty() && $collection->getSnapshot()) {

View File

@@ -20,23 +20,21 @@
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use function spl_object_hash;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/**
* @param \Doctrine\ORM\Persisters\Collection\CollectionPersister $persister The collection persister that will be cached.
* @param \Doctrine\ORM\Cache\ConcurrentRegion $region The collection region.
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param array $association The association mapping.
* @param CollectionPersister $persister The collection persister that will be cached.
* @param ConcurrentRegion $region The collection region.
* @param EntityManagerInterface $em The entity manager.
* @param mixed[] $association The association mapping.
*/
public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association)
{
@@ -100,7 +98,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
$this->queuedCache['delete'][spl_object_hash($collection)] = [
'key' => $key,
'lock' => $lock
'lock' => $lock,
];
}
@@ -112,7 +110,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
$isInitialized = $collection->isInitialized();
$isDirty = $collection->isDirty();
if ( ! $isInitialized && ! $isDirty) {
if (! $isInitialized && ! $isDirty) {
return;
}
@@ -128,7 +126,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
$this->queuedCache['update'][spl_object_hash($collection)] = [
'key' => $key,
'lock' => $lock
'lock' => $lock,
];
}
}

View File

@@ -20,117 +20,97 @@
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\EntityHydrator;
use Doctrine\ORM\Cache\Logging\CacheLogger;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Cache\TimestampRegion;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\UnitOfWork;
use function assert;
use function serialize;
use function sha1;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
abstract class AbstractEntityPersister implements CachedEntityPersister
{
/**
* @var \Doctrine\ORM\UnitOfWork
*/
/** @var UnitOfWork */
protected $uow;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadataFactory
*/
/** @var ClassMetadataFactory */
protected $metadataFactory;
/**
* @var \Doctrine\ORM\Persisters\Entity\EntityPersister
*/
/** @var EntityPersister */
protected $persister;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
/** @var ClassMetadata */
protected $class;
/**
* @var array
*/
/** @var mixed[] */
protected $queuedCache = [];
/**
* @var \Doctrine\ORM\Cache\Region
*/
/** @var Region */
protected $region;
/**
* @var \Doctrine\ORM\Cache\TimestampRegion
*/
/** @var TimestampRegion */
protected $timestampRegion;
/**
* @var \Doctrine\ORM\Cache\TimestampCacheKey
*/
/** @var TimestampCacheKey */
protected $timestampKey;
/**
* @var \Doctrine\ORM\Cache\EntityHydrator
*/
/** @var EntityHydrator */
protected $hydrator;
/**
* @var \Doctrine\ORM\Cache
*/
/** @var Cache */
protected $cache;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
*/
/** @var CacheLogger */
protected $cacheLogger;
/**
* @var string
*/
/** @var string */
protected $regionName;
/**
* Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
*
* @var array|null
* @var array<string>|null
*/
protected $joinedAssociations;
/**
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $persister The entity persister to cache.
* @param \Doctrine\ORM\Cache\Region $region The entity cache region.
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Mapping\ClassMetadata $class The entity metadata.
* @param EntityPersister $persister The entity persister to cache.
* @param Region $region The entity cache region.
* @param EntityManagerInterface $em The entity manager.
* @param ClassMetadata $class The entity metadata.
*/
public function __construct(EntityPersister $persister, Region $region, EntityManagerInterface $em, ClassMetadata $class)
{
$configuration = $em->getConfiguration();
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$configuration = $em->getConfiguration();
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$this->class = $class;
$this->region = $region;
$this->persister = $persister;
$this->cache = $em->getCache();
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->timestampRegion = $cacheFactory->getTimestampRegion();
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
$this->class = $class;
$this->region = $region;
$this->persister = $persister;
$this->cache = $em->getCache();
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->timestampRegion = $cacheFactory->getTimestampRegion();
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
}
/**
@@ -152,7 +132,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, array $orderBy = null)
public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, ?array $orderBy = null)
{
return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy);
}
@@ -192,9 +172,9 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function exists($entity, Criteria $extraConditions = null)
public function exists($entity, ?Criteria $extraConditions = null)
{
if (null === $extraConditions) {
if ($extraConditions === null) {
$key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity));
if ($this->region->contains($key)) {
@@ -214,7 +194,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* @return \Doctrine\ORM\Cache\EntityHydrator
* @return EntityHydrator
*/
public function getEntityHydrator()
{
@@ -226,8 +206,8 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function storeEntityCache($entity, EntityCacheKey $key)
{
$class = $this->class;
$className = ClassUtils::getClass($entity);
$class = $this->class;
$className = ClassUtils::getClass($entity);
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
@@ -252,10 +232,11 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$associations = [];
foreach ($this->class->associationMappings as $name => $assoc) {
if (isset($assoc['cache']) &&
if (
isset($assoc['cache']) &&
($assoc['type'] & ClassMetadata::TO_ONE) &&
($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ! $assoc['isOwningSide'])) {
($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ! $assoc['isOwningSide'])
) {
$associations[] = $name;
}
}
@@ -291,7 +272,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*
* @return string
*/
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null)
protected function getHash($query, $criteria, ?array $orderBy = null, $limit = null, $offset = null)
{
[$params] = $criteria instanceof Criteria
? $this->persister->expandCriteriaParameters($criteria)
@@ -361,7 +342,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, array $orderBy = null)
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, ?array $orderBy = null)
{
if ($entity !== null || $assoc !== null || ! empty($hints) || $lockMode !== null) {
return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
@@ -383,7 +364,9 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $result[0];
}
if (($result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy)) === null) {
$result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
if ($result === null) {
return null;
}
@@ -405,7 +388,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null)
public function loadAll(array $criteria = [], ?array $orderBy = null, $limit = null, $offset = null)
{
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
$hash = $this->getHash($query, $criteria, null, null, null);
@@ -469,8 +452,8 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return null;
}
$class = $this->class;
$className = ClassUtils::getClass($entity);
$class = $this->class;
$className = ClassUtils::getClass($entity);
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
@@ -479,7 +462,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
$cached = $this->region->put($cacheKey, $cacheEntry);
if ($cached && (null === $this->joinedAssociations || $this->joinedAssociations)) {
if ($cached && ($this->joinedAssociations === null || $this->joinedAssociations)) {
$this->storeJoinedAssociations($entity);
}
@@ -549,7 +532,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$persister = $this->uow->getCollectionPersister($assoc);
$hasCache = ($persister instanceof CachedPersister);
if ( ! $hasCache) {
if (! $hasCache) {
return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection);
}
@@ -584,7 +567,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$persister = $this->uow->getCollectionPersister($assoc);
$hasCache = ($persister instanceof CachedPersister);
if ( ! $hasCache) {
if (! $hasCache) {
return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection);
}
@@ -636,15 +619,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* @param array $association
* @param array $ownerId
* @param array<string, mixed> $association
* @param array<string, mixed> $ownerId
*
* @return CollectionCacheKey
*/
protected function buildCollectionCacheKey(array $association, $ownerId)
{
/** @var ClassMetadata $metadata */
$metadata = $this->metadataFactory->getMetadataFor($association['sourceEntity']);
assert($metadata instanceof ClassMetadata);
return new CollectionCacheKey($metadata->rootEntityName, $association['fieldName'], $ownerId);
}

View File

@@ -21,27 +21,24 @@
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\EntityHydrator;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
/**
* Interface for second level cache entity persisters.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
interface CachedEntityPersister extends CachedPersister, EntityPersister
{
/**
* @return \Doctrine\ORM\Cache\EntityHydrator
* @return EntityHydrator
*/
public function getEntityHydrator();
/**
* @param object $entity
* @param \Doctrine\ORM\Cache\EntityCacheKey $key
* @param object $entity
*
* @return boolean
* @return bool
*/
public function storeEntityCache($entity, EntityCacheKey $key);
}

View File

@@ -22,12 +22,10 @@ namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\EntityCacheKey;
use function get_class;
/**
* Specific non-strict read/write cached entity persister
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5
*/
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
{
@@ -108,11 +106,11 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
*/
private function updateCache($entity, $isChanged)
{
$class = $this->metadataFactory->getMetadataFor(get_class($entity));
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
$isChanged = $isChanged ?: $cached;
$class = $this->metadataFactory->getMetadataFor(get_class($entity));
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
$isChanged = $isChanged ?: $cached;
if ($this->cacheLogger && $cached) {
$this->cacheLogger->entityCachePut($this->regionName, $key);

View File

@@ -20,14 +20,11 @@
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Cache\CacheException;
/**
* Specific read-only region entity persister
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister
{

View File

@@ -20,26 +20,22 @@
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
/**
* Specific read-write entity persister
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5
*/
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
{
/**
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $persister The entity persister to cache.
* @param \Doctrine\ORM\Cache\ConcurrentRegion $region The entity cache region.
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Mapping\ClassMetadata $class The entity metadata.
* @param EntityPersister $persister The entity persister to cache.
* @param ConcurrentRegion $region The entity cache region.
* @param EntityManagerInterface $em The entity manager.
* @param ClassMetadata $class The entity metadata.
*/
public function __construct(EntityPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, ClassMetadata $class)
{
@@ -115,7 +111,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache['delete'][] = [
'lock' => $lock,
'key' => $key
'key' => $key,
];
return $deleted;
@@ -137,7 +133,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache['update'][] = [
'lock' => $lock,
'key' => $key
'key' => $key,
];
}
}

View File

@@ -25,38 +25,31 @@ use Doctrine\ORM\Query\ResultSetMapping;
/**
* Defines the contract for caches capable of storing query results.
* These caches should only concern themselves with storing the matching result ids.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface QueryCache
{
/**
* @return boolean
* @return bool
*/
public function clear();
/**
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
* @param mixed $result
* @param array $hints
* @param mixed $result
* @param mixed[] $hints
*
* @return boolean
* @return bool
*/
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = []);
/**
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
* @param array $hints
* @param mixed[] $hints
*
* @return array|null
* @return mixed[]|null
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []);
/**
* @return \Doctrine\ORM\Cache\Region
* @return Region
*/
public function getRegion();
}

View File

@@ -20,18 +20,17 @@
namespace Doctrine\ORM\Cache;
use function microtime;
/**
* Query cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class QueryCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array List of entity identifiers
* @var array<string, mixed> List of entity identifiers
*/
public $result;
@@ -43,8 +42,8 @@ class QueryCacheEntry implements CacheEntry
public $time;
/**
* @param array $result
* @param float $time
* @param array<string, mixed> $result
* @param float $time
*/
public function __construct($result, $time = null)
{
@@ -53,7 +52,7 @@ class QueryCacheEntry implements CacheEntry
}
/**
* @param array $values
* @param array<string, mixed> $values
*
* @return QueryCacheEntry
*/

View File

@@ -24,23 +24,20 @@ use Doctrine\ORM\Cache;
/**
* A cache key that identifies a particular query.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class QueryCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var integer Cache key lifetime
* @var int Cache key lifetime
*/
public $lifetime;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var integer Cache mode (Doctrine\ORM\Cache::MODE_*)
* @var int Cache mode (Doctrine\ORM\Cache::MODE_*)
*/
public $cacheMode;
@@ -52,16 +49,15 @@ class QueryCacheKey extends CacheKey
public $timestampKey;
/**
* @param string $hash Result cache id
* @param integer $lifetime Query lifetime
* @param int $cacheMode Query cache mode
* @param TimestampCacheKey|null $timestampKey
* @param string $hash Result cache id
* @param int $lifetime Query lifetime
* @param int $cacheMode Query cache mode
*/
public function __construct(
$hash,
$lifetime = 0,
$cacheMode = Cache::MODE_NORMAL,
TimestampCacheKey $timestampKey = null
?TimestampCacheKey $timestampKey = null
) {
$this->hash = $hash;
$this->lifetime = $lifetime;

View File

@@ -22,19 +22,13 @@ namespace Doctrine\ORM\Cache;
/**
* Cache query validator interface.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface QueryCacheValidator
{
/**
* Checks if the query entry is valid
*
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param \Doctrine\ORM\Cache\QueryCacheEntry $entry
*
* @return boolean
* @return bool
*/
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry);
}

View File

@@ -22,9 +22,6 @@ namespace Doctrine\ORM\Cache;
/**
* Defines a contract for accessing a particular named region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface Region extends MultiGetRegion
{
@@ -38,47 +35,47 @@ interface Region extends MultiGetRegion
/**
* Determine whether this region contains data for the given key.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The cache key
* @param CacheKey $key The cache key
*
* @return boolean TRUE if the underlying cache contains corresponding data; FALSE otherwise.
* @return bool TRUE if the underlying cache contains corresponding data; FALSE otherwise.
*/
public function contains(CacheKey $key);
/**
* Get an item from the cache.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to be retrieved.
* @param CacheKey $key The key of the item to be retrieved.
*
* @return \Doctrine\ORM\Cache\CacheEntry|null The cached entry or NULL
* @return CacheEntry|null The cached entry or NULL
*
* @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the item or region.
* @throws CacheException Indicates a problem accessing the item or region.
*/
public function get(CacheKey $key);
/**
* Put an item into the cache.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item.
* @param \Doctrine\ORM\Cache\CacheEntry $entry The entry to cache.
* @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained.
* @param CacheKey $key The key under which to cache the item.
* @param CacheEntry $entry The entry to cache.
* @param Lock $lock The lock previously obtained.
*
* @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region.
* @throws CacheException Indicates a problem accessing the region.
*/
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null);
public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null);
/**
* Remove an item from the cache.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item.
* @param CacheKey $key The key under which to cache the item.
*
* @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region.
* @throws CacheException Indicates a problem accessing the region.
*/
public function evict(CacheKey $key);
/**
* Remove all contents of this particular cache region.
*
* @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region.
* @throws CacheException Indicates problem accessing the region.
*/
public function evictAll();
}

View File

@@ -20,15 +20,16 @@
namespace Doctrine\ORM\Cache\Region;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\MultiGetCache;
use Doctrine\ORM\Cache\CacheEntry;
use Doctrine\ORM\Cache\CollectionCacheEntry;
use function assert;
use function count;
/**
* A cache region that enables the retrieval of multiple elements with one call
*
* @since 2.5
* @author Asmir Mustafic <goetas@gmail.com>
*/
class DefaultMultiGetRegion extends DefaultRegion
{
@@ -36,7 +37,7 @@ class DefaultMultiGetRegion extends DefaultRegion
* Note that the multiple type is due to doctrine/cache not integrating the MultiGetCache interface
* in its signature due to BC in 1.x
*
* @var MultiGetCache|\Doctrine\Common\Cache\Cache
* @var MultiGetCache|Cache
*/
protected $cache;
@@ -47,7 +48,7 @@ class DefaultMultiGetRegion extends DefaultRegion
*/
public function __construct($name, MultiGetCache $cache, $lifetime = 0)
{
/* @var $cache \Doctrine\Common\Cache\Cache */
assert($cache instanceof Cache);
parent::__construct($name, $cache, $lifetime);
}

View File

@@ -20,7 +20,9 @@
namespace Doctrine\ORM\Cache\Region;
use BadMethodCallException;
use Doctrine\Common\Cache\Cache as CacheAdapter;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\ClearableCache;
use Doctrine\ORM\Cache\CacheEntry;
use Doctrine\ORM\Cache\CacheKey;
@@ -28,41 +30,34 @@ use Doctrine\ORM\Cache\CollectionCacheEntry;
use Doctrine\ORM\Cache\Lock;
use Doctrine\ORM\Cache\Region;
use function get_class;
use function sprintf;
/**
* The simplest cache region compatible with all doctrine-cache drivers.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultRegion implements Region
{
const REGION_KEY_SEPARATOR = '_';
public const REGION_KEY_SEPARATOR = '_';
/**
* @var CacheAdapter
*/
/** @var CacheAdapter */
protected $cache;
/**
* @var string
*/
/** @var string */
protected $name;
/**
* @var integer
*/
/** @var int */
protected $lifetime = 0;
/**
* @param string $name
* @param CacheAdapter $cache
* @param integer $lifetime
* @param string $name
* @param int $lifetime
*/
public function __construct($name, CacheAdapter $cache, $lifetime = 0)
{
$this->cache = $cache;
$this->name = (string) $name;
$this->lifetime = (integer) $lifetime;
$this->lifetime = (int) $lifetime;
}
/**
@@ -74,7 +69,7 @@ class DefaultRegion implements Region
}
/**
* @return \Doctrine\Common\Cache\CacheProvider
* @return CacheProvider
*/
public function getCache()
{
@@ -125,7 +120,6 @@ class DefaultRegion implements Region
}
/**
* @param CacheKey $key
* @return string
*/
protected function getCacheEntryKey(CacheKey $key)
@@ -136,7 +130,7 @@ class DefaultRegion implements Region
/**
* {@inheritdoc}
*/
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null)
{
return $this->cache->save($this->getCacheEntryKey($key), $entry, $this->lifetime);
}
@@ -155,7 +149,7 @@ class DefaultRegion implements Region
public function evictAll()
{
if (! $this->cache instanceof ClearableCache) {
throw new \BadMethodCallException(sprintf(
throw new BadMethodCallException(sprintf(
'Clearing all cache entries is not supported by the supplied cache adapter of type %s',
get_class($this->cache)
));

View File

@@ -20,53 +20,62 @@
namespace Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CacheEntry;
use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\CollectionCacheEntry;
use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\Cache\Lock;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\CacheEntry;
use Doctrine\ORM\Cache\ConcurrentRegion;
use InvalidArgumentException;
use function array_filter;
use function array_map;
use function chmod;
use function file_get_contents;
use function file_put_contents;
use function fileatime;
use function glob;
use function is_dir;
use function is_file;
use function is_writable;
use function mkdir;
use function sprintf;
use function time;
use function unlink;
use const DIRECTORY_SEPARATOR;
use const LOCK_EX;
/**
* Very naive concurrent region, based on file locks.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silvagmail.com>
*/
class FileLockRegion implements ConcurrentRegion
{
const LOCK_EXTENSION = 'lock';
public const LOCK_EXTENSION = 'lock';
/**
* var \Doctrine\ORM\Cache\Region
*/
/** @var Region */
private $region;
/**
* @var string
*/
/** @var string */
private $directory;
/**
* var integer
*/
/** @psalm-var numeric-string */
private $lockLifetime;
/**
* @param \Doctrine\ORM\Cache\Region $region
* @param string $directory
* @param string $lockLifetime
* @param string $directory
* @param string $lockLifetime
*
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
*/
public function __construct(Region $region, $directory, $lockLifetime)
{
if ( ! is_dir($directory) && ! @mkdir($directory, 0775, true)) {
throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory));
if (! is_dir($directory) && ! @mkdir($directory, 0775, true)) {
throw new InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory));
}
if ( ! is_writable($directory)) {
throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory));
if (! is_writable($directory)) {
throw new InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory));
}
$this->region = $region;
@@ -75,23 +84,20 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* @param \Doctrine\ORM\Cache\CacheKey $key
* @param \Doctrine\ORM\Cache\Lock $lock
*
* @return boolean
* @return bool
*/
private function isLocked(CacheKey $key, Lock $lock = null)
private function isLocked(CacheKey $key, ?Lock $lock = null)
{
$filename = $this->getLockFileName($key);
if ( ! is_file($filename)) {
if (! is_file($filename)) {
return false;
}
$time = $this->getLockTime($filename);
$content = $this->getLockContent($filename);
$time = $this->getLockTime($filename);
$content = $this->getLockContent($filename);
if ( ! $content || ! $time) {
if (! $content || ! $time) {
@unlink($filename);
return false;
@@ -102,7 +108,7 @@ class FileLockRegion implements ConcurrentRegion
}
// outdated lock
if (($time + $this->lockLifetime) <= time()) {
if ($time + $this->lockLifetime <= time()) {
@unlink($filename);
return false;
@@ -112,8 +118,6 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* @param \Doctrine\ORM\Cache\CacheKey $key
*
* @return string
*/
private function getLockFileName(CacheKey $key)
@@ -134,7 +138,7 @@ class FileLockRegion implements ConcurrentRegion
/**
* @param string $filename
*
* @return integer
* @return int
*/
private function getLockTime($filename)
{
@@ -188,7 +192,7 @@ class FileLockRegion implements ConcurrentRegion
/**
* {@inheritdoc}
*/
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null)
{
if ($this->isLocked($key, $lock)) {
return false;
@@ -216,7 +220,7 @@ class FileLockRegion implements ConcurrentRegion
{
// The check below is necessary because on some platforms glob returns false
// when nothing matched (even though no errors occurred)
$filenames = glob(sprintf("%s/*.%s" , $this->directory, self::LOCK_EXTENSION));
$filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION));
if ($filenames) {
foreach ($filenames as $filename) {
@@ -239,9 +243,10 @@ class FileLockRegion implements ConcurrentRegion
$lock = Lock::createLockRead();
$filename = $this->getLockFileName($key);
if ( ! @file_put_contents($filename, $lock->value, LOCK_EX)) {
if (! @file_put_contents($filename, $lock->value, LOCK_EX)) {
return null;
}
chmod($filename, 0664);
return $lock;
@@ -258,10 +263,6 @@ class FileLockRegion implements ConcurrentRegion
return false;
}
if ( ! @unlink($this->getLockFileName($key))) {
return false;
}
return true;
return @unlink($this->getLockFileName($key));
}
}

View File

@@ -20,15 +20,12 @@
namespace Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\TimestampCacheEntry;
use Doctrine\ORM\Cache\TimestampRegion;
use Doctrine\ORM\Cache\CacheKey;
/**
* Tracks the timestamps of the most recent updates to particular keys.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
{
@@ -37,6 +34,6 @@ class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
*/
public function update(CacheKey $key)
{
$this->put($key, new TimestampCacheEntry);
$this->put($key, new TimestampCacheEntry());
}
}

View File

@@ -22,44 +22,33 @@ namespace Doctrine\ORM\Cache;
/**
* Cache regions configuration
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class RegionsConfiguration
{
/**
* @var array
*/
/** @var array<string,int> */
private $lifetimes = [];
/**
* @var array
*/
/** @var array<string,int> */
private $lockLifetimes = [];
/**
* @var integer
*/
/** @var int */
private $defaultLifetime;
/**
* @var integer
*/
/** @var int */
private $defaultLockLifetime;
/**
* @param integer $defaultLifetime
* @param integer $defaultLockLifetime
* @param int $defaultLifetime
* @param int $defaultLockLifetime
*/
public function __construct($defaultLifetime = 3600, $defaultLockLifetime = 60)
{
$this->defaultLifetime = (integer) $defaultLifetime;
$this->defaultLockLifetime = (integer) $defaultLockLifetime;
$this->defaultLifetime = (int) $defaultLifetime;
$this->defaultLockLifetime = (int) $defaultLockLifetime;
}
/**
* @return integer
* @return int
*/
public function getDefaultLifetime()
{
@@ -67,15 +56,15 @@ class RegionsConfiguration
}
/**
* @param integer $defaultLifetime
* @param int $defaultLifetime
*/
public function setDefaultLifetime($defaultLifetime)
{
$this->defaultLifetime = (integer) $defaultLifetime;
$this->defaultLifetime = (int) $defaultLifetime;
}
/**
* @return integer
* @return int
*/
public function getDefaultLockLifetime()
{
@@ -83,52 +72,48 @@ class RegionsConfiguration
}
/**
* @param integer $defaultLockLifetime
* @param int $defaultLockLifetime
*/
public function setDefaultLockLifetime($defaultLockLifetime)
{
$this->defaultLockLifetime = (integer) $defaultLockLifetime;
$this->defaultLockLifetime = (int) $defaultLockLifetime;
}
/**
* @param string $regionName
*
* @return integer
* @return int
*/
public function getLifetime($regionName)
{
return isset($this->lifetimes[$regionName])
? $this->lifetimes[$regionName]
: $this->defaultLifetime;
return $this->lifetimes[$regionName] ?? $this->defaultLifetime;
}
/**
* @param string $name
* @param integer $lifetime
* @param string $name
* @param int $lifetime
*/
public function setLifetime($name, $lifetime)
{
$this->lifetimes[$name] = (integer) $lifetime;
$this->lifetimes[$name] = (int) $lifetime;
}
/**
* @param string $regionName
*
* @return integer
* @return int
*/
public function getLockLifetime($regionName)
{
return isset($this->lockLifetimes[$regionName])
? $this->lockLifetimes[$regionName]
: $this->defaultLockLifetime;
return $this->lockLifetimes[$regionName] ?? $this->defaultLockLifetime;
}
/**
* @param string $name
* @param integer $lifetime
* @param string $name
* @param int $lifetime
*/
public function setLockLifetime($name, $lifetime)
{
$this->lockLifetimes[$name] = (integer) $lifetime;
$this->lockLifetimes[$name] = (int) $lifetime;
}
}

View File

@@ -20,11 +20,10 @@
namespace Doctrine\ORM\Cache;
use function microtime;
/**
* Timestamp cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class TimestampCacheEntry implements CacheEntry
{
@@ -48,7 +47,7 @@ class TimestampCacheEntry implements CacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
* @param array<string,float> $values array containing property values
*
* @return TimestampCacheEntry
*/

View File

@@ -22,9 +22,6 @@ namespace Doctrine\ORM\Cache;
/**
* A key that identifies a timestamped space.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class TimestampCacheKey extends CacheKey
{

View File

@@ -20,20 +20,13 @@
namespace Doctrine\ORM\Cache;
/**
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
use function microtime;
class TimestampQueryCacheValidator implements QueryCacheValidator
{
/**
* @var TimestampRegion
*/
/** @var TimestampRegion */
private $timestampRegion;
/**
* @param TimestampRegion $timestampRegion
*/
public function __construct(TimestampRegion $timestampRegion)
{
$this->timestampRegion = $timestampRegion;
@@ -48,17 +41,14 @@ class TimestampQueryCacheValidator implements QueryCacheValidator
return false;
}
if ($key->lifetime == 0) {
if ($key->lifetime === 0) {
return true;
}
return ($entry->time + $key->lifetime) > microtime(true);
return $entry->time + $key->lifetime > microtime(true);
}
/**
* @param QueryCacheKey $key
* @param QueryCacheEntry $entry
*
* @return bool
*/
private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry)

View File

@@ -22,18 +22,15 @@ namespace Doctrine\ORM\Cache;
/**
* Defines the contract for a cache region which will specifically be used to store entity "update timestamps".
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface TimestampRegion extends Region
{
/**
* Update an specific key into the cache region.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to update the timestamp.
* @param CacheKey $key The key of the item to update the timestamp.
*
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
* @throws LockException Indicates a problem accessing the region.
*/
public function update(CacheKey $key);
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -35,23 +36,21 @@ use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\EntityListenerResolver;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\ObjectRepository;
use function interface_exists;
use ReflectionClass;
use function strtolower;
use function trim;
/**
* Configuration container for all configuration options of Doctrine.
* It combines all configuration options from DBAL & ORM.
*
* Internal note: When adding a new configuration option just write a getter/setter pair.
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class Configuration extends \Doctrine\DBAL\Configuration
{
@@ -70,38 +69,36 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the directory where Doctrine generates any necessary proxy class files.
*
* @return string|null
*
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
*
* @see https://github.com/Ocramius/ProxyManager
*
* @return string|null
*/
public function getProxyDir()
{
return isset($this->_attributes['proxyDir'])
? $this->_attributes['proxyDir']
: null;
return $this->_attributes['proxyDir'] ?? null;
}
/**
* Gets the strategy for automatically generating proxy classes.
*
* @return int Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
*
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
*
* @see https://github.com/Ocramius/ProxyManager
*
* @return int Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
*/
public function getAutoGenerateProxyClasses()
{
return isset($this->_attributes['autoGenerateProxyClasses'])
? $this->_attributes['autoGenerateProxyClasses']
: AbstractProxyFactory::AUTOGENERATE_ALWAYS;
return $this->_attributes['autoGenerateProxyClasses'] ?? AbstractProxyFactory::AUTOGENERATE_ALWAYS;
}
/**
* Sets the strategy for automatically generating proxy classes.
*
* @param boolean|int $autoGenerate Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
* True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
* @param bool|int $autoGenerate Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
* True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
*
* @return void
*/
@@ -113,16 +110,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the namespace where proxy classes reside.
*
* @return string|null
*
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
*
* @see https://github.com/Ocramius/ProxyManager
*
* @return string|null
*/
public function getProxyNamespace()
{
return isset($this->_attributes['proxyNamespace'])
? $this->_attributes['proxyNamespace']
: null;
return $this->_attributes['proxyNamespace'] ?? null;
}
/**
@@ -140,8 +136,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Sets the cache driver implementation that is used for metadata caching.
*
* @param MappingDriver $driverImpl
*
* @return void
*
* @todo Force parameter to be a Closure to ensure lazy evaluation
@@ -156,10 +150,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Adds a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader
* is true, the notation `@Entity` will work, otherwise, the notation `@ORM\Entity` will be supported.
*
* @param array $paths
* @param bool $useSimpleAnnotationReader
* @param bool $useSimpleAnnotationReader
*
* @return AnnotationDriver
*
* @psalm-param string|list<string> $paths
*/
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
{
@@ -204,7 +199,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getEntityNamespace($entityNamespaceAlias)
{
if ( ! isset($this->_attributes['entityNamespaces'][$entityNamespaceAlias])) {
if (! isset($this->_attributes['entityNamespaces'][$entityNamespaceAlias])) {
throw ORMException::unknownEntityNamespace($entityNamespaceAlias);
}
@@ -214,9 +209,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Sets the entity alias map.
*
* @param array $entityNamespaces
*
* @return void
*
* @psalm-param array<string, string> $entityNamespaces
*/
public function setEntityNamespaces(array $entityNamespaces)
{
@@ -226,7 +221,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Retrieves the list of registered entity namespace aliases.
*
* @return array
* @psalm-return array<string, string>
*/
public function getEntityNamespaces()
{
@@ -242,28 +237,22 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getMetadataDriverImpl()
{
return isset($this->_attributes['metadataDriverImpl'])
? $this->_attributes['metadataDriverImpl']
: null;
return $this->_attributes['metadataDriverImpl'] ?? null;
}
/**
* Gets the cache driver implementation that is used for the query cache (SQL cache).
*
* @return \Doctrine\Common\Cache\Cache|null
* @return CacheDriver|null
*/
public function getQueryCacheImpl()
{
return isset($this->_attributes['queryCacheImpl'])
? $this->_attributes['queryCacheImpl']
: null;
return $this->_attributes['queryCacheImpl'] ?? null;
}
/**
* Sets the cache driver implementation that is used for the query cache (SQL cache).
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*
* @return void
*/
public function setQueryCacheImpl(CacheDriver $cacheImpl)
@@ -274,20 +263,16 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the cache driver implementation that is used for the hydration cache (SQL cache).
*
* @return \Doctrine\Common\Cache\Cache|null
* @return CacheDriver|null
*/
public function getHydrationCacheImpl()
{
return isset($this->_attributes['hydrationCacheImpl'])
? $this->_attributes['hydrationCacheImpl']
: null;
return $this->_attributes['hydrationCacheImpl'] ?? null;
}
/**
* Sets the cache driver implementation that is used for the hydration cache (SQL cache).
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*
* @return void
*/
public function setHydrationCacheImpl(CacheDriver $cacheImpl)
@@ -298,20 +283,16 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the cache driver implementation that is used for metadata caching.
*
* @return \Doctrine\Common\Cache\Cache|null
* @return CacheDriver|null
*/
public function getMetadataCacheImpl()
{
return isset($this->_attributes['metadataCacheImpl'])
? $this->_attributes['metadataCacheImpl']
: null;
return $this->_attributes['metadataCacheImpl'] ?? null;
}
/**
* Sets the cache driver implementation that is used for metadata caching.
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*
* @return void
*/
public function setMetadataCacheImpl(CacheDriver $cacheImpl)
@@ -343,7 +324,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getNamedQuery($name)
{
if ( ! isset($this->_attributes['namedQueries'][$name])) {
if (! isset($this->_attributes['namedQueries'][$name])) {
throw ORMException::namedQueryNotFound($name);
}
@@ -369,14 +350,17 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name The name of the query.
*
* @return array A tuple with the first element being the SQL string and the second
* element being the ResultSetMapping.
*
* @throws ORMException
*
* @psalm-return array{string, ResultSetMapping} A tuple with the first
* element being the SQL
* string and the second
* element being the
* ResultSetMapping.
*/
public function getNamedNativeQuery($name)
{
if ( ! isset($this->_attributes['namedNativeQueries'][$name])) {
if (! isset($this->_attributes['namedNativeQueries'][$name])) {
throw ORMException::namedNativeQueryNotFound($name);
}
@@ -396,7 +380,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
$queryCacheImpl = $this->getQueryCacheImpl();
if ( ! $queryCacheImpl) {
if (! $queryCacheImpl) {
throw ORMException::queryCacheNotConfigured();
}
@@ -406,7 +390,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
$metadataCacheImpl = $this->getMetadataCacheImpl();
if ( ! $metadataCacheImpl) {
if (! $metadataCacheImpl) {
throw ORMException::metadataCacheNotConfigured();
}
@@ -442,15 +426,14 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $name
*
* @return string|null
*
* @psalm-return ?class-string
*/
public function getCustomStringFunction($name)
{
$name = strtolower($name);
return isset($this->_attributes['customStringFunctions'][$name])
? $this->_attributes['customStringFunctions'][$name]
: null;
return $this->_attributes['customStringFunctions'][$name] ?? null;
}
/**
@@ -461,9 +444,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* Any previously added string functions are discarded.
*
* @param array $functions The map of custom DQL string functions.
*
* @return void
*
* @psalm-param array<string, class-string> $functions The map of custom
* DQL string functions.
*/
public function setCustomStringFunctions(array $functions)
{
@@ -495,15 +479,14 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $name
*
* @return string|null
*
* @psalm-return ?class-string
*/
public function getCustomNumericFunction($name)
{
$name = strtolower($name);
return isset($this->_attributes['customNumericFunctions'][$name])
? $this->_attributes['customNumericFunctions'][$name]
: null;
return $this->_attributes['customNumericFunctions'][$name] ?? null;
}
/**
@@ -514,9 +497,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* Any previously added numeric functions are discarded.
*
* @param array $functions The map of custom DQL numeric functions.
*
* @return void
*
* @psalm-param array<string, class-string> $functions The map of custom
* DQL numeric functions.
*/
public function setCustomNumericFunctions(array $functions)
{
@@ -557,9 +541,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
$name = strtolower($name);
return isset($this->_attributes['customDatetimeFunctions'][$name])
? $this->_attributes['customDatetimeFunctions'][$name]
: null;
return $this->_attributes['customDatetimeFunctions'][$name] ?? null;
}
/**
@@ -586,7 +568,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Sets the custom hydrator modes in one pass.
*
* @param array $modes An array of ($modeName => $hydrator).
* @param array<string, class-string> $modes An array of ($modeName => $hydrator).
*
* @return void
*/
@@ -610,18 +592,17 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getCustomHydrationMode($modeName)
{
return isset($this->_attributes['customHydrationModes'][$modeName])
? $this->_attributes['customHydrationModes'][$modeName]
: null;
return $this->_attributes['customHydrationModes'][$modeName] ?? null;
}
/**
* Adds a custom hydration mode.
*
* @param string $modeName The hydration mode name.
* @param string $hydrator The hydrator class name.
*
* @return void
*
* @psalm-param class-string $hydrator The hydrator class name.
*/
public function addCustomHydrationMode($modeName, $hydrator)
{
@@ -649,7 +630,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getClassMetadataFactoryName()
{
if ( ! isset($this->_attributes['classMetadataFactoryName'])) {
if (! isset($this->_attributes['classMetadataFactoryName'])) {
$this->_attributes['classMetadataFactoryName'] = ClassMetadataFactory::class;
}
@@ -679,16 +660,12 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getFilterClassName($name)
{
return isset($this->_attributes['filters'][$name])
? $this->_attributes['filters'][$name]
: null;
return $this->_attributes['filters'][$name] ?? null;
}
/**
* Sets default repository class.
*
* @since 2.2
*
* @param string $className
*
* @return void
@@ -697,9 +674,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setDefaultRepositoryClassName($className)
{
$reflectionClass = new \ReflectionClass($className);
$reflectionClass = new ReflectionClass($className);
if ( ! $reflectionClass->implementsInterface(ObjectRepository::class)) {
if (! $reflectionClass->implementsInterface(ObjectRepository::class)) {
throw ORMException::invalidEntityRepository($className);
}
@@ -709,26 +686,18 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Get default repository class.
*
* @since 2.2
*
* @return string
*
* @psalm-return class-string
*/
public function getDefaultRepositoryClassName()
{
return isset($this->_attributes['defaultRepositoryClassName'])
? $this->_attributes['defaultRepositoryClassName']
: EntityRepository::class;
return $this->_attributes['defaultRepositoryClassName'] ?? EntityRepository::class;
}
/**
* Sets naming strategy.
*
* @since 2.3
*
* @param NamingStrategy $namingStrategy
*
* @return void
*/
public function setNamingStrategy(NamingStrategy $namingStrategy)
@@ -739,13 +708,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets naming strategy..
*
* @since 2.3
*
* @return NamingStrategy
*/
public function getNamingStrategy()
{
if ( ! isset($this->_attributes['namingStrategy'])) {
if (! isset($this->_attributes['namingStrategy'])) {
$this->_attributes['namingStrategy'] = new DefaultNamingStrategy();
}
@@ -755,10 +722,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Sets quote strategy.
*
* @since 2.3
*
* @param \Doctrine\ORM\Mapping\QuoteStrategy $quoteStrategy
*
* @return void
*/
public function setQuoteStrategy(QuoteStrategy $quoteStrategy)
@@ -769,13 +732,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets quote strategy.
*
* @since 2.3
*
* @return \Doctrine\ORM\Mapping\QuoteStrategy
* @return QuoteStrategy
*/
public function getQuoteStrategy()
{
if ( ! isset($this->_attributes['quoteStrategy'])) {
if (! isset($this->_attributes['quoteStrategy'])) {
$this->_attributes['quoteStrategy'] = new DefaultQuoteStrategy();
}
@@ -784,9 +745,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Set the entity listener resolver.
*
* @since 2.4
* @param \Doctrine\ORM\Mapping\EntityListenerResolver $resolver
*/
public function setEntityListenerResolver(EntityListenerResolver $resolver)
{
@@ -796,12 +754,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Get the entity listener resolver.
*
* @since 2.4
* @return \Doctrine\ORM\Mapping\EntityListenerResolver
* @return EntityListenerResolver
*/
public function getEntityListenerResolver()
{
if ( ! isset($this->_attributes['entityListenerResolver'])) {
if (! isset($this->_attributes['entityListenerResolver'])) {
$this->_attributes['entityListenerResolver'] = new DefaultEntityListenerResolver();
}
@@ -810,9 +767,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Set the entity repository factory.
*
* @since 2.4
* @param \Doctrine\ORM\Repository\RepositoryFactory $repositoryFactory
*/
public function setRepositoryFactory(RepositoryFactory $repositoryFactory)
{
@@ -822,45 +776,32 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Get the entity repository factory.
*
* @since 2.4
* @return \Doctrine\ORM\Repository\RepositoryFactory
* @return RepositoryFactory
*/
public function getRepositoryFactory()
{
return isset($this->_attributes['repositoryFactory'])
? $this->_attributes['repositoryFactory']
: new DefaultRepositoryFactory();
return $this->_attributes['repositoryFactory'] ?? new DefaultRepositoryFactory();
}
/**
* @since 2.5
*
* @return boolean
* @return bool
*/
public function isSecondLevelCacheEnabled()
{
return isset($this->_attributes['isSecondLevelCacheEnabled'])
? $this->_attributes['isSecondLevelCacheEnabled']
: false;
return $this->_attributes['isSecondLevelCacheEnabled'] ?? false;
}
/**
* @since 2.5
*
* @param boolean $flag
* @param bool $flag
*
* @return void
*/
public function setSecondLevelCacheEnabled($flag = true)
{
$this->_attributes['isSecondLevelCacheEnabled'] = (boolean) $flag;
$this->_attributes['isSecondLevelCacheEnabled'] = (bool) $flag;
}
/**
* @since 2.5
*
* @param \Doctrine\ORM\Cache\CacheConfiguration $cacheConfig
*
* @return void
*/
public function setSecondLevelCacheConfiguration(CacheConfiguration $cacheConfig)
@@ -869,39 +810,31 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* @since 2.5
*
* @return \Doctrine\ORM\Cache\CacheConfiguration|null
* @return CacheConfiguration|null
*/
public function getSecondLevelCacheConfiguration()
{
if ( ! isset($this->_attributes['secondLevelCacheConfiguration']) && $this->isSecondLevelCacheEnabled()) {
if (! isset($this->_attributes['secondLevelCacheConfiguration']) && $this->isSecondLevelCacheEnabled()) {
$this->_attributes['secondLevelCacheConfiguration'] = new CacheConfiguration();
}
return isset($this->_attributes['secondLevelCacheConfiguration'])
? $this->_attributes['secondLevelCacheConfiguration']
: null;
return $this->_attributes['secondLevelCacheConfiguration'] ?? null;
}
/**
* Returns query hints, which will be applied to every query in application
*
* @since 2.5
*
* @return array
* @psalm-return array<string, mixed>
*/
public function getDefaultQueryHints()
{
return isset($this->_attributes['defaultQueryHints']) ? $this->_attributes['defaultQueryHints'] : [];
return $this->_attributes['defaultQueryHints'] ?? [];
}
/**
* Sets array of query hints, which will be applied to every query in application
*
* @since 2.5
*
* @param array $defaultQueryHints
* @psalm-param array<string, mixed> $defaultQueryHints
*/
public function setDefaultQueryHints(array $defaultQueryHints)
{
@@ -911,24 +844,18 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the value of a default query hint. If the hint name is not recognized, FALSE is returned.
*
* @since 2.5
*
* @param string $name The name of the hint.
*
* @return mixed The value of the hint or FALSE, if the hint name is not recognized.
*/
public function getDefaultQueryHint($name)
{
return isset($this->_attributes['defaultQueryHints'][$name])
? $this->_attributes['defaultQueryHints'][$name]
: false;
return $this->_attributes['defaultQueryHints'][$name] ?? false;
}
/**
* Sets a default query hint. If the hint name is not recognized, it is silently ignored.
*
* @since 2.5
*
* @param string $name The name of the hint.
* @param mixed $value The value of the hint.
*/

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,26 +20,18 @@
namespace Doctrine\ORM\Decorator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\ObjectManagerDecorator;
/**
* Base class for EntityManager decorators
*
* @since 2.4
* @author Lars Strojny <lars@strojny.net
*/
abstract class EntityManagerDecorator extends ObjectManagerDecorator implements EntityManagerInterface
{
/**
* @var EntityManagerInterface
*/
/** @var EntityManagerInterface */
protected $wrapped;
/**
* @param EntityManagerInterface $wrapped
*/
public function __construct(EntityManagerInterface $wrapped)
{
$this->wrapped = $wrapped;

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,22 +20,38 @@
namespace Doctrine\ORM;
use BadMethodCallException;
use Doctrine\Common\EventManager;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\Mapping\MappingException;
use Doctrine\Persistence\ObjectRepository;
use InvalidArgumentException;
use Throwable;
use function array_keys;
use function call_user_func;
use function get_class;
use function gettype;
use function is_array;
use function is_callable;
use function is_object;
use function is_string;
use function ltrim;
use const E_USER_DEPRECATED;
use function sprintf;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* The EntityManager is the central access point to ORM functionality.
*
@@ -59,68 +76,62 @@ use function trigger_error;
* is not a valid extension point for the EntityManager. Instead you
* should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator}
* and wrap your entity manager in a decorator.
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
/* final */class EntityManager implements EntityManagerInterface
{
/**
* The used Configuration.
*
* @var \Doctrine\ORM\Configuration
* @var Configuration
*/
private $config;
/**
* The database connection used by the EntityManager.
*
* @var \Doctrine\DBAL\Connection
* @var Connection
*/
private $conn;
/**
* The metadata factory, used to retrieve the ORM metadata of entity classes.
*
* @var \Doctrine\ORM\Mapping\ClassMetadataFactory
* @var ClassMetadataFactory
*/
private $metadataFactory;
/**
* The UnitOfWork used to coordinate object-level transactions.
*
* @var \Doctrine\ORM\UnitOfWork
* @var UnitOfWork
*/
private $unitOfWork;
/**
* The event manager that is the central point of the event system.
*
* @var \Doctrine\Common\EventManager
* @var EventManager
*/
private $eventManager;
/**
* The proxy factory used to create dynamic proxies.
*
* @var \Doctrine\ORM\Proxy\ProxyFactory
* @var ProxyFactory
*/
private $proxyFactory;
/**
* The repository factory used to create dynamic repositories.
*
* @var \Doctrine\ORM\Repository\RepositoryFactory
* @var RepositoryFactory
*/
private $repositoryFactory;
/**
* The expression builder instance used to generate query expressions.
*
* @var \Doctrine\ORM\Query\Expr
* @var Expr
*/
private $expressionBuilder;
@@ -134,32 +145,26 @@ use function trigger_error;
/**
* Collection of query filters.
*
* @var \Doctrine\ORM\Query\FilterCollection
* @var FilterCollection
*/
private $filterCollection;
/**
* @var \Doctrine\ORM\Cache The second level cache regions API.
*/
/** @var Cache The second level cache regions API. */
private $cache;
/**
* Creates a new EntityManager that operates on the given database connection
* and uses the given Configuration and EventManager implementations.
*
* @param \Doctrine\DBAL\Connection $conn
* @param \Doctrine\ORM\Configuration $config
* @param \Doctrine\Common\EventManager $eventManager
*/
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
{
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
$this->metadataFactory = new $metadataFactoryClassName;
$this->metadataFactory = new $metadataFactoryClassName();
$this->metadataFactory->setEntityManager($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
@@ -173,9 +178,9 @@ use function trigger_error;
);
if ($config->isSecondLevelCacheEnabled()) {
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$this->cache = $cacheFactory->createCache($this);
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$this->cache = $cacheFactory->createCache($this);
}
}
@@ -190,7 +195,7 @@ use function trigger_error;
/**
* Gets the metadata factory used to gather the metadata of classes.
*
* @return \Doctrine\ORM\Mapping\ClassMetadataFactory
* @return ClassMetadataFactory
*/
public function getMetadataFactory()
{
@@ -203,7 +208,7 @@ use function trigger_error;
public function getExpressionBuilder()
{
if ($this->expressionBuilder === null) {
$this->expressionBuilder = new Query\Expr;
$this->expressionBuilder = new Query\Expr();
}
return $this->expressionBuilder;
@@ -230,8 +235,8 @@ use function trigger_error;
*/
public function transactional($func)
{
if (!is_callable($func)) {
throw new \InvalidArgumentException('Expected argument of type "callable", got "' . gettype($func) . '"');
if (! is_callable($func)) {
throw new InvalidArgumentException('Expected argument of type "callable", got "' . gettype($func) . '"');
}
$this->conn->beginTransaction();
@@ -281,7 +286,7 @@ use function trigger_error;
*
* @param string $className
*
* @return \Doctrine\ORM\Mapping\ClassMetadata
* @return ClassMetadata
*/
public function getClassMetadata($className)
{
@@ -295,7 +300,7 @@ use function trigger_error;
{
$query = new Query($this);
if ( ! empty($dql)) {
if (! empty($dql)) {
$query->setDQL($dql);
}
@@ -349,12 +354,12 @@ use function trigger_error;
* If an entity is explicitly passed to this method only this entity and
* the cascade-persist semantics + scheduled inserts/removals are synchronized.
*
* @param null|object|array $entity
* @param object|mixed[]|null $entity
*
* @return void
*
* @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that
* makes use of optimistic locking fails.
* @throws OptimisticLockException If a version check on an entity that
* makes use of optimistic locking fails.
* @throws ORMException
*/
public function flush($entity = null)
@@ -374,13 +379,13 @@ use function trigger_error;
/**
* Finds an Entity by its identifier.
*
* @param string $className The class name of the entity to find.
* @param mixed $id The identity of the entity to find.
* @param integer|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
* or NULL if no specific lock mode should be used
* during the search.
* @param integer|null $lockVersion The version of the entity to find when using
* optimistic locking.
* @param string $className The class name of the entity to find.
* @param mixed $id The identity of the entity to find.
* @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
* or NULL if no specific lock mode should be used
* during the search.
* @param int|null $lockVersion The version of the entity to find when using
* optimistic locking.
*
* @return object|null The entity instance or NULL if the entity can not be found.
*
@@ -390,7 +395,7 @@ use function trigger_error;
* @throws ORMException
*
* @template T
* @psalm-param class-string<T> $entityName
* @psalm-param class-string<T> $className
* @psalm-return ?T
*/
public function find($className, $id, $lockMode = null, $lockVersion = null)
@@ -401,7 +406,7 @@ use function trigger_error;
$this->checkLockRequirements($lockMode, $class);
}
if ( ! is_array($id)) {
if (! is_array($id)) {
if ($class->isIdentifierComposite) {
throw ORMInvalidArgumentException::invalidCompositeIdentifier();
}
@@ -422,7 +427,7 @@ use function trigger_error;
$sortedId = [];
foreach ($class->identifier as $identifier) {
if ( ! isset($id[$identifier])) {
if (! isset($id[$identifier])) {
throw ORMException::missingIdentifierField($class->name, $identifier);
}
@@ -436,20 +441,22 @@ use function trigger_error;
$unitOfWork = $this->getUnitOfWork();
$entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName);
// Check identity map first
if (($entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) {
if ( ! ($entity instanceof $class->name)) {
if ($entity !== false) {
if (! ($entity instanceof $class->name)) {
return null;
}
switch (true) {
case LockMode::OPTIMISTIC === $lockMode:
case $lockMode === LockMode::OPTIMISTIC:
$this->lock($entity, $lockMode, $lockVersion);
break;
case LockMode::NONE === $lockMode:
case LockMode::PESSIMISTIC_READ === $lockMode:
case LockMode::PESSIMISTIC_WRITE === $lockMode:
case $lockMode === LockMode::NONE:
case $lockMode === LockMode::PESSIMISTIC_READ:
case $lockMode === LockMode::PESSIMISTIC_WRITE:
$persister = $unitOfWork->getEntityPersister($class->name);
$persister->refresh($sortedId, $entity, $lockMode);
break;
@@ -461,15 +468,15 @@ use function trigger_error;
$persister = $unitOfWork->getEntityPersister($class->name);
switch (true) {
case LockMode::OPTIMISTIC === $lockMode:
case $lockMode === LockMode::OPTIMISTIC:
$entity = $persister->load($sortedId);
$unitOfWork->lock($entity, $lockMode, $lockVersion);
return $entity;
case LockMode::PESSIMISTIC_READ === $lockMode:
case LockMode::PESSIMISTIC_WRITE === $lockMode:
case $lockMode === LockMode::PESSIMISTIC_READ:
case $lockMode === LockMode::PESSIMISTIC_WRITE:
return $persister->load($sortedId, null, null, [], $lockMode);
default:
@@ -484,14 +491,14 @@ use function trigger_error;
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
if ( ! is_array($id)) {
if (! is_array($id)) {
$id = [$class->identifier[0] => $id];
}
$sortedId = [];
foreach ($class->identifier as $identifier) {
if ( ! isset($id[$identifier])) {
if (! isset($id[$identifier])) {
throw ORMException::missingIdentifierField($class->name, $identifier);
}
@@ -503,9 +510,11 @@ use function trigger_error;
throw ORMException::unrecognizedIdentifierFields($class->name, array_keys($id));
}
$entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName);
// Check identity map first, if its already in there just return it.
if (($entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) {
return ($entity instanceof $class->name) ? $entity : null;
if ($entity !== false) {
return $entity instanceof $class->name ? $entity : null;
}
if ($class->subClasses) {
@@ -526,12 +535,14 @@ use function trigger_error;
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
$entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName);
// Check identity map first, if its already in there just return it.
if (($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) !== false) {
return ($entity instanceof $class->name) ? $entity : null;
if ($entity !== false) {
return $entity instanceof $class->name ? $entity : null;
}
if ( ! is_array($identifier)) {
if (! is_array($identifier)) {
$identifier = [$class->identifier[0] => $identifier];
}
@@ -559,7 +570,7 @@ use function trigger_error;
*/
public function clear($entityName = null)
{
if (null !== $entityName && ! is_string($entityName)) {
if ($entityName !== null && ! is_string($entityName)) {
throw ORMInvalidArgumentException::invalidEntityName($entityName);
}
@@ -571,7 +582,7 @@ use function trigger_error;
}
$this->unitOfWork->clear(
null === $entityName
$entityName === null
? null
: $this->metadataFactory->getMetadataFor($entityName)->getName()
);
@@ -605,7 +616,7 @@ use function trigger_error;
*/
public function persist($entity)
{
if ( ! is_object($entity)) {
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()', $entity);
}
@@ -629,7 +640,7 @@ use function trigger_error;
*/
public function remove($entity)
{
if ( ! is_object($entity)) {
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()', $entity);
}
@@ -651,7 +662,7 @@ use function trigger_error;
*/
public function refresh($entity)
{
if ( ! is_object($entity)) {
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity);
}
@@ -667,19 +678,19 @@ use function trigger_error;
* Entities which previously referenced the detached entity will continue to
* reference it.
*
* @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
*
* @param object $entity The entity to detach.
*
* @return void
*
* @throws ORMInvalidArgumentException
*
* @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
*/
public function detach($entity)
{
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
if ( ! is_object($entity)) {
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()', $entity);
}
@@ -691,20 +702,20 @@ use function trigger_error;
* of this EntityManager and returns the managed copy of the entity.
* The entity passed to merge will not become associated/managed with this EntityManager.
*
* @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
*
* @param object $entity The detached entity to merge into the persistence context.
*
* @return object The managed copy of the entity.
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*
* @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
*/
public function merge($entity)
{
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
if ( ! is_object($entity)) {
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()', $entity);
}
@@ -720,7 +731,7 @@ use function trigger_error;
{
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
throw new \BadMethodCallException("Not implemented.");
throw new BadMethodCallException('Not implemented.');
}
/**
@@ -752,7 +763,7 @@ use function trigger_error;
*
* @param object $entity
*
* @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
* @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
*/
public function contains($entity)
{
@@ -796,7 +807,7 @@ use function trigger_error;
*/
public function isOpen()
{
return (!$this->closed);
return ! $this->closed;
}
/**
@@ -837,7 +848,9 @@ use function trigger_error;
return new Internal\Hydration\SimpleObjectHydrator($this);
default:
if (($class = $this->config->getCustomHydrationMode($hydrationMode)) !== null) {
$class = $this->config->getCustomHydrationMode($hydrationMode);
if ($class !== null) {
return new $class($this);
}
}
@@ -864,18 +877,18 @@ use function trigger_error;
/**
* Factory method to create EntityManager instances.
*
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
* @param array<string, mixed>|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
*
* @return EntityManager The created EntityManager.
*
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
* @throws ORMException
*/
public static function create($connection, Configuration $config, EventManager $eventManager = null)
public static function create($connection, Configuration $config, ?EventManager $eventManager = null)
{
if ( ! $config->getMetadataDriverImpl()) {
if (! $config->getMetadataDriverImpl()) {
throw ORMException::missingMappingDriverImpl();
}
@@ -887,23 +900,23 @@ use function trigger_error;
/**
* Factory method to create Connection instances.
*
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
* @param array<string, mixed>|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
*
* @return Connection
*
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
* @throws ORMException
*/
protected static function createConnection($connection, Configuration $config, EventManager $eventManager = null)
protected static function createConnection($connection, Configuration $config, ?EventManager $eventManager = null)
{
if (is_array($connection)) {
return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager());
}
if ( ! $connection instanceof Connection) {
throw new \InvalidArgumentException(
if (! $connection instanceof Connection) {
throw new InvalidArgumentException(
sprintf(
'Invalid $connection argument of type %s given%s.',
is_object($connection) ? get_class($connection) : gettype($connection),
@@ -924,7 +937,7 @@ use function trigger_error;
*/
public function getFilters()
{
if (null === $this->filterCollection) {
if ($this->filterCollection === null) {
$this->filterCollection = new FilterCollection($this);
}
@@ -936,7 +949,7 @@ use function trigger_error;
*/
public function isFiltersStateClean()
{
return null === $this->filterCollection || $this->filterCollection->isClean();
return $this->filterCollection === null || $this->filterCollection->isClean();
}
/**
@@ -944,12 +957,10 @@ use function trigger_error;
*/
public function hasFilters()
{
return null !== $this->filterCollection;
return $this->filterCollection !== null;
}
/**
* @param int $lockMode
* @param ClassMetadata $class
* @throws OptimisticLockException
* @throws TransactionRequiredException
*/
@@ -957,13 +968,14 @@ use function trigger_error;
{
switch ($lockMode) {
case LockMode::OPTIMISTIC:
if (!$class->isVersioned) {
if (! $class->isVersioned) {
throw OptimisticLockException::notVersioned($class->name);
}
break;
case LockMode::PESSIMISTIC_READ:
case LockMode::PESSIMISTIC_WRITE:
if (!$this->getConnection()->isTransactionActive()) {
if (! $this->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,15 +20,20 @@
namespace Doctrine\ORM;
use BadMethodCallException;
use DateTimeInterface;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\ObjectManager;
/**
* EntityManager interface
*
* @since 2.4
* @author Lars Strojny <lars@strojny.net>
*
* @method Mapping\ClassMetadata getClassMetadata($className)
*/
interface EntityManagerInterface extends ObjectManager
@@ -35,14 +41,14 @@ interface EntityManagerInterface extends ObjectManager
/**
* Returns the cache API for managing the second level cache regions or NULL if the cache is not enabled.
*
* @return \Doctrine\ORM\Cache|null
* @return Cache|null
*/
public function getCache();
/**
* Gets the database connection object used by the EntityManager.
*
* @return \Doctrine\DBAL\Connection
* @return Connection
*/
public function getConnection();
@@ -58,7 +64,7 @@ interface EntityManagerInterface extends ObjectManager
* ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2)));
* </code>
*
* @return \Doctrine\ORM\Query\Expr
* @return Expr
*/
public function getExpressionBuilder();
@@ -196,21 +202,21 @@ interface EntityManagerInterface extends ObjectManager
*
* @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
*
* @param object $entity The entity to copy.
* @param boolean $deep FALSE for a shallow copy, TRUE for a deep copy.
* @param object $entity The entity to copy.
* @param bool $deep FALSE for a shallow copy, TRUE for a deep copy.
*
* @return object The new entity.
*
* @throws \BadMethodCallException
* @throws BadMethodCallException
*/
public function copy($entity, $deep = false);
/**
* Acquire a lock on the given entity.
*
* @param object $entity
* @param int $lockMode
* @param int|null $lockVersion
* @param object $entity
* @param int $lockMode
* @param int|DateTimeInterface|null $lockVersion
*
* @return void
*
@@ -222,7 +228,7 @@ interface EntityManagerInterface extends ObjectManager
/**
* Gets the EventManager used by the EntityManager.
*
* @return \Doctrine\Common\EventManager
* @return EventManager
*/
public function getEventManager();
@@ -248,17 +254,17 @@ interface EntityManagerInterface extends ObjectManager
public function getUnitOfWork();
/**
* Gets a hydrator for the given hydration mode.
*
* This method caches the hydrator instances which is used for all queries that don't
* selectively iterate over the result.
*
* @deprecated
*
* @param string|int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
* Gets a hydrator for the given hydration mode.
*
* This method caches the hydrator instances which is used for all queries that don't
* selectively iterate over the result.
*
* @deprecated
*
* @param string|int $hydrationMode
*
* @return AbstractHydrator
*/
public function getHydrator($hydrationMode);
/**
@@ -266,7 +272,7 @@ interface EntityManagerInterface extends ObjectManager
*
* @param string|int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
* @return AbstractHydrator
*
* @throws ORMException
*/
@@ -275,28 +281,28 @@ interface EntityManagerInterface extends ObjectManager
/**
* Gets the proxy factory used by the EntityManager to create entity proxies.
*
* @return \Doctrine\ORM\Proxy\ProxyFactory
* @return ProxyFactory
*/
public function getProxyFactory();
/**
* Gets the enabled filters.
*
* @return \Doctrine\ORM\Query\FilterCollection The active filter collection.
* @return FilterCollection The active filter collection.
*/
public function getFilters();
/**
* Checks whether the state of the filter collection is clean.
*
* @return boolean True, if the filter collection is clean.
* @return bool True, if the filter collection is clean.
*/
public function isFiltersStateClean();
/**
* Checks whether the Entity Manager has filters.
*
* @return boolean True, if the EM has a filter collection.
* @return bool True, if the EM has a filter collection.
*/
public function hasFilters();
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,11 +20,10 @@
namespace Doctrine\ORM;
use function implode;
/**
* Exception thrown when a Proxy fails to retrieve an Entity result.
*
* @author robo
* @since 2.0
*/
class EntityNotFoundException extends ORMException
{
@@ -43,7 +43,6 @@ class EntityNotFoundException extends ORMException
$ids[] = $key . '(' . $value . ')';
}
return new self(
'Entity of type \'' . $className . '\'' . ($ids ? ' for IDs ' . implode(', ', $ids) : '') . ' was not found'
);

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,16 +20,25 @@
namespace Doctrine\ORM;
use BadMethodCallException;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Selectable;
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\Persistence\ObjectRepository;
use const E_USER_DEPRECATED;
use function array_slice;
use function lcfirst;
use function sprintf;
use function strpos;
use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* An EntityRepository serves as a repository for entities with generic as well as
* business specific methods for retrieving entities.
@@ -36,31 +46,19 @@ use function trigger_error;
* This class is designed for inheritance and users can subclass this class to
* write their own repositories with business-specific methods to locate entities.
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*
* @template T
* @template-implements Selectable<int,T>
* @template-implements ObjectRepository<T>
*/
class EntityRepository implements ObjectRepository, Selectable
{
/**
* @var string
*/
/** @var string */
protected $_entityName;
/**
* @var EntityManager
*/
/** @var EntityManager */
protected $_em;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
/** @var ClassMetadata */
protected $_class;
/** @var Inflector */
@@ -69,7 +67,7 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Initializes a new <tt>EntityRepository</tt>.
*
* @psalm-param Mapping\ClassMetadata<T>
* @psalm-param Mapping\ClassMetadata $class
*/
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
{
@@ -131,8 +129,8 @@ class EntityRepository implements ObjectRepository, Selectable
*/
public function createNativeNamedQuery($queryName)
{
$queryMapping = $this->_class->getNamedNativeQuery($queryName);
$rsm = new Query\ResultSetMappingBuilder($this->_em);
$queryMapping = $this->_class->getNamedNativeQuery($queryName);
$rsm = new Query\ResultSetMappingBuilder($this->_em);
$rsm->addNamedNativeQueryMapping($this->_class, $queryMapping);
return $this->_em->createNativeQuery($queryMapping['query'], $rsm);
@@ -141,9 +139,9 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Clears the repository, causing all managed entities to become detached.
*
* @return void
*
* @deprecated 2.8 This method is being removed from the ORM and won't have any replacement
*
* @return void
*/
public function clear()
{
@@ -173,9 +171,7 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds all entities in the repository.
*
* @return array The entities.
*
* @psalm-return list<T>
* @psalm-return list<T> The entities.
*/
public function findAll()
{
@@ -185,16 +181,14 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds entities by a set of criteria.
*
* @param array $criteria
* @param array|null $orderBy
* @param int|null $limit
* @param int|null $offset
* @param int|null $limit
* @param int|null $offset
*
* @return array The objects.
*
* @psalm-return list<T>
* @psalm-param array<string, mixed> $criteria
* @psalm-param list<string>|null $orderBy
* @psalm-return list<T> The objects.
*/
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
{
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
@@ -204,14 +198,13 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds a single entity by a set of criteria.
*
* @param array $criteria
* @param array|null $orderBy
*
* @return object|null The entity instance or NULL if the entity can not be found.
*
* @psalm-param array<string, mixed> $criteria
* @psalm-param array<string, string>|null $orderBy
* @psalm-return ?T
*/
public function findOneBy(array $criteria, array $orderBy = null)
public function findOneBy(array $criteria, ?array $orderBy = null)
{
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
@@ -221,11 +214,10 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Counts entities by a set of criteria.
*
* @todo Add this method to `ObjectRepository` interface in the next major release
*
* @param array $criteria
*
* @return int The cardinality of the objects that match the given criteria.
*
* @psalm-param array<string, mixed> $criteria
* @todo Add this method to `ObjectRepository` interface in the next major release
*/
public function count(array $criteria)
{
@@ -236,31 +228,33 @@ class EntityRepository implements ObjectRepository, Selectable
* Adds support for magic method calls.
*
* @param string $method
* @param array $arguments
*
* @return mixed The returned value from the resolved method.
*
* @throws ORMException
* @throws \BadMethodCallException If the method called is invalid
* @throws BadMethodCallException If the method called is invalid.
*
* @psalm-param list<mixed> $arguments
*/
public function __call($method, $arguments)
{
if (0 === strpos($method, 'findBy')) {
if (strpos($method, 'findBy') === 0) {
return $this->resolveMagicCall('findBy', substr($method, 6), $arguments);
}
if (0 === strpos($method, 'findOneBy')) {
if (strpos($method, 'findOneBy') === 0) {
return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
}
if (0 === strpos($method, 'countBy')) {
if (strpos($method, 'countBy') === 0) {
return $this->resolveMagicCall('count', substr($method, 7), $arguments);
}
throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ".
"either findBy, findOneBy or countBy!"
);
throw new BadMethodCallException(sprintf(
'Undefined method "%s". The method name must start with ' .
'either findBy, findOneBy or countBy!',
$method
));
}
/**
@@ -299,11 +293,7 @@ class EntityRepository implements ObjectRepository, Selectable
* Select all elements from a selectable that match the expression and
* return a new collection containing these elements.
*
* @param \Doctrine\Common\Collections\Criteria $criteria
*
* @return \Doctrine\Common\Collections\Collection
*
* @psalm-return \Doctrine\Common\Collections\Collection<int, T>
* @psalm-return Collection<int, T>
*/
public function matching(Criteria $criteria)
{
@@ -315,15 +305,16 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Resolves a magic method call to the proper existent method at `EntityRepository`.
*
* @param string $method The method to call
* @param string $by The property name used as condition
* @param array $arguments The arguments to pass at method call
*
* @throws ORMException If the method called is invalid or the requested field/association does not exist
* @param string $method The method to call
* @param string $by The property name used as condition
*
* @return mixed
*
* @throws ORMException If the method called is invalid or the requested field/association does not exist.
*
* @psalm-param list<mixed> $arguments The arguments to pass at method call
*/
private function resolveMagicCall($method, $by, array $arguments)
private function resolveMagicCall(string $method, string $by, array $arguments)
{
if (! $arguments) {
throw ORMException::findByRequiresParameter($method . $by);

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,6 +20,7 @@
namespace Doctrine\ORM\Event;
use Doctrine\ORM\EntityManager;
use Doctrine\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs;
/**
@@ -26,9 +28,6 @@ use Doctrine\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs;
* of entities.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class LifecycleEventArgs extends BaseLifecycleEventArgs
{
@@ -45,7 +44,7 @@ class LifecycleEventArgs extends BaseLifecycleEventArgs
/**
* Retrieves associated EntityManager.
*
* @return \Doctrine\ORM\EntityManager
* @return EntityManager
*/
public function getEntityManager()
{

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -20,38 +21,33 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\Common\EventManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\EntityListenerResolver;
/**
* A method invoker based on entity lifecycle.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.4
*/
class ListenersInvoker
{
const INVOKE_NONE = 0;
const INVOKE_LISTENERS = 1;
const INVOKE_CALLBACKS = 2;
const INVOKE_MANAGER = 4;
public const INVOKE_NONE = 0;
public const INVOKE_LISTENERS = 1;
public const INVOKE_CALLBACKS = 2;
public const INVOKE_MANAGER = 4;
/**
* @var \Doctrine\ORM\Mapping\EntityListenerResolver The Entity listener resolver.
*/
/** @var EntityListenerResolver The Entity listener resolver. */
private $resolver;
/**
* The EventManager used for dispatching events.
*
* @var \Doctrine\Common\EventManager
* @var EventManager
*/
private $eventManager;
/**
* Initializes a new ListenersInvoker instance.
*
* @param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
@@ -62,10 +58,10 @@ class ListenersInvoker
/**
* Get the subscribed event systems
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
*
* @return integer Bitmask of subscribed event systems.
* @return int Bitmask of subscribed event systems.
*/
public function getSubscribedSystems(ClassMetadata $metadata, $eventName)
{
@@ -89,11 +85,11 @@ class ListenersInvoker
/**
* Dispatches the lifecycle event of the given entity.
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param object $entity The Entity on which the event occurred.
* @param \Doctrine\Common\EventArgs $event The Event args.
* @param integer $invoke Bitmask to invoke listeners.
* @param ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param object $entity The Entity on which the event occurred.
* @param EventArgs $event The Event args.
* @param int $invoke Bitmask to invoke listeners.
*/
public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke)
{
@@ -105,9 +101,9 @@ class ListenersInvoker
if ($invoke & self::INVOKE_LISTENERS) {
foreach ($metadata->entityListeners[$eventName] as $listener) {
$class = $listener['class'];
$method = $listener['method'];
$instance = $this->resolver->resolve($class);
$class = $listener['class'];
$method = $listener['method'];
$instance = $this->resolver->resolve($class);
$instance->$method($entity, $event);
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,25 +20,22 @@
namespace Doctrine\ORM\Event;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClassMetadataEventArgs;
/**
* Class that holds event arguments for a loadMetadata event.
*
* @author Jonathan H. Wage <jonwage@gmail.com>
* @since 2.0
*
* Note: method annotations are used instead of method overrides (due to BC policy)
*
* @method __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata, \Doctrine\ORM\EntityManager $objectManager)
* @method \Doctrine\ORM\Mapping\ClassMetadata getClassMetadata()
* @method __construct(ClassMetadata $classMetadata, EntityManager $objectManager)
* @method ClassMetadata getClassMetadata()
*/
class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
{
/**
* Retrieve associated EntityManager.
*
* @return \Doctrine\ORM\EntityManager
* @return EntityManager
*/
public function getEntityManager()
{

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -22,34 +23,23 @@ namespace Doctrine\ORM\Event;
use Doctrine\Persistence\Event\ManagerEventArgs;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\ObjectManager;
use function interface_exists;
/**
* Class that holds event arguments for a `onClassMetadataNotFound` event.
*
* This object is mutable by design, allowing callbacks having access to it to set the
* found metadata in it, and therefore "cancelling" a `onClassMetadataNotFound` event
*
* @author Marco Pivetta <ocramius@gmail.com>
* @since 2.5
*/
class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs
{
/**
* @var string
*/
/** @var string */
private $className;
/**
* @var ClassMetadata|null
*/
/** @var ClassMetadata|null */
private $foundMetadata;
/**
* Constructor.
*
* @param string $className
* @param ObjectManager $objectManager
* @param string $className
*/
public function __construct($className, ObjectManager $objectManager)
{
@@ -58,10 +48,7 @@ class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs
parent::__construct($objectManager);
}
/**
* @param ClassMetadata|null $classMetadata
*/
public function setFoundMetadata(ClassMetadata $classMetadata = null)
public function setFoundMetadata(?ClassMetadata $classMetadata = null)
{
$this->foundMetadata = $classMetadata;
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,34 +20,25 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
* Provides event arguments for the onClear event.
*
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.org
* @since 2.0
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class OnClearEventArgs extends \Doctrine\Common\EventArgs
class OnClearEventArgs extends EventArgs
{
/**
* @var EntityManagerInterface
*/
/** @var EntityManagerInterface */
private $em;
/**
* @var string|null
*/
/** @var string|null */
private $entityClass;
/**
* Constructor.
*
* @param EntityManagerInterface $em
* @param string|null $entityClass Optional entity class.
* @param string|null $entityClass Optional entity class.
*/
public function __construct(EntityManagerInterface $em, $entityClass = null)
{
@@ -57,7 +49,7 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
/**
* Retrieves associated EntityManager.
*
* @return \Doctrine\ORM\EntityManager
* @return EntityManager
*/
public function getEntityManager()
{
@@ -81,6 +73,6 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
*/
public function clearsAllEntities()
{
return ($this->entityClass === null);
return $this->entityClass === null;
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -20,29 +21,19 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
* Provides event arguments for the preFlush event.
*
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.org
* @since 2.0
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class OnFlushEventArgs extends EventArgs
{
/**
* @var EntityManagerInterface
*/
/** @var EntityManagerInterface */
private $em;
/**
* Constructor.
*
* @param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
@@ -51,11 +42,10 @@ class OnFlushEventArgs extends EventArgs
/**
* Retrieve associated EntityManager.
*
* @return \Doctrine\ORM\EntityManager
* @return EntityManager
*/
public function getEntityManager()
{
return $this->em;
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -16,31 +17,23 @@
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
* Provides event arguments for the postFlush event.
*
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.org
* @since 2.0
* @author Daniel Freudenberger <df@rebuy.de>
*/
class PostFlushEventArgs extends EventArgs
{
/**
* @var \Doctrine\ORM\EntityManager
*/
/** @var EntityManager */
private $em;
/**
* Constructor.
*
* @param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
@@ -49,7 +42,7 @@ class PostFlushEventArgs extends EventArgs
/**
* Retrieves associated EntityManager.
*
* @return \Doctrine\ORM\EntityManager
* @return EntityManager
*/
public function getEntityManager()
{

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -20,36 +21,26 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
* Provides event arguments for the preFlush event.
*
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.com
* @since 2.0
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class PreFlushEventArgs extends EventArgs
{
/**
* @var \Doctrine\ORM\EntityManager
*/
/** @var EntityManager */
private $em;
/**
* Constructor.
*
* @param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* @return \Doctrine\ORM\EntityManager
* @return EntityManager
*/
public function getEntityManager()
{

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -20,28 +21,22 @@
namespace Doctrine\ORM\Event;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use function get_class;
use function sprintf;
/**
* Class that holds event arguments for a preInsert/preUpdate event.
*
* @author Guilherme Blanco <guilehrmeblanco@hotmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
class PreUpdateEventArgs extends LifecycleEventArgs
{
/**
* @var array
*/
/** @var array<string,array<int,mixed>> */
private $entityChangeSet;
/**
* Constructor.
*
* @param object $entity
* @param EntityManagerInterface $em
* @param array $changeSet
* @param object $entity
* @param array<string,array<int,mixed>> $changeSet
*/
public function __construct($entity, EntityManagerInterface $em, array &$changeSet)
{
@@ -53,7 +48,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs
/**
* Retrieves entity changeset.
*
* @return array
* @return array<string,array<int,mixed>>
*/
public function getEntityChangeSet()
{
@@ -65,7 +60,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs
*
* @param string $field
*
* @return boolean
* @return bool
*/
public function hasChangedField($field)
{
@@ -122,12 +117,12 @@ class PreUpdateEventArgs extends LifecycleEventArgs
*
* @return void
*
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
*/
private function assertValidField($field)
{
if ( ! isset($this->entityChangeSet[$field])) {
throw new \InvalidArgumentException(sprintf(
if (! isset($this->entityChangeSet[$field])) {
throw new InvalidArgumentException(sprintf(
'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.',
$field,
get_class($this->getEntity())

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -23,9 +24,6 @@ namespace Doctrine\ORM;
* Container for all ORM events.
*
* This class cannot be instantiated.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
final class Events
{
@@ -41,30 +39,24 @@ final class Events
* EntityManager remove operation for that entity is executed.
*
* This is an entity lifecycle event.
*
* @var string
*/
const preRemove = 'preRemove';
public const preRemove = 'preRemove';
/**
* The postRemove event occurs for an entity after the entity has
* been deleted. It will be invoked after the database delete operations.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postRemove = 'postRemove';
public const postRemove = 'postRemove';
/**
* The prePersist event occurs for a given entity before the respective
* EntityManager persist operation for that entity is executed.
*
* This is an entity lifecycle event.
*
* @var string
*/
const prePersist = 'prePersist';
public const prePersist = 'prePersist';
/**
* The postPersist event occurs for an entity after the entity has
@@ -72,30 +64,24 @@ final class Events
* Generated primary key values are available in the postPersist event.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postPersist = 'postPersist';
public const postPersist = 'postPersist';
/**
* The preUpdate event occurs before the database update operations to
* entity data.
*
* This is an entity lifecycle event.
*
* @var string
*/
const preUpdate = 'preUpdate';
public const preUpdate = 'preUpdate';
/**
* The postUpdate event occurs after the database update operations to
* entity data.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postUpdate = 'postUpdate';
public const postUpdate = 'postUpdate';
/**
* The postLoad event occurs for an entity after the entity has been loaded
@@ -107,33 +93,27 @@ final class Events
* or event handler.
*
* This is an entity lifecycle event.
*
* @var string
*/
const postLoad = 'postLoad';
public const postLoad = 'postLoad';
/**
* The loadClassMetadata event occurs after the mapping metadata for a class
* has been loaded from a mapping source (annotations/xml/yaml).
*
* @var string
*/
const loadClassMetadata = 'loadClassMetadata';
public const loadClassMetadata = 'loadClassMetadata';
/**
* The onClassMetadataNotFound event occurs whenever loading metadata for a class
* failed.
*
* @var string
*/
const onClassMetadataNotFound = 'onClassMetadataNotFound';
public const onClassMetadataNotFound = 'onClassMetadataNotFound';
/**
* The preFlush event occurs when the EntityManager#flush() operation is invoked,
* but before any changes to managed entities have been calculated. This event is
* always raised right after EntityManager#flush() call.
*/
const preFlush = 'preFlush';
public const preFlush = 'preFlush';
/**
* The onFlush event occurs when the EntityManager#flush() operation is invoked,
@@ -141,10 +121,8 @@ final class Events
* actual database operations are executed. The event is only raised if there is
* actually something to do for the underlying UnitOfWork. If nothing needs to be done,
* the onFlush event is not raised.
*
* @var string
*/
const onFlush = 'onFlush';
public const onFlush = 'onFlush';
/**
* The postFlush event occurs when the EntityManager#flush() operation is invoked and
@@ -152,16 +130,12 @@ final class Events
* actually something to do for the underlying UnitOfWork. If nothing needs to be done,
* the postFlush event is not raised. The event won't be raised if an error occurs during the
* flush operation.
*
* @var string
*/
const postFlush = 'postFlush';
public const postFlush = 'postFlush';
/**
* The onClear event occurs when the EntityManager#clear() operation is invoked,
* after all references to entities have been removed from the unit of work.
*
* @var string
*/
const onClear = 'onClear';
public const onClear = 'onClear';
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -26,8 +27,8 @@ abstract class AbstractIdGenerator
/**
* Generates an identifier for an entity.
*
* @param EntityManager $em
* @param object|null $entity
*
* @return mixed
*/
abstract public function generate(EntityManager $em, $entity);

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -22,14 +23,10 @@ namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMException;
use function get_class;
/**
* Special generator for application-assigned identifiers (doesn't really generate anything).
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class AssignedGenerator extends AbstractIdGenerator
{
@@ -38,7 +35,7 @@ class AssignedGenerator extends AbstractIdGenerator
*
* {@inheritDoc}
*
* @throws \Doctrine\ORM\ORMException
* @throws ORMException
*/
public function generate(EntityManager $em, $entity)
{
@@ -49,7 +46,7 @@ class AssignedGenerator extends AbstractIdGenerator
foreach ($idFields as $idField) {
$value = $class->getFieldValue($entity, $idField);
if ( ! isset($value)) {
if (! isset($value)) {
throw ORMException::entityMissingAssignedIdForField($entity, $idField);
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -36,8 +37,6 @@ class BigIntegerIdentityGenerator extends AbstractIdGenerator
private $sequenceName;
/**
* Constructor.
*
* @param string|null $sequenceName The name of the sequence to pass to lastInsertId()
* to obtain the last generated identifier within the current
* database session/connection, if any.
@@ -63,4 +62,3 @@ class BigIntegerIdentityGenerator extends AbstractIdGenerator
return true;
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -36,8 +37,6 @@ class IdentityGenerator extends AbstractIdGenerator
private $sequenceName;
/**
* Constructor.
*
* @param string|null $sequenceName The name of the sequence to pass to lastInsertId()
* to obtain the last generated identifier within the current
* database session/connection, if any.

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -22,11 +23,11 @@ namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManager;
use Serializable;
use function serialize;
use function unserialize;
/**
* Represents an ID generator that uses a database sequence.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
*/
class SequenceGenerator extends AbstractIdGenerator implements Serializable
{
@@ -44,25 +45,21 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
*/
private $_sequenceName;
/**
* @var int
*/
/** @var int */
private $_nextValue = 0;
/**
* @var int|null
*/
/** @var int|null */
private $_maxValue = null;
/**
* Initializes a new sequence generator.
*
* @param string $sequenceName The name of the sequence.
* @param integer $allocationSize The allocation size of the sequence.
* @param string $sequenceName The name of the sequence.
* @param int $allocationSize The allocation size of the sequence.
*/
public function __construct($sequenceName, $allocationSize)
{
$this->_sequenceName = $sequenceName;
$this->_sequenceName = $sequenceName;
$this->_allocationSize = $allocationSize;
}
@@ -71,7 +68,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
*/
public function generate(EntityManager $em, $entity)
{
if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) {
if ($this->_maxValue === null || $this->_nextValue === $this->_maxValue) {
// Allocate new values
$conn = $em->getConnection();
$sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
@@ -87,7 +84,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
/**
* Gets the maximum value of the currently allocated bag of values.
*
* @return integer|null
* @return int|null
*/
public function getCurrentMaxValue()
{
@@ -97,7 +94,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
/**
* Gets the next value that will be returned by generate().
*
* @return integer
* @return int
*/
public function getNextValue()
{
@@ -111,8 +108,8 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
{
return serialize(
[
'allocationSize' => $this->_allocationSize,
'sequenceName' => $this->_sequenceName
'allocationSize' => $this->_allocationSize,
'sequenceName' => $this->_sequenceName,
]
);
}
@@ -126,7 +123,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
{
$array = unserialize($serialized);
$this->_sequenceName = $array['sequenceName'];
$this->_sequenceName = $array['sequenceName'];
$this->_allocationSize = $array['allocationSize'];
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -23,38 +24,22 @@ use Doctrine\ORM\EntityManager;
/**
* Id generator that uses a single-row database table and a hi/lo algorithm.
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class TableGenerator extends AbstractIdGenerator
{
/**
* @var string
*/
/** @var string */
private $_tableName;
/**
* @var string
*/
/** @var string */
private $_sequenceName;
/**
* @var int
*/
/** @var int */
private $_allocationSize;
/**
* @var int|null
*/
/** @var int|null */
private $_nextValue;
/**
* @var int|null
*/
/** @var int|null */
private $_maxValue;
/**
@@ -64,8 +49,8 @@ class TableGenerator extends AbstractIdGenerator
*/
public function __construct($tableName, $sequenceName = 'default', $allocationSize = 10)
{
$this->_tableName = $tableName;
$this->_sequenceName = $sequenceName;
$this->_tableName = $tableName;
$this->_sequenceName = $sequenceName;
$this->_allocationSize = $allocationSize;
}
@@ -73,9 +58,10 @@ class TableGenerator extends AbstractIdGenerator
* {@inheritDoc}
*/
public function generate(
EntityManager $em, $entity)
{
if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) {
EntityManager $em,
$entity
) {
if ($this->_maxValue === null || $this->_nextValue === $this->_maxValue) {
// Allocate new values
$conn = $em->getConnection();
@@ -84,15 +70,17 @@ class TableGenerator extends AbstractIdGenerator
$sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName);
$currentLevel = $conn->fetchColumn($sql);
if ($currentLevel != null) {
if ($currentLevel !== null) {
$this->_nextValue = $currentLevel;
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
$updateSql = $conn->getDatabasePlatform()->getTableHiLoUpdateNextValSql(
$this->_tableName, $this->_sequenceName, $this->_allocationSize
$this->_tableName,
$this->_sequenceName,
$this->_allocationSize
);
if ($conn->executeUpdate($updateSql, [1 => $currentLevel, 2 => $currentLevel+1]) !== 1) {
if ($conn->executeUpdate($updateSql, [1 => $currentLevel, 2 => $currentLevel + 1]) !== 1) {
// no affected rows, concurrency issue, throw exception
}
} else {

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -23,9 +24,6 @@ use Doctrine\ORM\EntityManager;
/**
* Represents an ID generator that uses the database UUID expression
*
* @since 2.3
* @author Maarten de Keizer <m.de.keizer@markei.nl>
*/
class UuidGenerator extends AbstractIdGenerator
{
@@ -35,7 +33,7 @@ class UuidGenerator extends AbstractIdGenerator
public function generate(EntityManager $em, $entity)
{
$conn = $em->getConnection();
$sql = 'SELECT ' . $conn->getDatabasePlatform()->getGuidExpression();
$sql = 'SELECT ' . $conn->getDatabasePlatform()->getGuidExpression();
return $conn->query($sql)->fetchColumn(0);
}

View File

@@ -20,22 +20,22 @@
namespace Doctrine\ORM\Internal;
use stdClass;
use function array_reverse;
/**
* CommitOrderCalculator implements topological sorting, which is an ordering
* algorithm for directed graphs (DG) and/or directed acyclic graphs (DAG) by
* using a depth-first searching (DFS) to traverse the graph built in memory.
* This algorithm have a linear running time based on nodes (V) and dependency
* between the nodes (E), resulting in a computational complexity of O(V + E).
*
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class CommitOrderCalculator
{
const NOT_VISITED = 0;
const IN_PROGRESS = 1;
const VISITED = 2;
public const NOT_VISITED = 0;
public const IN_PROGRESS = 1;
public const VISITED = 2;
/**
* Matrix of nodes (aka. vertex).
@@ -52,14 +52,14 @@ class CommitOrderCalculator
* - <b>dependencyList</b> (array<string>)
* Map of node dependencies defined as hashes.
*
* @var array<\stdClass>
* @var array<stdClass>
*/
private $nodeList = [];
/**
* Volatile variable holding calculated nodes during sorting process.
*
* @var array
* @psalm-var list<object>
*/
private $sortedNodeList = [];
@@ -68,7 +68,7 @@ class CommitOrderCalculator
*
* @param string $hash
*
* @return boolean
* @return bool
*/
public function hasNode($hash)
{
@@ -85,7 +85,7 @@ class CommitOrderCalculator
*/
public function addNode($hash, $node)
{
$vertex = new \stdClass();
$vertex = new stdClass();
$vertex->hash = $hash;
$vertex->state = self::NOT_VISITED;
@@ -98,16 +98,16 @@ class CommitOrderCalculator
/**
* Adds a new dependency (edge) to the graph using their hashes.
*
* @param string $fromHash
* @param string $toHash
* @param integer $weight
* @param string $fromHash
* @param string $toHash
* @param int $weight
*
* @return void
*/
public function addDependency($fromHash, $toHash, $weight)
{
$vertex = $this->nodeList[$fromHash];
$edge = new \stdClass();
$edge = new stdClass();
$edge->from = $fromHash;
$edge->to = $toHash;
@@ -122,7 +122,7 @@ class CommitOrderCalculator
*
* {@internal Highly performance-sensitive method.}
*
* @return array
* @psalm-return list<object>
*/
public function sort()
{
@@ -147,7 +147,7 @@ class CommitOrderCalculator
*
* {@internal Highly performance-sensitive method.}
*
* @param \stdClass $vertex
* @param stdClass $vertex
*/
private function visit($vertex)
{
@@ -162,9 +162,10 @@ class CommitOrderCalculator
break;
case self::IN_PROGRESS:
if (isset($adjacentVertex->dependencyList[$vertex->hash]) &&
$adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight) {
if (
isset($adjacentVertex->dependencyList[$vertex->hash]) &&
$adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight
) {
// If we have some non-visited dependencies in the in-progress dependency, we
// need to visit them before adding the node.
foreach ($adjacentVertex->dependencyList as $adjacentEdge) {
@@ -179,6 +180,7 @@ class CommitOrderCalculator
$this->sortedNodeList[] = $adjacentVertex->value;
}
break;
case self::NOT_VISITED:

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,36 +20,38 @@
namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker;
use Doctrine\ORM\UnitOfWork;
use PDO;
use const E_USER_DEPRECATED;
use ReflectionClass;
use function array_map;
use function array_merge;
use function count;
use function end;
use function in_array;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Base class for all hydrators. A hydrator is a class that provides some form
* of transformation of an SQL result set into another structure.
*
* @since 2.0
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
*/
abstract class AbstractHydrator
{
/**
* The ResultSetMapping.
*
* @var \Doctrine\ORM\Query\ResultSetMapping
* @var ResultSetMapping
*/
protected $_rsm;
@@ -62,42 +65,42 @@ abstract class AbstractHydrator
/**
* The dbms Platform instance.
*
* @var \Doctrine\DBAL\Platforms\AbstractPlatform
* @var AbstractPlatform
*/
protected $_platform;
/**
* The UnitOfWork of the associated EntityManager.
*
* @var \Doctrine\ORM\UnitOfWork
* @var UnitOfWork
*/
protected $_uow;
/**
* Local ClassMetadata cache to avoid going to the EntityManager all the time.
*
* @var array
* @var array<string, ClassMetadata>
*/
protected $_metadataCache = [];
/**
* The cache used during row-by-row hydration.
*
* @var array
* @var array<string, mixed[]|null>
*/
protected $_cache = [];
/**
* The statement that provides the data to hydrate.
*
* @var \Doctrine\DBAL\Driver\Statement
* @var ResultStatement
*/
protected $_stmt;
/**
* The query hints.
*
* @var array
* @var array<string, mixed>
*/
protected $_hints;
@@ -120,9 +123,10 @@ abstract class AbstractHydrator
*
* @param object $stmt
* @param object $resultSetMapping
* @param array $hints
*
* @return IterableResult
*
* @psalm-param array<string, mixed> $hints
*/
public function iterate($stmt, $resultSetMapping, array $hints = [])
{
@@ -147,11 +151,11 @@ abstract class AbstractHydrator
/**
* Initiates a row-by-row hydration.
*
* @param mixed[] $hints
*
* @return iterable<mixed>
*
* @psalm-param array<string, mixed> $hints
*/
public function toIterable(Statement $stmt, ResultSetMapping $resultSetMapping, array $hints = []) : iterable
public function toIterable(ResultStatement $stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable
{
$this->_stmt = $stmt;
$this->_rsm = $resultSetMapping;
@@ -163,8 +167,6 @@ abstract class AbstractHydrator
$this->prepare();
$result = [];
while (true) {
$row = $this->_stmt->fetch(FetchMode::ASSOCIATIVE);
@@ -174,9 +176,17 @@ abstract class AbstractHydrator
break;
}
$result = [];
$this->hydrateRowData($row, $result);
yield end($result);
$this->cleanupAfterRowIteration();
if (count($result) === 1) {
yield end($result);
} else {
yield $result;
}
}
}
@@ -185,9 +195,10 @@ abstract class AbstractHydrator
*
* @param object $stmt
* @param object $resultSetMapping
* @param array $hints
*
* @return array
* @return mixed[]
*
* @psalm-param array<string, string> $hints
*/
public function hydrateAll($stmt, $resultSetMapping, array $hints = [])
{
@@ -196,12 +207,13 @@ abstract class AbstractHydrator
$this->_hints = $hints;
$this->_em->getEventManager()->addEventListener([Events::onClear], $this);
$this->prepare();
$result = $this->hydrateAllData();
$this->cleanup();
try {
$result = $this->hydrateAllData();
} finally {
$this->cleanup();
}
return $result;
}
@@ -272,6 +284,10 @@ abstract class AbstractHydrator
->removeEventListener([Events::onClear], $this);
}
protected function cleanupAfterRowIteration(): void
{
}
/**
* Hydrates a single row from the current statement instance.
*
@@ -286,13 +302,13 @@ abstract class AbstractHydrator
*/
protected function hydrateRowData(array $row, array &$result)
{
throw new HydrationException("hydrateRowData() not implemented by this hydrator.");
throw new HydrationException('hydrateRowData() not implemented by this hydrator.');
}
/**
* Hydrates all rows from the current statement instance at once.
*
* @return array
* @return mixed[]
*/
abstract protected function hydrateAllData();
@@ -305,15 +321,15 @@ abstract class AbstractHydrator
* field names during this procedure as well as any necessary conversions on
* the values applied. Scalar values are kept in a specific key 'scalars'.
*
* @param array $data SQL Result Row.
* @param array &$id Dql-Alias => ID-Hash.
* @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value?
* @param mixed[] $data SQL Result Row.
*
* @return array<string, array<string, mixed>> An array with all the fields
* (name => value) of the data
* row, grouped by their
* component alias.
*
* @psalm-param array<string, string> $id Dql-Alias => ID-Hash.
* @psalm-param array<string, bool> $nonemptyComponents Does this DQL-Alias has at least one non NULL value?
* @psalm-return array{
* data: array<array-key, array>,
* newObjects?: array<array-key, array{
@@ -328,14 +344,15 @@ abstract class AbstractHydrator
$rowData = ['data' => []];
foreach ($data as $key => $value) {
if (($cacheKeyInfo = $this->hydrateColumnInfo($key)) === null) {
$cacheKeyInfo = $this->hydrateColumnInfo($key);
if ($cacheKeyInfo === null) {
continue;
}
$fieldName = $cacheKeyInfo['fieldName'];
switch (true) {
case (isset($cacheKeyInfo['isNewObjectParameter'])):
case isset($cacheKeyInfo['isNewObjectParameter']):
$argIndex = $cacheKeyInfo['argIndex'];
$objIndex = $cacheKeyInfo['objIndex'];
$type = $cacheKeyInfo['type'];
@@ -345,7 +362,7 @@ abstract class AbstractHydrator
$rowData['newObjects'][$objIndex]['args'][$argIndex] = $value;
break;
case (isset($cacheKeyInfo['isScalar'])):
case isset($cacheKeyInfo['isScalar']):
$type = $cacheKeyInfo['type'];
$value = $type->convertToPHPValue($value, $this->_platform);
@@ -359,7 +376,8 @@ abstract class AbstractHydrator
// If there are field name collisions in the child class, then we need
// to only hydrate if we are looking at the correct discriminator value
if (isset($cacheKeyInfo['discriminatorColumn'], $data[$cacheKeyInfo['discriminatorColumn']])
if (
isset($cacheKeyInfo['discriminatorColumn'], $data[$cacheKeyInfo['discriminatorColumn']])
&& ! in_array((string) $data[$cacheKeyInfo['discriminatorColumn']], $cacheKeyInfo['discriminatorValues'], true)
) {
break;
@@ -377,9 +395,10 @@ abstract class AbstractHydrator
: $value;
if ($cacheKeyInfo['isIdentifier'] && $value !== null) {
$id[$dqlAlias] .= '|' . $value;
$id[$dqlAlias] .= '|' . $value;
$nonemptyComponents[$dqlAlias] = true;
}
break;
}
}
@@ -395,16 +414,16 @@ abstract class AbstractHydrator
* values according to their types. The resulting row has the same number
* of elements as before.
*
* @param array $data
*
* @return array The processed row.
* @psalm-param array<string, mixed> $data
* @psalm-return array<string, mixed> The processed row.
*/
protected function gatherScalarRowData(&$data)
{
$rowData = [];
foreach ($data as $key => $value) {
if (($cacheKeyInfo = $this->hydrateColumnInfo($key)) === null) {
$cacheKeyInfo = $this->hydrateColumnInfo($key);
if ($cacheKeyInfo === null) {
continue;
}
@@ -430,7 +449,7 @@ abstract class AbstractHydrator
*
* @param string $key Column name
*
* @return array|null
* @psalm-return array<string, mixed>|null
*/
protected function hydrateColumnInfo($key)
{
@@ -440,13 +459,13 @@ abstract class AbstractHydrator
switch (true) {
// NOTE: Most of the times it's a field mapping, so keep it first!!!
case (isset($this->_rsm->fieldMappings[$key])):
case isset($this->_rsm->fieldMappings[$key]):
$classMetadata = $this->getClassMetadata($this->_rsm->declaringClasses[$key]);
$fieldName = $this->_rsm->fieldMappings[$key];
$fieldMapping = $classMetadata->fieldMappings[$fieldName];
$ownerMap = $this->_rsm->columnOwnerMap[$key];
$columnInfo = [
'isIdentifier' => \in_array($fieldName, $classMetadata->identifier, true),
'isIdentifier' => in_array($fieldName, $classMetadata->identifier, true),
'fieldName' => $fieldName,
'type' => Type::getType($fieldMapping['type']),
'dqlAlias' => $ownerMap,
@@ -455,7 +474,7 @@ abstract class AbstractHydrator
// the current discriminator value must be saved in order to disambiguate fields hydration,
// should there be field name collisions
if ($classMetadata->parentClasses && isset($this->_rsm->discriminatorColumns[$ownerMap])) {
return $this->_cache[$key] = \array_merge(
return $this->_cache[$key] = array_merge(
$columnInfo,
[
'discriminatorColumn' => $this->_rsm->discriminatorColumns[$ownerMap],
@@ -467,7 +486,7 @@ abstract class AbstractHydrator
return $this->_cache[$key] = $columnInfo;
case (isset($this->_rsm->newObjectMappings[$key])):
case isset($this->_rsm->newObjectMappings[$key]):
// WARNING: A NEW object is also a scalar, so it must be declared before!
$mapping = $this->_rsm->newObjectMappings[$key];
@@ -478,7 +497,7 @@ abstract class AbstractHydrator
'type' => Type::getType($this->_rsm->typeMappings[$key]),
'argIndex' => $mapping['argIndex'],
'objIndex' => $mapping['objIndex'],
'class' => new \ReflectionClass($mapping['className']),
'class' => new ReflectionClass($mapping['className']),
];
case isset($this->_rsm->scalarMappings[$key], $this->_hints[LimitSubqueryWalker::FORCE_DBAL_TYPE_CONVERSION]):
@@ -487,14 +506,15 @@ abstract class AbstractHydrator
'type' => Type::getType($this->_rsm->typeMappings[$key]),
'dqlAlias' => '',
];
case (isset($this->_rsm->scalarMappings[$key])):
case isset($this->_rsm->scalarMappings[$key]):
return $this->_cache[$key] = [
'isScalar' => true,
'fieldName' => $this->_rsm->scalarMappings[$key],
'type' => Type::getType($this->_rsm->typeMappings[$key]),
];
case (isset($this->_rsm->metaMappings[$key])):
case isset($this->_rsm->metaMappings[$key]):
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$fieldName = $this->_rsm->metaMappings[$key];
$dqlAlias = $this->_rsm->columnOwnerMap[$key];
@@ -522,10 +542,10 @@ abstract class AbstractHydrator
/**
* @return string[]
*/
private function getDiscriminatorValues(ClassMetadata $classMetadata) : array
private function getDiscriminatorValues(ClassMetadata $classMetadata): array
{
$values = array_map(
function (string $subClass) : string {
function (string $subClass): string {
return (string) $this->getClassMetadata($subClass)->discriminatorValue;
},
$classMetadata->subClasses
@@ -541,11 +561,11 @@ abstract class AbstractHydrator
*
* @param string $className
*
* @return \Doctrine\ORM\Mapping\ClassMetadata
* @return ClassMetadata
*/
protected function getClassMetadata($className)
{
if ( ! isset($this->_metadataCache[$className])) {
if (! isset($this->_metadataCache[$className])) {
$this->_metadataCache[$className] = $this->_em->getClassMetadata($className);
}
@@ -555,9 +575,8 @@ abstract class AbstractHydrator
/**
* Register entity as managed in UnitOfWork.
*
* @param ClassMetadata $class
* @param object $entity
* @param array $data
* @param object $entity
* @param mixed[] $data
*
* @return void
*
@@ -578,7 +597,7 @@ abstract class AbstractHydrator
$id = [
$fieldName => isset($class->associationMappings[$fieldName])
? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
: $data[$fieldName]
: $data[$fieldName],
];
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,47 +20,37 @@
namespace Doctrine\ORM\Internal\Hydration;
use PDO;
use Doctrine\ORM\Mapping\ClassMetadata;
use PDO;
use function count;
use function end;
use function is_array;
use function key;
use function reset;
/**
* The ArrayHydrator produces a nested array "graph" that is often (not always)
* interchangeable with the corresponding object graph for read-only access.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
*/
class ArrayHydrator extends AbstractHydrator
{
/**
* @var array
*/
/** @var array<string,bool> */
private $_rootAliases = [];
/**
* @var bool
*/
/** @var bool */
private $_isSimpleQuery = false;
/**
* @var array
*/
/** @var mixed[] */
private $_identifierMap = [];
/**
* @var array
*/
/** @var mixed[] */
private $_resultPointers = [];
/**
* @var array
*/
/** @var array<string,string> */
private $_idTemplate = [];
/**
* @var int
*/
/** @var int */
private $_resultCounter = 0;
/**
@@ -96,9 +87,9 @@ class ArrayHydrator extends AbstractHydrator
protected function hydrateRowData(array $row, array &$result)
{
// 1) Initialize
$id = $this->_idTemplate; // initialize the id-memory
$id = $this->_idTemplate; // initialize the id-memory
$nonemptyComponents = [];
$rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
$rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
// 2) Now hydrate the data found in the current row.
foreach ($rowData['data'] as $dqlAlias => $data) {
@@ -111,7 +102,7 @@ class ArrayHydrator extends AbstractHydrator
$path = $parent . '.' . $dqlAlias;
// missing parent data, skipping as RIGHT JOIN hydration is not supported.
if ( ! isset($nonemptyComponents[$parent]) ) {
if (! isset($nonemptyComponents[$parent])) {
continue;
}
@@ -121,7 +112,7 @@ class ArrayHydrator extends AbstractHydrator
$first = reset($this->_resultPointers);
// TODO: Exception if $key === null ?
$baseElement =& $this->_resultPointers[$parent][key($first)];
} else if (isset($this->_resultPointers[$parent])) {
} elseif (isset($this->_resultPointers[$parent])) {
$baseElement =& $this->_resultPointers[$parent];
} else {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
@@ -134,10 +125,10 @@ class ArrayHydrator extends AbstractHydrator
$relation = $parentClass->associationMappings[$relationAlias];
// Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
if (! ($relation['type'] & ClassMetadata::TO_ONE)) {
$oneToOne = false;
if ( ! isset($baseElement[$relationAlias])) {
if (! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = [];
}
@@ -146,7 +137,7 @@ class ArrayHydrator extends AbstractHydrator
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
if ( ! $indexExists || ! $indexIsValid) {
if (! $indexExists || ! $indexIsValid) {
$element = $data;
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
@@ -164,11 +155,11 @@ class ArrayHydrator extends AbstractHydrator
$oneToOne = true;
if (
( ! isset($nonemptyComponents[$dqlAlias])) &&
! isset($nonemptyComponents[$dqlAlias]) &&
( ! isset($baseElement[$relationAlias]))
) {
$baseElement[$relationAlias] = null;
} else if ( ! isset($baseElement[$relationAlias])) {
} elseif (! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = $data;
}
}
@@ -182,10 +173,10 @@ class ArrayHydrator extends AbstractHydrator
// It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
// if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if (! isset($nonemptyComponents[$dqlAlias])) {
$result[] = $this->_rsm->isMixed
? [$entityKey => null]
: null;
@@ -203,18 +194,18 @@ class ArrayHydrator extends AbstractHydrator
: $data;
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
$resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
$result[$resultKey] = $element;
} else {
$resultKey = $this->_resultCounter;
$result[] = $element;
$result[] = $element;
++$this->_resultCounter;
}
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
} else {
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
$resultKey = $index;
}
@@ -222,13 +213,13 @@ class ArrayHydrator extends AbstractHydrator
}
}
if ( ! isset($resultKey)) {
if (! isset($resultKey)) {
$this->_resultCounter++;
}
// Append scalar values to mixed result sets
if (isset($rowData['scalars'])) {
if ( ! isset($resultKey)) {
if (! isset($resultKey)) {
// this only ever happens when no object is fetched (scalar result only)
$resultKey = isset($this->_rsm->indexByMap['scalars'])
? $row[$this->_rsm->indexByMap['scalars']]
@@ -242,18 +233,18 @@ class ArrayHydrator extends AbstractHydrator
// Append new object to mixed result sets
if (isset($rowData['newObjects'])) {
if ( ! isset($resultKey)) {
if (! isset($resultKey)) {
$resultKey = $this->_resultCounter - 1;
}
$scalarCount = (isset($rowData['scalars'])? count($rowData['scalars']): 0);
$scalarCount = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0);
foreach ($rowData['newObjects'] as $objIndex => $newObject) {
$class = $newObject['class'];
$args = $newObject['args'];
$obj = $class->newInstanceArgs($args);
$class = $newObject['class'];
$args = $newObject['args'];
$obj = $class->newInstanceArgs($args);
if (count($args) == $scalarCount || ($scalarCount == 0 && count($rowData['newObjects']) == 1)) {
if (count($args) === $scalarCount || ($scalarCount === 0 && count($rowData['newObjects']) === 1)) {
$result[$resultKey] = $obj;
continue;
@@ -268,10 +259,10 @@ class ArrayHydrator extends AbstractHydrator
* Updates the result pointer for an Entity. The result pointers point to the
* last seen instance of each Entity type. This is used for graph construction.
*
* @param array $coll The element.
* @param boolean|integer $index Index of the element in the collection.
* @param string $dqlAlias
* @param boolean $oneToOne Whether it is a single-valued association or not.
* @param mixed[] $coll The element.
* @param bool|int $index Index of the element in the collection.
* @param string $dqlAlias
* @param bool $oneToOne Whether it is a single-valued association or not.
*
* @return void
*/
@@ -295,7 +286,7 @@ class ArrayHydrator extends AbstractHydrator
return;
}
if ( ! $coll) {
if (! $coll) {
return;
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,14 +20,19 @@
namespace Doctrine\ORM\Internal\Hydration;
class HydrationException extends \Doctrine\ORM\ORMException
use Doctrine\ORM\ORMException;
use function implode;
use function sprintf;
class HydrationException extends ORMException
{
/**
* @return HydrationException
*/
public static function nonUniqueResult()
{
return new self("The result returned by the query was not unique.");
return new self('The result returned by the query was not unique.');
}
/**
@@ -37,8 +43,12 @@ class HydrationException extends \Doctrine\ORM\ORMException
*/
public static function parentObjectOfRelationNotFound($alias, $parentAlias)
{
return new self("The parent object of entity result with alias '$alias' was not found."
. " The parent alias is '$parentAlias'.");
return new self(sprintf(
"The parent object of entity result with alias '%s' was not found."
. " The parent alias is '%s'.",
$alias,
$parentAlias
));
}
/**
@@ -48,16 +58,13 @@ class HydrationException extends \Doctrine\ORM\ORMException
*/
public static function emptyDiscriminatorValue($dqlAlias)
{
return new self("The DQL alias '" . $dqlAlias . "' contains an entity ".
"of an inheritance hierarchy with an empty discriminator value. This means " .
"that the database contains inconsistent data with an empty " .
"discriminator value in a table row."
);
return new self("The DQL alias '" . $dqlAlias . "' contains an entity " .
'of an inheritance hierarchy with an empty discriminator value. This means ' .
'that the database contains inconsistent data with an empty ' .
'discriminator value in a table row.');
}
/**
* @since 2.3
*
* @param string $entityName
* @param string $discrColumnName
* @param string $dqlAlias
@@ -68,13 +75,13 @@ class HydrationException extends \Doctrine\ORM\ORMException
{
return new self(sprintf(
'The discriminator column "%s" is missing for "%s" using the DQL alias "%s".',
$discrColumnName, $entityName, $dqlAlias
$discrColumnName,
$entityName,
$dqlAlias
));
}
/**
* @since 2.3
*
* @param string $entityName
* @param string $discrColumnName
* @param string $dqlAlias
@@ -85,21 +92,25 @@ class HydrationException extends \Doctrine\ORM\ORMException
{
return new self(sprintf(
'The meta mapping for the discriminator column "%s" is missing for "%s" using the DQL alias "%s".',
$discrColumnName, $entityName, $dqlAlias
$discrColumnName,
$entityName,
$dqlAlias
));
}
/**
* @param string $discrValue
* @param array $discrMap
*
* @return HydrationException
*
* @psalm-param array<string, string> $discrMap
*/
public static function invalidDiscriminatorValue($discrValue, $discrMap)
{
return new self(sprintf(
'The discriminator value "%s" is invalid. It must be one of "%s".',
$discrValue, implode('", "', $discrMap)
$discrValue,
implode('", "', $discrMap)
));
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,36 +20,30 @@
namespace Doctrine\ORM\Internal\Hydration;
use Iterator;
/**
* Represents a result structure that can be iterated over, hydrating row-by-row
* during the iteration. An IterableResult is obtained by AbstractHydrator#iterate().
*
* @deprecated
*/
class IterableResult implements \Iterator
class IterableResult implements Iterator
{
/**
* @var \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
/** @var AbstractHydrator */
private $_hydrator;
/**
* @var boolean
*/
/** @var bool */
private $_rewinded = false;
/**
* @var integer
*/
/** @var int */
private $_key = -1;
/**
* @var object|null
*/
/** @var object|null */
private $_current = null;
/**
* @param \Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator
* @param AbstractHydrator $hydrator
*/
public function __construct($hydrator)
{
@@ -62,10 +57,10 @@ class IterableResult implements \Iterator
*/
public function rewind()
{
if ($this->_rewinded == true) {
throw new HydrationException("Can only iterate a Result once.");
if ($this->_rewinded === true) {
throw new HydrationException('Can only iterate a Result once.');
} else {
$this->_current = $this->next();
$this->_current = $this->next();
$this->_rewinded = true;
}
}
@@ -73,7 +68,7 @@ class IterableResult implements \Iterator
/**
* Gets the next set of results.
*
* @return array|false
* @return mixed[]|false
*/
public function next()
{
@@ -104,6 +99,6 @@ class IterableResult implements \Iterator
*/
public function valid()
{
return ($this->_current!=false);
return $this->_current !== false;
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,59 +20,48 @@
namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\ORM\UnitOfWork;
use PDO;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Proxy\Proxy;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use PDO;
use function array_fill_keys;
use function array_keys;
use function count;
use function is_array;
use function key;
use function ltrim;
use function spl_object_hash;
/**
* The ObjectHydrator constructs an object graph out of an SQL result set.
*
* Internal note: Highly performance-sensitive code.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class ObjectHydrator extends AbstractHydrator
{
/**
* @var array
*/
/** @var mixed[] */
private $identifierMap = [];
/**
* @var array
*/
/** @var mixed[] */
private $resultPointers = [];
/**
* @var array
*/
/** @var mixed[] */
private $idTemplate = [];
/**
* @var integer
*/
/** @var int */
private $resultCounter = 0;
/**
* @var array
*/
/** @var mixed[] */
private $rootAliases = [];
/**
* @var array
*/
/** @var mixed[] */
private $initializedCollections = [];
/**
* @var array
*/
/** @var mixed[] */
private $existingCollections = [];
/**
@@ -79,7 +69,7 @@ class ObjectHydrator extends AbstractHydrator
*/
protected function prepare()
{
if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
if (! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
$this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true;
}
@@ -89,13 +79,13 @@ class ObjectHydrator extends AbstractHydrator
// Remember which associations are "fetch joined", so that we know where to inject
// collection stubs or proxies and where not.
if ( ! isset($this->_rsm->relationMap[$dqlAlias])) {
if (! isset($this->_rsm->relationMap[$dqlAlias])) {
continue;
}
$parent = $this->_rsm->parentAliasMap[$dqlAlias];
if ( ! isset($this->_rsm->aliasMap[$parent])) {
if (! isset($this->_rsm->aliasMap[$parent])) {
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $parent);
}
@@ -121,7 +111,7 @@ class ObjectHydrator extends AbstractHydrator
$class = $this->getClassMetadata($className);
$inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) {
if (! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) {
continue;
}
@@ -135,14 +125,14 @@ class ObjectHydrator extends AbstractHydrator
*/
protected function cleanup()
{
$eagerLoad = (isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) && $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] == true;
$eagerLoad = isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD]) && $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] === true;
parent::cleanup();
$this->identifierMap =
$this->identifierMap =
$this->initializedCollections =
$this->existingCollections =
$this->resultPointers = [];
$this->existingCollections =
$this->resultPointers = [];
if ($eagerLoad) {
$this->_uow->triggerEagerLoads();
@@ -151,6 +141,14 @@ class ObjectHydrator extends AbstractHydrator
$this->_uow->hydrationComplete();
}
protected function cleanupAfterRowIteration(): void
{
$this->identifierMap =
$this->initializedCollections =
$this->existingCollections =
$this->resultPointers = [];
}
/**
* {@inheritdoc}
*/
@@ -178,7 +176,7 @@ class ObjectHydrator extends AbstractHydrator
* @param string $fieldName The name of the field on the entity that holds the collection.
* @param string $parentDqlAlias Alias of the parent fetch joining this collection.
*
* @return \Doctrine\ORM\PersistentCollection
* @return PersistentCollection
*/
private function initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias)
{
@@ -190,9 +188,11 @@ class ObjectHydrator extends AbstractHydrator
$value = new ArrayCollection((array) $value);
}
if ( ! $value instanceof PersistentCollection) {
if (! $value instanceof PersistentCollection) {
$value = new PersistentCollection(
$this->_em, $this->_metadataCache[$relation['targetEntity']], $value
$this->_em,
$this->_metadataCache[$relation['targetEntity']],
$value
);
$value->setOwner($entity, $relation);
@@ -200,7 +200,7 @@ class ObjectHydrator extends AbstractHydrator
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
$this->initializedCollections[$oid . $fieldName] = $value;
} else if (
} elseif (
isset($this->_hints[Query::HINT_REFRESH]) ||
isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) &&
! $value->isInitialized()
@@ -222,12 +222,13 @@ class ObjectHydrator extends AbstractHydrator
/**
* Gets an entity instance.
*
* @param array $data The instance data.
* @param string $dqlAlias The DQL alias of the entity's class.
*
* @return object The entity.
*
* @throws HydrationException
*
* @psalm-param array<string, mixed> $data The instance data.
*/
private function getEntity(array $data, $dqlAlias)
{
@@ -236,24 +237,24 @@ class ObjectHydrator extends AbstractHydrator
if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
$fieldName = $this->_rsm->discriminatorColumns[$dqlAlias];
if ( ! isset($this->_rsm->metaMappings[$fieldName])) {
if (! isset($this->_rsm->metaMappings[$fieldName])) {
throw HydrationException::missingDiscriminatorMetaMappingColumn($className, $fieldName, $dqlAlias);
}
$discrColumn = $this->_rsm->metaMappings[$fieldName];
if ( ! isset($data[$discrColumn])) {
if (! isset($data[$discrColumn])) {
throw HydrationException::missingDiscriminatorColumn($className, $discrColumn, $dqlAlias);
}
if ($data[$discrColumn] === "") {
if ($data[$discrColumn] === '') {
throw HydrationException::emptyDiscriminatorValue($dqlAlias);
}
$discrMap = $this->_metadataCache[$className]->discriminatorMap;
$discrMap = $this->_metadataCache[$className]->discriminatorMap;
$discriminatorValue = (string) $data[$discrColumn];
if ( ! isset($discrMap[$discriminatorValue])) {
if (! isset($discrMap[$discriminatorValue])) {
throw HydrationException::invalidDiscriminatorValue($discriminatorValue, array_keys($discrMap));
}
@@ -273,16 +274,16 @@ class ObjectHydrator extends AbstractHydrator
/**
* @param string $className
* @param array $data
*
* @return mixed
*
* @psalm-param array<string, mixed> $data
*/
private function getEntityFromIdentityMap($className, array $data)
{
// TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
$class = $this->_metadataCache[$className];
/* @var $class ClassMetadata */
if ($class->isIdentifierComposite) {
$idHash = '';
@@ -293,7 +294,7 @@ class ObjectHydrator extends AbstractHydrator
}
return $this->_uow->tryGetByIdHash(ltrim($idHash), $class->rootEntityName);
} else if (isset($class->associationMappings[$class->identifier[0]])) {
} elseif (isset($class->associationMappings[$class->identifier[0]])) {
return $this->_uow->tryGetByIdHash($data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']], $class->rootEntityName);
}
@@ -317,15 +318,15 @@ class ObjectHydrator extends AbstractHydrator
* level of the hydrated result. A typical example are the objects of the type
* specified by the FROM clause in a DQL query.
*
* @param array $row The data of the row to process.
* @param array $result The result array to fill.
* @param mixed[] $row The data of the row to process.
* @param mixed[] $result The result array to fill.
*
* @return void
*/
protected function hydrateRowData(array $row, array &$result)
{
// Initialize
$id = $this->idTemplate; // initialize the id-memory
$id = $this->idTemplate; // initialize the id-memory
$nonemptyComponents = [];
// Split the row data into chunks of class data.
$rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
@@ -346,28 +347,28 @@ class ObjectHydrator extends AbstractHydrator
$path = $parentAlias . '.' . $dqlAlias;
// We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs
if ( ! isset($nonemptyComponents[$parentAlias])) {
if (! isset($nonemptyComponents[$parentAlias])) {
// TODO: Add special case code where we hydrate the right join objects into identity map at least
continue;
}
$parentClass = $this->_metadataCache[$this->_rsm->aliasMap[$parentAlias]];
$relationField = $this->_rsm->relationMap[$dqlAlias];
$relation = $parentClass->associationMappings[$relationField];
$reflField = $parentClass->reflFields[$relationField];
$parentClass = $this->_metadataCache[$this->_rsm->aliasMap[$parentAlias]];
$relationField = $this->_rsm->relationMap[$dqlAlias];
$relation = $parentClass->associationMappings[$relationField];
$reflField = $parentClass->reflFields[$relationField];
// Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->rootAliases[$parentAlias])) {
$objectClass = $this->resultPointers[$parentAlias];
$objectClass = $this->resultPointers[$parentAlias];
$parentObject = $objectClass[key($objectClass)];
} else if (isset($this->resultPointers[$parentAlias])) {
} elseif (isset($this->resultPointers[$parentAlias])) {
$parentObject = $this->resultPointers[$parentAlias];
} else {
// Parent object of relation not found, mark as not-fetched again
$element = $this->getEntity($data, $dqlAlias);
// Update result pointer and provide initial fetch data for parent
$this->resultPointers[$dqlAlias] = $element;
$this->resultPointers[$dqlAlias] = $element;
$rowData['data'][$parentAlias][$relationField] = $element;
// Mark as not-fetched again
@@ -378,7 +379,7 @@ class ObjectHydrator extends AbstractHydrator
$oid = spl_object_hash($parentObject);
// Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
if (! ($relation['type'] & ClassMetadata::TO_ONE)) {
// PATH A: Collection-valued association
$reflFieldValue = $reflField->getValue($parentObject);
@@ -386,18 +387,19 @@ class ObjectHydrator extends AbstractHydrator
$collKey = $oid . $relationField;
if (isset($this->initializedCollections[$collKey])) {
$reflFieldValue = $this->initializedCollections[$collKey];
} else if ( ! isset($this->existingCollections[$collKey])) {
} elseif (! isset($this->existingCollections[$collKey])) {
$reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
}
$indexExists = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
$index = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false;
$indexExists = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
$index = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false;
if ( ! $indexExists || ! $indexIsValid) {
if (! $indexExists || ! $indexIsValid) {
if (isset($this->existingCollections[$collKey])) {
// Collection exists, only look for the element in the identity map.
if ($element = $this->getEntityFromIdentityMap($entityName, $data)) {
$element = $this->getEntityFromIdentityMap($entityName, $data);
if ($element) {
$this->resultPointers[$dqlAlias] = $element;
} else {
unset($this->resultPointers[$dqlAlias]);
@@ -414,6 +416,7 @@ class ObjectHydrator extends AbstractHydrator
$reflFieldValue->last();
$this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
}
// Update result pointer
$this->resultPointers[$dqlAlias] = $element;
}
@@ -421,17 +424,16 @@ class ObjectHydrator extends AbstractHydrator
// Update result pointer
$this->resultPointers[$dqlAlias] = $reflFieldValue[$index];
}
} else if ( ! $reflFieldValue) {
} elseif (! $reflFieldValue) {
$this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
} else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) {
} elseif ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) {
$reflFieldValue->setInitialized(true);
}
} else {
// PATH B: Single-valued association
$reflFieldValue = $reflField->getValue($parentObject);
if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && !$reflFieldValue->__isInitialized__)) {
if (! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && ! $reflFieldValue->__isInitialized__)) {
// we only need to take action if this value is null,
// we refresh the entity or its an uninitialized proxy.
if (isset($nonemptyComponents[$dqlAlias])) {
@@ -449,7 +451,7 @@ class ObjectHydrator extends AbstractHydrator
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject);
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject);
}
} else if ($parentClass === $targetClass && $relation['mappedBy']) {
} elseif ($parentClass === $targetClass && $relation['mappedBy']) {
// Special case: bi-directional self-referencing one-one on the same class
$targetClass->reflFields[$relationField]->setValue($element, $parentObject);
}
@@ -458,6 +460,7 @@ class ObjectHydrator extends AbstractHydrator
$targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject);
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject);
}
// Update result pointer
$this->resultPointers[$dqlAlias] = $element;
} else {
@@ -473,22 +476,23 @@ class ObjectHydrator extends AbstractHydrator
} else {
// PATH C: Its a root result element
$this->rootAliases[$dqlAlias] = true; // Mark as root alias
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
// if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if (! isset($nonemptyComponents[$dqlAlias])) {
if ($this->_rsm->isMixed) {
$result[] = [$entityKey => null];
} else {
$result[] = null;
}
$resultKey = $this->resultCounter;
++$this->resultCounter;
continue;
}
// check for existing result from the iterations before
if ( ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) {
if (! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->getEntity($data, $dqlAlias);
if ($this->_rsm->isMixed) {
@@ -518,12 +522,11 @@ class ObjectHydrator extends AbstractHydrator
// Update result pointer
$this->resultPointers[$dqlAlias] = $element;
} else {
// Update result pointer
$index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]];
$index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]];
$this->resultPointers[$dqlAlias] = $result[$index];
$resultKey = $index;
$resultKey = $index;
}
}
@@ -532,14 +535,14 @@ class ObjectHydrator extends AbstractHydrator
}
}
if ( ! isset($resultKey) ) {
if (! isset($resultKey)) {
$this->resultCounter++;
}
// Append scalar values to mixed result sets
if (isset($rowData['scalars'])) {
if ( ! isset($resultKey) ) {
$resultKey = (isset($this->_rsm->indexByMap['scalars']))
if (! isset($resultKey)) {
$resultKey = isset($this->_rsm->indexByMap['scalars'])
? $row[$this->_rsm->indexByMap['scalars']]
: $this->resultCounter - 1;
}
@@ -551,19 +554,18 @@ class ObjectHydrator extends AbstractHydrator
// Append new object to mixed result sets
if (isset($rowData['newObjects'])) {
if ( ! isset($resultKey) ) {
if (! isset($resultKey)) {
$resultKey = $this->resultCounter - 1;
}
$scalarCount = (isset($rowData['scalars'])? count($rowData['scalars']): 0);
$scalarCount = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0);
foreach ($rowData['newObjects'] as $objIndex => $newObject) {
$class = $newObject['class'];
$args = $newObject['args'];
$obj = $class->newInstanceArgs($args);
$class = $newObject['class'];
$args = $newObject['args'];
$obj = $class->newInstanceArgs($args);
if ($scalarCount == 0 && count($rowData['newObjects']) == 1 ) {
if ($scalarCount === 0 && count($rowData['newObjects']) === 1) {
$result[$resultKey] = $obj;
continue;
@@ -586,7 +588,7 @@ class ObjectHydrator extends AbstractHydrator
{
parent::onClear($eventArgs);
$aliases = array_keys($this->identifierMap);
$aliases = array_keys($this->identifierMap);
$this->identifierMap = array_fill_keys($aliases, []);
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,14 +20,12 @@
namespace Doctrine\ORM\Internal\Hydration;
use PDO;
/**
* Hydrator that produces flat, rectangular results of scalar data.
* The created result is almost the same as a regular SQL result set, except
* that column names are mapped to field names and data type conversions take place.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/
class ScalarHydrator extends AbstractHydrator
{
@@ -37,7 +36,7 @@ class ScalarHydrator extends AbstractHydrator
{
$result = [];
while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) {
while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->hydrateRowData($data, $result);
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,17 +20,23 @@
namespace Doctrine\ORM\Internal\Hydration;
use PDO;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query;
use Exception;
use PDO;
use RuntimeException;
use function array_keys;
use function array_search;
use function count;
use function in_array;
use function key;
use function reset;
use function sprintf;
class SimpleObjectHydrator extends AbstractHydrator
{
/**
* @var ClassMetadata
*/
/** @var ClassMetadata */
private $class;
/**
@@ -38,11 +45,11 @@ class SimpleObjectHydrator extends AbstractHydrator
protected function prepare()
{
if (count($this->_rsm->aliasMap) !== 1) {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result.");
throw new RuntimeException('Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result.');
}
if ($this->_rsm->scalarMappings) {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings.");
throw new RuntimeException('Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings.');
}
$this->class = $this->getClassMetadata(reset($this->_rsm->aliasMap));
@@ -89,7 +96,8 @@ class SimpleObjectHydrator extends AbstractHydrator
$discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']);
// Find mapped discriminator column from the result set.
if ($metaMappingDiscrColumnName = array_search($discrColumnName, $this->_rsm->metaMappings)) {
$metaMappingDiscrColumnName = array_search($discrColumnName, $this->_rsm->metaMappings);
if ($metaMappingDiscrColumnName) {
$discrColumnName = $metaMappingDiscrColumnName;
}
@@ -116,17 +124,17 @@ class SimpleObjectHydrator extends AbstractHydrator
foreach ($row as $column => $value) {
// An ObjectHydrator should be used instead of SimpleObjectHydrator
if (isset($this->_rsm->relationMap[$column])) {
throw new \Exception(sprintf('Unable to retrieve association information for column "%s"', $column));
throw new Exception(sprintf('Unable to retrieve association information for column "%s"', $column));
}
$cacheKeyInfo = $this->hydrateColumnInfo($column);
if ( ! $cacheKeyInfo) {
if (! $cacheKeyInfo) {
continue;
}
// Check if value is null before conversion (because some types convert null to something else)
$valueIsNull = null === $value;
$valueIsNull = $value === null;
// Convert field to a valid PHP value
if (isset($cacheKeyInfo['type'])) {
@@ -137,7 +145,7 @@ class SimpleObjectHydrator extends AbstractHydrator
$fieldName = $cacheKeyInfo['fieldName'];
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
if ( ! isset($data[$fieldName]) || ! $valueIsNull) {
if (! isset($data[$fieldName]) || ! $valueIsNull) {
// If we have inheritance in resultset, make sure the field belongs to the correct class
if (isset($cacheKeyInfo['discriminatorValues']) && ! in_array((string) $discrColumnValue, $cacheKeyInfo['discriminatorValues'], true)) {
continue;

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -19,15 +20,16 @@
namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use PDO;
use function array_shift;
use function count;
use function key;
/**
* Hydrator that hydrates a single scalar value from the result set.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/
class SingleScalarHydrator extends AbstractHydrator
{
@@ -36,7 +38,7 @@ class SingleScalarHydrator extends AbstractHydrator
*/
protected function hydrateAllData()
{
$data = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC);
$data = $this->_stmt->fetchAll(PDO::FETCH_ASSOC);
$numRows = count($data);
if ($numRows === 0) {
@@ -46,7 +48,7 @@ class SingleScalarHydrator extends AbstractHydrator
if ($numRows > 1) {
throw new NonUniqueResultException('The query returned multiple rows. Change the query or use a different result function like getScalarResult().');
}
if (count($data[key($data)]) > 1) {
throw new NonUniqueResultException('The query returned a row containing multiple columns. Change the query or use a different result function like getScalarResult().');
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -28,32 +29,20 @@ use Doctrine\ORM\Mapping\ClassMetadata;
/**
* Class, which can handle completion of hydration cycle and produce some of tasks.
* In current implementation triggers deferred postLoad event.
*
* @author Artur Eshenbrener <strate@yandex.ru>
* @since 2.5
*/
final class HydrationCompleteHandler
{
/**
* @var ListenersInvoker
*/
/** @var ListenersInvoker */
private $listenersInvoker;
/**
* @var EntityManagerInterface
*/
/** @var EntityManagerInterface */
private $em;
/**
* @var array[]
*/
/** @var mixed[][] */
private $deferredPostLoadInvocations = [];
/**
* Constructor for this object
*
* @param ListenersInvoker $listenersInvoker
* @param EntityManagerInterface $em
*/
public function __construct(ListenersInvoker $listenersInvoker, EntityManagerInterface $em)
{
@@ -64,8 +53,7 @@ final class HydrationCompleteHandler
/**
* Method schedules invoking of postLoad entity to the very end of current hydration cycle.
*
* @param ClassMetadata $class
* @param object $entity
* @param object $entity
*/
public function deferPostLoadInvoking(ClassMetadata $class, $entity)
{

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