Compare commits

..

985 Commits

Author SHA1 Message Date
Grégoire Paris
1f7a7a58ae Merge pull request #8506 from greg0ire/drop-branch-alias
Drop branch alias
2021-02-23 09:17:20 +01:00
Grégoire Paris
51063f502f Drop branch alias
It should not conflict with the future, brand new 3.0.x.
2021-02-23 09:10:11 +01:00
Menno Holtkamp
e8c4d7b177 Add Doctrine\ORM\Query\Expr::mod() (#6747) 2020-09-22 15:21:21 +02:00
Alexander Berl
2a98a98cd3 Replace "Blacklist" with "Banlist" in join example (#8175) 2020-06-09 07:39:41 +02:00
Grégoire Paris
01de90c141 Use correct target branch info in PR templates (#8171)
The current branch for bugfixes is 2.7, and new features should go on
2.8.x, not master.
2020-06-04 22:19:41 +02:00
Guilliam Xavier
dddd899322 Show full XML filename for dataset in test failure report (#7346) 2020-05-31 19:03:45 +02:00
Guilherme Blanco
72bc09926d Updated persisters 2020-05-10 23:43:54 -04:00
Guilherme Blanco
49dd4c81d0 Updated persister->update() 2020-05-10 15:39:33 -04:00
Guilherme Blanco
3460805efe Disable embedded temporarily 2020-05-10 11:40:43 -04:00
Guilherme Blanco
722f4a86b3 More work towards embeddables 2020-05-09 16:04:35 -04:00
Guilherme Blanco
1af6270095 Initial work on adding support back to embeddables on ORM 3 2020-05-05 10:02:40 -04:00
Guilherme Blanco
9a8e1fb477 phpstan 2020-05-04 17:27:09 -04:00
Guilherme Blanco
426ddb87a5 PHPCS 2020-05-04 17:25:46 -04:00
Guilherme Blanco
e7d2ac5277 PHP 7.4 2020-05-04 16:35:32 -04:00
Guilherme Blanco
d1302eabad Moving Github actions to PHP 7.3, since DBAL requires 7.3 as minimum dependency 2020-05-04 15:23:33 -04:00
Guilherme Blanco
5592535a56 Introduced LazyDataType support. 2020-05-04 14:18:58 -04:00
Guilherme Blanco
a3e53b6a23 Updated composer.json and made unit tests run, not necessarily pass... (#7921)
* Updated composer.json and made unit tests run, not necessarily pass...

* Addressed code review comments. Fixed BC break in AbstractPlatform now relying on column name to be provided when generating the column sql definition. Switched a skipped embedded test to its proper group

* Several test fixes

* Update doctrine/dbal dep to concrete commit

dev-snapshot/develop/2018-11-26 is not found by composer

* Update Tidelift item to fix auto-verification

* Add Tidelift enterprise marketing language

* Adapt contributing guide to new target versions

* Set link to contribution guide for target branches

* Run Static analysis and coding standards in Github Actions (#8049)

* Run Static analysis and coding standards in Github Actions

* Remove static analysis from Travis.

* Run on PRs and Push to master branch

* Add name suggestions by Gregoire

* Downgrade version to lowest supported.

* Suggestions by Gabriel, Luis and Claudio

* Update .github/workflows/ci.yml

Co-Authored-By: Gabriel Caruso <carusogabriel34@gmail.com>

Co-authored-by: Gabriel Caruso <carusogabriel34@gmail.com>

* Fix: Give workflow a name (#8060)

* Updated composer.json and made unit tests run, not necessarily pass...

* Addressed code review comments. Fixed BC break in AbstractPlatform now relying on column name to be provided when generating the column sql definition. Switched a skipped embedded test to its proper group

* Several test fixes

* Several fixes to unit tests and codebase to make it run on DBAL 3.0.x

* Unit test suite now passes

* Test for 7.4

* Remove 7.2

* Finished fixing phpunit, phpstan and phpcs

* Make scrutinizer run with PHP 7.4

* Attempt to improve travis

* Take 2

* More work on travis

* Take 4

* xdebug

* xdebug

Co-authored-by: Simon Podlipsky <simon@podlipsky.net>
Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
Co-authored-by: Blaine Bublitz <blaine@tidelift.com>
Co-authored-by: Jonathan H. Wage <jonwage@gmail.com>
Co-authored-by: Claudio Zizza <claudio@budgegeria.de>
Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
Co-authored-by: Gabriel Caruso <carusogabriel34@gmail.com>
2020-05-01 14:17:07 -04:00
Benjamin Eberlei
8c259ea5cb Fix: Give workflow a name (#8060) 2020-03-04 23:32:11 +01:00
Benjamin Eberlei
7de263c740 Run Static analysis and coding standards in Github Actions (#8049)
* Run Static analysis and coding standards in Github Actions

* Remove static analysis from Travis.

* Run on PRs and Push to master branch

* Add name suggestions by Gregoire

* Downgrade version to lowest supported.

* Suggestions by Gabriel, Luis and Claudio

* Update .github/workflows/ci.yml

Co-Authored-By: Gabriel Caruso <carusogabriel34@gmail.com>

Co-authored-by: Gabriel Caruso <carusogabriel34@gmail.com>
2020-03-02 22:29:15 +01:00
Claudio Zizza
c7faafa192 Merge pull request #8021 from SenseException/contribute-version
Update target branches of CONTRIBUTING.md
2020-02-17 11:11:59 +01:00
Claudio Zizza
f9a01ae9c4 Set link to contribution guide for target branches 2020-02-14 23:16:20 +01:00
Claudio Zizza
c673a3daf2 Adapt contributing guide to new target versions 2020-02-14 21:33:29 +01:00
Jonathan H. Wage
e77091399a Merge pull request #8001 from phated/patch-2
Add Tidelift enterprise marketing language
2020-01-23 13:41:39 -06:00
Jonathan H. Wage
5b3f9ee3c7 Merge pull request #8000 from phated/patch-1
Update Tidelift item to fix auto-verification
2020-01-23 13:40:58 -06:00
Blaine Bublitz
eae20efb24 Add Tidelift enterprise marketing language 2020-01-23 11:19:36 -07:00
Blaine Bublitz
8d0c6a8746 Update Tidelift item to fix auto-verification 2020-01-23 10:58:45 -07:00
Benjamin Eberlei
ff65cae170 Merge pull request #7938 from simPod/fix-dbal-dep
Update doctrine/dbal dep to concrete commit
2020-01-07 23:54:53 +01:00
Simon Podlipsky
662a71b08a Update doctrine/dbal dep to concrete commit
dev-snapshot/develop/2018-11-26 is not found by composer
2019-12-01 12:04:16 +01:00
Luís Cobucci
b06efe3408 Merge pull request #7914 from lcobucci/update-branch-information
Add branch 2.8 to docs
2019-11-22 14:00:49 +01:00
Luís Cobucci
f12d24cce7 Add branch 2.8 to docs 2019-11-21 13:27:32 +01:00
Guilherme Blanco
6c7bb5a273 Merge pull request #7828 from andrews05/andrews05-jti-properties
Fix for TransientMetadata on joined subclass
2019-11-15 00:14:58 -05:00
Claudio Zizza
027344969e Merge pull request #7647 from SenseException/entity-readOnly
Add readOnly attribute to annotation example
2019-11-10 23:09:13 +01:00
Claudio Zizza
9190b73d72 Merge pull request #7613 from SenseException/dql-select-subheadlines
Add headlines for direct references to DQL examples
2019-11-06 21:38:35 +01:00
Grégoire Paris
df7e000ab0 Merge pull request #7669 from iliyaZelenko/patch-1
fix: information about units in DATE_ADD, DATE_SUB
2019-10-31 17:52:42 +01:00
Marco Pivetta
533d09467f Merge pull request #7883 from Pictor13/patch-1
Fix bullet list not rendering correctly on Github
2019-10-31 15:18:17 +01:00
Igor Pellegrini
c9fc364c2c Fix bullet list not rendering correctly on Github
As *rts*, if reading the tutorial on the Github repository, the items are not seen as elements of a bullet list, hence they are not rendered and they are collapsed in a single line of text.

Fix won't affect how it renders on [doctrine-project.org](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html).
2019-10-31 14:30:36 +01:00
andrews05
f2624e953e Skip TransientMetadata properties while inserting
This prevents an exception being thrown if a non-ORM property is declared on a joined subclass.
Fixes #7824.
2019-10-04 09:47:11 +13:00
mikeSimonson
d528f70568 Merge pull request #7840 from peter-gribanov/reflFieldValue
Remove not used variable $reflFieldValue in ObjectHydrator
2019-09-30 10:30:06 +02:00
Peter Gribanov
ccbe5ded06 remove not used variable $reflFieldValue in ObjectHydrator 2019-09-30 11:05:59 +03:00
Guilherme Blanco
21bc805dc9 Implemented and adopted ClassMetadataBuilder in both Annotation and XML drivers. Implemented and adopted PropertyMetadataBuilder in Annotation driver. 2019-08-21 22:42:00 -04:00
Guilherme Blanco
b07393ece8 Cleanup after AssociationBuilders and prep for ClassMetadataBuilder 2019-08-19 00:54:01 -04:00
Guilherme Blanco
1488955ee3 Merge pull request #7799 from doctrine/m2m-association-builder
Many to many association builder
2019-08-18 22:04:10 -04:00
Guilherme Blanco
5afceb9f10 Implemented AssociationBuilders 2019-08-18 22:03:18 -04:00
Marco Pivetta
4bfa22353a Merge pull request #7782 from doctrine/bump-phpstan
Update to PHPStan v0.11
2019-07-28 19:17:35 +02:00
Marco Pivetta
8c125b1065 Merge pull request #7776 from doctrine/chore/remove-hack-this-scope
Remove hack to access class scope inside closures
2019-07-28 15:03:06 +02:00
Gabriel Caruso
0187cf53a2 Remove hack to access class scope inside closures
This is possible since 5.4.
2019-07-28 13:27:09 +02:00
Gabriel Caruso
05d54788fd Update to PHPStan v0.11 2019-07-28 13:20:23 +02:00
Luís Cobucci
a896421be2 Merge pull request #7781 from tybulewicz/patch-1
Missing method `getUtc` added
2019-07-26 18:15:14 +02:00
Tomasz Tybulewicz
4993c4ae9b Missing method getUtc added 2019-07-26 10:51:47 +02:00
Luís Cobucci
376c5e1628 Merge pull request #7777 from doctrine/chore/fix-phpcs-errors
Fix Code Style issues in master branch
2019-07-26 10:04:48 +02:00
Guilherme Blanco
8601d9e525 Fixing build 2019-07-23 23:15:57 -04:00
Gabriel Caruso
4e988bb103 Fix Code Style issues in master branch 2019-07-21 21:35:59 +02:00
Guilherme Blanco
d606941284 Introduced ValueGeneratorMetadataBuilder 2019-07-19 00:19:46 -04:00
Guilherme Blanco
fa7802b1c7 Introduced FieldMetadataBuilder 2019-07-10 00:37:58 -04:00
Luís Cobucci
4f8027aa62 Merge pull request #7771 from Arman-Hosseini/patch-1
Remove year from license
2019-07-09 16:59:51 +02:00
Arman
8220bb3ced Update license year 2019-07-09 16:46:31 +02:00
Guilherme Blanco
57b72b1390 Merge pull request #7772 from lcobucci/fix-build
Fix build
2019-07-09 09:53:05 -04:00
Luís Cobucci
dabbfc70a9 Fix static analysis issues 2019-07-09 14:34:20 +02:00
Luís Cobucci
9906068426 Fix coding standard violations 2019-07-09 10:37:31 +02:00
Guilherme Blanco
b115284932 Introduced JoinTableMetadataBuilder 2019-07-04 00:51:09 -04:00
Guilherme Blanco
6971e74b4a Introduced JoinColumnMetadataBuilder. 2019-07-03 00:50:59 -04:00
Guilherme Blanco
99b7d1e9d4 Eliminated the differentiation between class declared versus inheritance declared 2019-07-02 23:09:33 -04:00
Guilherme Blanco
906c1465c2 Removed completeRuntimeMetadata() method from ClassMetadataFactory and moved to the appropriate location 2019-07-02 22:47:41 -04:00
Guilherme Blanco
04762015c6 Decoupled DiscriminatorColumnMetadataBuilder from drivers 2019-06-28 10:26:51 -04:00
Guilherme Blanco
f185a6f30f Added transient metadata builder. Fixed cache bug for missing column name for fields. More typehint 2019-06-27 00:04:59 -04:00
Guilherme Blanco
c35142f0ae Added TableMetadataBuilder 2019-06-25 23:37:06 -04:00
Guilherme Blanco
3cda3f1ae3 Eliminated AbstractClassMetadataFactory, as there is no purpose for abstracting 2019-06-23 23:58:16 -04:00
Guilherme Blanco
9b8b5d9bf6 Fixes for non-cached behavior 2019-06-23 23:41:51 -04:00
Guilherme Blanco
e1bb9e005d Simplified entity listeners. Implemented cache builder 2019-06-23 23:37:57 -04:00
Guilherme Blanco
b9880bb30f Optimized VersionFieldMetadata support. General fixes for phpstan. Moved validation and comppletion of FieldMetadata to corresponding methods. More optimizations around sequencing generation. 2019-06-21 00:56:21 -04:00
Luís Cobucci
becf73050e Remove unnecessary argument 2019-06-18 10:46:37 +02:00
Luís Cobucci
27f431c628 Fix coding standard violations 2019-06-18 10:45:44 +02:00
Guilherme Blanco
5b15a614a7 Expanded featureset of ClassMetadataBuildingContext. Added more typehints. Added parent ClalassMetadata to constructor. Optimized sequencing generation by delegating its creation to ValueGenerator. 2019-06-18 00:28:34 -04:00
Luís Cobucci
e747f7b9c4 Merge pull request #7725 from doctrine/sync-master
Sync bug fixes from v2.6
2019-06-18 02:17:21 +02:00
Darryl Hein
2b0e49d173 change variable name
to make it consistent throughout document
2019-06-18 01:59:09 +02:00
Sergiu Pirlici
b2f27f6310 Update ordered-associations.rst
Fixed some typos
2019-06-18 01:59:09 +02:00
Marc Plotz
3af277ca55 fix typo
`has to allow null values` vs `has to allows null values`
2019-06-18 01:59:08 +02:00
Claudio Zizza
fb50004d58 Fix of links and php version after review 2019-06-18 01:59:08 +02:00
Claudio Zizza
6bf8d61ffc Fix of links and anchors 2019-06-18 01:59:07 +02:00
Guilherme Blanco
057a97d93c Swap properties via entity manager mock 2019-06-18 01:59:07 +02:00
Marco Pivetta
b5c4c7aaec #7527 automated CS checks 2019-06-18 01:59:06 +02:00
Marco Pivetta
9bd6c59285 As an example result:
```
./phpbench.phar run tests/Doctrine/Performance/Query --iterations=50 --revs=50 --report=aggregate
PhpBench 0.15-dev (dcbe193). Running benchmarks.
Using configuration file: /home/ocramius/Documents/doctrine/doctrine2/phpbench.json

\Doctrine\Performance\Query\QueryBoundParameterProcessingBench

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

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

This indicates that the performance impact for NOT declaring parameter types
explicitly is *MASSIVE*.
2019-06-18 01:59:05 +02:00
Marco Pivetta
a988d5808e Note: this will still lead to the UnitOfWork#getSingleIdentifierValue() still being
called when not specifying the type of a DQL parameter being bound via
`Doctrine\ORM\Query#setParameter()`:

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

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

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

This is up for discussion with patch reviewers.
2019-06-18 01:59:05 +02:00
Marco Pivetta
8c451a6a66 As previously reported by @flaushi in https://github.com/doctrine/doctrine2/pull/7471#discussion_r241949045, we discovered
that binding a parameter causes a `ClassMetadataFactory#getClassMetadata()` call, which in turn leads to large performance
regression when using any `object` type as parameter.

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

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

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

This is due to following portion of code:

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

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

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

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

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

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

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

The test expectation to be set is for `UnitOfWork#getSingleIdentifierValue()` to never be called.
2019-06-18 01:59:05 +02:00
Michael Moravec
96d22a6ca8 Lock dependencies for Code Quality stage 2019-06-18 01:59:04 +02:00
Luís Cobucci
cc57d33e65 Isolate entities used by the new test
To ensure we don't have any unintended side-effect.
2019-06-18 01:59:04 +02:00
Tobias Schultze
55b06ea6e1 Fix applying collation on foreign key columns 2019-06-18 01:59:03 +02:00
Alex Denvir
35a9f20cc4 [XML] Fix default value of many-to-many order-by to ASC 2019-06-18 01:59:03 +02:00
Farhad Safarov
93a18c4b2e fix incorrect phpdoc typehint 2019-06-18 01:59:02 +02:00
James Titcumb
cdd64e7bcb $hydrationMode throughout can be a string as well as int (for custom modes) 2019-06-18 01:59:02 +02:00
Andreas Braun
6f07bbf482 Fix parameter value processing for objects with unloaded metadata 2019-06-18 01:59:01 +02:00
Luís Cobucci
31d9626b61 Use HTTPS endpoint for XML schema location 2019-06-18 01:59:01 +02:00
naitsirch
84170a735d Fixed URLs of doctrine-mapping.xsd in docs
Until now the references to the `doctrine-mapping.xsd` consisted of different URLs.

A grep of docs showed:
* /Users/robo/dev/php/Doctrine/doctrine-mapping.xsd
* http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd
* http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd
* https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd

Now it is used http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd everywhere.
2019-06-18 01:59:00 +02:00
Michael Moravec
b58a73aed3 Add UPGRADE note for EntityRepository::count() 2019-06-18 01:59:00 +02:00
Jarek Jakubowski
d7a385105e Unnecessary newline removed, small improvements in text 2019-06-18 01:58:59 +02:00
Jarek Jakubowski
de18565fdc Add info about Doctrine not using constructor 2019-06-18 01:58:58 +02:00
Jarek Jakubowski
cecd03d920 Mention that Doctrine does not use Entities public API 2019-06-18 01:58:58 +02:00
Donovan Bourlard
ce9d4c739d Fix default value of one-to-many order-by to ASC, #7141 2019-06-18 01:58:57 +02:00
Nicolas FRANÇOIS
4ce24a4d66 Fix handling entities with post generated IDs as FK
This prevents a throw in UnitOfWork#addToIdentityMap because some fields
are null.
2019-06-18 01:58:57 +02:00
Guilherme Blanco
d964eb6d50 Fix mock creation 2019-06-18 01:58:56 +02:00
Luís Cobucci
4885e1c7fe Remove duplicated tag from PHPUnit config 2019-06-18 01:58:55 +02:00
Luís Cobucci
42f08f6e3e Remove useless nested switch case
This is simpler and doesn't make PHPCS go crazy when trying to indent
things properly.
2019-06-18 01:58:55 +02:00
Marco Pivetta
23a0608c47 Merge pull request #7703 from thomasbisignani/fix/comments-syntax
[Documentation] Fixed comments syntax
2019-05-28 01:42:25 +02:00
Marco Pivetta
2b734405d2 Merge pull request #7715 from sangio90/patch-1
Typo in class description
2019-05-28 00:27:44 +02:00
Marco Pivetta
0a34e4cef6 Merge pull request #7719 from bocharsky-bw/patch-1
Fix a few examples: Start i var from 1 instead of 0
2019-05-27 23:42:10 +02:00
Victor Bocharsky
b8ebde9d07 Start i var from 1 instead of 0
Because (0 % $batchSize) === 0 but we don't want to execute flush() and
clear() on the first iteration.
2019-05-27 19:49:18 +02:00
Jonathan H. Wage
a6a9455bdd Fix tidelift funding link. 2019-05-24 12:33:04 -05:00
Jonathan H. Wage
6a6370b843 Fix tidelift funding link. 2019-05-24 12:27:09 -05:00
Jonathan H. Wage
706de46ef1 Merge pull request #7720 from jwage/github-funding
Add .github/FUNDING.yml file.
2019-05-24 12:23:53 -05:00
Jonathan H. Wage
b4338ad0d8 Add .github/FUNDING.yml file. 2019-05-24 12:22:47 -05:00
Guido Sangiovanni
b762189c97 typo in class description
fixed typo in class description
2019-05-20 16:39:48 +02:00
Thomas Bisignani
398ddb426f [Documentation] Fixed comments syntax 2019-05-08 00:45:01 +02:00
Jonathan H. Wage
8bb91280ed Merge pull request #7695 from Vehsamrak/patch-1
Annotation fix in example block
2019-04-29 10:56:04 -05:00
Petr Karmashev
87a6eef400 Annotation fix in example block 2019-04-29 13:48:11 +03:00
Jonathan H. Wage
bf14cca3f3 Merge pull request #7687 from muglug/patch-2
Remove full stops in docblocks
2019-04-22 13:52:07 -05:00
Matthew Brown
5ef41cc923 Remove another full stop 2019-04-21 00:52:37 -04:00
Илья
01a4292b0d fix: information about units in DATE_ADD, DATE_SUB 2019-04-04 10:04:08 +03:00
Jonathan H. Wage
ebb53acda9 Merge pull request #7609 from SenseException/type-mapping
Add missing types to property mapping documentation
2019-04-02 09:52:45 -05:00
Claudio Zizza
d1bcb1f0a0 Add missing types to property mapping documentation 2019-04-01 21:10:58 +02:00
Marco Pivetta
04036f2244 Merge pull request #7648 from amaabdou/improvement/upgrade_doctrine_cs_6_0
Update doctrine cs to 6.0
2019-03-17 20:23:43 +01:00
Ahmed Abdou
6c0537325c remove un necessary returns 2019-03-17 19:18:14 +01:00
Ahmed Abdou
1ab9d0b194 Update doctrine cs to 6.0 2019-03-17 14:17:06 +01:00
Claudio Zizza
01f26bba53 Add readOnly attribute to annotation example 2019-03-14 20:49:05 +01:00
Marco Pivetta
d8f67108c9 Merge pull request #7632 from yethee/tests-for-gh-7629
Failing tests for #7926 - `scheduledForSynchronization` leaks memory when using `@ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
2019-03-02 19:17:36 +01:00
yethee
2167536792 Prevent memory leak on an empty commit 2019-03-02 15:02:03 +03:00
yethee
34933416d6 Failing tests 2019-03-02 14:51:44 +03:00
Claudio Zizza
93d55d7fbc Add headlines for direct references to DQL examples 2019-02-17 02:57:27 +01:00
Marco Pivetta
e8eaf8386d Merge pull request #7599 from Majkl578/travis-php7.4-3.0
[3.0] CI: Test against PHP 7.4snapshot instead of nightly (8.0)
2019-02-11 15:38:22 +01:00
Marco Pivetta
97e275ff31 Merge pull request #7603 from creocoder/patch-1
`AbstractQuery::getSingleScalarResult()` never conditionally returns `null`: corrected docblock accordingly
2019-02-11 15:36:56 +01:00
Alexander Kochetov
5e3db26738 Fix AbstractQuery::getSingleScalarResult() phpDoc
This method never return `null`.
2019-02-11 13:01:15 +03:00
Michael Moravec
71393f7d5a CI: Test against PHP 7.4snapshot instead of nightly (8.0) 2019-02-09 18:52:21 +01:00
Marco Pivetta
ed61f0e647 Merge pull request #7576 from SenseException/links-and-typos-master
Update information and links of documentation (master)
2019-01-24 11:42:57 +01:00
Claudio Zizza
11cfaf1233 Fix of links, anchors and php version 2019-01-23 21:30:20 +01:00
Marco Pivetta
08d34341c5 Merge pull request #7578 from SenseException/remove-codeigniter
Remove codeigniter Framework example (master)
2019-01-23 07:47:44 +01:00
Claudio Zizza
5ac805fc4d Remove codeigniter example from docs 2019-01-22 23:07:31 +01:00
Marco Pivetta
6615520bf7 Merge pull request #7559 from doctrine/malarzm-patch-1
Change Stackoverflow tag to doctrine-orm
2019-01-05 21:59:19 +01:00
Maciej Malarz
541fb5968d Change Stackoverflow tag to doctrine-orm 2019-01-05 21:28:16 +01:00
Marco Pivetta
22bf778711 Merge pull request #7558 from nico-incubiq/nico-incubiq-3.0-datetimeimmutable-version
Allow all datetime types to be used as version
2019-01-05 15:42:28 +01:00
Nicolas Severin
322516337e Allow all datetime types to be used as version 2019-01-05 12:01:21 +00:00
Marco Pivetta
d00453dc7e Merge pull request #7552 from Majkl578/repo-rename/3.0
[3.0] Migrate repository name doctrine/doctrine2 -> doctrine/orm
2019-01-03 17:28:36 +01:00
Michael Moravec
0cfc82ddae Migrate repository name doctrine/doctrine2 -> doctrine/orm 2019-01-03 09:07:49 +01:00
Luís Cobucci
e2189307f4 Merge pull request #7526 from greg0ire/support_channels
Add support metadata
2018-12-17 14:39:07 +01:00
Grégoire Paris
b5ba7cddd5 Add support metadata
This should result in more links available on Packagist.
2018-12-17 13:15:10 +01:00
Jonathan H. Wage
2269510e8a Merge pull request #7521 from doctrine/update-chat-link
Update chat link from Gitter to Slack.
2018-12-12 14:05:15 -06:00
Jonathan H. Wage
4bc261aa46 Update chat link from Gitter to Slack. 2018-12-12 20:04:01 +00:00
Marco Pivetta
d20dfcfa8d Merge pull request #7509 from chucky2305/bug/issue-7508
ISSUE-7508: changed phpdoc for parameter `$value`
2018-12-07 12:20:58 +01:00
max.schindler
8744d1936f ISSUE-7508: changed phpdoc for parameter $value to match the actual code 2018-12-07 08:24:55 +01:00
Marco Pivetta
a5ed7ed89c Merge pull request #7507 from mayflower/typos
fix typos in security.rst
2018-12-06 16:11:42 +01:00
Michael Moll
2e9f533746 fix typos in security.rst 2018-12-06 16:10:07 +01:00
Marco Pivetta
3e0b70b4dc Merge pull request #7494 from shadowhand/fix/cli-apcu-warning
Do not allow cache clearing with APCu
2018-12-05 13:18:31 +01:00
Woody Gilk
1feac20c72 Do not allow cache clearing with APCu 2018-12-02 15:15:30 -06:00
Marco Pivetta
3cef55ec39 Merge pull request #7497 from Majkl578/eliminate-common
Drop dependency on doctrine/common
2018-12-01 02:12:27 +01:00
Michael Moravec
cd57cafe47 Drop dependency on doctrine/common 2018-12-01 01:55:30 +01:00
Marco Pivetta
fa4d3bf799 Merge pull request #7488 from Majkl578/update-dbal
Update DBAL 3.x version from 2018-04-17 to 2018-11-26
2018-12-01 00:03:05 +01:00
Marco Pivetta
7c9ab76896 Merge pull request #7491 from thierrymarianne/remove-dead-code
Remove dead code
2018-11-27 21:08:50 +01:00
Thierry Marianne
c4d335ed10 Remove dead code 2018-11-27 20:36:03 +01:00
Michael Moravec
5d6b5a706f Update DBAL 3.x version from 2018-04-17 to 2018-11-26 2018-11-26 21:43:02 +01:00
Marco Pivetta
62de42cfa1 Merge pull request #7484 from TomasVotruba/docs
remove unneded docs
2018-11-21 11:22:34 +01:00
Tomas Votruba
f4cd3df403 remove unneded docs 2018-11-21 11:06:19 +01:00
Luís Cobucci
929bcaf704 Merge pull request #7475 from carusogabriel/file-permission
Fix file permission
2018-11-20 09:24:58 +01:00
Luís Cobucci
4594380bf4 Merge pull request #7474 from carusogabriel/if-statment
Remove unecessary need for if statment
2018-11-20 09:07:34 +01:00
Luís Cobucci
d250ee79c5 Merge pull request #7476 from BenMorel/patch-3
Replace APC with APCu
2018-11-16 00:26:33 +01:00
Benjamin Morel
4d7ffa0e4f Replace APC with APCu 2018-11-15 23:57:53 +01:00
Gabriel Caruso
65c13cf15b Fix file permission 2018-11-14 15:57:11 -02:00
Gabriel Caruso
fc62b8f997 Remove unecessary need for if statment 2018-11-14 15:54:16 -02:00
Guilherme Blanco
4d49b7778c Merge pull request #7469 from doctrine/sync-master-with-2.x
Sync master with 2.x
2018-11-12 00:01:57 -05:00
Alexandru Ungureanu
c780f014b9 Fixes small typo 2018-11-11 23:26:39 +01:00
Farhad Safarov
97248866ce JIRA to Github issues 2018-11-11 23:26:31 +01:00
Oguz Dumanoglu
dd8fcfd851 Fix a typo
There was a typo in Working with Associations page.
2018-11-11 23:25:19 +01:00
Thomas Landauer
54beb95f68 Some formatting improvements 2018-11-10 21:14:12 +01:00
Thomas Landauer
d0b47f3aa9 Update association-mapping.rst
Added info about owning and inverse side.
2018-11-10 20:40:57 +01:00
Michael Moravec
8a79638faa CI: Test against PHP 7.3 2018-11-10 20:38:27 +01:00
Marco Pivetta
6dc46e78cc Merge pull request #7413 from Majkl578/cs-5.0
Apply Doctrine CS 5.0
2018-11-04 22:41:39 +01:00
Michael Moravec
0c2ccec915 Apply Doctrine CS 5.0 2018-11-04 22:08:30 +01:00
Marco Pivetta
72971fd438 Merge pull request #7457 from Majkl578/drop-discriminator-autodiscovery
Drop automatic discriminator map discovery
2018-11-04 19:47:53 +01:00
Michael Moravec
cf94ec8136 Drop automatic discriminator map discovery 2018-11-02 22:32:43 +01:00
Marco Pivetta
d81284b506 Merge pull request #7440 from carusogabriel/dedicated-assertions
Use dedicated assertContainsOnlyInstancesOf PHPUnit assertion
2018-10-30 12:44:10 +01:00
Gabriel Caruso
b0825e490b Use dedicated assertContainsOnlyInstancesOf PHPUnit assertion 2018-10-22 12:04:43 -03:00
Marco Pivetta
91346c41e7 Merge pull request #7425 from mbessolov/patch-1
Minor grammar corrections in the documentation.
2018-10-15 15:21:38 +02:00
Marco Pivetta
1760b4f655 Merge pull request #7429 from carusogabriel/simplify-conditions
Simplify conditions
2018-10-15 12:43:29 +02:00
Marco Pivetta
e6a79ef9b2 Merge pull request #7430 from carusogabriel/use-in-array
Use `in_array` instead of `array_search` + comparison
2018-10-15 12:41:04 +02:00
Gabriel Caruso
15e49aff35 Use in_array instead of array_search + condition 2018-10-14 17:23:04 -03:00
Gabriel Caruso
5839553356 Simplify conditions 2018-10-14 17:21:21 -03:00
Michael Bessolov
5a9fe624b6 Minor grammar corrections in the documentation. 2018-10-10 11:50:09 -07:00
Marco Pivetta
b40c2c6b78 Merge pull request #7404 from doctrine/tidelift-readme
Add tidelift badge and links to the doctrine/orm readme.
2018-09-25 11:35:14 +02:00
Marco Pivetta
36aeed46f8 Merge pull request #7405 from Majkl578/2.x-into-master
Cherry-pick stuff from 2.x into master
2018-09-24 09:28:56 +02:00
Tim Lieberman
ce76688865 Fix for BC break #7366 when calling EM::find() with LockMode::OPTIMISTIC outside of a TX 2018-09-23 06:48:02 +02:00
sserbin
2f520244df Query\Expr::andX(): added string as allowed parameter type 2018-09-23 06:37:59 +02:00
Christophe Coevoet
a59ae2e5e3 Fix the computation of commit order for circular dependencies
When finding a circular dependencies, we must ensure that all dependencies
of a node have been visited before adding it to the sorted list.
2018-09-23 05:51:54 +02:00
Christophe Coevoet
2431bc4076 Add a unit test reproducing the commit order regression 2018-09-23 05:50:51 +02:00
Christophe Coevoet
6219f72dc6 Add a test reproducing GH7259 2018-09-23 05:49:47 +02:00
Bob den Otter
6ff677d2ab Fix docblock in inheritance-mapping.rst 2018-09-23 05:47:56 +02:00
philippe-unitiz
730543f17e Fix constructor and add() argument types in Query\Base 2018-09-23 05:47:56 +02:00
Guilliam Xavier
1157a845ec correct load-only DOMDocument constructor in test 2018-09-23 05:47:56 +02:00
Benjamin Morel
f62cccbfd1 Typo fix 2018-09-23 05:47:55 +02:00
Pierre-Louis FORT
a20373449c Handle removed parameters by tree walker in Paginator 2018-09-23 05:47:55 +02:00
Michael Moravec
362ccb4d24 Fix #7286: StringPrimary no longer accepts aggregate functions as argument 2018-09-23 05:46:37 +02:00
Michael Moravec
2df74442a0 Fix compatibility with DBAL 2.8 where OFFSET 0 is no longer generated (doctrine/dbal#3157) 2018-09-23 04:28:04 +02:00
Michael Moravec
b9feae09c0 Use non-deprecated version of Lexer and Inflector 2018-09-23 04:24:29 +02:00
Jonathan H. Wage
5c773554dd Add tidelift badge and links to the doctrine/orm readme. 2018-09-21 01:32:25 +01:00
Marco Pivetta
bfa65207b0 Merge pull request #7388 from AndreyMashukov/issue-7359-added-test-for-filter-disable
#7359 Added test for filter disable, refactored code and test
2018-09-05 12:04:20 +02:00
AMashukov
53b7f9d6f8 Closes #7359 Added test for filter disable, refactored code and test 2018-09-05 12:23:39 +03:00
Michael Moravec
afd79c189f Merge pull request #7381 from Majkl578/homepage
Update homepage
2018-09-01 03:27:57 +02:00
Michael Moravec
8d83dfa05c Update homepage 2018-09-01 01:07:01 +02:00
Jonathan H. Wage
d311888ef9 Merge pull request #7380 from doctrine/doctrine-project-json
Add .doctrine-project.json to root of the project.
2018-08-31 18:02:41 -05:00
Jonathan H. Wage
46a880ac0f Add .doctrine-project.json to root of the project. 2018-08-31 20:05:15 +01:00
Michael Moravec
f4c84897ba Merge pull request #7373 from SenseException/remove-yaml-remnants
Remove remnants of yaml from documentation
2018-08-29 18:09:50 +02:00
Claudio Zizza
890d544161 Remove remnants of yaml from documentation 2018-08-25 23:36:22 +02:00
Marco Pivetta
87ac04d10e Merge pull request #7229 from ThomasLandauer/patch-1
Added `UNIQUE INDEX` to generated SQL schema
2018-08-21 12:46:34 +02:00
Luís Cobucci
7b261676d2 Merge branch 'fix-partial-reference-docblock'
Applying https://github.com/doctrine/doctrine2/pull/7360 in master
2018-08-19 16:29:28 +02:00
Luís Cobucci
da69537408 Document getPartialReference() properly
According to the current implementation that method also returns `null`,
however the interface's documentation was incorrect.
2018-08-19 16:29:03 +02:00
Marco Pivetta
d6ec5a3861 Merge branch 'fix/#7225-correct-type-hint-for-identifier-criteria'
Close #7225
2018-08-12 10:07:28 +02:00
Hannes Giesenow
82972f697a fixed type hint 2018-08-12 10:07:14 +02:00
Hannes Giesenow
0538f93913 Fixed typehint 2018-08-12 10:07:14 +02:00
Marco Pivetta
0977c8926a Merge pull request #7339 from guilliamxavier/patch-1
Fix renamed depended test method
2018-08-08 11:19:45 +02:00
Guilliam Xavier
2500025ee0 Fix renamed depended test method 2018-08-08 10:35:43 +02:00
Marco Pivetta
5e57fcf9ee Merge pull request #7337 from vladyslavstartsev/patch-1
small fix - `as` in lowercase
2018-08-07 20:53:13 +02:00
vladyslavstartsev
7d34b64885 fixed non camelCase method name in Xml 2018-08-07 18:47:52 +03:00
vladyslavstartsev
2245ad2751 small fix - as in lowercase 2018-08-07 18:28:44 +03:00
Marco Pivetta
68635faf83 Merge pull request #7335 from PowerKiKi/docs-fix
Correct method names and broken link in docs
2018-08-06 14:15:20 +02:00
Adrien Crivelli
6f3ca36b08 Correct method names and broken link in docs
The methods name were incorrectly prefixed with a `\_` and the link
syntax was broken and its URL appeared in plain text in rendered HTML.
2018-08-06 20:23:56 +09:00
Marco Pivetta
cb3d4caec2 Merge pull request #7333 from ostrolucky/fix-2935
Fix #2935 [DDC-2236] Add note that Paginator might screw aggreg. queries
2018-08-05 17:11:16 +02:00
Gabriel Ostrolucký
863dabe73f Fix #2935 [DDC-2236] Add note that Paginator might screw aggreg. queries 2018-08-04 23:33:50 +02:00
Marco Pivetta
5ac6a83560 Merge pull request #7331 from doctrine/fix-2351
#2351 [DDC-1702] Fix EBNF of InExpression (StateFieldPath -> Arithmetic)
2018-08-03 01:47:13 +02:00
Gabriel Ostrolucký
7ad0dcbd88 #2351 [DDC-1702] Fix EBNF of InExpression (StateFieldPath -> Arithmetic) 2018-08-03 00:14:19 +02:00
Marco Pivetta
1d649ad952 Merge pull request #7330 from Majkl578/drop-UuidGenerator
Drop UuidGenerator
2018-08-02 18:55:23 +02:00
Michael Moravec
c4c489e7d9 Drop UuidGenerator 2018-08-02 15:22:41 +02:00
Marco Pivetta
f319f10d2e Merge pull request #7327 from greg0ire/fix_inaccurate_comment
Fix inaccurate description of the `ORMException` interface
2018-07-31 08:55:25 +02:00
Grégoire Paris
1b5bd54290 Fix inaccurate comment 2018-07-31 08:36:57 +02:00
Marco Pivetta
847d5bc666 Merge pull request #7313 from gamingumar/patch-1
typo fix in getting started guide
2018-07-17 15:36:47 +02:00
UmaR Aamer
e540c4a8c4 typo fix in getting started guide
changed rasily to easily
2018-07-17 17:54:12 +05:00
Michael Moravec
b361aa438e Merge pull request #7308 from janvernieuwe/patch-1
Update Bug.md
2018-07-13 15:39:32 +02:00
janvernieuwe
396b7f6718 Update Bug.md
typo
2018-07-13 14:33:53 +02:00
Marco Pivetta
39c5dcb79d Merge pull request #7302 from localheinz/fix/attribute
Fix: Attributes vs. annotations
2018-07-09 23:41:02 +02:00
Marco Pivetta
c17339e64e Merge pull request #7301 from localheinz/fix/format
Fix: Consistently format inversedBy and mappedBy attributes
2018-07-09 23:39:52 +02:00
Andreas Möller
4179a9676f Fix: Attributes vs. annotations 2018-07-09 17:01:14 +02:00
Andreas Möller
e16500ae72 Fix: Consistently format inversedBy and mappedBy attributes 2018-07-09 16:59:44 +02:00
Marco Pivetta
f7309d898f Merge pull request #7295 from afoeder/task/fix-2ndlevelcache-apidoc-links
[DOC] Fix links to API doc for 2nd level cache
2018-07-04 15:06:08 +02:00
Adrian Föder
28f78a0e60 [DOC] Fix links to API doc for 2nd level cache 2018-07-04 14:42:05 +02:00
Marco Pivetta
8bcd13f5c7 Merge pull request #7283 from BackEndTea/patch-1
Add a polyfill for ctype
2018-07-03 10:51:10 +02:00
Marco Pivetta
079f398bd2 Merge pull request #7288 from SenseException/patch-2
Fix of variable typo in cache usage example
2018-07-03 10:47:28 +02:00
Marco Pivetta
035d9edf95 Merge pull request #7075 from BreiteSeite/fix/#7068-entitymanager-find-with-pessimistic-lock-does-not-check-for-transaction
Fix for #7068: `EntityManager::find()` with pessimistic lock should check for transaction
2018-07-03 09:43:14 +02:00
Marco Pivetta
984641d20f Merge pull request #7287 from afoeder/patch-1
Streamline documentation code block type hints for readability
2018-07-03 09:38:20 +02:00
Marco Pivetta
f584bae121 Merge pull request #7292 from greg0ire/mapped_superclass
Use a more concrete, less confusing example
2018-07-03 09:36:12 +02:00
Grégoire Paris
9ce1f0d13f Use a more concrete, less confusing example
I picked a toothbrush because you usually do not share it with other
persons, feel free to propose something else.
2018-07-03 09:01:10 +02:00
Michael Kühn
0cbc87a888 Fix for #7068: EntityManager::find() with pessimistic lock should check for transaction 2018-07-03 03:09:37 +02:00
Claudio Zizza
f4ed9534cc Fix of variable typo in cache usage example 2018-07-02 18:38:47 +02:00
Adrian Föder
497bbfcec8 [TASK] Streamline code block type hints for readability
This corrects a mistake with a given type hint in
the PHP code blocks as well as adds further for
consistency and brevity.
2018-07-02 16:43:32 +02:00
Marco Pivetta
2d194dee53 Merge pull request #7285 from mkurzeja/fix-flush-phpdoc
Removed outdated entity passed phpdoc for EntityManager::flush
2018-07-02 08:53:06 +02:00
Michał Kurzeja
76bc652d88 Removed outdated entity passed phpdoc for EntityManager::flush 2018-07-02 08:27:13 +02:00
Gert de Pagter
d98d8bb0c2 Require the ext 2018-07-01 20:34:43 +02:00
Gert de Pagter
55f238f790 Add a polyfill for ctype 2018-07-01 17:54:47 +02:00
Marco Pivetta
2726636e55 Merge pull request #7272 from odahcam/patch-1
Fixed anchor typo
2018-06-24 03:47:02 +02:00
Luiz Machado
305308278a fixed anchor typo 2018-06-23 22:25:33 -03:00
Marco Pivetta
c5191fa3f9 Merge pull request #7255 from SenseException/arbitrary-join-doc
Extend documentation of arbitrary joins
2018-06-11 10:20:37 +02:00
Claudio Zizza
e5b88b3fe5 Extend documentation of arbitrary joins 2018-06-10 22:55:38 +02:00
Michael Moravec
fae518aaef Merge pull request #7237 from jvasseur/patch-1
Removed redundant `Doctrine\ORM\EntityRepository#__construct()` phpdoc
2018-05-23 20:25:31 +02:00
Jérôme Vasseur
5756a7fc73 Fix EntityRepository constructor phpdoc
Remove redundant phpdoc from EntityRepository constructor
2018-05-23 19:36:51 +02:00
Thomas Landauer
3d9a4b8ed8 Added UNIQUE INDEX to generated SQL schema
When I tried the example code, I got this unique index too.

Plus my `ALTER TABLE` statement was a little different from the one in the docs:
> ALTER TABLE cart ADD CONSTRAINT FK_BA388B79395C3F3 FOREIGN KEY (customer_id) REFERENCES customer (id)
Should I change that too?
2018-05-18 23:18:50 +02:00
Marco Pivetta
12b28571f5 Merge pull request #7224 from hkdobrev/patch-1
Remove obsolete backslashes from underscores
2018-05-15 14:37:34 +02:00
Haralan Dobrev
ec573d1b75 Remove obsolete backslashes from underscores
These backslashes were not correctly migrated when updating the website and it prevents search for some important terms like the query hints.
2018-05-15 14:55:47 +03:00
Marco Pivetta
347e33e345 Merge pull request #7218 from garak/patch-1
Fix typo
2018-05-09 12:14:59 +02:00
Massimiliano Arione
6ad04f7d51 Fix typo 2018-05-09 11:02:19 +02:00
Jonathan H. Wage
f6cc1290d2 Merge pull request #7216 from Majkl578/issue-templates
Add issue and PR templates
2018-05-07 21:13:11 -05:00
Michael Moravec
bbd2460e06 Add issue and PR templates 2018-05-07 22:53:58 +02:00
Marco Pivetta
9019297c55 Merge pull request #7208 from someniatko/someniatko-patch-1
Better PHPDoc for AbstractQuery cache methods
2018-04-28 15:04:34 +02:00
someniatko
1ed8bc705f Fix PHPDoc whitespacing in AbstractQuery 2018-04-27 17:49:55 +03:00
someniatko
063c402dd5 Better PHPDoc for AbstractQuery cache methods
Updated useResultCache() and setResultCacheLifetime() docs to clearly state $lifetime is measured in seconds;
Added description to useResultCache()'s params. Renamed $bool => $useCache.
2018-04-27 14:16:18 +03:00
Michael Moravec
46b695d67c Merge pull request #7201 from greg0ire/adapt_to_signature_bc_break
Adapt to new signatures
2018-04-18 00:37:37 +02:00
Grégoire Paris
b881f14b4d Lock DBAL to a specific branch
From now on, a new branch will be created on each BC-break
2018-04-17 23:56:20 +02:00
Grégoire Paris
e2ea883786 Adapt to new signatures
This should fix the build with the latest ORM version
2018-04-17 23:56:20 +02:00
Marco Pivetta
77e3e5c96c Merge pull request #6743 from greg0ire/split_orm_exception
Split orm exception
2018-04-13 16:18:30 +01:00
mikeSimonson
c7718efff8 Merge pull request #7177 from doctrine/feature/docs-cleanup
Cleanup and add sidebar.rst
2018-04-12 22:27:08 +02:00
Jonathan H. Wage
42dc7830d7 Add sidebar.rst for documentation sidebar. 2018-04-12 19:06:39 +01:00
Grégoire Paris
10aac31e8d Avoid extending \Exception directly 2018-04-12 19:38:30 +02:00
Grégoire Paris
16a1877d58 cs 2018-04-12 19:38:30 +02:00
Grégoire Paris
6d282cdc43 Avoid directly extending \Exception 2018-04-12 19:38:30 +02:00
Grégoire Paris
491a382f91 Use named constructor 2018-04-12 19:38:30 +02:00
Grégoire Paris
563833340a Move ORMException inside the Exception namespace 2018-04-12 19:38:30 +02:00
Grégoire Paris
b128753960 Return, don't throw 2018-04-12 19:38:30 +02:00
Grégoire Paris
79f17d02f7 cs 2018-04-12 19:38:30 +02:00
Grégoire Paris
28e7e5392c Move exceptions in an Exception sub-namespace 2018-04-12 19:38:30 +02:00
Grégoire Paris
46d342d0cf Add missing extension 2018-04-12 19:38:30 +02:00
Grégoire Paris
3fd15f0cc7 Instanciate self 2018-04-12 19:38:30 +02:00
Grégoire Paris
8c16a20fd2 Make method static 2018-04-12 19:38:29 +02:00
Grégoire Paris
6ec3ff02b7 Get rid of ORMException instanciation 2018-04-12 19:38:29 +02:00
Grégoire Paris
39634ad91e Remove unused use statement 2018-04-12 19:38:29 +02:00
Grégoire Paris
1e60554556 Add missing use statements 2018-04-12 19:38:29 +02:00
Grégoire Paris
ed62eb8672 Extract MatchingAssociationFieldRequiresObject 2018-04-12 19:38:29 +02:00
Grégoire Paris
0998a46480 cs 2018-04-12 19:38:29 +02:00
Grégoire Paris
50b35fc6aa Ensure all exceptions implement ORMException 2018-04-12 19:38:29 +02:00
Grégoire Paris
9f4a6922a4 Use new phpunit API 2018-04-12 19:38:29 +02:00
Grégoire Paris
9d30fdf9c7 Refactor ORMException into an interface 2018-04-12 19:38:29 +02:00
Grégoire Paris
d6da264404 Extract invalid result cache driver exception 2018-04-12 19:38:29 +02:00
Grégoire Paris
fbca71af39 Extract cache not configured exceptions 2018-04-12 19:38:29 +02:00
Grégoire Paris
25b5aa6570 Extract QueryCacheUsesNonPersistentCache 2018-04-12 19:38:29 +02:00
Grégoire Paris
c030a213f5 Extract MetadataCacheUsesNonPersistentCache 2018-04-12 19:38:28 +02:00
Grégoire Paris
2f0b5a503a Split cache exception class 2018-04-12 19:38:28 +02:00
Grégoire Paris
cf13e0d10f Extract unrecognized field exception 2018-04-12 19:38:28 +02:00
Grégoire Paris
c2c6c623f3 Extract invalid orientation exception 2018-04-12 19:38:28 +02:00
Grégoire Paris
5db00b15d2 Extract CantUseInOperatorOnCompositeKeys exception 2018-04-12 19:38:28 +02:00
Grégoire Paris
d2a679a780 Extract unrecognized identifier fields exception 2018-04-12 19:38:28 +02:00
Grégoire Paris
398e1128f2 Extract missing identifier field exception 2018-04-12 19:38:28 +02:00
Grégoire Paris
4132f408df Extrace mismatched event manager exception 2018-04-12 19:38:28 +02:00
Grégoire Paris
c0453603e1 Extract invalid hydrate mode exception 2018-04-12 19:38:28 +02:00
Grégoire Paris
d9b0ab7b41 Extract entity manager closed exception 2018-04-12 19:38:28 +02:00
Grégoire Paris
119610b81e Extract unknown entity namespace exception 2018-04-12 19:38:28 +02:00
Grégoire Paris
59b5e5ae6c Extract invalid entity repository exception 2018-04-12 19:38:27 +02:00
Grégoire Paris
b7a2434ef7 Extract Proxy classes always regenerating exception 2018-04-12 19:38:27 +02:00
Grégoire Paris
82796e8711 Extract NotSupported exception 2018-04-12 19:38:27 +02:00
Grégoire Paris
145d5139cb Extract missing mapping driver implementation 2018-04-12 19:38:27 +02:00
Grégoire Paris
ea12b6f82f extract findByRequiresParameter 2018-04-12 19:38:27 +02:00
Grégoire Paris
ae166d773b Find better naming for find by call exception 2018-04-12 19:38:27 +02:00
Grégoire Paris
de31b2abc9 Extract invalidFindByInverseAssociation 2018-04-12 19:38:27 +02:00
Grégoire Paris
b1bec0ee6c Use better suited parent exception 2018-04-12 19:38:27 +02:00
Grégoire Paris
9b8f9786d9 Rename method to something more concise 2018-04-12 19:38:27 +02:00
Grégoire Paris
fdfd2de658 Add missing type hint 2018-04-12 19:38:27 +02:00
Grégoire Paris
fcf012075a Split the ORMException class 2018-04-12 19:38:24 +02:00
Jonathan H. Wage
2ac4142e5f Cleanup docs so the index and title is consistent across all projects. 2018-04-11 04:24:51 +01:00
Marco Pivetta
967d34473e Merge pull request #7185 from afoeder/master
[FEATURE] Mention and link identifier generation strategies
2018-04-10 13:19:57 +02:00
Adrian Föder
dd3c5c8dc3 [FEATURE] Mention and link identifier generation strategies 2018-04-10 12:06:57 +02:00
Marco Pivetta
c4c59df1b4 Merge pull request #7173 from doctrine/malarzm-patch-1
Fix Criteria's orderBy example
2018-04-04 11:00:44 +02:00
Maciej Malarz
ac92e9f8d3 Fix Criteria's orderBy example 2018-04-04 10:31:49 +02:00
Marco Pivetta
54833ec31b Merge pull request #7171 from javiereguiluz/patch-8
Fixed a minor reStructuredText syntax issue
2018-04-03 14:14:50 +02:00
Javier Eguiluz
1b6a1ee315 Fixed a minor reStructuredText syntax issue 2018-04-03 13:30:27 +02:00
Marco Pivetta
6fb6b9d32d Merge pull request #7170 from lcobucci/document-bc-break-due-to-return-type-declaration
Document BC-break due to type declaration
2018-04-03 11:22:38 +02:00
Luís Cobucci
1f0660dcfd Document BC-break due to type declaration
Identifier generators and value generation plan are now using type
declaration as much as possible.
2018-04-03 10:33:01 +02:00
Marco Pivetta
1afbf141fc Merge pull request #7163 from delboy1978uk/master
Add constructor types to SqlWalker and TreeWalker
2018-03-29 14:14:39 +02:00
Derek Stephen McLean
d372416350 use inheritdoc 2018-03-29 11:33:05 +02:00
Derek Stephen McLean
f6581657de Update TreeWalker.php 2018-03-28 16:35:58 +02:00
Derek Stephen McLean
935f3606a2 Fix docblock in SqlWalker 2018-03-28 16:28:19 +02:00
Jonathan H. Wage
cf8f71e56e Merge pull request #7158 from guilbaudc2/patch-1
Proposed corrected typo in demo code.
2018-03-25 18:35:31 -05:00
Cathy Guilbaud
b01bb234f1 Proposed corrected typo in demo code.
changed $payed to $paid.
2018-03-25 17:10:49 -04:00
Jonathan H. Wage
0bb3624404 Merge pull request #7156 from Pierstoval/dto
Minor fixes for getting started doc
2018-03-24 14:25:58 -05:00
Alex Rock Ancelet
5f6d758ddc Minor fixes for getting started doc 2018-03-24 14:57:51 +01:00
Marco Pivetta
88f6485dea Merge pull request #7154 from ogizanagi/patch-1
Fix getting started syntax issue causing rendering glitches
2018-03-23 23:10:40 +01:00
Maxime Steinhausser
15a149c9f6 Fix getting started syntax issue causing rendering glitches 2018-03-23 22:24:01 +01:00
Marco Pivetta
d199c92c95 Merge pull request #7150 from erusso87/patch-1
Redundant condition to check if a query is cacheable
2018-03-22 15:58:46 +01:00
Ezequiel Russo
c98f40251a Redundant If term
It's redundant to check the `$this->cacheable` property.
2018-03-22 14:01:49 +01:00
Marco Pivetta
41292e85df Merge pull request #7136 from carusogabriel/doctrine-cs
Some fixes from PHPStan l2
2018-03-20 10:26:40 +01:00
Gabriel Caruso
dfd9d1b9e7 Some fixes from PHPStan l2 2018-03-19 22:28:39 -03:00
Marco Pivetta
3570057358 Merge pull request #7139 from deeky666/fix-typo
Fix typo in upgrade notes
2018-03-19 12:33:10 +01:00
Steve Müller
7eb6e00940 fix typo 2018-03-19 11:27:05 +01:00
Marco Pivetta
f5a0b568f3 Merge pull request #7137 from doctrine/docs/remove-leftover-sentence-in-getting-started
Removing leftover sentence in `getting-started.rst`
2018-03-19 00:16:31 +01:00
Marco Pivetta
64dffe0ede Removing leftover sentence in getting-started.rst
Ref: https://github.com/doctrine/doctrine2/pull/6902/files#r175304353
2018-03-18 23:57:36 +01:00
Marco Pivetta
8836954787 Merge pull request #6902 from Pierstoval/dto-and-vo
Documentation about getters/setters to DTOs and Value Objects
2018-03-18 16:53:07 +01:00
Alex Rock Ancelet
3ce3aa978f More and more fixes \o/ 2018-03-18 13:12:32 +01:00
Alex Rock Ancelet
31e19239d1 Moar fixes, thanks @greg0ire 2018-03-16 23:20:57 +01:00
Alex Rock Ancelet
86dc7f46a1 More english misunderstanding fixes 2018-03-16 23:12:23 +01:00
Alex Rock Ancelet
042cff9f22 Make updates after @greg0ire, @mheki and @ostrolucky's comments 2018-03-16 23:06:21 +01:00
Alex Rock Ancelet
5f7210b441 WIP to document using DTOs and entities as ValueObjects 2018-03-16 19:53:13 +01:00
Marco Pivetta
3ac8930613 Merge pull request #7132 from dbrumann/patch-1
Fixes a typo
2018-03-15 10:42:38 +01:00
Denis Brumann
29c86ff5e9 Fixes a typo 2018-03-15 10:09:25 +01:00
Marco Pivetta
43c252458b Merge pull request #7122 from Majkl578/dev/tests/typing-setup-teardown-tests
Tests: Return types for setUp, tearDown and all test methods
2018-03-09 10:40:11 +01:00
Michael Moravec
a754acb44a Tests: Return types for setUp, tearDown and all test methods 2018-03-08 19:00:58 +01:00
Marco Pivetta
1a14c85c54 Merge pull request #7121 from Majkl578/dev/scrutinizer-update
Update Scrutinizer config to use our CS setup
2018-03-08 17:46:30 +01:00
Michael Moravec
9f572d4a4a Update Scrutinizer config to use our CS setup 2018-03-08 15:49:36 +01:00
Marco Pivetta
1bbdc5b597 Merge pull request #7046 from carusogabriel/cs-fixes
[CS] Fix CS in tests
2018-03-08 10:33:27 +01:00
Gabriel Caruso
1b845c7c84 [CS] Apply Doctrine CS 4.0 in tests 2018-03-08 05:32:59 -03:00
Michael Moravec
f3c72dc2d1 CS: Fix excludes for tests 2018-03-08 03:51:38 +01:00
Marco Pivetta
e5ef92555b Merge pull request #7114 from carusogabriel/remove-tools-phpcs
Remove tools from PHPCs
2018-03-04 17:25:22 +01:00
Gabriel Caruso
2be99346b1 Remove tools from PHPCs 2018-03-04 13:10:53 -03:00
Marco Pivetta
ec508aff81 Merge branch 'fix/#7113-#7095-remove-dead-code-leftovers-in-classmetadata'
Close #7113
2018-03-04 13:24:35 +01:00
Marco Pivetta
9f8af8ee4c #7113 #7095 removed unused imports 2018-03-04 13:24:20 +01:00
Michael Moravec
a0cd82b9bc Remove dead code leftovers after #7095 2018-03-04 13:21:43 +01:00
Marco Pivetta
89e39b8192 Merge pull request #7112 from Majkl578/cs-4.0
Apply Doctrine CS 4.0, version composer.lock
2018-03-04 13:17:28 +01:00
Michael Moravec
554489c7e8 Start versioning composer.lock and use it for CS/SA analysis on CI 2018-03-04 04:06:58 +01:00
Michael Moravec
bb1fe1d6c9 [CS] Apply Doctrine CS 4.0
* Exclude abstract/exception naming sniff
* Exclude EntityManagerInterface from interface naming sniff
* Use inline comments for single `@var` annotations
* Use short type casts
* Remove empty comments
* Apply other fixes
2018-03-04 03:37:55 +01:00
Marco Pivetta
cf548e9b02 Merge pull request #7111 from greg0ire/nitpicks_contrib
Nitpicks on `CONTRIBUTING.md`
2018-03-03 23:30:17 +01:00
Marco Pivetta
70d6150412 Merge pull request #7110 from Majkl578/dev/drop-sandbox
Drop tools/sandbox
2018-03-03 23:29:34 +01:00
Grégoire Paris
09d3226db9 Add verbs
It feels less awkward.
2018-03-03 21:16:39 +01:00
Grégoire Paris
449a6ead69 Add missing article 2018-03-03 21:16:39 +01:00
Michael Moravec
99c6a510e6 Drop tools/sandbox 2018-03-03 21:06:01 +01:00
Marco Pivetta
d00cabafbf Merge pull request #7107 from Majkl578/dev/update-contributing
Update README & CONTRIBUTING for 3.0 changes and workflow
2018-03-03 12:35:15 +01:00
Michael Moravec
61778f95d4 CONTRIBUTING.md: Update with newer instructions & workflow 2018-03-02 21:31:09 +01:00
Michael Moravec
f100bcec5f README.md: Add warning about changes in 3.0 and branch info 2018-03-02 21:31:09 +01:00
Marco Pivetta
f4ef0a1d5f Merge pull request #7048 from malukenho/enhancement/remove-redundant-docs
Remove redundant php-docs
2018-03-02 09:50:43 +01:00
Marco Pivetta
bbc1ed93b1 Merge branch 'fix/#7082-entity-persister-confuses-pdo-data-types'
Close #7082
Close #7062
2018-02-27 09:05:23 +01:00
Marco Pivetta
2343a58ffe #7062 #7082 adapting test case to new ORM master (3.x) suite and mapping changes 2018-02-27 09:03:06 +01:00
Marius Klocke
bd6ae78940 Add a failing test for issue 7062 2018-02-27 08:34:24 +01:00
Marco Pivetta
a0e414ba57 Merge pull request #7088 from Majkl578/dev/drop-pdo-dependency
Drop explicit PDO dependency
2018-02-26 00:19:03 +01:00
Marco Pivetta
d952c1247e Merge pull request #7095 from doctrine/EntityRepository3
[EntityRepository] Cleanup named native features
2018-02-26 00:12:54 +01:00
Benjamin Eberlei
07a5861503 Remove Named Native Query feature 2018-02-25 23:06:44 +01:00
Luís Cobucci
45c3805edf Merge branch 'port/patch-association-indentifier-not-quoted'
Port https://github.com/doctrine/doctrine2/pull/7093, only introducing
the tests since quoting issues have been solved already in `master`.
2018-02-25 20:31:13 +01:00
Jan Langer
aadf295b9a Fix updating entities with quoted identifier association 2018-02-25 20:11:40 +01:00
Luís Cobucci
91acb40433 Merge pull request #7054 from cb8/foreign-id
Fix ID generation of foreign keys

Ports https://github.com/doctrine/doctrine2/pull/6701 using v3.0 structure.
2018-02-25 19:37:49 +01:00
Charles Bell
5fde371852 Avoid flattening of post-insert IDs
When setting deferred ID values, the original data should contain
complete objects (as it would with non-deferred IDs).
2018-02-25 19:30:37 +01:00
Charles Bell
67fe52f36c Fix ID generation of foreign keys 2018-02-25 19:30:35 +01:00
Vašek Henzl
849d0ac3cc Add failing tests for #6531
Tests are based on examples from "Composite and Foreign Keys as Primary Key" tutorial:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html
2018-02-25 19:19:33 +01:00
Marco Pivetta
3e1d124f23 Merge pull request #7092 from lcobucci/declare-some-types
Declare some types
2018-02-25 19:08:20 +01:00
Luís Cobucci
408704b845 Declare types in Sequence\Generator 2018-02-25 19:03:13 +01:00
Luís Cobucci
f6fde34297 Use object type declaration in ValueGenerationPlan 2018-02-25 19:03:13 +01:00
Luís Cobucci
b0cade71ba Use object type declaration in ValueGenerationExecutor 2018-02-25 19:03:13 +01:00
Marco Pivetta
46ea9d1b52 Merge pull request #7091 from carusogabriel/cs-fixes-lib
Fix from CS for lib and tools
2018-02-25 17:37:50 +01:00
Gabriel Caruso
9e57633204 Fix from CS for lib and tools 2018-02-25 09:35:34 -03:00
Marco Pivetta
faab3225bd Merge pull request #7090 from greg0ire/patch-2
Use proper grammar
2018-02-24 20:54:50 +01:00
Grégoire Paris
86f3b6afe7 Use proper grammar
Not sure what idiot wrote it that way.
2018-02-24 20:24:26 +01:00
Marco Pivetta
3d3f8b7a8b Merge pull request #7089 from greg0ire/patch-2
Proofread upgrade file
2018-02-24 15:45:14 +01:00
Grégoire Paris
98b9a7dd34 Proofread upgrade file 2018-02-24 15:41:46 +01:00
Marco Pivetta
6e4daa3997 Merge pull request #7085 from doctrine/tighten-owning-side-resolution
[RFC] Tighten owning side resolution
2018-02-23 16:13:49 +01:00
Guilherme Blanco
9179aeb343 Break unit tests as per @lcobucci request 2018-02-23 10:04:53 -05:00
Guilherme Blanco
0b04aa9000 Added unit tests on both Annotations and XML drivers. Fixed bug with table names not accepting dashes (included unit tests). 2018-02-23 00:19:04 -05:00
Michael Moravec
e6fa8622d7 Drop explicit PDO dependency 2018-02-22 23:34:43 +01:00
Guilherme Blanco
df794b648f Tighten owning side resolution. Fixed bug with potential double owning side in OneToOne when JoinColumns were mapped on both association sides. In that case, the ownership was determined based on association flow (in A -> B, A:b was owning, while in B -> A, B:a was owning). 2018-02-22 00:25:26 -05:00
Luís Cobucci
695edb9fdc Add missing function import 2018-02-19 22:07:22 +01:00
Luís Cobucci
d376afb53c Add @group to delete query test 2018-02-19 22:05:26 +01:00
Marco Pivetta
0822f8a456 Merge branch 'fix/#6988-#6304-ensure-inheritance-middle-layer-is-hydrated'
Close #6988
Close #6304
2018-02-19 12:10:20 +01:00
Luís Cobucci
bbf0c0355d Extract private method to retrieve discriminator values 2018-02-19 12:10:02 +01:00
Luís Cobucci
cbfd52e2fe Remove loose comparison on discriminator values
According to mapping drivers the discriminator values can always be
converted to strings so it's safe to assume that we can actually do a
strict comparison during hydration.
2018-02-19 12:08:32 +01:00
Toni Cornelissen
6cc43d884b Use partial discriminator map on multi-inheritance
Hydrator was ignoring data from subclasses when using multiple
inheritance levels. With this patch it will now use the discriminator
values from all subclasses of the class being hydrated.
2018-02-19 12:07:37 +01:00
Marco Pivetta
e7e4247395 #6988 #6304 adapted test case to ORM test suite changes in 3.x
- `em` protected property
 - annotation namespace change
 - `QueryBuilder` instantiation
2018-02-19 12:02:28 +01:00
Kevin Bond
3035470d06 Inheritance middle-layer doesn't get hydrated with HYDRATE_OBJECT 2018-02-19 11:51:39 +01:00
Marco Pivetta
92445d095e Merge branch 'fix/#7077-#7053-allow-alias-less-DQL-delete-statement'
Close #7077
Close #7053
2018-02-19 11:32:55 +01:00
Marco Pivetta
4e2a2975c2 #7077 #7053 in ORM 3.x, everything is quoted by default, so the test needs adaptation 2018-02-19 11:32:02 +01:00
Luís Cobucci
e479e0daf4 Use early-returns to improve readability of the Parser 2018-02-19 11:23:24 +01:00
Luís Cobucci
f41c6083c3 Fix BC-break on delete queries with nasty workaround
The `v2.5.x` series of the ORM allowed to have DELETE DQLs without using
an alias, even though it didn't follow the grammar rules of the parser.
We fixed that issue on `v2.6.0` however that was a BC-breaking change
and lots of people were relying on this faulty behaviour.

This workaround fixes the BC-break, without even trying to be elegant.
In `v2.7.0.` we should raise a deprecation notice to notify people that
we'll drop that "feature" in `v3.0`.
2018-02-19 11:22:04 +01:00
Carnage
ab03f16e03 Adds sql generation test 2018-02-19 11:20:58 +01:00
Luís Cobucci
f351f5ab3b Merge pull request #7065 from Majkl578/dev/dbal-compat
Fix compatibility with DBAL develop
2018-02-18 02:29:54 +01:00
Michael Moravec
6aabdec97b Fix compatibility with DBAL develop
* ResultStatement signature BC break
* PDO::FETCH_* -> FetchMode::*
* PDO::PARAM_* -> ParameterType::*
* AbstractPlatform::DATE_INTERVAL_UNIT_* -> DateIntervalUnit::*
* AbstractPlatform::TRIM_* -> TrimMode::*
2018-02-18 02:16:16 +01:00
Luís Cobucci
df0cc3452b Fix incorrect value in L2C+lock test
Which was causing the optimistic lock to fail in MySQL since it was
trying to update the data with exact same value.
2018-02-17 19:50:11 +01:00
Luís Cobucci
65ebadec49 Merge pull request #7069 from wtorsi/master
Test for Second level cache with Version, DDC #7067
2018-02-17 18:54:28 +01:00
‘Andrey Lukin’
38032c166e Add version fields into L2C data 2018-02-17 18:38:05 +01:00
‘Andrey Lukin’
35d8c87fad Add test for L2C using optimistic locks
As explained in #7067, fields with `@ORM\Version` annotation were not
being added to L2C cached data.
2018-02-17 18:38:04 +01:00
Luís Cobucci
ec265bf3a3 Merge pull request #7076 from lcobucci/fix-build
Make mocks compatible with latest version of interface
2018-02-17 18:37:03 +01:00
Luís Cobucci
9f7df926cf Make mocks compatible with latest version of interface
Since we changed the signature in DBAL v3.x-dev we must apply the
changes here too.
2018-02-17 18:17:28 +01:00
Marco Pivetta
09266d5361 Merge pull request #7064 from carusogabriel/https
Use HTTPS instead of HTTP
2018-02-14 11:11:58 +01:00
Gabriel Caruso
46e27d0cde Use HTTPS instead of HTTP 2018-02-14 07:57:14 -02:00
Jefersson Nathan
a31ce59097 Remove redundant php-docs
Signed-off-by: Jefersson Nathan <admin@phpse.net>
2018-02-12 14:19:19 +01:00
Marco Pivetta
0a00a3f044 Merge pull request #7057 from Majkl578/fix/composer-cs-constraint
Fix CS constraint in composer.json
2018-02-11 02:25:21 +01:00
Michael Moravec
e64820f21e Fix CS constraint in composer.json 2018-02-11 02:21:39 +01:00
Marco Pivetta
0b7d878cd3 Merge pull request #7056 from Majkl578/cs-3.0-lib
[CS] Apply Doctrine CS 3.0
2018-02-11 00:03:20 +01:00
Michael Moravec
0151657ea1 [CS] Apply Doctrine CS 3.0
* Import all functions and constants in `use`
* Remove useless comments
* Use new with parenthesis
* Unqualified class references in annotations
2018-02-09 22:38:17 +01:00
Luís Cobucci
06ffd851c7 Merge pull request #7055 from lcobucci/fix-date-issues-once-and-for-all
Fix date issues once and for all
2018-02-09 17:47:06 +01:00
Luís Cobucci
97534219db Add missing tests for day calculation
For the DATE_SUB() and DATE_ADD() functions.
2018-02-09 17:17:10 +01:00
Luís Cobucci
df94e79ece Fix date calculation in tests (again)
Now using PHP to calculate the expected date manipulation, keeping a day
as delta since PHP resets the hour when performing operations with
days/weeks/months/years.

February is a wonderful month, isn't it?
2018-02-09 17:17:10 +01:00
Marco Pivetta
4509c770da Merge pull request #7049 from coudenysj/remove-yaml-documentation
Remove the YAML metadata driver from the documentation
2018-02-08 13:27:30 +01:00
Jachim Coudenys
bc0c0986c5 Remove the YAML metadata driver from the documentation
The code was removed in #5932
2018-02-08 13:14:16 +01:00
mikeSimonson
e0ee4b1ad7 Merge pull request #6943 from mikeSimonson/xsd-mapping
replacing all the sequence by choice
2018-02-06 22:47:15 +01:00
Marco Pivetta
bcc9da2f4a Merge pull request #7040 from coudenysj/query-cache-documentation
Fix the result cache documentation
2018-02-06 12:25:10 +01:00
Jachim Coudenys
e351954ec6 Fix the result cache documentation (it caches raw data, not hydrated entities) 2018-02-06 11:49:50 +01:00
Marco Pivetta
1a435b2de4 Merge pull request #7045 from Cladis/patch-2
Change bug reporting link from dead Jira to GitHub
2018-02-06 11:42:46 +01:00
Cladis
8b66f88661 Change bug reporting link from dead Jira to GitHub
As per the desc, I am not sure I have chosen the best location though (not sure if it is general enough).
2018-02-06 12:39:59 +02:00
Marco Pivetta
1c7770794b Merge pull request #7044 from Cladis/patch-1
Fixing link to DDC-213 from Jira to Github
2018-02-06 11:38:53 +01:00
Cladis
692a6d941f Fixing link to DDC-213 from Jira to Github
As per the desc; I previously reported this in #7043
2018-02-06 12:36:30 +02:00
mike
af0b0dbdc3 Removing all the occurence of any
If someone wants to override the doctrine mapping that person should
write his own mapping file
2018-02-06 00:03:24 +01:00
mike
193563e3ed replacing all the sequence by choice
The order is never important in the declaration
2018-02-05 22:52:03 +01:00
Marco Pivetta
eef6308b19 Merge pull request #7034 from carusogabriel/patch-1
PHPUnit 7
2018-02-02 13:23:10 +01:00
Gabriel Caruso
1751560970 Fix test for PHPUnit 7 2018-02-02 08:13:48 -02:00
Gabriel Caruso
bc44d8bceb [WIP] PHPUnit 7 2018-02-02 07:39:03 -02:00
Marco Pivetta
9d27f22ee6 Merge pull request #7033 from malukenho/enhancement/remove-useless-condition
Remove comparison with `null` as we do it with `instanceof`
2018-02-02 10:18:42 +01:00
Jefersson Nathan
e2d077c62d Remove comparison with null as we do it with instanceof
There is no need to check against a white-list type and a black-list at
the same time.

Signed-off-by: Jefersson Nathan <admin@phpse.net>
2018-02-02 09:50:08 +01:00
Luís Cobucci
7d37fe0060 Merge pull request #7032 from Majkl578/fix/7031-tests-february
QueryDqlFunctionTest: Increase delta for testDateAdd() to work in February
2018-02-02 07:41:24 +01:00
Michael Moravec
8cc949808a QueryDqlFunctionTest: Increase delta for testDateAdd() to work in February 2018-02-02 03:08:24 +01:00
Marco Pivetta
7d93958cba Merge pull request #7028 from malukenho/hotfix/remove-unused-return
Remove useless `return` statement
2018-02-01 14:17:16 +01:00
Marco Pivetta
43eebb270f Merge pull request #7029 from malukenho/hotfix/remove-redundant-check
Remove redundant check for DateTime instance
2018-02-01 13:23:56 +01:00
Jefersson Nathan
52769aa118 Remove redundant check for DateTime instance
Signed-off-by: Jefersson Nathan <admin@phpse.net>
2018-02-01 12:59:16 +01:00
Jefersson Nathan
762fdd166e Remove unused return statement
Signed-off-by: Jefersson Nathan <admin@phpse.net>
2018-02-01 12:21:38 +01:00
Michael Moravec
2de6c807c8 Merge pull request #7020 from Majkl578/fix/phpstan-0.9.2
SchemaValidator: Fixed bug in inverse JoinTable column name
2018-01-30 22:48:22 +01:00
Michael Moravec
c181836233 Merge pull request #7023 from malukenho/remove-return-from-void-methods
Avoid `returning` on void methods
2018-01-30 22:40:57 +01:00
Jefersson Nathan
a346c3f84e Avoid return on void methods
Signed-off-by: Jefersson Nathan <admin@phpse.net>
2018-01-30 20:48:57 +01:00
Michael Moravec
93558d4786 SchemaValidator: Use more meaningful variable name 2018-01-30 02:14:32 +01:00
Rolando Caldas
579f084202 Exception Call to undefined method Doctrine\Common\Cache\MemcachedCache::setMemcache()
When memcached extension is loaded Doctrine\ORM\Tools\Setup.php  calls
to setMemcache method. The MemcachedCache class has the setMemcached
method instead. Changed this call in Setup to setMemcached and $memcache
to $memcached to keep the name like the extension
2018-01-30 01:56:37 +01:00
Nicolas FRANÇOIS
79282317d1 Add test case for many-to-many collection deletion, when owning side has a composite PK 2018-01-30 01:50:45 +01:00
Michael Moravec
da7288e541 SchemaValidator: Fixed bug in inverse JoinTable column name 2018-01-29 17:10:01 +01:00
Marco Pivetta
c14dbc2409 Merge branch 'fix/#7013-green-phpstan-analysis'
Close #7013
2018-01-29 08:42:07 +01:00
Marco Pivetta
e559f823ea Documented that Doctrine\ORM\Mapping\Exporter\VariableExporter::INDENTATION has been removed 2018-01-29 08:40:42 +01:00
Michael Moravec
63d5a07f06 Fix issues found by PHPStan 2018-01-29 08:40:11 +01:00
Marco Pivetta
d72936caab Merge pull request #7014 from Majkl578/dev/fix-benchmark
Fix benchmark CI stage
2018-01-29 08:33:16 +01:00
Marco Pivetta
e98654342f Merge pull request #7018 from kimhemsoe/remove_named_query_support
Removed support for named queries
2018-01-29 07:55:05 +01:00
Kim Hemsø Rasmussen
217d28f767 Removed named queries from xsd 2018-01-29 00:48:34 +01:00
Kim Hemsø Rasmussen
e6557cbee3 Removed support for named queries 2018-01-29 00:42:19 +01:00
Michael Moravec
b44ba04d1f Fix benchmark CI stage 2018-01-27 08:01:13 +01:00
Michael Moravec
ee4e267595 Merge pull request #6903 from Majkl578/dev/ci-disable-tests
Travis: Allow failures for tests on non-SQLite RDBMS, disable SLC tests
2018-01-27 07:41:21 +01:00
Michael Moravec
13f173a126 Require symfony/console 3.2+ with $COLUMNS env support 2018-01-27 07:13:49 +01:00
Michael Moravec
7fc285dd32 Travis: Allow test failures on non-SQLite RDBMS, disable SLC tests 2018-01-27 07:13:48 +01:00
Michael Moravec
346d2380bd Fix CS issues from recent changes 2018-01-27 06:55:33 +01:00
Michael Moravec
d791f7f6ca Fix DDC3192Test after #6974 2018-01-27 05:31:55 +01:00
Michael Moravec
d455a023f8 Fix parse error in ORMException caused by #7008 2018-01-27 05:21:02 +01:00
Marco Pivetta
2ccf239628 Merge pull request #6967 from egircys/internal_phpdoc_fix
replace 'internal note:' with {@internal .. }}
2018-01-26 14:55:36 +01:00
Egidijus Gircys
d559c933e1 Merge branch 'master' into internal_phpdoc_fix 2018-01-26 12:51:23 +01:00
Marco Pivetta
45ea294233 Merge pull request #6981 from muglug/fix-signatures
Fix exporter signatures
2018-01-26 11:55:07 +01:00
Marco Pivetta
d4e02aadc0 Merge pull request #6983 from muglug/property-fixes
Fix property docblocks where type is easy to infer
2018-01-26 11:53:11 +01:00
Marco Pivetta
4476ec467d Merge pull request #7004 from coudenysj/patch-1
Fix the link to the 'Change Tracking Policies' documentation
2018-01-26 11:28:19 +01:00
Guilherme Blanco
ddb3cd17bd Merge pull request #7008 from greg0ire/remove_unused_exceptions
Remove unused exceptions
2018-01-24 14:30:49 -05:00
Grégoire Paris
3e8aa71554 Remove unused exceptions 2018-01-24 20:03:32 +01:00
Luís Cobucci
82c91a2606 Merge pull request #7005 from SenseException/patch-1
Update license's copyright
2018-01-24 10:20:48 +01:00
Claudio Zizza
2cd3f1f909 Update license's copyright 2018-01-23 21:56:11 +01:00
Jachim Coudenys
7968dfae76 Fix the link to the 'Change Tracking Policies' documentation 2018-01-23 14:15:39 +01:00
Marco Pivetta
577a590987 Merge pull request #6993 from caciobanu/master
Updated docblocks
2018-01-18 13:04:47 +01:00
Catalin Ciobanu
9e122e7a89 Updated docblock 2018-01-18 12:05:17 +02:00
Matthew Brown
9855f56bd5 Fix property types 2018-01-12 23:42:13 -05:00
Michael Moravec
ea95507da4 Merge pull request #6935 from Majkl578/dev/entity-namespace-removal
Remove support for entity namespaces (Foo:Bar)
2018-01-12 21:48:37 +01:00
Michael Moravec
98f51794d8 Merge pull request #6974 from carusogabriel/strict
Use $strict param in functions that have it
2018-01-12 21:38:48 +01:00
Michael Moravec
e3360573ab Merge pull request #6962 from carusogabriel/null-coalesce-operator
Use Null Coalesce Operator
2018-01-12 21:36:10 +01:00
Gabriel Caruso
8a75d3d6f6 Use $strict param in functions that have it 2018-01-12 06:10:09 -02:00
Gabriel Caruso
142fe6a01d Use Null Coalesce Operator 2018-01-12 05:58:20 -02:00
Matt Brown
872571f4a5 Add asserts for contravariance 2018-01-11 18:36:05 -05:00
Michael Moravec
63cb8018dd Remove support for entity namespace aliases 2018-01-11 23:16:38 +01:00
Marco Pivetta
a0071b17de Merge pull request #6956 from Majkl578/readme/update-versions
README: Update versions
2018-01-11 22:26:15 +01:00
Matt Brown
f2fab1ac36 Fix exporter signatures 2018-01-11 16:21:24 -05:00
Marco Pivetta
e3936d0411 Merge pull request #6964 from doctrine/cs
[CS] Ported lib/ to updated CS 2.0
2018-01-11 10:19:58 +01:00
Egidijus Gircys
37d4927118 replace 'internal note:' with the suggested phpdoc {@internal .. }} 2018-01-07 12:58:31 +01:00
Michael Moravec
dc7fca409c Stop allowing failures for CS errors coming from lib/ 2018-01-05 01:54:12 +01:00
Michael Moravec
cb590fc17e [CS] Port Mapping\* 2018-01-05 00:29:12 +01:00
Michael Moravec
ccdf44d9f1 [CS] Port Mapping\Factory\* 2018-01-05 00:29:10 +01:00
Michael Moravec
1fcb7b5005 [CS] Port Mapping\Exporter\* 2018-01-05 00:29:08 +01:00
Michael Moravec
f567970a85 [CS] Port Mapping\Driver\* 2018-01-05 00:29:05 +01:00
Michael Moravec
386fc0b010 [CS] Port Query\* 2018-01-05 00:29:02 +01:00
Michael Moravec
51300d63f4 [CS] Port Query\AST\* 2018-01-05 00:29:00 +01:00
Michael Moravec
0d1684873b [CS] Port Query\AST\Functions\* 2018-01-05 00:28:58 +01:00
Michael Moravec
066d135b21 [CS] Port Query\Filter\* 2018-01-05 00:28:56 +01:00
Michael Moravec
0cef113a5b [CS] Port Query\Expr\* 2018-01-05 00:28:41 +01:00
Michael Moravec
45a8a0c412 [CS] Port Query\Exec\* 2018-01-05 00:28:38 +01:00
Michael Moravec
afa2768f28 [CS] Port Tools\* 2018-01-05 00:28:37 +01:00
Michael Moravec
34ca9fc86e [CS] Port Tools\Pagination\* 2018-01-05 00:28:35 +01:00
Michael Moravec
75e25c1e7c [CS] Port Tools\Event\* 2018-01-05 00:28:33 +01:00
Michael Moravec
4952f82e0b [CS] Port Tools\Console\* 2018-01-05 00:28:31 +01:00
Michael Moravec
2bc1778f80 [CS] Port Utility\* 2018-01-05 00:28:29 +01:00
Michael Moravec
bacb18bd42 [CS] Port Sequencing\* 2018-01-05 00:28:26 +01:00
Michael Moravec
0c7745a0a6 [CS] Port Repository\* 2018-01-05 00:28:24 +01:00
Michael Moravec
f94e5eff85 [CS] Port Reflection\* 2018-01-05 00:28:22 +01:00
Michael Moravec
aef6f17d9c [CS] Port Proxy\* 2018-01-05 00:28:20 +01:00
Michael Moravec
654f3e30ce [CS] Port Persisters\* 2018-01-05 00:28:18 +01:00
Michael Moravec
5221e999c2 [CS] Port Internal\* 2018-01-05 00:28:16 +01:00
Michael Moravec
66cf6d9f58 [CS] Port Cache\* 2018-01-05 00:28:14 +01:00
Michael Moravec
4108b05401 [CS] Port Cache\Region\* 2018-01-05 00:28:12 +01:00
Michael Moravec
b227daa982 [CS] Port Cache\Persister\* 2018-01-05 00:28:10 +01:00
Michael Moravec
f138011843 [CS] Port Cache\Logging\* 2018-01-05 00:28:08 +01:00
Michael Moravec
0ec1e1ceb2 [CS] Port Event\* 2018-01-05 00:28:06 +01:00
Michael Moravec
bd8dd42336 [CS] Port Decorator\EntityManagerDecorator 2018-01-05 00:28:05 +01:00
Michael Moravec
0c0b3e9682 [CS] Port Configuration\MetadataConfiguration 2018-01-05 00:28:02 +01:00
Michael Moravec
557d7600b2 Port Annotation\* 2018-01-05 00:26:44 +01:00
Michael Moravec
37db536a66 [CS] Port UnitOfWork 2018-01-05 00:26:42 +01:00
Michael Moravec
05e7049c44 [CS] Port UnexpectedResultException 2018-01-05 00:26:41 +01:00
Michael Moravec
b9c3d3dfcf [CS] Port TransactionRequiredException 2018-01-05 00:26:39 +01:00
Michael Moravec
b497a079d3 [CS] Port QueryBuilder 2018-01-05 00:26:36 +01:00
Michael Moravec
0b4276f738 [CS] Port Query 2018-01-05 00:26:34 +01:00
Michael Moravec
c236f3e846 [CS] Port PessimisticLockException 2018-01-05 00:26:32 +01:00
Michael Moravec
3dab1396c5 [CS] Port PersistentObject 2018-01-05 00:26:29 +01:00
Michael Moravec
17c06f5d04 [CS] Port PersistentCollection 2018-01-05 00:26:27 +01:00
Michael Moravec
3f3996cc47 [CS] Port ORMInvalidArgumentException 2018-01-05 00:26:23 +01:00
Michael Moravec
a1037fee9b [CS] Port ORMException 2018-01-05 00:26:17 +01:00
Michael Moravec
b7776e1ca3 [CS] Port OptimisticLockException 2018-01-05 00:26:14 +01:00
Michael Moravec
34b8b4f086 [CS] Port NoResultException 2018-01-05 00:26:12 +01:00
Michael Moravec
3c9172bad8 [CS] Port NonUniqueResultException 2018-01-05 00:26:09 +01:00
Michael Moravec
d6332695fd [CS] Port NativeQuery 2018-01-05 00:26:07 +01:00
Michael Moravec
8796f460a7 [CS] Port AbstractQuery 2018-01-05 00:26:01 +01:00
Michael Moravec
36c4e7df43 [CS] Port LazyCriteriaCollection 2018-01-05 00:25:59 +01:00
Michael Moravec
67d275f451 [CS] Port Events 2018-01-05 00:25:58 +01:00
Michael Moravec
088d3b0217 [CS] Port EntityRepository 2018-01-05 00:25:55 +01:00
Michael Moravec
96ed87b2a5 [CS] Port EntityNotFoundException 2018-01-05 00:25:54 +01:00
Michael Moravec
a8235bcfc3 [CS] Port Cache 2018-01-05 00:25:51 +01:00
Michael Moravec
d2a70d030e [CS] Port Configuration 2018-01-05 00:25:48 +01:00
Michael Moravec
0bd7ffc266 [CS] Port EntityManagerAware 2018-01-05 00:25:45 +01:00
Michael Moravec
5411108c83 [CS] Port EntityManager 2018-01-05 00:25:32 +01:00
Michael Moravec
7681b89137 [CS] Port EntityManagerInterface 2018-01-05 00:25:07 +01:00
Michael Moravec
e42a888dad Use newer CS 2018-01-05 00:19:43 +01:00
Marco Pivetta
8be1e3f1d3 Merge pull request #6952 from Slamdunk/cs_explicit_indirect_variable
[CS] Add curly braces to indirect variables
2018-01-04 08:14:48 +01:00
Marco Pivetta
9452f024c7 Merge pull request #6963 from SenseException/schema-event-doc
[Documentation] Add documentation for ToolEvents
2018-01-04 07:54:07 +01:00
Claudio Zizza
f5b434af25 Add root namespace to full classname 2018-01-03 23:59:30 +01:00
Claudio Zizza
81b1f277a0 Update loadClassMetadata example and add namespaces 2018-01-03 23:59:19 +01:00
Claudio Zizza
dcdc8a5a45 Add ToolEvents of SchemaTool 2018-01-03 23:59:02 +01:00
Marco Pivetta
53ace82169 Merge pull request #6960 from hultberg/patch-1
fix UPGRADE instruction to correct version
2018-01-03 10:36:11 +01:00
Edvin Hultberg
83a312187b fix UPGRADE instruction to correct version
For v2.6.0, $className argument was removed from `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()` via #6216. However, the documented minor BC break for upgrading to v2.6 was put under Upgrade to 2.5. This commit fixes this by moving it to v2.6
2018-01-03 10:33:54 +01:00
Michael Moravec
e48c033839 README: Update versions 2018-01-02 21:56:40 +01:00
Marco Pivetta
f53b0399f3 Merge pull request #6955 from doctrine/develop
Merge develop into master
2018-01-02 21:11:23 +01:00
Michael Moravec
a064d9fd17 Merge pull request #6909 from carusogabriel/combine_consecutive_unsets
Combine consecutive unsets
2018-01-02 19:55:14 +01:00
Michael Moravec
21d521e187 Merge pull request #6951 from Slamdunk/cs_no_whitespace_in_blank_line
[CS] Remove trailing whitespace at the end of blank lines
2018-01-02 19:50:55 +01:00
Michael Moravec
a67b533d00 Merge pull request #6950 from Slamdunk/cs_phpdoc_trim
[CS] PHPDoc: remove empty lines at start/end
2018-01-02 19:45:49 +01:00
Michael Moravec
ff6541503e Merge pull request #6949 from Slamdunk/cs_single_blank_line_at_eof
[CS] Exactly one line-feed at end-of-file
2018-01-02 19:43:17 +01:00
Michael Moravec
5f6cdb150f Merge pull request #6953 from Slamdunk/cs_method_argument_space
[CS] Method argument space: no space before comma, at least one space after
2018-01-02 19:41:36 +01:00
Michael Moravec
2537c7f52b SetupTest: Fix namespace name for dynamic directory name (introduced in cebe3005) 2018-01-02 18:12:16 +01:00
Filippo Tessarotto
a0d8a05a7b Apply method_argument_space 2018-01-02 10:28:19 +01:00
Filippo Tessarotto
d1fedbf05d Apply explicit_indirect_variable 2018-01-02 10:09:21 +01:00
Filippo Tessarotto
3102d97173 Apply no_whitespace_in_blank_line 2018-01-02 10:00:37 +01:00
Filippo Tessarotto
77edd5310c Apply phpdoc_trim 2018-01-02 09:55:15 +01:00
Filippo Tessarotto
24114afb14 Apply single_blank_line_at_eof 2018-01-02 09:52:06 +01:00
Marco Pivetta
d1cf9a7617 Merge pull request #6947 from Slamdunk/visibility
[CS] Require explicit visibility
2018-01-02 09:46:23 +01:00
Marco Pivetta
9800ca7657 Merge pull request #6948 from Slamdunk/cs_is_null
[CS] is_null($var) -> null === $var
2018-01-02 09:45:26 +01:00
Filippo Tessarotto
cfba9efd4e Apply visibility 2018-01-02 09:40:23 +01:00
Filippo Tessarotto
85ab80a039 Apply is_null 2018-01-02 09:39:46 +01:00
Marco Pivetta
d1e561eb13 Merge pull request #6936 from Majkl578/dev/entity-same-namespace-removal
Remove support for same-namespace class resolution
2018-01-02 09:38:25 +01:00
Michael Moravec
cd590f2f33 Remove support for same-namespace class resolution 2018-01-01 21:13:55 +01:00
Michael Moravec
d4ed772b07 Require PHP 7.2, drop <7.2 in Composer & on Travis 2018-01-01 20:30:36 +01:00
Marco Pivetta
324dfa4e23 Merge pull request #6945 from carusogabriel/clean-up
Whitespaces clean-up in docs
2018-01-01 19:08:24 +01:00
Marco Pivetta
391e101cba Merge pull request #6929 from carusogabriel/null-coalesce-operator
[CS] Clean elses
2018-01-01 19:06:49 +01:00
Marco Pivetta
63b8c4db3b Merge pull request #6931 from Slamdunk/cs_phpdoc_opening_closing
[CS] Standardize docblock asterisks
2018-01-01 19:06:17 +01:00
Marco Pivetta
fee52ddcfa Merge pull request #6925 from Slamdunk/co_braces
[CS] Phpdoc (and related) blocks indentation
2018-01-01 13:48:20 +01:00
Filippo Tessarotto
9ad525a34f Apply braces 2018-01-01 11:15:38 +01:00
Filippo Tessarotto
d0f998ce45 Apply phpdoc_opening_closing 2018-01-01 11:00:01 +01:00
Gabriel Caruso
1ffde77a87 Remove extra lines 2017-12-31 17:42:54 -02:00
Gabriel Caruso
f4c84883ec Trailing whitespaces 2017-12-31 17:42:14 -02:00
Gabriel Caruso
46836d38ac [CS] Clean elses 2017-12-31 17:39:11 -02:00
Gabriel Caruso
57ee603fec Combine consecutive unsets 2017-12-31 17:35:00 -02:00
Luís Cobucci
cebe3005a6 Use a valid directory to not break the test 2017-12-31 18:37:19 +01:00
Luís Cobucci
111898a269 Mark test as incomplete
So that we can check if they're still relevant and, if so, adapt them.
2017-12-31 18:37:18 +01:00
Luís Cobucci
a0342c87dd Add assertion to ManyToManyBasicAssociationTest#testUpdateDeleteSizeSubselectQueries 2017-12-31 18:37:18 +01:00
Luís Cobucci
7da3c2d678 Revert sync with master changes
Removing changes introduced in #6767, which we should sort out before
of merging `develop` into `master`.
2017-12-31 18:37:18 +01:00
Michael Moravec
81ace85f29 Replace spl_object_hash() with more efficient spl_object_id() 2017-12-31 18:37:18 +01:00
Filippo Tessarotto
b01a9a3ea1 Apply line_ending 2017-12-31 18:37:18 +01:00
Filippo Tessarotto
945e98dcaf Apply ternary_operator_spaces 2017-12-31 18:37:17 +01:00
Filippo Tessarotto
52a5ac4bb8 Apply no_null_property_initialization 2017-12-31 18:37:17 +01:00
Filippo Tessarotto
75c1cfc0f4 Apply no_unused_imports 2017-12-31 18:37:17 +01:00
Filippo Tessarotto
489eb411a3 Apply align_multiline_comment 2017-12-31 18:37:17 +01:00
Filippo Tessarotto
2bc55103fd Apply lowercase_keywords 2017-12-31 18:37:16 +01:00
Filippo Tessarotto
368e10a4df Apply lowercase_constants 2017-12-31 18:37:16 +01:00
Filippo Tessarotto
5d496f145e Apply blank_line_after_namespace 2017-12-31 18:37:16 +01:00
Filippo Tessarotto
b7f2147015 Apply no_alias_functions 2017-12-31 18:37:16 +01:00
Filippo Tessarotto
2cb9df201f Apply no_empty_statement 2017-12-31 18:37:16 +01:00
Filippo Tessarotto
fc8942786d Apply blank_line_after_opening_tag 2017-12-31 18:37:15 +01:00
Filippo Tessarotto
a580ef9911 Apply escape_implicit_backslashes,heredoc_to_nowdoc 2017-12-31 18:37:15 +01:00
Filippo Tessarotto
d9e419f4a2 Apply bare heredoc_to_nowdoc 2017-12-31 18:37:15 +01:00
Filippo Tessarotto
7b574c25d2 Apply elseif 2017-12-31 18:37:15 +01:00
Gabriel Caruso
8d3736ddae Combine consecutives issets 2017-12-31 18:37:15 +01:00
Guilherme Blanco
d05e8e8285 Ephemeral UnitOfWork 2017-12-31 18:37:14 +01:00
Michael Moravec
e376962ab5 Also run Scrutinizer under PHP 7.2 2017-12-31 18:37:14 +01:00
Gabriel Caruso
5798192e61 Use Null Coalesce Operator 2017-12-31 18:37:14 +01:00
Michael Moravec
e4ce75a8dc Remove code generation tools & commands 2017-12-31 18:37:14 +01:00
Claudio Zizza
c4eb2b016d Remove copy method from EntityManager 2017-12-31 18:37:14 +01:00
Gabriel Caruso
ede29990e3 Refactoring more tests 2017-12-31 18:37:13 +01:00
Marco Pivetta
01bb774273 #6867 as per discussion with @guilhermeblanco, dropping ObjectManagerAware support, as the ClassMetadata API radically changed 2017-12-31 18:37:13 +01:00
Marco Pivetta
29beae2daa Ensuring that the ObjectManagerAware and EntityManagerAware objects are initialized even if they are un-initialized proxies 2017-12-31 18:37:13 +01:00
Marco Pivetta
28941f37cd Ensuring that ObjectManagerAware and EntityManagerAware are equally handled 2017-12-31 18:37:13 +01:00
Marco Pivetta
d1ceb55c68 Hardening DDC2231 test - both ObjectManagerAware and EntityManagerAware should work 2017-12-31 18:37:12 +01:00
Marco Pivetta
575639cb49 Forgot to set the EntityManager instance also when dealing with EntityManagerAware objects (was only using ObjectManagerAware) 2017-12-31 18:37:12 +01:00
Marco Pivetta
55863d6101 Verifying that the entity in DDC2231Test is actually EntityManagerAware 2017-12-31 18:37:12 +01:00
Guilherme Blanco
aa9f11e11d Fixes after rebase 2017-12-31 18:37:12 +01:00
Marco Pivetta
cd1d527b57 Static analysis fixes as reported by scrutinizer-ci 2017-12-31 18:37:12 +01:00
Marco Pivetta
a25fdb56fc Making sure the ProxyFactory#createProxy() is always given a ClassMetadata instance
Ref: https://github.com/doctrine/doctrine2/pull/6719#discussion_r140646281
2017-12-31 18:37:11 +01:00
Marco Pivetta
36e6526cd9 Documented removal of getProxyDir, getProxyNamespace and getAutoGenerateProxyClasses methods 2017-12-31 18:37:11 +01:00
Marco Pivetta
059d7ad08d BC Break: Removing getProxyDir, getProxyNamespace and getAutoGenerateProxyClasses methods
Also cleans up the `Doctrine\ORM\Configuration` class, which used weird array access to save some memory (no longer true since PHP 5.4+)
2017-12-31 18:37:11 +01:00
Marco Pivetta
9b91c2dc6c Adding missing trailing comma as per @jaapio's review
Ref: https://github.com/doctrine/doctrine2/pull/6719#discussion_r140642661
2017-12-31 18:37:11 +01:00
Marco Pivetta
c0be5ab403 Removing NonLoadingPersister specifics that are not to be used in benchmarks 2017-12-31 18:37:11 +01:00
Marco Pivetta
83e7e07464 Ignoring phpbench.phar and the associated public key 2017-12-31 18:37:10 +01:00
Marco Pivetta
0ce63c0160 Cleaning up performance tests before attempting a fix of the ProxyInitializationTimeBench 2017-12-31 18:37:10 +01:00
Marco Pivetta
45b7cd0d01 Clarifying that also reflection now triggers lazy-loading 2017-12-31 18:37:10 +01:00
Marco Pivetta
67d6da5226 Documenting introduced BC breaks in the proxy refactoring 2017-12-31 18:37:10 +01:00
Marco Pivetta
4e06a15dc8 Marking the StaticProxyFactory as internal, fixing type hint documentation 2017-12-31 18:37:10 +01:00
Marco Pivetta
3411950c6c Removing erroneous documentation about final methods, c__lone and __wakeup details that no longer hold true 2017-12-31 18:37:09 +01:00
Marco Pivetta
8315460083 Cleaning documentation around proxy behavior 2017-12-31 18:37:09 +01:00
Marco Pivetta
e7ace6f8a0 Replacing generated proxy example with a stub resembling current state 2017-12-31 18:37:09 +01:00
Marco Pivetta
52cc90b205 Removing reference to previous proxy model 2017-12-31 18:37:09 +01:00
Marco Pivetta
f00c433746 Removing documented quirks about proxy lazy-loading semantics combined with reflection: now always holding true 2017-12-31 18:37:09 +01:00
Marco Pivetta
62d664b6b1 Removing FAQ entry for item that no longer holds true about public property lazy-loading semantics 2017-12-31 18:37:09 +01:00
Marco Pivetta
7f292d3ee0 s/lazy-load/lazy-loading 2017-12-31 18:37:08 +01:00
Marco Pivetta
4c0f6fc513 Updating documentation about proxy serialization 2017-12-31 18:37:08 +01:00
Marco Pivetta
5eea2a6944 func_get_args() now works within proxies: YAY! 2017-12-31 18:37:08 +01:00
Marco Pivetta
a30a0b9cb6 Removing redundant/conflicting proxy documentation, correcting proxy performance/autoloading docs 2017-12-31 18:37:08 +01:00
Marco Pivetta
566b777834 Adapting proxy configuration documentation to the new changes in the proxy handling in ORM v3 2017-12-31 18:37:08 +01:00
Marco Pivetta
cae465b850 Using AUTOGENERATE_EVAL during test suite runs 2017-12-31 18:37:07 +01:00
Marco Pivetta
e3592b5ca7 Verifying friend object "Comparable" behavior when using proxies that access each other's private state 2017-12-31 18:37:07 +01:00
Marco Pivetta
08661094fc Verifying that ghost object friend objects are not initialized when not reading lazy state 2017-12-31 18:37:07 +01:00
Marco Pivetta
4e7d5c84e3 Removing fixed TODO about identifier handling 2017-12-31 18:37:07 +01:00
Marco Pivetta
aeff25f7df Verifying that identifier order being swapped does not cause identifier references to be lost 2017-12-31 18:37:07 +01:00
Marco Pivetta
4810a308eb Verifying that identifiers are preserved when using EntityManager#getReference() 2017-12-31 18:37:07 +01:00
Marco Pivetta
092457fb2e Extracting NormalizeIdentifierTest assets to a Model namespace 2017-12-31 18:37:06 +01:00
Marco Pivetta
8af59cd3de Marking utility classes as internal 2017-12-31 18:37:06 +01:00
Marco Pivetta
ded2e05be7 Inlining variables that are only used once 2017-12-31 18:37:06 +01:00
Marco Pivetta
07844788ca Minimal NormalizeIdentifier documentation 2017-12-31 18:37:06 +01:00
Marco Pivetta
fe0df7f47d Using NormalizeIdentifier inside the UnitOfWork instead of relying on an additional internal method 2017-12-31 18:37:06 +01:00
Marco Pivetta
076c184d7e Implemented NormalizeIdentifier as per specification
Also made sure the `EntityManager` does not ruin identifiers when `getReference` is being called
2017-12-31 18:37:05 +01:00
Marco Pivetta
449111cecb Completing specification for nested reference identifier normalization 2017-12-31 18:37:05 +01:00
Marco Pivetta
567879d932 Simplified spec for an identifier normalizer 2017-12-31 18:37:05 +01:00
Marco Pivetta
58f11eaec7 Simplifying generateProxyClasses code 2017-12-31 18:37:05 +01:00
Marco Pivetta
d79c6672c5 Removing duplicate TODO that was moved to a private method documentation 2017-12-31 18:37:05 +01:00
Marco Pivetta
f2645f0157 Removing solved TODOs and improved documentation around why we work with an un-managed proxy anyway 2017-12-31 18:37:05 +01:00
Marco Pivetta
a28197a321 Removing solved TODO in the Parser changes 2017-12-31 18:37:04 +01:00
Marco Pivetta
1e40657862 Removing unused import 2017-12-31 18:37:04 +01:00
Marco Pivetta
d1b2f62b3c Dead code removal 2017-12-31 18:37:04 +01:00
Marco Pivetta
4e42bfc45f Removed ClassUtils shim: class has been internalized 2017-12-31 18:37:04 +01:00
Marco Pivetta
245e1dc2ea Verifying all of the StaticClassNameConverterTest API 2017-12-31 18:37:04 +01:00
Marco Pivetta
82062b6df3 Basic tests for the StaticClassNameConverterTest 2017-12-31 18:37:03 +01:00
Marco Pivetta
3e3b7cf1f2 Replacing ClassUtils shim usage with a new StaticClassNameConverter with less features 2017-12-31 18:37:03 +01:00
Marco Pivetta
1e7a0574fe Removing all unused proxy-related components 2017-12-31 18:37:03 +01:00
Marco Pivetta
df583fa2cb Improving performance of the StaticProxyFactory by caching all internal components that do not require repeated fetch/build steps 2017-12-31 18:37:03 +01:00
Marco Pivetta
c3ffd0cd22 Using the LazyLoadingGhostFactory to trigger proxy generation 2017-12-31 18:37:03 +01:00
Marco Pivetta
a81754342a Rewriting StaticProxyFactory so its constructor just receives a GhostObjectFactory as constructor argument, and that's it 2017-12-31 18:37:02 +01:00
Marco Pivetta
5ae48b3799 SetupTest was using invalid proxy paths 2017-12-31 18:37:02 +01:00
Marco Pivetta
05463f43ca setProxyNamespace() should also update the wrapped ProxyManager\Configuration instance 2017-12-31 18:37:02 +01:00
Marco Pivetta
d1cb92e497 Adding Doctrine\ORM\Configuration API as per test specification 2017-12-31 18:37:02 +01:00
Marco Pivetta
820ffc5f2a Verifying that the Doctrine\ORM\Configuration builds separate and independent ghost object factory instances 2017-12-31 18:37:02 +01:00
Marco Pivetta
e510015692 Changing auto-generation flag should also change the ProxyManager\Configuration referenced generator strategy 2017-12-31 18:37:02 +01:00
Marco Pivetta
68cf7661ad Extracting temporary dir generation to a private method 2017-12-31 18:37:01 +01:00
Marco Pivetta
1ccc4ffcfc Setting a proxy path should update the ProxyManager\Configuration instance too 2017-12-31 18:37:01 +01:00
Marco Pivetta
e645a67ab6 Beginning work on the Configuration overhaul - we want to manage a ProxyManager\Configuration instance in the ORM config 2017-12-31 18:37:01 +01:00
Marco Pivetta
f26a43c58a Replace ClassUtils with a temporary, local, deprecated shim that knows about ProxyManager proxy class names 2017-12-31 18:37:01 +01:00
Marco Pivetta
ee47d37f7c The NotifyPropertyChanged API should only be used once we decided to overwrite the entity data, in which case the UnitOfWork is indeed tracking entity state 2017-12-31 18:37:01 +01:00
Marco Pivetta
d18144bef1 Stop registering cloned proxies (real instance being managed) as managed 2017-12-31 18:37:00 +01:00
Marco Pivetta
f0d7fa5344 Correcting ReferenceProxyTest: proxy identifiers are no longer unset while cloning, but they are not considered managed 2017-12-31 18:37:00 +01:00
Marco Pivetta
7e6e89a8f7 Explicit BC break: we no longer call __wakeup on proxies 2017-12-31 18:37:00 +01:00
Marco Pivetta
dd4684d72e Adding stub implementation of logic that converts a flat identifier into a deep identifier 2017-12-31 18:37:00 +01:00
Marco Pivetta
b33abd3c14 SecondLevelCacheManyToOneTest should not cause more queries for fetching associated identifiers (which are already available) 2017-12-31 18:37:00 +01:00
Marco Pivetta
3d204ac1f4 Adding ocramius/proxy-manager dependency 2017-12-31 18:36:59 +01:00
Marco Pivetta
247e7ec711 Correcting metadata factory tests - fetching a class by name does indeed fetch the class even if prefixed 2017-12-31 18:36:59 +01:00
Marco Pivetta
1cc5b8ee91 Correcting DDC-1238 to remove non-sensical semantics that cleared proxy data after detach operations 2017-12-31 18:36:59 +01:00
Marco Pivetta
012a4e2668 Documenting DDC-1022 tests to be removed, as proxy behavior changed around __clone and __wakeup semantics 2017-12-31 18:36:59 +01:00
Marco Pivetta
1308662ce5 Removing DDC-1238 related code. Issue seems to be invalid, as cloning a proxy should NOT modify its state, but just make it non-managed 2017-12-31 18:36:59 +01:00
Marco Pivetta
fa5292bf7c Hardening DDC3223Test with additional pre- and post-conditions 2017-12-31 18:36:59 +01:00
Marco Pivetta
310bed63b1 Correcting DDC2231Test, since proxies no longer initialize if no state is mutated 2017-12-31 18:36:58 +01:00
Marco Pivetta
d36438b21e The DefaultQueryCache still contained instanceof Proxy checks - replaced with GhostObjectInterface checks 2017-12-31 18:36:58 +01:00
Marco Pivetta
fea7f52362 Correcting DDC1690Test: even if a proxy is not yet loaded, it must have notify property listeners to function correctly 2017-12-31 18:36:58 +01:00
Marco Pivetta
5ef465820c Corrected DDC1228Test, which was quite broken and relying on the old Proxy API 2017-12-31 18:36:58 +01:00
Marco Pivetta
51da2adf74 Correcting DDC1193Test, which was using the old Proxy API 2017-12-31 18:36:58 +01:00
Marco Pivetta
79f51bf51c Cleaning up ReferenceProxyTest, which no longer reflects current invariants 2017-12-31 18:36:57 +01:00
Marco Pivetta
2045869985 Fixed reference to non-existing method 2017-12-31 18:36:57 +01:00
Marco Pivetta
7141cb7f9e Transient fields should be skipped by proxy initialization semantics 2017-12-31 18:36:57 +01:00
Marco Pivetta
662a8e43f0 Cascade operations do not apply to non-initialized proxies: no associations are set there 2017-12-31 18:36:57 +01:00
Marco Pivetta
c6bb99113e Hardening ProxiesLikeEntitiesTest by adding some precondition checks 2017-12-31 18:36:57 +01:00
Marco Pivetta
1f0d55686f ORM proxy names may be looked up in the class metadata factory - the parser should not manually do class_exists checks unless strictly required 2017-12-31 18:36:56 +01:00
Marco Pivetta
7fab08bc70 Correcting reference to non-existing method 2017-12-31 18:36:56 +01:00
Marco Pivetta
642574e17d Fixing faulty test that was expecting a different entity to bbe hydrated during proxy initialization 2017-12-31 18:36:56 +01:00
Marco Pivetta
bd53f1980c Correcting ProxyFactoryTests, which were opinionated around past proxy definition 2017-12-31 18:36:56 +01:00
Marco Pivetta
291457f1aa Proxy identifier must be set when the proxy is instantiated 2017-12-31 18:36:56 +01:00
Marco Pivetta
a68c3b4239 Prevent setting any values in un-initialized proxies 2017-12-31 18:36:56 +01:00
Marco Pivetta
b5b3e30d6a Ensuring that the BasicFunctionalTest relies on the GhostObjectInterface API 2017-12-31 18:36:55 +01:00
Marco Pivetta
b45c4fb441 Stubbing out a retry on the s/Proxy/GhostObjectInterface replacement patch 2017-12-31 18:36:55 +01:00
Michael Moravec
3c9737b3d6 Use Generators instead of AppendIterator for properties iteration 2017-12-31 18:36:55 +01:00
Marco Pivetta
5f90854ea6 #6653 removing unused variable as per @alcaeus' review
Ref: https://github.com/doctrine/doctrine2/pull/6653#discussion_r135503849
2017-12-31 18:36:55 +01:00
Marco Pivetta
3127a2a787 #6653 adding note to UPGRADE.md about the removal of Doctrine\ORM\Version 2017-12-31 18:36:55 +01:00
Marco Pivetta
8e46fcda86 Removing build references to the Version class, which is finally gone-gone-gone 2017-12-31 18:36:54 +01:00
Marco Pivetta
6d4168f189 Dropping references to the ORM Version class - using PackageVersions instead 2017-12-31 18:36:54 +01:00
Marco Pivetta
6dabfa3984 Adding ocramius/package-versions as a replacement for the internal ORM Version class 2017-12-31 18:36:54 +01:00
Marco Pivetta
f5a95710a4 #1577 clarifying session user fetching as per @Majkl578's review
Ref: https://github.com/doctrine/doctrine2/pull/1577#discussion_r124162444
2017-12-31 18:36:54 +01:00
Marco Pivetta
5de0435d99 #1577 rephrasing reference to detach that is now clear() instead
Ref: https://github.com/doctrine/doctrine2/pull/1577#discussion_r124126422 - @lcobucci's review
2017-12-31 18:36:54 +01:00
Marco Pivetta
a6526abadb #1577 removed detach reference from the docs, as per @lcobucci's review
Ref: https://github.com/doctrine/doctrine2/pull/1577#discussion_r124127083
2017-12-31 18:36:53 +01:00
Marco Pivetta
ee6b58be97 #1577 removing references to detach and merge cascade operations in newer metadata driver implementations 2017-12-31 18:36:53 +01:00
Marco Pivetta
37c0225f7d #1577 re-enabling clear($entityName) tests 2017-12-31 18:36:53 +01:00
Marco Pivetta
4c95f57996 Corrected XmlExporter order of exported cascades 2017-12-31 18:36:53 +01:00
Marco Pivetta
e5cb4e5455 #1577 removing merge and detach related mapping operations in cascades in tests 2017-12-31 18:36:53 +01:00
Marco Pivetta
165f395aef #1577 merge and detach are no longer accepted cascade mappings 2017-12-31 18:36:52 +01:00
Marco Pivetta
911ec07f33 #1577 removing all references to merge operations in the docs 2017-12-31 18:36:52 +01:00
Marco Pivetta
784653ad6f #1577 removing documentation about merge/detach methods, replaced by clear() documentation only 2017-12-31 18:36:52 +01:00
Marco Pivetta
434bb7f68d #1577 removing references to merge/detach cascade operations 2017-12-31 18:36:52 +01:00
Marco Pivetta
a13dd43f96 #1577 removing cascade-merge and cascade-detach from mapping examples 2017-12-31 18:36:52 +01:00
Marco Pivetta
30463c452b #1577 removing cascade-merge and cascade-detach from mapping examples 2017-12-31 18:36:52 +01:00
Marco Pivetta
e4c9057a47 #1577 removing <cascade-merge/> and <cascade-detach/> from the XSD 2017-12-31 18:36:51 +01:00
Marco Pivetta
8f993266fe #1577 removing detach() calls references from the batch processing docs, adding links to existing batch processing utilities 2017-12-31 18:36:51 +01:00
Marco Pivetta
c14bc2f34f #1577 re-wording 'working with sessions' section, so that users are discouraged from storing entities in sessions 2017-12-31 18:36:51 +01:00
Marco Pivetta
065fdfdde6 #1577 re-adding merge() and detach(), since a release of doctrine/common is required to completely remove those methods from the ObjectManager interface first 2017-12-31 18:36:51 +01:00
Marco Pivetta
854d069627 #1577 adding upgrade notes about the BC break caused by merge() and detach() semantics removal 2017-12-31 18:36:51 +01:00
Marco Pivetta
909993ae3e #1577 remove newly introduced tests around merge/detach semantics, which were dropped 2017-12-31 18:36:50 +01:00
Marco Pivetta
3f29ff74ee Removing clear($entityName) functionality (for now) 2017-12-31 18:36:50 +01:00
Marco Pivetta
5dfe5069ab Disabling test around the EntityManager#clear($entityName) functionality (improvement needed) 2017-12-31 18:36:50 +01:00
Marco Pivetta
df397e1d68 Removing EntityManager#detach() 2017-12-31 18:36:50 +01:00
Marco Pivetta
98d93ad74a Removing tests around the detach functionality 2017-12-31 18:36:50 +01:00
Marco Pivetta
f26bfadaef Note about sections to be removed 2017-12-31 18:36:49 +01:00
Marco Pivetta
b80a676e9a Adding @TODO about batch-processing and detach() operations: clear() may deal with it directly. 2017-12-31 18:36:49 +01:00
Marco Pivetta
16a085ff67 Adding TODO: serialization of entities is something the ORM shouldn't deal with (directly) anymore 2017-12-31 18:36:49 +01:00
Marco Pivetta
f6b2e3fe63 Removing merge operations from the UnitOfWork 2017-12-31 18:36:49 +01:00
Marco Pivetta
cf4d4cc260 Removing EntityManager#merge() logic 2017-12-31 18:36:49 +01:00
Marco Pivetta
06a94e2688 Removing tests around EntityManager#merge() and similar operations 2017-12-31 18:36:49 +01:00
Marco Pivetta
edf3335e60 Removing functional test cases covering EntityManager#merge() semantics 2017-12-31 18:36:48 +01:00
Guilherme Blanco
4fb9f48f1a TableMetadata should only exist if ClassMetadata has a table. Started some concept work around Mapping Binders. 2017-12-31 18:36:48 +01:00
Marco Pivetta
8b40c6ef90 Removing is_array check on a type that can only have two possible states - using instanceof instead 2017-12-31 18:36:48 +01:00
Marco Pivetta
0f36bc769f Removing useless array_key_exists() calls, using ?? operator instead 2017-12-31 18:36:48 +01:00
Marco Pivetta
3a93402720 Removing useless post-array-population association hydration via array_walk() 2017-12-31 18:36:48 +01:00
Marco Pivetta
49d553808d Removing useless variable 2017-12-31 18:36:47 +01:00
Marco Pivetta
fa563476a3 Removing useless checking for never-used parameter, inlining merge operation 2017-12-31 18:36:47 +01:00
Marco Pivetta
c6116d4e55 Replacing possible O(n^2) operation from PersistentCollection diffing operations 2017-12-31 18:36:47 +01:00
Marco Pivetta
f2cf2ea203 Reverting dangerous index lookup operations - resultset may contain gaps in the results 2017-12-31 18:36:47 +01:00
Guilherme Blanco
8be6b5862e Moved class metadata binding to proper methods 2017-12-31 18:36:47 +01:00
Marco Pivetta
368a2f26e8 Avoiding fallback function lookup when counting expression parts in an OrderBy query builder expression 2017-12-31 18:36:46 +01:00
Marco Pivetta
eaebc57f7c Avoiding fallback function lookup when counting expression parts in a generic query builder expression 2017-12-31 18:36:46 +01:00
Marco Pivetta
4e2d1809ba Removed typical if (count(expr)) { issue from sql walker 2017-12-31 18:36:46 +01:00
Marco Pivetta
12d36551c5 Removed counting of sql parts when deciding whether to wrap parts in parentheses 2017-12-31 18:36:46 +01:00
Marco Pivetta
2417eb8e3f Removing counting in an iteration when generating aliases 2017-12-31 18:36:46 +01:00
Marco Pivetta
1cda08cf11 Removing some useless counting operations from the DQL parser 2017-12-31 18:36:45 +01:00
Marco Pivetta
1aeda7ffdb Removing counting from getSelectConditionStatementSQL 2017-12-31 18:36:45 +01:00
Marco Pivetta
5df39072db Removed counting of local variable when generating filter clauses for many to many collections 2017-12-31 18:36:45 +01:00
Marco Pivetta
b37e8d6fb7 Removing useless counting on whether subclasses are present 2017-12-31 18:36:45 +01:00
Marco Pivetta
48e8fdeb25 Fixing check of inheritance on a mapped superclass 2017-12-31 18:36:45 +01:00
Marco Pivetta
0b70c8f239 Removing useless counting of the discriminator map during metadata loading 2017-12-31 18:36:45 +01:00
Marco Pivetta
0768fa502f Quicker check on whether an identifier is composite - removes counting 2017-12-31 18:36:44 +01:00
Marco Pivetta
0e8fad8fc3 Removing counting of generated id fields when checking for composite identifiers 2017-12-31 18:36:44 +01:00
Marco Pivetta
7fca3b151b Avoiding counting scalars at every iteration - simply checking once is enough 2017-12-31 18:36:44 +01:00
Marco Pivetta
9a6eb7ddb8 Avoid repeated counting of root aliases in ArrayHydrator 2017-12-31 18:36:44 +01:00
Marco Pivetta
61845d390c Avoiding counting aliases when determining whether a query is simple or not 2017-12-31 18:36:44 +01:00
Marco Pivetta
d248dc4084 Removing counting operations in the UnitOfWork 2017-12-31 18:36:43 +01:00
Marco Pivetta
499147f256 Removing counting operations around parameter handling in QueryBuilder 2017-12-31 18:36:43 +01:00
Marco Pivetta
cb530da8db Removing useless counting around query resultset handling 2017-12-31 18:36:43 +01:00
Marco Pivetta
aa155c8cd4 Removing useless count() operations from the PersistentCollection internals 2017-12-31 18:36:43 +01:00
Marco Pivetta
b3a27357d6 Removing count() operations from the AbstractQuery implementations 2017-12-31 18:36:42 +01:00
Guilherme Blanco
58f212d85d Fixed broken tests 2017-12-31 18:36:42 +01:00
Guilherme Blanco
ca83c24d90 Removed PHP and Static PHP mapping drivers, since they will be irrelevant with the new ClassMetadataFactory 2017-12-31 18:36:42 +01:00
Guilherme Blanco
5b5caf7c58 More simplifications in ClassMetadataFactory. 2017-12-31 18:36:42 +01:00
Guilherme Blanco
c39a8d2fac ClassMetadataFactory changed to bring ClassMetadata instantiation closer to MappingDriver loading call. Changed how ReflectionService is called for ClassMetadata initialization, simplifying calls. 2017-12-31 18:36:42 +01:00
Usman Zafar
e9c7a49961 updated the heading for upgrade doc 2017-12-31 18:36:41 +01:00
Usman Zafar
e409a16d2c Reverted the public method on construct to protected. Updated the tests. 2017-12-31 18:36:41 +01:00
Guilherme Blanco
37eaea65d9 Some quick typehints 2017-12-31 18:36:41 +01:00
Usman Zafar
0b33ee53c1 change the em property to fix the tests and UPGRADE file as per the PR comments. 2017-12-31 18:36:41 +01:00
Guilherme Blanco
a84cac9787 More optimizations and preparation for ClassMetadataFactory replacement. Integration with ComponentMetadata started. 2017-12-31 18:36:41 +01:00
Michael Moravec
53ae602eeb Typehint EntityManagerInterface instead of EntityManager where possible 2017-12-31 18:36:40 +01:00
Usman Zafar
4e17974139 fixed the broken tests and updated the UPGRADE guide to notify about BC 2017-12-31 18:36:40 +01:00
mike
7b15963025 Add missing strict type declaration 2017-12-31 18:36:40 +01:00
mike
69893797f7 Remove another copyright header 2017-12-31 18:36:40 +01:00
mike
1c9221df0d removing copyright headers in every files 2017-12-31 18:36:39 +01:00
Usman Zafar
fb7f3bc01c added the test and updated the entitymanager 2017-12-31 18:36:39 +01:00
Michael Moravec
578b1d3dd2 Typehint EntityManagerInterface instead of EntityManager where possible 2017-12-31 18:36:38 +01:00
Usman Zafar
6b7a81690d added the final keyword EntityManager to indicate that extending EntityManager is not a valid extension point. 2017-12-31 18:36:38 +01:00
Guilherme Blanco
808e07d97c Added concept of SecondPass to ClassMetadataFactory 2017-12-31 18:36:38 +01:00
Guilherme Blanco
d5653b22b9 Added information of action items for later. 2017-12-31 18:36:38 +01:00
Guilherme Blanco
e626d1ca95 Fixed issue with associatied entities implementing __toString() 2017-12-31 18:36:38 +01:00
Michael Moravec
06d56de0e9 Drop AssignedGenerator (obsoleted by ValueGenerationPlan) 2017-12-31 18:36:38 +01:00
Michael Moravec
88fcda9a02 Added ValueGeneratorMetadata, implemented ValueGeneratorPlan 2017-12-31 18:36:37 +01:00
Michael Moravec
17e7c2a42e Fix type errors 2017-12-31 18:36:37 +01:00
Michael Moravec
6a9b817717 Enable strict types 2017-12-31 18:36:36 +01:00
Guilherme Blanco
38c21f361f Moved ClassMetadata::assignIdentifier to EntityPersister::setIdentifier. Renamed EntityPersister::getIdentifierValues to EntityPersister::getIdentifier 2017-12-31 18:36:35 +01:00
Guilherme Blanco
81ced236e5 Moved getIdentifierValues to EntityPersister 2017-12-31 18:36:35 +01:00
Guilherme Blanco
fa3d2f9102 Moved computation of FCQN resolution to Metadata building step. 2017-12-31 18:36:35 +01:00
Guilherme Blanco
2db53bdbfa Decoupled Generator and GeneratorStrategy 2017-12-31 18:36:35 +01:00
Guilherme Blanco
4a427b8b7b Eliminated the concept of ClassMetadataInterface temporarily until refactoring is complete. Replaced ObjectManagerAware with EntityManagerAware sinc eit was bound to the Common\Persistence namespace with hard dependencies. The same happened to PersistentObject. 2017-12-31 18:36:34 +01:00
Guilherme Blanco
802c94fa05 Moved MappingDriver to ORM to allow changes in its API 2017-12-31 18:36:34 +01:00
Guilherme Blanco
91ae8e25d3 Simplified RSM building for result class 2017-12-31 18:36:34 +01:00
Guilherme Blanco
e48f537590 Simplified logic and stored information around named native query. 2017-12-31 18:36:34 +01:00
Guilherme Blanco
b8d7d82b73 Implemented runtime resolution of __CLASS__ calls to SqlResultSetMapping, NativeNamedQuery and NamedQuery. Dropped isSelfClass from mappings 2017-12-31 18:36:34 +01:00
Guilherme Blanco
ff7a18aeb9 Simplified NamedQuery support 2017-12-31 18:36:33 +01:00
Luís Cobucci
ea6723eca8 Replace $this->assertXXX with self::assertXXX
Since PHPUnit test methods are all static
2017-12-31 18:36:33 +01:00
Guilherme Blanco
f954914db1 Consume custom repository class through methods only. 2017-12-31 18:36:33 +01:00
Guilherme Blanco
9c562b24d5 Removed EntityListenerBuilder 2017-12-31 18:36:33 +01:00
Guilherme Blanco
f2d1048e15 Removed direct access to ClassMetadata->name 2017-12-31 18:36:33 +01:00
Guilherme Blanco
abace42835 Removed direct access to ClassMetadata->name 2017-12-31 18:36:32 +01:00
Guilherme Blanco
c0da37443b Ensure we are using getClassName() which will be later available in ComponentMetadata 2017-12-31 18:36:32 +01:00
Guilherme Blanco
50efd6827b Return array of identifier field names instead of hardcoded identifier 2017-12-31 18:36:32 +01:00
Guilherme Blanco
f4d031ca4d Removed enqueueing for insert support from EntityPersister. There was no reason for that since concurrent inserts NEVER guarantee incremental order on a single INSERT statement. Any attempt to reliably implement is not accurate and thus I am removing any possibility of this ever becoming a reality. 2017-12-31 18:36:32 +01:00
Michael Moravec
df33284028 Move identifier generation to FieldMetadata 2017-12-31 18:36:31 +01:00
Guilherme Blanco
a5cf75c3ab Optimizations around EntityPersisters 2017-12-31 18:36:31 +01:00
Guilherme Blanco
f8061618ef Moved IdentifierFlattener temporarily to EntityManager and removed one todo 2017-12-31 18:36:31 +01:00
Guilherme Blanco
c197fb4ffe Revert "[3.0] Moved IdentifierGenerator to Property." 2017-12-31 18:36:31 +01:00
Michael Moravec
88a3f9bfb9 WIP 2017-12-31 18:36:31 +01:00
Michael Moravec
6cf0d72587 Populate FieldMetadata ID generator 2017-12-31 18:36:30 +01:00
Michael Moravec
e0e90ce5c4 OrmTestCase: Always use full AnnotationReader, drop legacy versions 2017-12-31 18:36:30 +01:00
Guilherme Blanco
bd50902da0 Removed sequence methods from class metadata 2017-12-31 18:36:30 +01:00
Guilherme Blanco
ef030bbfc2 Removed Instantiator from ClassMetadata and concentrate into UnitOfWork 2017-12-31 18:36:30 +01:00
Guilherme Blanco
c112f27362 Added generation strategy for class metadata 2017-12-31 18:36:30 +01:00
Guilherme Blanco
6cb027cd4f Quick fix 2017-12-31 18:36:29 +01:00
Marco Pivetta
4c63a562be #6118 upgrade notes mentioning the BC break in EntityManager#flush() 2017-12-31 18:36:29 +01:00
Marco Pivetta
af5281b6d7 #6118 removing UnitOfWork#commit\($entity\) signature 2017-12-31 18:36:29 +01:00
Marco Pivetta
c948b3e2e5 #6118 removing flush\($entity\) usage from the test suite 2017-12-31 18:36:29 +01:00
Marco Pivetta
e204b96d1d #6118 remove flush\($entity\)` signature 2017-12-31 18:36:29 +01:00
Marco Pivetta
ea2ec0740f #6118 remove reference to single entity flush operations 2017-12-31 18:36:28 +01:00
Marco Pivetta
2725e2e705 #6118 removing DDC-720 related tests for multiple flushed entities 2017-12-31 18:36:28 +01:00
Marco Pivetta
f40ea70ac4 #6118 removing DDC-720 related tests 2017-12-31 18:36:28 +01:00
Guilherme Blanco
2366b43b1e Removed Builders 2017-12-31 18:36:28 +01:00
Guilherme Blanco
6af9320dc8 Fixed tetss after rebase 2017-12-31 18:36:28 +01:00
Guilherme Blanco
7f1eedaaa6 Fix bugs related to rebase 2017-12-31 18:36:27 +01:00
Guilherme Blanco
46341a6e17 Quick fixes 2017-12-31 18:36:27 +01:00
Guilherme Blanco
d7fd378b3c Refactored Proxy support 2017-12-31 18:36:27 +01:00
Guilherme Blanco
8688b1626c Work on ClassMetadataFactory. Updated ProxyFactory reducing dependencies 2017-12-31 18:36:27 +01:00
Guilherme Blanco
bd1ff948b8 Quick rename 2017-12-31 18:36:26 +01:00
Guilherme Blanco
1505ef2c99 Removed isIdentifierComposite flag from ClassMetadata 2017-12-31 18:36:26 +01:00
Guilherme Blanco
213ea60923 More work on MappingFactory. Decoupled ProxyConfiguration. Centralized PersistentCollection creation. 2017-12-31 18:36:26 +01:00
Guilherme Blanco
e9406531a9 work 2017-12-31 18:36:26 +01:00
Guilherme Blanco
8b78a64e8a More work on cleanup 2017-12-31 18:36:26 +01:00
Guilherme Blanco
3bc4126246 Intermediate work for ClassMetadataFactory refactoring 2017-12-31 18:36:25 +01:00
Guilherme Blanco
e2c0f2b7c2 Simplified version field metadata setup. Implemented more exporters 2017-12-31 18:36:25 +01:00
Guilherme Blanco
bd5fb5e7d1 Added TransientMetadata support, allowing to track properties not directly mapped into ORM. This enables ChangeTrackingPolicy to assess changes happening to transient class members. Removed ReflectionPropertiesGetter hack. 2017-12-31 18:36:25 +01:00
Guilherme Blanco
fee3cd5367 Incorporated LocalColumnMetadata, simplifying runtime memory consumption over JoinColumnMetadata. Implemented more Exporters. 2017-12-31 18:36:25 +01:00
Guilherme Blanco
081986eae2 Ported AbstractClassMetadataFactory from Common to ORM, allowing us to change its implementation 2017-12-31 18:36:25 +01:00
Guilherme Blanco
941cf83a1a Encapsulated CacheMetadata from ClassMetadata into ComponentMetadata 2017-12-31 18:36:24 +01:00
Guilherme Blanco
656290d156 Encapsulated ReflectionClass 2017-12-31 18:36:24 +01:00
Guilherme Blanco
b80b30b10b More centralization around identifier flattening 2017-12-31 18:36:24 +01:00
Guilherme Blanco
946e35c2ec Minor optimizations 2017-12-31 18:36:24 +01:00
Guilherme Blanco
ad8d16a31f Fixes #6302 2017-12-31 18:36:23 +01:00
Guilherme Blanco
c404aa66ac Merged setAttributeOverride and setAssociationOverride into setPropertyOverride 2017-12-31 18:36:23 +01:00
Guilherme Blanco
9110eebadb Merged addInheritedProperty and addInheritedAssociation 2017-12-31 18:36:23 +01:00
Guilherme Blanco
830985e259 Merged addProperty and addAssociation 2017-12-31 18:36:23 +01:00
Guilherme Blanco
87f1aa7875 Merged associationMappings with properties 2017-12-31 18:36:23 +01:00
Guilherme Blanco
41919e69dc Quick changes 2017-12-31 18:36:22 +01:00
Guilherme Blanco
03927e8cff Changed isReadOnly to be private and also added wrapper method 2017-12-31 18:36:22 +01:00
Guilherme Blanco
000759790e Missing file from previous commit 2017-12-31 18:36:22 +01:00
Guilherme Blanco
99b92853cf Quick optimizations 2017-12-31 18:36:22 +01:00
Guilherme Blanco
e676d6c30e Removed ClassMetadata::getAssociationMapping and ClassMetadata::getAssociationMappings methods 2017-12-31 18:36:21 +01:00
Guilherme Blanco
6841c919fa Fixed DDC2780Test to be compliant with develop branch 2017-12-31 18:36:21 +01:00
Guilherme Blanco
347e2a55f4 Removed ClassMetadata::reflFields, delegating the work to Property instances 2017-12-31 18:36:21 +01:00
Guilherme Blanco
607a6691be Ignore .iml file 2017-12-31 18:36:21 +01:00
Guilherme Blanco
c1a46219ed Eliminated QuoteStrategy support completely and simplified SQL alias generation 2017-12-31 18:36:21 +01:00
Guilherme Blanco
0c16cce8bc Eliminated mapXToY methods and normalized into only addAssociation 2017-12-31 18:33:08 +01:00
Guilherme Blanco
6286b69fbd Introduced AssociationMetadata and its subclasses as classes. \o/ 2017-12-31 18:33:07 +01:00
Guilherme Blanco
4d12dbf30e Introduced AssociationMetadata and its subclasses as classes. \o/ 2017-12-31 18:33:07 +01:00
Guilherme Blanco
045e8b8782 Docs update 2017-12-31 18:33:07 +01:00
Guilherme Blanco
61e57aa5f4 Adios SimpleAnnotationReader! 2017-12-31 18:33:05 +01:00
Guilherme Blanco
aad3576953 Small code optimization 2017-12-31 18:30:15 +01:00
Guilherme Blanco
1d3af800df Fixed issue with EntityManagerDecoratorTest 2017-12-31 18:30:14 +01:00
Guilherme Blanco
51d6ff6c7f PHPUnit 6 WIP 2017-12-31 18:30:14 +01:00
Guilherme Blanco
56e93548f5 Added foreign key columns to the maps of columns to property names. 2017-12-31 18:30:14 +01:00
Guilherme Blanco
e28086d23b Removed ClassMetadata->containsForeignIdentifier 2017-12-31 18:30:13 +01:00
Guilherme Blanco
e805498f95 CacheMetadata is now immutable 2017-12-31 18:30:13 +01:00
Guilherme Blanco
f80561edc9 Improvement over last commit 2017-12-31 18:30:13 +01:00
Guilherme Blanco
c98970b7c3 Renamed ClassMetadata->enableCache to ClasMetadata->setCache 2017-12-31 18:30:12 +01:00
Guilherme Blanco
75f5260a89 Introduced CacheMetadata 2017-12-31 18:30:12 +01:00
Guilherme Blanco
d070fc62a7 Optimizations improvement over clear() 2017-12-31 18:30:12 +01:00
Guilherme Blanco
6b28e558cf Fixes #5855. Removed ability to EntityManager::clear($entityName), clear($entityClass), clear($proxyClass), clear($rootOfInheritance) 2017-12-31 18:30:11 +01:00
Guilherme Blanco
4bbe058e02 Fixed DDC-536 (GH #5044). Remove the _ prefix from private and protected members 2017-12-31 18:30:11 +01:00
Luís Cobucci
242f3f0343 Use objects instead of arrays for join tables and columns 2017-12-31 18:30:10 +01:00
Luís Cobucci
edd92d8ae6 Required development version of DBAL 2017-12-31 18:30:10 +01:00
Jáchym Toušek
9205519217 Fix typehints in EntityManagerInterface implementations 2017-12-31 18:30:10 +01:00
Jáchym Toušek
a9821eae7b EntityManager::transactional() doesn't use call_user_func 2017-12-31 18:30:10 +01:00
Jáchym Toušek
97378f7cdc Refactor EntityManagerTest 2017-12-31 18:30:09 +01:00
Jáchym Toušek
377d2dc190 EntityManager::transactional() catches Throwable 2017-12-31 18:30:09 +01:00
Jáchym Toušek
7e62f21a23 EntityManager::transactional() uses callable type-hint 2017-12-31 18:30:09 +01:00
Jáchym Toušek
e18d5da193 EntityManager::transactional() returns value returned by the closure 2017-12-31 18:30:09 +01:00
Guilherme Blanco
ffbb835b05 More optimizations and cleanup 2017-12-31 18:30:09 +01:00
Guilherme Blanco
fbf081814c Removed code duplication of helper methods, inlining calls. 2017-12-31 18:30:08 +01:00
Guilherme Blanco
9615382856 Removed ClassMetadata::GENERATOR_TYPE_* constants and created GeneratorType::* 2017-12-31 18:30:08 +01:00
Guilherme Blanco
8e6cf48908 Removed ClassMetadata::CHANGETRACKING_* constants and created ChangeTrackingPolicy::* 2017-12-31 18:30:08 +01:00
Guilherme Blanco
15d45cecbd Removed ClassMetadata::INHERITANCE_TYPE_* constants and created InheritanceType::* 2017-12-31 18:30:07 +01:00
Guilherme Blanco
7403ca0ce3 Removed ClassMetadata::CACHE_USAGE_* constants and created CacheUsage::* 2017-12-31 18:30:07 +01:00
Guilherme Blanco
30ec740011 Removed ClassMetadata::FETCH_* constants and created FetchMode::* 2017-12-31 18:30:07 +01:00
Guilherme Blanco
792d9cbe61 Removed relationToTargetKeyColumns and relationToSourceKeyColumns from association mapping 2017-12-31 18:30:07 +01:00
Guilherme Blanco
2ed4447b64 Eliminated sourceToTargetKeyColumns from association mapping 2017-12-31 18:30:06 +01:00
Guilherme Blanco
ef378ccd94 Eliminated targetToSourceKeyColumns from association mapping 2017-12-31 18:30:06 +01:00
Guilherme Blanco
7007d4c0fc Removed most of targetToSourceKeyColumns usages 2017-12-31 18:30:06 +01:00
Guilherme Blanco
3cb30021c5 Removed support for joinColumnFieldNames 2017-12-31 18:30:06 +01:00
Guilherme Blanco
169ff83b11 Implemented JoinTableMetadata 2017-12-31 18:30:05 +01:00
Guilherme Blanco
5908cac18f Removed QuoteStrategy::getTableName() 2017-12-31 18:30:05 +01:00
Guilherme Blanco
b7f0567273 Finalized TableMetadata implementation 2017-12-31 18:30:05 +01:00
Guilherme Blanco
8a4ca55e8b Introduced concept of TableMetadata. Still a few tests to finish fixing 2017-12-31 18:30:05 +01:00
Guilherme Blanco
e5b216c523 Decoupled unique constraint from unique index 2017-12-31 18:30:04 +01:00
Guilherme Blanco
2f7f3fbf59 Allow unique index creation from ORM mapping 2017-12-31 18:30:04 +01:00
Guilherme Blanco
e7aa7d5b39 Moved FieldMetadata creation out of ClassMetadata 2017-12-31 18:30:04 +01:00
Guilherme Blanco
36d8b337af Simplified property inheritance logic when loading class metadata 2017-12-31 18:30:03 +01:00
Guilherme Blanco
13e53d12a0 Checks for references, proxies and retrievals on EM. Removed todo from UnitOfWork 2017-12-31 18:30:03 +01:00
Guilherme Blanco
392850cf2f Merged sequenceGeneratorDefinition and customGeneratorDefinition into a single, unified structure generatorDefinition 2017-12-31 18:30:03 +01:00
Guilherme Blanco
47bfece156 Strict types 2017-12-31 18:30:03 +01:00
Guilherme Blanco
cda24bf8c3 Fixing remaining possible to be fixed unit tests 2017-12-31 18:30:03 +01:00
Guilherme Blanco
b4df1ec96f Starting the concept of Builders on Drivers 2017-12-31 18:30:02 +01:00
Guilherme Blanco
246349b8e5 Introduced JoinColumnMetadata and refactored most tests. Still 14 failing related to OO change. 2017-12-31 18:30:02 +01:00
Guilherme Blanco
48da5b8cc6 More work around JoinColumns. Should start AssociationMetadata changes by next commit 2017-12-31 18:30:02 +01:00
Marco Pivetta
af259157a2 s/should/**MUST** as per @afoeder's review
Ref: 0f76b969cd..8f5ebd93c1 (r70573803)
2017-12-31 18:30:01 +01:00
Luís Cobucci
0dcb732a3c Adding YAML removal as BC break. 2017-12-31 18:30:01 +01:00
Luís Cobucci
f54f4a5b97 Removing YAML mappings on docblocks and messages. 2017-12-31 18:30:01 +01:00
Luís Cobucci
1e6d30da83 Removing YAML drivers and all its references. 2017-12-31 18:30:01 +01:00
Luís Cobucci
4e2d754ae7 Removing YAML class metadata exporter and its references. 2017-12-31 18:30:01 +01:00
Luís Cobucci
e1ee929dad Removing Doctrine ORM 1 schema conversion tool.
This tool reads and writes YAML files so it should be removed.
2017-12-31 18:30:00 +01:00
Guilherme Blanco
6446a9626c Removed some methods from QuoteStrategy (beginning of the end of quoting saga). Renamed identity generator to become sequencing generators. 2017-12-31 18:30:00 +01:00
Guilherme Blanco
6fb1bdefec Fix for test 2017-12-31 18:30:00 +01:00
Guilherme Blanco
bd1c03fb15 Fixes and implemented incomplete tests 2017-12-31 18:30:00 +01:00
Guilherme Blanco
6aa543978c Fixes to bugs and made test more consistent 2017-12-31 18:30:00 +01:00
Guilherme Blanco
b9d38d94b9 More work related to ClassMetadataBuilder changes and some fixes to broken tests 2017-12-31 18:29:59 +01:00
Guilherme Blanco
7c39836ce6 Breaking everything with start migration to use ClassMetadataBuilder in Mapping Drivers 2017-12-31 18:29:59 +01:00
Guilherme Blanco
c498805966 Removed isCascadeXXX 2017-12-31 18:29:59 +01:00
Guilherme Blanco
32c28ffe90 Cosmetic 2017-12-31 18:29:59 +01:00
Guilherme Blanco
a5375e3a1a Cosmetic 2017-12-31 18:29:59 +01:00
Guilherme Blanco
c63a0ab56a Removed the need for FielMetadata::$currentClass property 2017-12-31 18:29:58 +01:00
Guilherme Blanco
a77cdaf5eb Switched versionField to versionProperty 2017-12-31 18:29:58 +01:00
Guilherme Blanco
d2835395ad Removed isVersioned property. 2017-12-31 18:29:58 +01:00
Guilherme Blanco
917e5ea8c7 Finalized changes to ensure everything breaking now is due to quoting being applied everywhere 2017-12-31 18:29:58 +01:00
Guilherme Blanco
8b856c799a Moved Discriminator Column to OO approach and refactored a bit for persisters 2017-12-31 18:29:57 +01:00
Guilherme Blanco
b97f85af46 Decoupled FieldMetadata into FieldMetadata and ColumnMetadata 2017-12-31 18:25:15 +01:00
Guilherme Blanco
80b05a889a Quick improvements 2017-12-31 18:25:15 +01:00
Guilherme Blanco
53ba16d747 Merged FieldMetadata and InheritedFieldMetadata into a single unit 2017-12-31 18:25:15 +01:00
Guilherme Blanco
1d2b5962b0 Refactored QuoteStrategy::getColumnName() 2017-12-31 18:25:15 +01:00
Guilherme Blanco
8d1e7b3fd5 Normalized quote strategy execution for discriminator columns and join columns 2017-12-31 18:25:14 +01:00
Guilherme Blanco
975ebef19d No more errors (just failures) for FieldMetadata 2017-12-31 18:25:14 +01:00
Guilherme Blanco
548865b621 More work towards FieldMetadata 2017-12-31 18:25:14 +01:00
Guilherme Blanco
db187bb8a9 Removed unused methods 2017-12-31 18:25:14 +01:00
Guilherme Blanco
5e29c80ca3 Removed traces of fieldMappings from codebase 2017-12-31 18:25:14 +01:00
Guilherme Blanco
08e2f779e3 Removed quoting support and make sure we always quote data. Still missing checks for discriminator column and version column. 2017-12-31 18:25:13 +01:00
Guilherme Blanco
7c4e5444d6 More fixed unit tests 2017-12-31 18:25:13 +01:00
Guilherme Blanco
169ac6d13d Converted ClassMetadtaa::setVersionMapping to use FieldMetadata instead of mapping 2017-12-31 18:25:13 +01:00
Guilherme Blanco
fe1e3c0fa9 Fixed a bunch of unit tests 2017-12-31 18:25:13 +01:00
Guilherme Blanco
97f077a120 More work related to FieldMetadata and InheritedFieldMetadata 2017-12-31 18:25:13 +01:00
Guilherme Blanco
0a0203fa43 Added InheritedFieldMetadata and got rid of most fieldMappings in the codebase 2017-12-31 18:25:12 +01:00
Guilherme Blanco
ecc711cb73 Moved all mapField to addProperty 2017-12-31 18:25:12 +01:00
Guilherme Blanco
83b91b81ca Commented embeddeds support until fields are complete. Embeddeds will be brought back with proper implementation 2017-12-31 18:25:12 +01:00
Guilherme Blanco
9f6dc1d8a8 Initial code for FieldMetadata 2017-12-31 18:25:12 +01:00
Guilherme Blanco
ac34415bf3 Fixed unit test 2017-12-31 18:25:12 +01:00
Guilherme Blanco
ed40837b45 Fixed unit test 2017-12-31 18:25:11 +01:00
Guilherme Blanco
2f69aece64 Upgrade file 2017-12-31 18:25:11 +01:00
Guilherme Blanco
516f2e1083 Fix for tableName in STI 2017-12-31 18:25:11 +01:00
Guilherme Blanco
a5d136d5e6 Added tableName on fields, association join columns and embeddeds. 2017-12-31 18:25:11 +01:00
Guilherme Blanco
db7ecb2721 Made fields, associations, join columns, version fields, identifiers, embeddeds and discriminator columns to hold its owning table name. 2017-12-31 18:25:11 +01:00
Guilherme Blanco
6adcb5ed68 Switched getSQLTableAlias to use table names instead of class names 2017-12-31 18:25:10 +01:00
Guilherme Blanco
ff6d24d104 Finalized inclusion of support for declaringClass 2017-12-31 18:25:10 +01:00
Guilherme Blanco
c73f4cff8e Added declaringClass and fixed almost all unit tests with the exception of EntityGeneratorTest. No explanation why it iss failing yet 2017-12-31 18:25:10 +01:00
Guilherme Blanco
aac92169b7 $this->assertXXX changes to self::assertXXX as PHPUnit test methods are all static 2017-12-31 18:25:09 +01:00
Guilherme Blanco
4f76085494 ClassMetadata::[]['type'] now holds a Type instance instead of string 2017-12-31 11:36:40 +01:00
Guilherme Blanco
c8ddbddc19 Some CS fixes 2017-12-31 11:32:36 +01:00
Guilherme Blanco
e4d363122a Optimized EntityPersister::getSelectColumnSQL() and EntityInheritancePersistence::getSelectJoinColumnSQL() 2017-12-31 11:32:36 +01:00
Guilherme Blanco
a568530c6b Removed ClassMetadata::getTypeOfColumn() method 2017-12-31 11:32:35 +01:00
Guilherme Blanco
55596e9d07 Removed ClassMetadata::$fieldMappings[$field][requireSQLConversion] 2017-12-31 11:32:35 +01:00
Guilherme Blanco
6eb2ca4689 Removed ClassMetadata::$namespace 2017-12-31 11:32:35 +01:00
Guilherme Blanco
8731c086f9 Removed ClassMetadata::$columnNames. Improved overall test suite by 10% due to less serialization/deserialization 2017-12-31 11:32:35 +01:00
Guilherme Blanco
2d36a5da6b Renamed setIdentifierValues to assignIdentifier 2017-12-31 11:32:35 +01:00
Guilherme Blanco
56b245e4a6 Removed ClassMetadataInfo. Before suite run: 7.19 seconds. After: 6.7 seconds 2017-12-31 11:32:31 +01:00
2685 changed files with 155458 additions and 160428 deletions

View File

@@ -6,99 +6,43 @@
"docsSlug": "doctrine-orm",
"versions": [
{
"name": "4.0",
"branchName": "4.0.x",
"name": "3.0",
"branchName": "master",
"slug": "latest",
"upcoming": true
},
{
"name": "3.3",
"branchName": "3.3.x",
"slug": "3.3",
"name": "2.8",
"branchName": "2.8.x",
"slug": "2.8",
"upcoming": true
},
{
"name": "3.2",
"branchName": "3.2.x",
"slug": "3.2",
"current": true
"name": "2.7",
"branchName": "2.7",
"slug": "2.7",
"current": true,
"aliases": [
"current",
"stable"
]
},
{
"name": "3.1",
"branchName": "3.1.x",
"slug": "3.1",
"name": "2.6",
"branchName": "2.6",
"slug": "2.6",
"maintained": false
},
{
"name": "3.0",
"branchName": "3.0.x",
"slug": "3.0",
"name": "2.5",
"branchName": "2.5",
"slug": "2.5",
"maintained": false
},
{
"name": "2.20",
"branchName": "2.20.x",
"slug": "2.20",
"upcoming": true
},
{
"name": "2.19",
"branchName": "2.19.x",
"slug": "2.19",
"maintained": true
},
{
"name": "2.18",
"branchName": "2.18.x",
"slug": "2.18",
"maintained": false
},
{
"name": "2.17",
"branchName": "2.17.x",
"slug": "2.17",
"maintained": false
},
{
"name": "2.16",
"branchName": "2.16.x",
"slug": "2.16",
"maintained": false
},
{
"name": "2.15",
"branchName": "2.15.x",
"slug": "2.15",
"maintained": false
},
{
"name": "2.14",
"branchName": "2.14.x",
"slug": "2.14",
"maintained": false
},
{
"name": "2.13",
"branchName": "2.13.x",
"slug": "2.13",
"maintained": false
},
{
"name": "2.12",
"branchName": "2.12.x",
"slug": "2.12",
"maintained": false
},
{
"name": "2.11",
"branchName": "2.11.x",
"slug": "2.11",
"maintained": false
},
{
"name": "2.10",
"branchName": "2.10.x",
"slug": "2.10",
"name": "2.4",
"branchName": "2.4",
"slug": "2.4",
"maintained": false
}
]

15
.gitattributes vendored
View File

@@ -1,11 +1,9 @@
/.github export-ignore
/ci export-ignore
/docs export-ignore
/tests export-ignore
/tools export-ignore
.doctrine-project.json export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.gitmodules export-ignore
.travis.yml export-ignore
build.properties export-ignore
build.properties.dev export-ignore
build.xml export-ignore
@@ -13,11 +11,4 @@ CONTRIBUTING.md export-ignore
phpunit.xml.dist export-ignore
run-all.sh export-ignore
phpcs.xml.dist export-ignore
phpbench.json export-ignore
phpstan.neon export-ignore
phpstan-baseline.neon export-ignore
phpstan-dbal2.neon export-ignore
phpstan-params.neon export-ignore
phpstan-persistence2.neon export-ignore
psalm.xml export-ignore
psalm-baseline.xml export-ignore
composer.lock export-ignore

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
patreon: phpdoctrine
tidelift: packagist/doctrine/orm
custom: https://www.doctrine-project.org/sponsorship.html

View File

@@ -4,7 +4,7 @@ about: Have you encountered an issue during upgrade? 💣
---
<!--
Before reporting a BC break, please consult the upgrading document to make sure it's not an expected change: https://github.com/doctrine/orm/blob/2.9.x/UPGRADE.md
Before reporting a BC break, please consult the upgrading document to make sure it's not an expected change: https://github.com/doctrine/orm/blob/master/UPGRADE.md
-->
### BC Break Report

View File

@@ -3,4 +3,18 @@ name: ❓ Support Question
about: Have a problem that you can't figure out? 🤔
---
Please use https://github.com/doctrine/orm/discussions instead.
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | -----
| Version | x.y.z
<!--
Before asking question here, please try asking on Gitter or Slack first.
Find out more about Doctrine support channels here: https://www.doctrine-project.org/community/
Keep in mind that GitHub is primarily an issue tracker.
-->
### Support Question
<!-- Describe the issue you are facing here. -->

View File

@@ -6,9 +6,9 @@ about: You have implemented some neat idea that you want to make part of Doctrin
<!--
Thank you for submitting new feature!
Pick the target branch based according to these criteria:
* submitting a bugfix: target the lowest active stable branch: 2.9.x
* submitting a new feature: target the next minor branch: 2.10.x
* submitting a BC-breaking change: target the next major branch: 3.0.x
* submitting a bugfix: target the lowest active stable branch: 2.7
* submitting a new feature: target the next minor branch: 2.8.x
* submitting a BC-breaking change: target the master branch
-->
### New Feature

View File

@@ -1,9 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "CI"
target-branch: "2.19.x"

47
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: CI
on:
pull_request:
push:
branches:
- master
jobs:
coding-standards:
name: "Coding Standards"
runs-on: ubuntu-18.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
extensions: mbstring
tools: composer, cs2pr
- name: composer install
run: "composer install --no-progress --no-suggest --no-interaction --prefer-dist --optimize-autoloader"
- name: phpcs
run: "php vendor/bin/phpcs -q --report=checkstyle --no-colors | cs2pr"
static-analysis:
name: "Static Analysis"
runs-on: ubuntu-18.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
extensions: mbstring
tools: composer, cs2pr
- name: composer install
run: "composer install --no-progress --no-suggest --no-interaction --prefer-dist --optimize-autoloader"
- name: phpstan
run: "php vendor/bin/phpstan analyse --error-format=checkstyle --no-progress | cs2pr"

View File

@@ -1,27 +0,0 @@
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- src/**
- phpcs.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- src/**
- phpcs.xml.dist
- tests/**
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.0.1"

View File

@@ -1,344 +0,0 @@
name: "Continuous Integration"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- src/**
- phpunit.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- src/**
- phpunit.xml.dist
- tests/**
env:
fail-fast: true
jobs:
phpunit-smoke-check:
name: "PHPUnit with SQLite"
runs-on: "ubuntu-22.04"
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
dbal-version:
- "default"
- "3.7"
extension:
- "sqlite3"
- "pdo_sqlite"
deps:
- "highest"
include:
- php-version: "8.2"
dbal-version: "4@dev"
extension: "pdo_sqlite"
- php-version: "8.2"
dbal-version: "4@dev"
extension: "sqlite3"
- php-version: "8.1"
dbal-version: "default"
deps: "lowest"
extension: "pdo_sqlite"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "apcu, pdo, ${{ matrix.extension }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
- name: "Run PHPUnit with Second Level Cache"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-coverage"
path: "coverage*.xml"
phpunit-postgres:
name: "PHPUnit with PostgreSQL"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
dbal-version:
- "default"
- "3.7"
postgres-version:
- "15"
extension:
- pdo_pgsql
- pgsql
include:
- php-version: "8.2"
dbal-version: "4@dev"
postgres-version: "14"
extension: pdo_pgsql
- php-version: "8.2"
dbal-version: "3.7"
postgres-version: "9.6"
extension: pdo_pgsql
services:
postgres:
image: "postgres:${{ matrix.postgres-version }}"
env:
POSTGRES_PASSWORD: "postgres"
options: >-
--health-cmd "pg_isready"
ports:
- "5432:5432"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "pgsql pdo_pgsql"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
path: "coverage.xml"
phpunit-mariadb:
name: "PHPUnit with MariaDB"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
dbal-version:
- "default"
- "3.7"
- "4@dev"
mariadb-version:
- "11.4"
extension:
- "mysqli"
- "pdo_mysql"
services:
mariadb:
image: "mariadb:${{ matrix.mariadb-version }}"
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes
MARIADB_DATABASE: "doctrine_tests"
options: >-
--health-cmd "healthcheck.sh --connect --innodb_initialized"
ports:
- "3306:3306"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
phpunit-mysql:
name: "PHPUnit with MySQL"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
dbal-version:
- "default"
- "3.7"
mysql-version:
- "5.7"
- "8.0"
extension:
- "mysqli"
- "pdo_mysql"
include:
- php-version: "8.2"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "mysqli"
- php-version: "8.2"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "pdo_mysql"
services:
mysql:
image: "mysql:${{ matrix.mysql-version }}"
options: >-
--health-cmd "mysqladmin ping --silent"
-e MYSQL_ALLOW_EMPTY_PASSWORD=yes
-e MYSQL_DATABASE=doctrine_tests
ports:
- "3306:3306"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
- name: "Run PHPUnit with Second Level Cache"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v4"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
upload_coverage:
name: "Upload coverage to Codecov"
runs-on: "ubuntu-22.04"
needs:
- "phpunit-smoke-check"
- "phpunit-postgres"
- "phpunit-mariadb"
- "phpunit-mysql"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v4"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v4"
with:
directory: reports
env:
CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"

View File

@@ -1,49 +0,0 @@
name: "Documentation"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**
push:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**
jobs:
validate-with-guides:
name: "Validate documentation with phpDocumentor/guides"
runs-on: "ubuntu-22.04"
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.3"
- name: "Remove existing composer file"
run: "rm composer.json"
- name: "Require phpdocumentor/guides-cli"
run: "composer require --dev phpdocumentor/guides-cli --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "highest"
- name: "Add orphan metadata where needed"
run: |
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/reference/installation.rst)" > docs/en/reference/installation.rst
- name: "Run guides-cli"
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )"

View File

@@ -1,54 +0,0 @@
name: "Performance benchmark"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- src/**
- phpbench.json
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- src/**
- phpbench.json
- tests/**
env:
fail-fast: true
jobs:
phpbench:
name: "PHPBench"
runs-on: "ubuntu-22.04"
strategy:
matrix:
php-version:
- "8.1"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
- name: "Run PHPBench"
run: "vendor/bin/phpbench run --report=default"

View File

@@ -1,15 +0,0 @@
name: "Automatic Releases"
on:
milestone:
types:
- "closed"
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.0.1"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}

View File

@@ -1,89 +0,0 @@
name: "Static Analysis"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- src/**
- phpstan*
- psalm*
- tests/StaticAnalysis/**
push:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- src/**
- phpstan*
- psalm*
- tests/StaticAnalysis/**
jobs:
static-analysis-phpstan:
name: Static Analysis with PHPStan
runs-on: ubuntu-22.04
strategy:
matrix:
include:
- dbal-version: default
config: phpstan.neon
- dbal-version: 3.8.2
config: phpstan-dbal3.neon
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
coverage: none
php-version: "8.3"
tools: cs2pr
- name: Require specific DBAL version
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: Install dependencies with Composer
uses: ramsey/composer-install@v2
- name: Run static analysis with phpstan/phpstan
run: "vendor/bin/phpstan analyse -c ${{ matrix.config }} --error-format=checkstyle | cs2pr"
static-analysis-psalm:
name: Static Analysis with Psalm
runs-on: ubuntu-22.04
strategy:
matrix:
dbal-version:
- default
- 3.8.2
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
coverage: none
php-version: "8.3"
tools: cs2pr
- name: Require specific DBAL version
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: Install dependencies with Composer
uses: ramsey/composer-install@v3
- name: Run static analysis with Vimeo Psalm
run: vendor/bin/psalm --shepherd

12
.gitignore vendored
View File

@@ -3,15 +3,17 @@ logs/
reports/
dist/
download/
lib/api/
lib/Doctrine/Common
lib/Doctrine/DBAL
/.settings/
*.iml
.buildpath
.project
.idea
*.iml
vendor/
composer.phar
/tests/Doctrine/Performance/history.db
/.phpcs-cache
composer.lock
.phpunit.cache
.phpunit.result.cache
/*.phpunit.xml
phpbench.phar
phpbench.phar.pubkey

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "docs/en/_theme"]
path = docs/en/_theme
url = git://github.com/doctrine/doctrine-sphinx-theme.git
[submodule "lib/vendor/doctrine-build-common"]
path = lib/vendor/doctrine-build-common
url = git://github.com/doctrine/doctrine-build-common.git

32
.scrutinizer.yml Normal file
View File

@@ -0,0 +1,32 @@
build:
nodes:
analysis:
environment:
php:
version: 7.4
cache:
disabled: false
directories:
- ~/.composer/cache
project_setup:
override: true
tests:
override:
- php-scrutinizer-run
- phpcs-run
dependencies:
override:
- composer install --no-interaction --prefer-dist
tools:
external_code_coverage:
timeout: 3600
filter:
excluded_paths:
- docs
build_failure_conditions:
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
- 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
- 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection

82
.travis.yml Normal file
View File

@@ -0,0 +1,82 @@
dist: trusty
sudo: false
language: php
php:
- 7.3
- 7.4
env:
- DB=mariadb
- DB=mysql
- DB=pgsql
- DB=sqlite
before_install:
- |
if [[ "$COVERAGE" != "1" ]]; then
phpenv config-rm ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini || echo "xdebug is not installed"
fi
- echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- travis_retry composer self-update
install:
- rm composer.lock
- travis_retry composer update --no-interaction --prefer-dist --no-suggest --no-progress
script:
- |
if [[ "$DB" == "mysql" || "$DB" == "mariadb" ]]; then
mysql -e "CREATE SCHEMA doctrine_tests; GRANT ALL PRIVILEGES ON doctrine_tests.* to travis@'%'";
fi
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml
# temporarily disabled
#- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
jobs:
include:
- stage: Test
env: DB=mariadb
addons:
mariadb: "10.4"
- stage: Test
env: DB=sqlite DEPENDENCIES=low
install:
- travis_retry composer update --no-interaction --prefer-dist --no-suggest --no-progress --prefer-lowest
- stage: Test
if: type = cron
php: 7.3
env: DB=sqlite DEV_DEPENDENCIES
install:
- rm composer.lock
- composer config minimum-stability dev
- travis_retry composer update --no-interaction --prefer-dist --no-suggest --no-progress
- stage: Test
env: DB=sqlite COVERAGE
before_script:
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
script:
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --coverage-clover ./build/logs/clover.xml
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml
- stage: Code Quality
env: DB=none BENCHMARK
before_script:
- wget https://phpbench.github.io/phpbench/phpbench.phar https://phpbench.github.io/phpbench/phpbench.phar.pubkey
script:
- php phpbench.phar run --bootstrap=tests/Doctrine/Tests/TestInit.php -l dots --report=default
allow_failures:
# temporarily disabled
- env: DB=mysql
- env: DB=mariadb
- env: DB=pgsql
cache:
directories:
- $HOME/.composer/cache

View File

@@ -1,64 +1,81 @@
# Contribute to Doctrine
# Contributing to Doctrine ORM
Thank you for contributing to Doctrine!
Thank you for contributing to Doctrine ORM!
Before we can merge your Pull-Request here are some guidelines that you need to follow.
Before we can merge your pull request here are some guidelines that you need to follow.
These guidelines exist not to annoy you, but to keep the code base clean,
unified and future proof.
Doctrine has [general contributing guidelines][contributor workflow], make
sure you follow them.
## Obtaining a copy
[contributor workflow]: https://www.doctrine-project.org/contribute/index.html
In order to submit a pull request, you will need to [fork the project][Fork] and obtain a
fresh copy of the source code:
```sh
git clone git@github.com:<your-github-name>/orm.git
cd orm
```
Then you will have to run a Composer installation in the project:
```sh
curl -sS https://getcomposer.org/installer | php
./composer.phar install
```
## Choosing the branch
* I am submitting a bugfix for a stable release
* Your PR should target the [lowest active stable branch (2.7)][2.7].
* I am submitting a new feature
* Your PR should target the [master branch (3.0)][Master].
* I am submitting a BC-breaking change
* Your PR must target the [master branch (3.0)][Master].
* Please also try to provide a deprecation path in a PR targeting the [2.8 branch][2.8].
Please always create a new branch for your changes (i.e. do not commit directly into `master`
in your fork), otherwise you would run into troubles with creating multiple pull requests.
## Coding Standard
This project follows [`doctrine/coding-standard`][coding standard homepage].
You may fix many some of the issues with `vendor/bin/phpcbf`.
We follow the [Doctrine Coding Standard][CS].
Please refer to this repository to learn about the rules your code should follow.
You can also use `vendor/bin/phpcs` to validate your changes locally.
[coding standard homepage]: https://github.com/doctrine/coding-standard
## Tests
## Unit-Tests
Please try to add a test for your pull-request.
Please try to add a test for your pull request.
* If you want to fix a bug or provide a reproduce case, create a test file in
``tests/Tests/ORM/Functional/Ticket`` with the name of the ticket,
``DDC1234Test.php`` for example.
* If you want to contribute new functionality add unit- or functional tests
``tests/Doctrine/Tests/ORM/Functional/Ticket`` with the identifier of the issue,
i.e. ``GH1234Test.php`` for an issue with id `#1234`.
* If you want to contribute new functionality, add unit or functional tests
depending on the scope of the feature.
You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project.
It will run all the tests with an in memory SQLite database.
In order to do that, you will need a fresh copy of the ORM, and you
will have to run a composer installation in the project:
```sh
git clone git@github.com:doctrine/orm.git
cd orm
composer install
```
You will also need to enable the PHP extension that provides the SQLite driver
for PDO: `pdo_sqlite`. How to do so depends on your system, but checking that it
is enabled can universally be done with `php -m`: that command should list the
extension.
You can run the tests by calling ``vendor/bin/phpunit`` from the root of the project.
It will run all the tests with an in-memory SQLite database.
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 ``ci/github/phpunit`` directory for some examples. Then run:
take a look at the ``tests/travis`` folder 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/3.0.x/tests/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
example.
1. If you put a test into the `Ticket` namespace as described above, put the testcase
and all entities into the same file.
See [DDC2306Test][Test Example] for an example.
## CI
We automatically run all pull requests through [Travis CI][Travis].
* The test suite is ran against SQLite, MySQL, MariaDB and PostgreSQL on all supported PHP versions.
* The code is validated against our [Coding Standard](#coding-standard).
* The code is checked by a static analysis tool.
If you break the tests, we cannot merge your code,
so please make sure that your code is working before opening a pull request.
## Getting merged
@@ -67,3 +84,10 @@ everything as fast as possible, but cannot always live up to our own expectation
Thank you very much again for your contribution!
[Master]: https://github.com/doctrine/orm/tree/master
[2.8]: https://github.com/doctrine/orm/tree/2.8.x
[2.7]: https://github.com/doctrine/orm/tree/2.7
[CS]: https://github.com/doctrine/coding-standard
[Fork]: https://guides.github.com/activities/forking/
[Travis]: https://www.travis-ci.org
[Test Example]: https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php

View File

@@ -1,40 +1,52 @@
| [4.0.x][4.0] | [3.3.x][3.3] | [3.2.x][3.2] | [2.20.x][2.20] | [2.19.x][2.19] |
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
| [![Build status][4.0 image]][4.0] | [![Build status][3.3 image]][3.3] | [![Build status][3.2 image]][3.2] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.3 coverage image]][3.3 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] |
[![Tidelift](https://tidelift.com/badges/github/doctrine/orm)](https://tidelift.com/subscription/pkg/packagist-doctrine-orm?utm_source=packagist-doctrine-orm&utm_medium=referral&utm_campaign=readme)
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
| [Master][Master] | [2.8][2.8] | [2.7][2.7] |
|:----------------:|:----------:|:----------:|
| [![Build status][Master image]][Master] | [![Build status][2.8 image]][2.8] | [![Build status][2.7 image]][2.7] |
| [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][2.8 coverage image]][2.8 coverage] | [![Coverage Status][2.7 coverage image]][2.7 coverage] |
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
##### :warning: You are browsing the code of upcoming Doctrine 3.0.
##### Things changed a lot here and major code changes should be expected. If you are rather looking for a stable version, refer to the [2.7 branch][2.7] for the current stable release or [2.8 branch][2.8] for the upcoming release. If you are submitting a pull request, please see the _[Which branch should I choose?](#which-branch-should-i-choose)_ section below.
-----
Doctrine 3 is an object-relational mapper (ORM) for PHP 7.2+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
without requiring unnecessary code duplication.
-----
### Which branch should I choose?
Please see [Choosing the branch](CONTRIBUTING.md#choosing-the-branch) to get more information about which branch
you should target your pull request at.
## Doctrine ORM for enterprise
Available as part of the Tidelift Subscription.
The maintainers of Doctrine ORM and thousands of other packages are working with Tidelift to deliver commercial support
and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve
code health, while paying the maintainers of the exact dependencies you use.
[Learn more.](https://tidelift.com/subscription/pkg/packagist-doctrine-orm?utm_source=packagist-doctrine-orm&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## More resources:
* [Website](http://www.doctrine-project.org)
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html)
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
[3.3 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.3.x
[3.3]: https://github.com/doctrine/orm/tree/3.3.x
[3.3 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.3.x/graph/badge.svg
[3.3 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.3.x
[3.2 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.2.x
[3.2]: https://github.com/doctrine/orm/tree/3.2.x
[3.2 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.2.x/graph/badge.svg
[3.2 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.2.x
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
[2.19 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.19.x
[2.19]: https://github.com/doctrine/orm/tree/2.19.x
[2.19 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.19.x/graph/badge.svg
[2.19 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.19.x
[Master image]: https://img.shields.io/travis/doctrine/orm/master.svg?style=flat-square
[Master]: https://travis-ci.org/doctrine/orm
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/master.svg?style=flat-square
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=master
[2.8 image]: https://img.shields.io/travis/doctrine/orm/2.8.x.svg?style=flat-square
[2.8]: https://github.com/doctrine/orm/tree/2.8.x
[2.8 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.8.x.svg?style=flat-square
[2.8 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.8.x
[2.7 image]: https://img.shields.io/travis/doctrine/orm/2.7.svg?style=flat-square
[2.7]: https://github.com/doctrine/orm/tree/2.7
[2.7 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.7.svg?style=flat-square
[2.7 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.7

View File

@@ -10,8 +10,9 @@ 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://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/security.html)
- [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/security.html)
- [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)
If you find a Security bug in Doctrine, please follow our
[Security reporting guidelines](https://www.doctrine-project.org/policies/security.html#reporting).
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
developers and you only.

1743
UPGRADE.md

File diff suppressed because it is too large Load Diff

6
bin/doctrine Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
include('doctrine.php');

36
bin/doctrine-pear.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
require_once 'Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Symfony');
$classLoader->register();
$configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php';
$helperSet = null;
if (file_exists($configFile)) {
if ( ! is_readable($configFile)) {
trigger_error(
'Configuration file [' . $configFile . '] does not have read permission.', E_USER_ERROR
);
}
require $configFile;
foreach ($GLOBALS as $helperSetCandidate) {
if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
$helperSet = $helperSetCandidate;
break;
}
}
}
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);

9
bin/doctrine.bat Normal file
View File

@@ -0,0 +1,9 @@
@echo off
if "%PHPBIN%" == "" set PHPBIN=@php_bin@
if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH
GOTO RUN
:USE_PEAR_PATH
set PHPBIN=%PHP_PEAR_PHP_BIN%
:RUN
"%PHPBIN%" "@bin_dir@\doctrine" %*

54
bin/doctrine.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
use Symfony\Component\Console\Helper\HelperSet;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
$autoloadFiles = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php'
];
foreach ($autoloadFiles as $autoloadFile) {
if (file_exists($autoloadFile)) {
require_once $autoloadFile;
break;
}
}
$directories = [getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config'];
$configFile = null;
foreach ($directories as $directory) {
$configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php';
if (file_exists($configFile)) {
break;
}
}
if ( ! file_exists($configFile)) {
ConsoleRunner::printCliConfigTemplate();
exit(1);
}
if ( ! is_readable($configFile)) {
echo 'Configuration file [' . $configFile . '] does not have read permission.' . "\n";
exit(1);
}
$commands = [];
$helperSet = require $configFile;
if ( ! ($helperSet instanceof HelperSet)) {
foreach ($GLOBALS as $helperSetCandidate) {
if ($helperSetCandidate instanceof HelperSet) {
$helperSet = $helperSetCandidate;
break;
}
}
}
ConsoleRunner::run($helperSet, $commands);

0
build.properties Normal file
View File

16
build.properties.dev Normal file
View File

@@ -0,0 +1,16 @@
version=2.0.0BETA2
dependencies.common=2.0.0BETA4
dependencies.dbal=2.0.0BETA4
stability=beta
build.dir=build
dist.dir=dist
report.dir=reports
log.archive.dir=logs
project.pirum_dir=
project.download_dir=
project.xsd_dir=
test.phpunit_configuration_file=
test.phpunit_generate_coverage=0
test.pmd_reports=0
test.pdepend_exec=
test.phpmd_exec=

78
build.xml Normal file
View File

@@ -0,0 +1,78 @@
<?xml version="1.0"?>
<project name="DoctrineORM" default="build" basedir=".">
<property file="build.properties" />
<target name="php">
<exec executable="which" outputproperty="php_executable">
<arg value="php" />
</exec>
</target>
<target name="prepare">
<mkdir dir="build" />
</target>
<target name="build" depends="check-git-checkout-clean,prepare,php,composer">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="archive" />
<arg value="--dir=build" />
</exec>
</target>
<target name="composer" depends="php,composer-check,composer-download">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="install" />
</exec>
</target>
<target name="composer-check" depends="prepare">
<available file="build/composer.phar" property="composer.present"/>
</target>
<target name="composer-download" unless="composer.present">
<exec executable="wget">
<arg value="-Obuild/composer.phar" />
<arg value="http://getcomposer.org/composer.phar" />
</exec>
</target>
<target name="check-git-checkout-clean">
<exec executable="git" failonerror="true">
<arg value="diff-index" />
<arg value="--quiet" />
<arg value="HEAD" />
</exec>
</target>
<macrodef name="git-commit">
<attribute name="file" default="NOT SET"/>
<attribute name="message" default="NOT SET"/>
<sequential>
<exec executable="git">
<arg value="add" />
<arg value="@{file}" />
</exec>
<exec executable="git">
<arg value="commit" />
<arg value="-m" />
<arg value="@{message}" />
</exec>
</sequential>
</macrodef>
<macrodef name="git-tag">
<attribute name="version" default="NOT SET" />
<sequential>
<exec executable="git">
<arg value="tag" />
<arg value="-m" />
<arg value="v@{version}" />
<arg value="v@{version}" />
</exec>
</sequential>
</macrodef>
</project>

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="mysqli"/>
<var name="db_host" value="127.0.0.1" />
<var name="db_port" value="3306"/>
<var name="db_user" value="root" />
<var name="db_dbname" value="doctrine_tests" />
<var name="db_default_table_option_charset" value="utf8mb4" />
<var name="db_default_table_option_collation" value="utf8mb4_unicode_ci" />
<var name="db_default_table_option_engine" value="InnoDB" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="pdo_mysql"/>
<var name="db_host" value="127.0.0.1" />
<var name="db_port" value="3306"/>
<var name="db_user" value="root" />
<var name="db_dbname" value="doctrine_tests" />
<var name="db_default_table_option_charset" value="utf8mb4" />
<var name="db_default_table_option_collation" value="utf8mb4_unicode_ci" />
<var name="db_default_table_option_engine" value="InnoDB" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="pdo_pgsql"/>
<var name="db_host" value="localhost" />
<var name="db_user" value="postgres" />
<var name="db_password" value="postgres" />
<var name="db_dbname" value="doctrine_tests" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<!-- 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>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="pgsql"/>
<var name="db_host" value="localhost" />
<var name="db_user" value="postgres" />
<var name="db_password" value="postgres" />
<var name="db_dbname" value="doctrine_tests" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<!-- use an in-memory sqlite database -->
<var name="db_driver" value="sqlite3"/>
<var name="db_memory" value="true"/>
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@@ -1,8 +1,18 @@
{
"name": "doctrine/orm",
"type": "library",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"description": "PHP object relational mapper (ORM) that sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL). This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication.",
"keywords": [
"php",
"orm",
"mysql",
"object",
"data",
"mapper",
"mapping",
"query",
"dql"
],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"license": "MIT",
"authors": [
@@ -12,54 +22,50 @@
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
],
"support": {
"chat": "https://www.doctrine-project.org/slack",
"docs": "https://www.doctrine-project.org/projects/orm.html",
"email": "doctrine-user@googlegroups.com",
"issues": "https://github.com/doctrine/orm/issues",
"rss": "https://github.com/doctrine/orm/releases.atom",
"source": "https://github.com/doctrine/orm"
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
},
"require": {
"php": "^8.1",
"composer-runtime-api": "^2",
"php": "^7.3",
"ext-ctype": "*",
"doctrine/collections": "^2.2",
"doctrine/dbal": "^3.8.2 || ^4",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3 || ^2",
"doctrine/lexer": "^3",
"doctrine/persistence": "^3.3.1",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/var-exporter": "^6.3.9 || ^7.0"
"doctrine/annotations": "~1.7",
"doctrine/cache": "~1.6",
"doctrine/collections": "^1.4",
"doctrine/dbal": "dev-missed-commits",
"doctrine/event-manager": "^1.0",
"doctrine/inflector": "~1.0",
"doctrine/instantiator": "~1.1",
"doctrine/persistence": "^1.1",
"doctrine/reflection": "^1.0",
"ocramius/package-versions": "^1.1.2",
"ocramius/proxy-manager": "^2.1.1",
"symfony/console": "~4.0|~5.0",
"symfony/var-dumper": "^4.1"
},
"require-dev": {
"doctrine/coding-standard": "^12.0",
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "1.11.1",
"phpunit/phpunit": "^10.4.0",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^5.4 || ^6.2 || ^7.0",
"vimeo/psalm": "5.24.0"
},
"suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files",
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0"
"doctrine/coding-standard": "^6.0",
"phpstan/phpstan": "^0.11",
"phpunit/phpunit": "^7.0"
},
"autoload": {
"psr-4": { "Doctrine\\ORM\\": "src" }
"psr-4": { "Doctrine\\ORM\\": "lib/Doctrine/ORM" }
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/Tests",
"Doctrine\\StaticAnalysis\\": "tests/StaticAnalysis",
"Doctrine\\Performance\\": "tests/Performance"
"Doctrine\\Tests\\": "tests/Doctrine/Tests",
"Doctrine\\Performance\\": "tests/Doctrine/Performance"
}
},
"bin": ["bin/doctrine"],
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
}
}

4214
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
The Doctrine ORM documentation is licensed under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
The Doctrine2 documentation is licensed under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
Creative Commons Legal Code
@@ -337,7 +337,6 @@ BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty

View File

@@ -1,18 +0,0 @@
# Doctrine ORM Documentation
## How to Generate:
Using Ubuntu 14.04 LTS:
1. Run ./bin/install-dependencies.sh
2. Run ./bin/generate-docs.sh
It will generate the documentation into the build directory of the checkout.
## Theme issues
If you get a "Theme error", check if the `en/_theme` subdirectory is empty,
in which case you will need to run:
1. git submodule init
2. git submodule update

View File

@@ -1,10 +0,0 @@
#!/bin/bash
EXECPATH=`dirname $0`
cd $EXECPATH
cd ..
rm build -Rf
sphinx-build en build
sphinx-build -b latex en build/pdf
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex

View File

@@ -1,2 +0,0 @@
#!/bin/bash
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments

View File

@@ -1,89 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORM.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

@@ -1,91 +0,0 @@
#Copyright (c) 2010 Fabien Potencier
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is furnished
#to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
from docutils.parsers.rst import Directive, directives
from docutils import nodes
from string import upper
class configurationblock(nodes.General, nodes.Element):
pass
class ConfigurationBlock(Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
formats = {
'html': 'HTML',
'xml': 'XML',
'php': 'PHP',
'jinja': 'Twig',
'html+jinja': 'Twig',
'jinja+html': 'Twig',
'php+html': 'PHP',
'html+php': 'PHP',
'ini': 'INI',
}
def run(self):
env = self.state.document.settings.env
node = nodes.Element()
node.document = self.state.document
self.state.nested_parse(self.content, self.content_offset, node)
entries = []
for i, child in enumerate(node):
if isinstance(child, nodes.literal_block):
# add a title (the language name) before each block
#targetid = "configuration-block-%d" % env.new_serialno('configuration-block')
#targetnode = nodes.target('', '', ids=[targetid])
#targetnode.append(child)
innernode = nodes.emphasis(self.formats[child['language']], self.formats[child['language']])
para = nodes.paragraph()
para += [innernode, child]
entry = nodes.list_item('')
entry.append(para)
entries.append(entry)
resultnode = configurationblock()
resultnode.append(nodes.bullet_list('', *entries))
return [resultnode]
def visit_configurationblock_html(self, node):
self.body.append(self.starttag(node, 'div', CLASS='configuration-block'))
def depart_configurationblock_html(self, node):
self.body.append('</div>\n')
def visit_configurationblock_latex(self, node):
pass
def depart_configurationblock_latex(self, node):
pass
def setup(app):
app.add_node(configurationblock,
html=(visit_configurationblock_html, depart_configurationblock_html),
latex=(visit_configurationblock_latex, depart_configurationblock_latex))
app.add_directive('configuration-block', ConfigurationBlock)

Submodule docs/en/_theme deleted from 6f1bc8bead

View File

@@ -32,39 +32,61 @@ The entity class:
namespace Geo\Entity;
use Geo\ValueObject\Point;
use Doctrine\ORM\Annotation as ORM;
#[Entity]
/**
* @ORM\Entity
*/
class Location
{
#[Column(type: 'point')]
private Point $point;
/**
* @ORM\Column(type="point")
*
* @var \Geo\ValueObject\Point
*/
private $point;
#[Column]
private string $address;
/**
* @ORM\Column(type="string")
*
* @var string
*/
private $address;
public function setPoint(Point $point): void
/**
* @param \Geo\ValueObject\Point $point
*/
public function setPoint(\Geo\ValueObject\Point $point)
{
$this->point = $point;
}
public function getPoint(): Point
/**
* @return \Geo\ValueObject\Point
*/
public function getPoint()
{
return $this->point;
}
public function setAddress(string $address): void
/**
* @param string $address
*/
public function setAddress($address)
{
$this->address = $address;
}
public function getAddress(): string
/**
* @return string
*/
public function getAddress()
{
return $this->address;
}
}
We use the custom type ``point`` in the ``#[Column]`` attribute of the
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
``$point`` field. We will create this custom mapping type in the next chapter.
The point class:
@@ -77,18 +99,29 @@ The point class:
class Point
{
public function __construct(
private float $latitude,
private float $longitude,
) {
/**
* @param float $latitude
* @param float $longitude
*/
public function __construct($latitude, $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
}
public function getLatitude(): float
/**
* @return float
*/
public function getLatitude()
{
return $this->latitude;
}
public function getLongitude(): float
/**
* @return float
*/
public function getLongitude()
{
return $this->longitude;
}
@@ -140,6 +173,11 @@ Now we're going to create the ``point`` type and implement all required methods.
return $value;
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('AsText(%s)', $sqlExpr);
@@ -191,7 +229,7 @@ Example usage
<?php
// Bootstrapping stuff...
// $em = new \Doctrine\ORM\EntityManager($connection, $config);
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
// Setup custom mapping type
use Doctrine\DBAL\Types\Type;
@@ -213,8 +251,8 @@ Example usage
$em->clear();
// Fetch the Location object
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$location = $query->getSingleResult();
/** @var Geo\ValueObject\Point */
/** @var Geo\ValueObject\Point $point */
$point = $location->getPoint();

View File

@@ -6,7 +6,7 @@ Aggregate Fields
You will often come across the requirement to display aggregate
values of data that can be computed by using the MIN, MAX, COUNT or
SUM SQL functions. For any ORM this is a tricky issue
traditionally. Doctrine ORM offers several ways to get access to
traditionally. Doctrine 2 offers several ways to get access to
these values and this article will describe all of them from
different perspectives.
@@ -22,7 +22,7 @@ into the account can either be of positive or negative money
values. Each account has a credit limit and the account is never
allowed to have a balance below that value.
For simplicity we live in a world where money is composed of
For simplicity we live in a world were money is composed of
integers only. Also we omit the receiver/sender name, stated reason
for transfer and the execution date. These all would have to be
added on the ``Entry`` object.
@@ -35,52 +35,55 @@ Our entities look like:
namespace Bank\Entities;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Annotation as ORM;
#[ORM\Entity]
/**
* @ORM\Entity
*/
class Account
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id;
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
private $id;
#[ORM\OneToMany(targetEntity: Entry::class, mappedBy: 'account', cascade: ['persist'])]
private Collection $entries;
/** @ORM\Column(type="string", unique=true) */
private $no;
/** @ORM\OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"}) */
private $entries;
public function __construct(
#[ORM\Column(type: 'string', unique: true)]
private string $no,
/** @ORM\Column(type="integer") */
private $maxCredit = 0;
#[ORM\Column(type: 'integer')]
private int $maxCredit = 0,
) {
$this->entries = new ArrayCollection();
public function __construct($no, $maxCredit = 0)
{
$this->no = $no;
$this->maxCredit = $maxCredit;
$this->entries = new \Doctrine\Common\Collections\ArrayCollection();
}
}
#[ORM\Entity]
/**
* @ORM\Entity
*/
class Entry
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id;
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
private $id;
public function __construct(
#[ORM\ManyToOne(targetEntity: Account::class, inversedBy: 'entries')]
private Account $account,
/** @ORM\ManyToOne(targetEntity="Account", inversedBy="entries") */
private $account;
#[ORM\Column(type: 'integer')]
private int $amount,
) {
/** @ORM\Column(type="integer") */
private $amount;
public function __construct($account, $amount)
{
$this->account = $account;
$this->amount = $amount;
// more stuff here, from/to whom, stated reason, execution date and such
}
public function getAmount(): Amount
public function getAmount()
{
return $this->amount;
}
@@ -138,14 +141,12 @@ collection, which means we can compute this value at runtime:
class Account
{
// .. previous code
public function getBalance(): int
public function getBalance()
{
$balance = 0;
foreach ($this->entries as $entry) {
$balance += $entry->getAmount();
}
return $balance;
}
}
@@ -169,11 +170,13 @@ relation with this method:
<?php
class Account
{
public function addEntry(int $amount): void
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$this->entries[] = new Entry($this, $amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
return $e;
}
}
@@ -182,14 +185,11 @@ Now look at the following test-code for our entities:
.. code-block:: php
<?php
use PHPUnit\Framework\TestCase;
class AccountTest extends TestCase
class AccountTest extends \PHPUnit_Framework_TestCase
{
public function testAddEntry()
{
$account = new Account("123456", maxCredit: 200);
$account = new Account("123456", $maxCredit = 200);
$this->assertEquals(0, $account->getBalance());
$account->addEntry(500);
@@ -201,9 +201,9 @@ Now look at the following test-code for our entities:
public function testExceedMaxLimit()
{
$account = new Account("123456", maxCredit: 200);
$account = new Account("123456", $maxCredit = 200);
$this->expectException(Exception::class);
$this->setExpectedException("Exception");
$account->addEntry(-1000);
}
}
@@ -214,12 +214,9 @@ To enforce our rule we can now implement the assertion in
.. code-block:: php
<?php
class Account
{
// .. previous code
private function assertAcceptEntryAllowed(int $amount): void
private function assertAcceptEntryAllowed($amount)
{
$futureBalance = $this->getBalance() + $amount;
$allowedMinimalBalance = ($this->maxCredit * -1);
@@ -263,20 +260,24 @@ entries collection) we want to add an aggregate field called
<?php
class Account
{
#[ORM\Column(type: 'integer')]
private int $balance = 0;
/**
* @ORM\Column(type="integer")
*/
private $balance = 0;
public function getBalance(): int
public function getBalance()
{
return $this->balance;
}
public function addEntry(int $amount): void
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$this->entries[] = new Entry($this, $amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
$this->balance += $amount;
return $e;
}
}
@@ -300,15 +301,12 @@ potentially lead to inconsistent state. See this example:
.. code-block:: php
<?php
use Bank\Entities\Account;
// The Account $accId has a balance of 0 and a max credit limit of 200:
// request 1 account
$account1 = $em->find(Account::class, $accId);
$account1 = $em->find('Bank\Entities\Account', $accId);
// request 2 account
$account2 = $em->find(Account::class, $accId);
$account2 = $em->find('Bank\Entities\Account', $accId);
$account1->addEntry(-200);
$account2->addEntry(-200);
@@ -329,12 +327,10 @@ Optimistic locking is as easy as adding a version column:
.. code-block:: php
<?php
class Account
{
#[ORM\Column(type: 'integer')]
#[ORM\Version]
private int $version;
/** @ORM\Column(type="integer") @ORM\Version */
private $version;
}
The previous example would then throw an exception in the face of
@@ -348,11 +344,9 @@ the database using a FOR UPDATE.
.. code-block:: php
<?php
use Bank\Entities\Account;
use Doctrine\DBAL\LockMode;
$account = $em->find(Account::class, $accId, LockMode::PESSIMISTIC_READ);
$account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ);
Keeping Updates and Deletes in Sync
-----------------------------------
@@ -373,3 +367,4 @@ field that offers serious performance benefits over iterating all
the related objects that make up an aggregate value. Finally I
showed how you can ensure that your aggregate fields do not get out
of sync due to race-conditions and concurrent access.

View File

@@ -95,7 +95,7 @@ class name. Now the new type can be used when mapping columns:
<?php
class MyPersistentClass
{
/** @Column(type="mytype") */
/** @ORM\Column(type="mytype") */
private $field;
}

View File

@@ -4,7 +4,7 @@ Persisting the Decorator Pattern
.. sectionauthor:: Chris Woodford <chris.woodford@gmail.com>
This recipe will show you a simple example of how you can use
Doctrine ORM to persist an implementation of the
Doctrine 2 to persist an implementation of the
`Decorator Pattern <https://en.wikipedia.org/wiki/Decorator_pattern>`_
Component
@@ -23,32 +23,51 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
namespace Test;
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['cc' => Component\ConcreteComponent::class,
'cd' => Decorator\ConcreteDecorator::class])]
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="discr", type="string")
* @ORM\DiscriminatorMap({
* "cc" = "Test\Component\ConcreteComponent",
* "cd" = "Test\Decorator\ConcreteDecorator"
* })
*/
abstract class Component
{
/**
* @ORM\Id @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
#[Id, Column]
#[GeneratedValue(strategy: 'AUTO')]
protected int|null $id = null;
#[Column(type: 'string', nullable: true)]
/** @ORM\Column(type="string", nullable=true) */
protected $name;
public function getId(): int|null
/**
* Get id
* @return integer $id
*/
public function getId()
{
return $this->id;
}
public function setName(string $name): void
/**
* Set name
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
public function getName(): string
/**
* Get name
* @return string $name
*/
public function getName()
{
return $this->name;
}
@@ -68,9 +87,10 @@ purpose of keeping this example simple).
namespace Test\Component;
use Doctrine\ORM\Annotation as ORM;
use Test\Component;
#[Entity]
/** @ORM\Entity */
class ConcreteComponent extends Component
{}
@@ -87,11 +107,16 @@ use a ``MappedSuperclass`` for this.
namespace Test;
#[MappedSuperclass]
use Doctrine\ORM\Annotation as ORM;
/** @ORM\MappedSuperclass */
abstract class Decorator extends Component
{
#[OneToOne(targetEntity: Component::class, cascade: ['all'])]
#[JoinColumn(name: 'decorates', referencedColumnName: 'id')]
/**
* @ORM\OneToOne(targetEntity="Test\Component", cascade={"all"})
* @ORM\JoinColumn(name="decorates", referencedColumnName="id")
*/
protected $decorates;
/**
@@ -107,19 +132,25 @@ use a ``MappedSuperclass`` for this.
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName(): string
public function getName()
{
return 'Decorated ' . $this->getDecorates()->getName();
}
/** the component being decorated */
protected function getDecorates(): Component
/**
* the component being decorated
* @return Component
*/
protected function getDecorates()
{
return $this->decorates;
}
/** sets the component being decorated */
protected function setDecorates(Component $c): void
/**
* sets the component being decorated
* @param Component $c
*/
protected function setDecorates(Component $c)
{
$this->decorates = $c;
}
@@ -160,21 +191,30 @@ of the getSpecial() method to its return value.
namespace Test\Decorator;
use Doctrine\ORM\Annotation as ORM;
use Test\Decorator;
#[Entity]
/** @ORM\Entity */
class ConcreteDecorator extends Decorator
{
#[Column(type: 'string', nullable: true)]
protected string|null $special = null;
/** @ORM\Column(type="string", nullable=true) */
protected $special;
public function setSpecial(string|null $special): void
/**
* Set special
* @param string $special
*/
public function setSpecial($special)
{
$this->special = $special;
}
public function getSpecial(): string|null
/**
* Get special
* @return string $special
*/
public function getSpecial()
{
return $this->special;
}
@@ -183,7 +223,7 @@ of the getSpecial() method to its return value.
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName(): string
public function getName()
{
return '[' . $this->getSpecial()
. '] ' . parent::getName();
@@ -204,7 +244,7 @@ objects
use Test\Component\ConcreteComponent,
Test\Decorator\ConcreteDecorator;
// assumes Doctrine ORM is configured and an instance of
// assumes Doctrine 2 is configured and an instance of
// an EntityManager is available as $em
// create a new concrete component
@@ -237,3 +277,4 @@ objects
echo $d->getName();
// prints: [Really] Decorated Test Component 2

View File

@@ -1,4 +1,4 @@
Extending DQL in Doctrine ORM: Custom AST Walkers
Extending DQL in Doctrine 2: Custom AST Walkers
===============================================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
@@ -12,7 +12,7 @@ the Doctrine ORM.
In Doctrine 1 the DQL language was not implemented using a real
parser. This made modifications of the DQL by the user impossible.
Doctrine ORM in contrast has a real parser for the DQL language,
Doctrine 2 in contrast has a real parser for the DQL language,
which transforms the DQL statement into an
`Abstract Syntax Tree <https://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
and generates the appropriate SQL statement for it. Since this
@@ -28,19 +28,17 @@ generating the SQL statement.
There are two types of custom tree walkers that you can hook into
the DQL parser:
- An output walker. This one actually generates the SQL, and there
is only ever one of them. We implemented the default SqlWalker
implementation for it.
- A tree walker. There can be many tree walkers, they cannot
generate the SQL, however they can modify the AST before its
rendered to SQL.
generate the sql, however they can modify the AST before its
rendered to sql.
Now this is all awfully technical, so let me come to some use-cases
fast to keep you motivated. Using walker implementation you can for
example:
- Modify the AST to generate a Count Query to be used with a
paginator for any given DQL query.
- Modify the Output Walker to generate vendor-specific SQL
@@ -50,7 +48,7 @@ example:
- Modify the Output walker to pretty print the SQL for debugging
purposes.
In this cookbook-entry I will show examples of the first two
In this cookbook-entry I will show examples on the first two
points. There are probably much more use-cases.
Generic count query for pagination
@@ -64,7 +62,7 @@ like:
SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now in this query the blog post is the root entity, meaning it's the
Now in this query the blog post is the root entity, meaning its the
one that is hydrated directly from the query and returned as an
array of blog posts. In contrast the comment and author are loaded
for deeper use in the object tree.
@@ -79,7 +77,7 @@ query for pagination would look like:
SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now you could go and write each of these queries by hand, or you
can use a tree walker to modify the AST for you. Let's see how the
can use a tree walker to modify the AST for you. Lets see how the
API would look for this use-case:
.. code-block:: php
@@ -130,7 +128,7 @@ implementation is:
{
$parent = null;
$parentName = null;
foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) {
foreach ($this->getQueryComponents() as $dqlAlias => $qComp) {
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
$parent = $qComp;
$parentName = $dqlAlias;
@@ -167,7 +165,7 @@ can be set via ``Query::setHint($name, $value)`` as shown in the
previous example with the ``HINT_CUSTOM_TREE_WALKERS`` query hint.
We will implement a custom Output Walker that allows to specify the
``SQL_NO_CACHE`` query hint.
SQL\_NO\_CACHE query hint.
.. code-block:: php
@@ -180,7 +178,7 @@ We will implement a custom Output Walker that allows to specify the
Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will
modify the generation of the SELECT clause, adding the
``SQL_NO_CACHE`` on those queries that need it:
SQL\_NO\_CACHE on those queries that need it:
.. code-block:: php
@@ -214,4 +212,3 @@ understanding of the DQL Parser and Walkers, but may offer your
huge benefits with using vendor specific features. This would still
allow you write DQL queries instead of NativeQueries to make use of
vendor specific features.

View File

@@ -10,14 +10,13 @@ change it during the life of your project. This decision for a
specific vendor potentially allows you to make use of powerful SQL
features that are unique to the vendor.
It is worth to mention that Doctrine ORM also allows you to handwrite
It is worth to mention that Doctrine 2 also allows you to handwrite
your SQL instead of extending the DQL parser. Extending DQL is sort of an
advanced extension point. You can map arbitrary SQL to your objects
and gain access to vendor specific functionalities using the
``EntityManager#createNativeQuery()`` API as described in
the :doc:`Native Query <../reference/native-sql>` chapter.
The DQL Parser has hooks to register functions that can then be
used in your DQL queries and transformed into SQL, allowing to
extend Doctrines Query capabilities to the vendors strength. This
@@ -46,7 +45,7 @@ configuration:
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = new EntityManager($connection, $config);
$em = EntityManager::create($dbParams, $config);
The ``$name`` is the name the function will be referred to in the
DQL query. ``$class`` is a string of a class-name which has to
@@ -99,12 +98,12 @@ discuss it step by step:
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(TokenType::T_IDENTIFIER); // (2)
$parser->match(TokenType::T_OPEN_PARENTHESIS); // (3)
$parser->match(Lexer::T_IDENTIFIER); // (2)
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (3)
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
$parser->match(TokenType::T_COMMA); // (5)
$parser->match(Lexer::T_COMMA); // (5)
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(TokenType::T_CLOSE_PARENTHESIS); // (3)
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
@@ -131,8 +130,8 @@ generation of a DateDiff FunctionNode somewhere in the AST of the
dql statement.
The ``ArithmeticPrimary`` method call is the most common
denominator of valid EBNF tokens taken from the :ref:`DQL EBNF grammar
<dql_ebnf_grammar>`
denominator of valid EBNF tokens taken from the
`DQL EBNF grammar <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#ebnf>`_
that matches our requirements for valid input into the DateDiff Dql
function. Picking the right tokens for your methods is a tricky
business, but the EBNF grammar is pretty helpful finding it, as is
@@ -183,23 +182,23 @@ I'll skip the blah and show the code for this function:
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(TokenType::T_COMMA);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(Lexer::T_IDENTIFIER);
/** @var Lexer $lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
@@ -232,33 +231,6 @@ vendors SQL parser to show us further errors in the parsing
process, for example if the Unit would not be one of the supported
values by MySql.
Typed functions
---------------
By default, result of custom functions is fetched as-is from the database driver.
If you want to be sure that the type is always the same, then your custom function needs to
implement ``Doctrine\ORM\Query\AST\TypedExpression``. Then, the result is wired
through ``Doctrine\DBAL\Types\Type::convertToPhpValue()`` of the ``Type`` returned in ``getReturnType()``.
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\TypedExpression;
class DateDiff extends FunctionNode implements TypedExpression
{
// ...
public function getReturnType(): Type
{
return Type::getType(Types::INTEGER);
}
}
Conclusion
----------
@@ -267,7 +239,7 @@ functionalities in DQL, we would be excited to see user extensions
that add vendor specific function packages, for example more math
functions, XML + GIS Support, Hashing functions and so on.
For ORM we will come with the current set of functions, however for
For 2.0 we will come with the current set of functions, however for
a future version we will re-evaluate if we can abstract even more
vendor sql functions and extend the DQL languages scope.

View File

@@ -3,91 +3,53 @@ Entities in the Session
There are several use-cases to save entities in the session, for example:
1. User data
1. User object
2. Multi-step forms
To achieve this with Doctrine you have to pay attention to some details to get
this working.
Updating an entity
------------------
Merging entity into an EntityManager
------------------------------------
In Doctrine an entity objects has to be "managed" by an EntityManager to be
updatable. Entities saved into the session are not managed in the next request
anymore. This means that you have to update the entities with the stored session
data after you fetch the entities from the EntityManager again.
In Doctrine, an entity objects has to be "managed" by an EntityManager to be
updated. Entities saved into the session are not managed in the next request
anymore. This means that you have to register these entities with an
EntityManager again if you want to change them or use them as part of
references between other entities.
For a representative User object the code to get data from the session into a
managed Doctrine object can look like these examples:
It is a good idea to avoid storing entities in serialized formats such as
``$_SESSION``: instead, store the entity identifiers or raw data.
Working with scalars
~~~~~~~~~~~~~~~~~~~~
In simpler applications there is no need to work with objects in sessions and you can use
separate session elements.
For a representative User object the code to get turn an instance from
the session into a managed Doctrine object looks like this:
.. code-block:: php
<?php
require_once 'bootstrap.php';
$em = GetEntityManager(); // creates an EntityManager
session_start();
if (isset($_SESSION['userId']) && is_int($_SESSION['userId'])) {
$userId = $_SESSION['userId'];
if (isset($_SESSION['user'])) {
$user = $em->find(User::class, $_SESSION['user']);
$em = GetEntityManager(); // creates an EntityManager
$user = $em->find(User::class, $userId);
$user->setValue($_SESSION['storedValue']);
$em->flush();
if (! $user instanceof User) {
// user not found in the database
$_SESSION['user'] = null;
}
}
Working with custom data transfer objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If objects are needed, we discourage the storage of entity objects in the session. It's
preferable to use a `DTO (data transfer object) <https://en.wikipedia.org/wiki/Data_transfer_object>`_
instead and merge the DTO data later with the entity.
.. code-block:: php
<?php
require_once 'bootstrap.php';
session_start();
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof UserDto) {
$userDto = $_SESSION['user'];
$em = GetEntityManager(); // creates an EntityManager
$userEntity = $em->find(User::class, $userDto->getId());
$userEntity->populateFromDto($userDto);
$em->flush();
}
Serializing entity into the session
-----------------------------------
Entities that are serialized into the session normally contain references to
other entities as well. Think of the user entity has a reference to their
articles, groups, photos or many other different entities. If you serialize
this object into the session then you don't want to serialize the related
entities as well. This is why you shouldn't serialize an entity and use
only the needed values of it. This can happen with the help of a DTO.
.. code-block:: php
<?php
require_once 'bootstrap.php';
$em = GetEntityManager(); // creates an EntityManager
$user = $em->find("User", 1);
$userDto = new UserDto($user->getId(), $user->getFirstName(), $user->getLastName());
// or "UserDto::createFrom($user);", but don't store an entity in a property. Only its values without relations.
$_SESSION['user'] = $userDto;
Serializing entities into the session
-------------------------------------
Serializing entities in the session means serializing also all associated
entities and collections. While this might look like a quick solution in
simple applications, you will encounter problems due to the fact that the
data in the session is stale.
In order to prevent working with stale data, try saving only minimal
information about your entities in your session, without storing entire
entity objects. Should you need the full information of an object, so it
is suggested to re-query the database, which is usually the most
authoritative source of information in typical PHP applications.

View File

@@ -1,7 +1,7 @@
Implementing ArrayAccess for Domain Objects
===========================================
.. sectionauthor:: Roman Borschel <roman@code-factory.org>
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
This recipe will show you how to implement ArrayAccess for your
domain objects in order to allow more uniform access, for example
@@ -16,7 +16,6 @@ In this implementation we will make use of PHPs highly dynamic
nature to dynamically access properties of a subtype in a supertype
at runtime. Note that this implementation has 2 main caveats:
- It will not work with private fields
- It will not go through any getters/setters
@@ -28,15 +27,15 @@ at runtime. Note that this implementation has 2 main caveats:
public function offsetExists($offset) {
return isset($this->$offset);
}
public function offsetSet($offset, $value) {
$this->$offset = $value;
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetUnset($offset) {
$this->$offset = null;
}
@@ -50,7 +49,6 @@ Again we use PHPs dynamic nature to invoke methods on a subtype
from a supertype at runtime. This implementation has the following
caveats:
- It relies on a naming convention
- The semantics of offsetExists can differ
- offsetUnset will not work with typehinted setters
@@ -65,15 +63,15 @@ caveats:
$value = $this->{"get$offset"}();
return $value !== null;
}
public function offsetSet($offset, $value) {
$this->{"set$offset"}($value);
}
public function offsetGet($offset) {
return $this->{"get$offset"}();
}
public function offsetUnset($offset) {
$this->{"set$offset"}(null);
}
@@ -95,18 +93,17 @@ exception (i.e. BadMethodCallException).
public function offsetExists($offset) {
// option 1 or option 2
}
public function offsetSet($offset, $value) {
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
}
public function offsetGet($offset) {
// option 1 or option 2
}
public function offsetUnset($offset) {
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
}
}

View File

@@ -0,0 +1,71 @@
Implementing the Notify ChangeTracking Policy
=============================================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
The NOTIFY change-tracking policy is the most effective
change-tracking policy provided by Doctrine but it requires some
boilerplate code. This recipe will show you how this boilerplate
code should look like. We will implement it on a
`Layer Supertype <https://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
Implementing NotifyPropertyChanged
----------------------------------
The NOTIFY policy is based on the assumption that the entities
notify interested listeners of changes to their properties. For
that purpose, a class that wants to use this policy needs to
implement the ``NotifyPropertyChanged`` interface from the
``Doctrine\Common`` namespace.
.. code-block:: php
<?php
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->listeners[] = $listener;
}
/** Notifies listeners of a change. */
protected function onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
}
Then, in each property setter of concrete, derived domain classes,
you need to invoke onPropertyChanged as follows to notify
listeners:
.. code-block:: php
<?php
// Mapping not shown, either in annotations or xml as usual
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
The check whether the new value is different from the old one is
not mandatory but recommended. That way you can avoid unnecessary
updates and also have full control over when you consider a
property changed.

View File

@@ -1,12 +1,12 @@
Mysql Enums
===========
The type system of Doctrine ORM consists of flyweights, which means there is only
The type system of Doctrine 2 consists of flyweights, which means there is only
one instance of any given type. Additionally types do not contain state. Both
assumptions make it rather complicated to work with the Enum Type of MySQL that
is used quite a lot by developers.
When using Enums with a non-tweaked Doctrine ORM application you will get
When using Enums with a non-tweaked Doctrine 2 application you will get
errors from the Schema-Tool commands due to the unknown database type "enum".
By default Doctrine does not map the MySQL enum type to a Doctrine type.
This is because Enums contain state (their allowed values) and Doctrine
@@ -43,13 +43,16 @@ entities:
.. code-block:: php
<?php
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
class Article
{
const STATUS_VISIBLE = 'visible';
const STATUS_INVISIBLE = 'invisible';
#[Column(type: "string")]
/** @ORM\Column(type="string") */
private $status;
public function setStatus($status)
@@ -67,10 +70,13 @@ the **columnDefinition** attribute.
.. code-block:: php
<?php
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
class Article
{
#[Column(type: "string", columnDefinition: "ENUM('visible', 'invisible')")]
/** @ORM\Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
private $status;
}
@@ -131,10 +137,13 @@ Then in your entity you can just use this type:
.. code-block:: php
<?php
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
class Article
{
#[Column(type: "enumvisibility")]
/** @ORM\Column(type="enumvisibility") */
private $status;
}
@@ -196,3 +205,4 @@ With this base class you can define an enum as easily as:
protected $name = 'enumvisibility';
protected $values = array('visible', 'invisible');
}

View File

@@ -1,11 +1,13 @@
Keeping your Modules independent
=================================
.. versionadded:: 2.2
One of the goals of using modules is to create discrete units of functionality
that do not have many (if any) dependencies, allowing you to use that
functionality in other applications without including unnecessary items.
Doctrine ORM includes a new utility called the ``ResolveTargetEntityListener``,
Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``,
that functions by intercepting certain calls inside Doctrine and rewrite
targetEntity parameters in your metadata mapping at runtime. It means that
in your bundle you are able to use an interface or abstract class in your
@@ -47,8 +49,10 @@ A Customer entity
use Acme\CustomerModule\Entity\Customer as BaseCustomer;
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
#[ORM\Entity]
#[ORM\Table(name: 'customer')]
/**
* @ORM\Entity
* @ORM\Table(name="customer")
*/
class Customer extends BaseCustomer implements InvoiceSubjectInterface
{
// In our example, any methods defined in the InvoiceSubjectInterface
@@ -64,15 +68,22 @@ An Invoice entity
namespace Acme\InvoiceModule\Entity;
use Doctrine\ORM\Mapping AS ORM;
use Doctrine\ORM\Mapping as ORM;
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
#[ORM\Entity]
#[ORM\Table(name: 'invoice')]
/**
* Represents an Invoice.
*
* @ORM\Entity
* @ORM\Table(name="invoice")
*/
class Invoice
{
#[ORM\ManyToOne(targetEntity: InvoiceSubjectInterface::class)]
protected InvoiceSubjectInterface $subject;
/**
* @ORM\ManyToOne(targetEntity="Acme\InvoiceModule\Model\InvoiceSubjectInterface")
* @var InvoiceSubjectInterface
*/
protected $subject;
}
An InvoiceSubjectInterface
@@ -118,8 +129,7 @@ the targetEntity resolution will occur reliably:
// Add the ResolveTargetEntityListener
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
$connection = \Doctrine\DBAL\DriverManager::getConnection($connectionOptions, $config, $evm);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
Final Thoughts
--------------
@@ -128,3 +138,4 @@ With the ``ResolveTargetEntityListener``, we are able to decouple our
bundles, keeping them usable by themselves, but still being able to
define relationships between different objects. By using this method,
I've found my bundles end up being easier to maintain independently.

View File

@@ -23,31 +23,32 @@ appropriate autoloaders.
.. code-block:: php
<?php
namespace DoctrineExtensions;
use \Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Mapping;
class TablePrefix
{
protected $prefix = '';
public function __construct($prefix)
{
$this->prefix = (string) $prefix;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) {
$classMetadata->setPrimaryTable([
'name' => $this->prefix . $classMetadata->getTableName()
]);
if ($classMetadata->inheritanceType !== Mapping\InheritanceType::SINGLE_TABLE ||
$classMetadata->getName() === $classMetadata->rootEntityName) {
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
}
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) {
foreach ($classMetadata->associationMappings as $fieldName => $mapping) {
if ($mapping['type'] == Mapping\ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) {
$mappedTableName = $mapping['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
@@ -68,17 +69,17 @@ before the prefix has been set.
If you set this listener up, be aware that you will need
to clear your caches and drop then recreate your database schema.
.. code-block:: php
<?php
// $connectionOptions and $config set earlier
$evm = new \Doctrine\Common\EventManager;
// Table Prefix
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);

View File

@@ -12,7 +12,6 @@ Scenario / Problem
Given a Content-Management-System, we probably want to add / edit
some so-called "blocks" and "panels". What are they for?
- A block might be a registration form, some text content, a table
with information. A good example might also be a small calendar.
- A panel is by definition a block that can itself contain blocks.
@@ -23,14 +22,13 @@ So, in this scenario, when building your CMS, you will surely add
lots of blocks and panels to your pages and you will find yourself
highly uncomfortable because of the following:
- Every existing page needs to know about the panels it contains -
therefore, you'll have an association to your panels. But if you've
got several types of panels - what do you do? Add an association to
every panel-type? This wouldn't be flexible. You might be tempted
to add an AbstractPanelEntity and an AbstractBlockEntity that use
class inheritance. Your page could then only confer to the
AbstractPanelType and Doctrine ORM would do the rest for you, i.e.
AbstractPanelType and Doctrine 2 would do the rest for you, i.e.
load the right entities. But - you'll for sure have lots of panels
and blocks, and even worse, you'd have to edit the discriminator
map *manually* every time you or another developer implements a new
@@ -58,7 +56,6 @@ the middle of your page, for example).
Such an interface could look like this:
.. code-block:: php
<?php
@@ -152,16 +149,16 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
/**
* This var contains the classname of the strategy
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine ORM)
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2)
*
* This is a doctrine field, so make sure that you use a
#[Column] attribute or setup your xml files correctly
* This is a doctrine field, so make sure that you use an @column annotation or setup your
* xml files correctly
* @var string
*/
protected $strategyClassName;
/**
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine ORM.
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2.
*
* @var BlockStrategyInterface
*/
@@ -199,7 +196,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
$strategy->setBlockEntity($this);
}
Now, the important point is that $strategyClassName is a Doctrine ORM
Now, the important point is that $strategyClassName is a Doctrine 2
field, i.e. Doctrine will persist this value. This is only the
class name of your strategy and not an instance!
@@ -213,15 +210,14 @@ This might look like this:
.. code-block:: php
<?php
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM,
Doctrine\Common;
/**
* The BlockStrategyEventListener will initialize a strategy after the
* block itself was loaded.
*/
class BlockStrategyEventListener implements EventSubscriber {
class BlockStrategyEventListener implements Common\EventSubscriber {
protected $view;
@@ -230,11 +226,11 @@ This might look like this:
}
public function getSubscribedEvents() {
return array(Events::postLoad);
return array(ORM\Events::postLoad);
}
public function postLoad(LifecycleEventArgs $args) {
$blockItem = $args->getObject();
public function postLoad(ORM\Event\LifecycleEventArgs $args) {
$blockItem = $args->getEntity();
// Both blocks and panels are instances of Block\AbstractBlock
if ($blockItem instanceof Block\AbstractBlock) {
@@ -251,3 +247,4 @@ This might look like this:
In this example, even some variables are set - like a view object
or a specific configuration object.

View File

@@ -3,7 +3,7 @@ Validation of Entities
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
Doctrine ORM does not ship with any internal validators, the reason
Doctrine 2 does not ship with any internal validators, the reason
being that we think all the frameworks out there already ship with
quite decent ones that can be integrated into your Domain easily.
What we offer are hooks to execute any kind of validation.
@@ -11,11 +11,10 @@ What we offer are hooks to execute any kind of validation.
.. note::
You don't need to validate your entities in the lifecycle
events. It is only one of many options. Of course you can also
events. Its only one of many options. Of course you can also
perform validations in value setters or any other method of your
entities that are used in your code.
Entities can register lifecycle event methods with Doctrine that
are called on different occasions. For validation we would need to
hook into the events called before persisting and updating. Even
@@ -25,8 +24,8 @@ the additional benefit of being able to re-use your validation in
any other part of your domain.
Say we have an ``Order`` with several ``OrderLine`` instances. We
never want to allow any customer to order for a larger sum than they
are allowed to:
never want to allow any customer to order for a larger sum than he
is allowed to:
.. code-block:: php
@@ -53,21 +52,23 @@ code, enforcing it at any time is important so that customers with
a unknown reputation don't owe your business too much money.
We can enforce this constraint in any of the metadata drivers.
First Attributes:
First Annotations:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Doctrine\ORM\Mapping\PrePersist;
use Doctrine\ORM\Mapping\PreUpdate;
#[Entity]
#[HasLifecycleCallbacks]
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
*/
class Order
{
#[PrePersist, PreUpdate]
/**
* @ORM\PrePersist @ORM\PreUpdate
*/
public function assertCustomerAllowedBuying() {}
}
@@ -78,8 +79,8 @@ In XML Mappings:
<doctrine-mapping>
<entity name="Order">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
<lifecycle-callback type="prePersist" method="assertCustomerAllowedBuying" />
<lifecycle-callback type="preUpdate" method="assertCustomerAllowedBuying" />
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
@@ -97,9 +98,14 @@ validation callbacks.
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
class Order
{
#[PrePersist, PreUpdate]
/**
* @ORM\PrePersist @ORM\PreUpdate
*/
public function validate()
{
if (!($this->plannedShipDate instanceof DateTime)) {

View File

@@ -3,7 +3,7 @@ Working with DateTime Instances
There are many nitty gritty details when working with PHPs DateTime instances. You have to know their inner
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
interesting pieces of information on how to work with PHP DateTime instances in ORM.
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
DateTime changes are detected by Reference
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -16,15 +16,15 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
<?php
use DateTime;
use Doctrine\ORM\Annotation as ORM;
#[Entity]
/** @ORM\Entity */
class Article
{
#[Column(type: 'datetime')]
private DateTime $updated;
/** @ORM\Column(type="datetime") */
private $updated;
public function setUpdated(): void
public function setUpdated()
{
// will NOT be saved in the database
$this->updated->modify("now");
@@ -36,14 +36,12 @@ The way to go would be:
.. code-block:: php
<?php
use DateTime;
class Article
{
public function setUpdated(): void
public function setUpdated()
{
// WILL be saved in the database
$this->updated = new DateTime("now");
$this->updated = new \DateTime("now");
}
}
@@ -63,7 +61,7 @@ Handling different Timezones with the DateTime Type
If you first come across the requirement to save different timezones you may be still optimistic about how
to manage this mess,
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine ORM)
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
that supports timezones correctly. Correctly here means that you can cover all the use-cases that
can come up with timezones. If you don't believe me you should read up on `Storing DateTime
in Databases <https://derickrethans.nl/storing-date-time-in-database.html>`_.
@@ -72,7 +70,7 @@ The problem is simple. Not a single database vendor saves the timezone, only the
However with frequent daylight saving and political timezone changes you can have a UTC offset that moves
in different offset directions depending on the real location.
The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine ORM. However there is a workaround
The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround
that even allows correct date-time handling with timezones:
1. Always convert any DateTime instance to UTC.
@@ -89,14 +87,13 @@ the UTC time at the time of the booking and the timezone the event happened in.
namespace DoctrineExtensions\DBAL\Types;
use DateTimeZone;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateTimeType;
class UTCDateTimeType extends DateTimeType
{
private static DateTimeZone $utc;
static private $utc;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
@@ -129,10 +126,10 @@ the UTC time at the time of the booking and the timezone the event happened in.
return $converted;
}
private static function getUtc(): DateTimeZone
private static function getUtc()
{
return self::$utc ??= new DateTimeZone('UTC');
return self::$utc ? self::$utc : self::$utc = new \DateTimeZone('UTC');
}
}
@@ -152,7 +149,6 @@ code before bootstrapping the ORM:
Type::overrideType('datetime', UTCDateTimeType::class);
Type::overrideType('datetimetz', UTCDateTimeType::class);
To be able to transform these values
back into their real timezone you have to save the timezone in a separate field of the entity
requiring timezoned datetimes:
@@ -160,15 +156,20 @@ requiring timezoned datetimes:
.. code-block:: php
<?php
namespace Shipping;
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
*/
class Event
{
#[Column(type: 'datetime')]
/** @ORM\Column(type="datetime") */
private $created;
#[Column(type: 'string')]
/** @ORM\Column(type="string") */
private $timezone;
/**

View File

@@ -1,10 +1,8 @@
Welcome to Doctrine ORM's documentation!
==========================================
ORM Documentation
=================
The Doctrine documentation is comprised of tutorials, a reference section and
cookbook articles that explain different parts of the Object Relational mapper.
Doctrine DBAL and Doctrine Common both have their own documentation.
The Doctrine ORM documentation is comprised of tutorials, a reference section and
cookbook articles that explain different parts of the Object Relational Mapper.
Getting Help
------------
@@ -16,107 +14,11 @@ Doctrine ORM don't panic. You can get help from different sources:
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
If you need more structure over the different topics you can browse the table
of contents.
Getting Started
---------------
* **Tutorial**:
:doc:`Getting Started with Doctrine <tutorials/getting-started>`
* **Setup**:
:doc:`Installation & Configuration <reference/configuration>`
Mapping Objects onto a Database
-------------------------------
* **Mapping**:
:doc:`Objects <reference/basic-mapping>` \|
:doc:`Associations <reference/association-mapping>` \|
:doc:`Inheritance <reference/inheritance-mapping>`
* **Drivers**:
:doc:`Attributes <reference/attributes-reference>` \|
:doc:`XML <reference/xml-mapping>` \|
:doc:`PHP <reference/php-mapping>`
Working with Objects
--------------------
* **Basic Reference**:
:doc:`Entities <reference/working-with-objects>` \|
:doc:`Associations <reference/working-with-associations>` \|
:doc:`Events <reference/events>`
* **Query Reference**:
:doc:`DQL <reference/dql-doctrine-query-language>` \|
:doc:`QueryBuilder <reference/query-builder>` \|
:doc:`Native SQL <reference/native-sql>`
* **Internals**:
:doc:`Internals explained <reference/unitofwork>` \|
:doc:`Associations <reference/unitofwork-associations>`
Advanced Topics
---------------
* :doc:`Architecture <reference/architecture>`
* :doc:`Advanced Configuration <reference/advanced-configuration>`
* :doc:`Limitations and known issues <reference/limitations-and-known-issues>`
* :doc:`Commandline Tools <reference/tools>`
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`TypedFieldMapper <reference/typedfieldmapper>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
* :doc:`Best Practices <reference/best-practices>`
* :doc:`Metadata Drivers <reference/metadata-drivers>`
* :doc:`Batch Processing <reference/batch-processing>`
* :doc:`Second Level Cache <reference/second-level-cache>`
Tutorials
---------
* :doc:`Indexed associations <tutorials/working-with-indexed-associations>`
* :doc:`Extra Lazy Associations <tutorials/extra-lazy-associations>`
* :doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
* :doc:`Ordered associations <tutorials/ordered-associations>`
* :doc:`Pagination <tutorials/pagination>`
* :doc:`Override Field/Association Mappings In Subclasses <tutorials/override-field-association-mappings-in-subclasses>`
* :doc:`Embeddables <tutorials/embeddables>`
Changelogs
----------
* `Upgrade <https://github.com/doctrine/orm/blob/HEAD/UPGRADE.md>`_
Cookbook
--------
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` \|
:doc:`Decorator Pattern <cookbook/decorator-pattern>` \|
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
* **DQL Extension Points**:
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` \|
:doc:`DQL User-Defined-Functions <cookbook/dql-user-defined-functions>`
* **Implementation**:
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` \|
:doc:`Working with DateTime <cookbook/working-with-datetime>` \|
:doc:`Validation <cookbook/validation-of-entities>` \|
:doc:`Entities in the Session <cookbook/entities-in-session>` \|
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
* **Hidden Gems**
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`
* **Custom Datatypes**
:doc:`MySQL Enums <cookbook/mysql-enums>`
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`
The best way to get started is with the :doc:`Getting Started with Doctrine <tutorials/getting-started>` tutorial.
Use the sidebar to browse other tutorials and documentation for the Doctrine PHP ORM.

View File

@@ -1,113 +0,0 @@
@ECHO OFF
REM Command file for Sphinx documentation
set SPHINXBUILD=sphinx-build
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

View File

@@ -9,61 +9,50 @@ steps of configuration.
.. code-block:: php
<?php
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\ORMSetup;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Doctrine\ORM\Configuration;
use Doctrine\Common\Proxy\ProxyFactory;
// ...
if ($applicationMode == "development") {
$queryCache = new ArrayAdapter();
$metadataCache = new ArrayAdapter();
$cache = new \Doctrine\Common\Cache\ArrayCache;
} else {
$queryCache = new PhpFilesAdapter('doctrine_queries');
$metadataCache = new PhpFilesAdapter('doctrine_metadata');
$cache = new \Doctrine\Common\Cache\ApcuCache;
}
$config = new Configuration;
$config->setMetadataCache($metadataCache);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
$config->setQueryCacheImpl($cache);
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
$config->setProxyNamespace('MyProject\Proxies');
$config->setAutoGenerateProxyClasses($applicationMode === 'development')
if ($applicationMode == "development") {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
if ('development' === $applicationMode) {
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
}
$connection = DriverManager::getConnection([
$connectionOptions = [
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite',
], $config);
'path' => 'database.sqlite'
];
$em = new EntityManager($connection, $config);
Doctrine and Caching
--------------------
Doctrine is optimized for working with caches. The main parts in Doctrine
that are optimized for caching are the metadata mapping information with
the metadata cache and the DQL to SQL conversions with the query cache.
These 2 caches require only an absolute minimum of memory yet they heavily
improve the runtime performance of Doctrine.
Doctrine does not bundle its own cache implementation anymore. Instead,
the PSR-6 standard interfaces are used to access the cache. In the examples
in this documentation, Symfony Cache is used as a reference implementation.
$em = EntityManager::create($connectionOptions, $config);
.. note::
Do not use Doctrine without a metadata and query cache!
Doctrine is optimized for working with caches. The main
parts in Doctrine that are optimized for caching are the metadata
mapping information with the metadata cache and the DQL to SQL
conversions with the query cache. These 2 caches require only an
absolute minimum of memory yet they heavily improve the runtime
performance of Doctrine. The recommended cache driver to use with
Doctrine is `APCu <https://php.net/apcu>`_. APCu provides you with
a very fast in-memory cache storage that you can use for the metadata and
query caches as seen in the previous code snippet.
Configuration Options
---------------------
@@ -71,30 +60,32 @@ Configuration Options
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
Proxy Directory (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy Directory
~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setProxyDir($dir);
$config->getProxyDir();
Gets or sets the directory where Doctrine generates any proxy
Sets the directory where Doctrine generates any proxy
classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Proxy Namespace (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Setting the proxy target directory will also implicitly cause a
call to ``Doctrine\ORM\Configuration#setAutoGenerateProxyClasses()``
with a value of ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``.
Proxy Namespace
~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setProxyNamespace($namespace);
$config->getProxyNamespace();
Gets or sets the namespace to use for generated proxy classes. For
Sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
@@ -111,28 +102,26 @@ Gets or sets the metadata driver implementation that is used by
Doctrine to acquire the object-relational metadata for your
classes.
There are currently 3 available implementations:
There are currently 4 available implementations:
- ``Doctrine\ORM\Mapping\Driver\AttributeDriver``
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
Throughout the most part of this manual the AttributeDriver is
used in the examples. For information on the usage of the
XmlDriver please refer to the dedicated chapter ``XML Mapping``.
Throughout the most part of this manual the AnnotationDriver is
used in the examples. For information on the usage of the XmlDriver
please refer to the dedicated chapters ``XML Mapping``.
The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
The annotation driver can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the attribute
The path information to the entities is required for the annotation
driver, because otherwise mass-operations on all entities through
the console could not work correctly. All of metadata drivers
accept either a single directory as a string or an array of
@@ -145,21 +134,28 @@ Metadata Cache (***RECOMMENDED***)
.. code-block:: php
<?php
$config->setMetadataCache($cache);
$config->getMetadataCache();
$config->setMetadataCacheImpl($cache);
$config->getMetadataCacheImpl();
Gets or sets the cache adapter to use for caching metadata
information, that is, all the information you supply via attributes,
xml, so that they do not need to be parsed and loaded from scratch on
every single request which is a waste of resources. The cache
implementation must implement the PSR-6
``Psr\Cache\CacheItemPoolInterface`` interface.
Gets or sets the cache implementation to use for caching metadata
information, that is, all the information you supply via
annotations or xml, so that they do not need to be parsed and
loaded from scratch on every single request which is a waste of
resources. The cache implementation must implement the
``Doctrine\Common\Cache\Cache`` interface.
Usage of a metadata cache is highly recommended.
For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
For development you should use the
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
per-request basis.
Query Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -167,8 +163,8 @@ Query Cache (***RECOMMENDED***)
.. code-block:: php
<?php
$config->setQueryCache($cache);
$config->getQueryCache();
$config->setQueryCacheImpl($cache);
$config->getQueryCacheImpl();
Gets or sets the cache implementation to use for caching DQL
queries, that is, the result of a DQL parsing process that includes
@@ -180,9 +176,16 @@ minimal memory usage in your cache).
Usage of a query cache is highly recommended.
For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
For development you should use the
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
per-request basis.
SQL Logger (***Optional***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -195,10 +198,13 @@ SQL Logger (***Optional***)
Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
``Doctrine\DBAL\Logging\SQLLogger`` interface. A simple default
implementation that logs to the standard output using ``echo`` and
``var_dump`` can be found at
``Doctrine\DBAL\Logging\EchoSQLLogger``.
Auto-generating Proxy Classes (***OPTIONAL***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Auto-generating Proxy Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy classes can either be generated manually through the Doctrine
Console or automatically at runtime by Doctrine. The configuration
@@ -211,45 +217,76 @@ option that controls this behavior is:
Possible values for ``$mode`` are:
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Never autogenerate a proxy. You will need to generate the proxies
manually, for this use the Doctrine Console like so:
Generate the proxy class when the proxy file does not exist.
This strategy can potentially cause disk access.
Note that autoloading will be attempted before falling back
to generating a proxy class: if an already existing proxy class
is found, then no file write operations will be performed.
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via ``eval()``,
avoiding writing the proxies to disk.
This strategy is only sane for development and long running
processes.
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
This flag is deprecated, and is an alias
of ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
This flag is deprecated, and is an alias
of ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Before v2.4, ``setAutoGenerateProxyClasses`` would accept a boolean
value. This is still possible, ``FALSE`` being equivalent to
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
Manually generating Proxy Classes for performance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
While the ORM can generate proxy classes when required, it is suggested
to not let this happen for production environments, as it has a major
impact on your application's performance.
In a production environment, it is highly recommended to use
``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
in combination with a well-configured
`composer class autoloader<https://getcomposer.org/doc/01-basic-usage.md#autoloading>`_.
Here is an example of such setup:
.. code-block:: json
{
"autoload": {
"psr-4": {
"MyProject\\": "path/to/project/sources/",
"GeneratedProxies\\": "path/to/generated/proxies/"
}
}
}
You would then configure the ORM to use the ``"GeneratedProxies"``
and the ``"path/to/generated/proxies/"`` for the proxy classes:
.. code-block:: php
$ ./doctrine orm:generate-proxies
<?php
$config->setProxyDir('path/to/generated/proxies/');
$config->setProxyNamespace('GeneratedProxies');
When you do this in a development environment,
be aware that you may get class/file not found errors if certain proxies
are not yet generated. You may also get failing lazy-loads if new
methods were added to the entity class that are not yet in the proxy class.
In such a case, simply use the Doctrine Console to (re)generate the
proxy classes.
To make sure proxies are never generated by Doctrine, you'd forcefully
generate them during deployment operations:
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
.. code-block:: sh
Always generates a new proxy in every request and writes it to disk.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Generate the proxy class when the proxy file does not exist.
This strategy causes a file exists call whenever any proxy is
used the first time in a request.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via eval(),
avoiding writing the proxies to disk.
This strategy is only sane for development.
In a production environment, it is highly recommended to use
AUTOGENERATE_NEVER to allow for optimal performances. The other
options are interesting in development environment.
``setAutoGenerateProxyClasses`` can accept a boolean
value. This is still possible, ``FALSE`` being equivalent to
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
$ ./vendor/bin/doctrine orm:generate-proxies
$ composer dump-autoload
Development vs Production Configuration
---------------------------------------
@@ -259,25 +296,20 @@ runtime models in mind. There are some serious benefits of using
APCu or Memcache in production. In development however this will
frequently give you fatal errors, when you change your entities and
the cache still keeps the outdated metadata. That is why we
recommend an array cache for development.
recommend the ``ArrayCache`` for development.
Furthermore you should have the Auto-generating Proxy Classes
option to true in development and to false in production. If this
option is set to ``TRUE`` it can seriously hurt your script
performance if several proxy classes are re-generated during script
execution. Filesystem calls of that magnitude can even slower than
all the database queries Doctrine issues. Additionally writing a
proxy sets an exclusive file lock which can cause serious
performance bottlenecks in systems with regular concurrent
requests.
Furthermore you should disable the Auto-generating Proxy Classes
option in production.
Connection
----------
Connection Options
------------------
The ``$connection`` passed as the first argument to he constructor of
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
to create such a connection. The DBAL configuration is explained in the
The ``$connectionOptions`` passed as the first argument to
``EntityManager::create()`` has to be either an array or an
instance of ``Doctrine\DBAL\Connection``. If an array is passed it
is directly passed along to the DBAL Factory
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
configuration is explained in the
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
Proxy Objects
@@ -285,7 +317,7 @@ Proxy Objects
A proxy object is an object that is put in place or used instead of
the "real" object. A proxy object can add behavior to the object
being proxied without that object being aware of it. In ORM,
being proxied without that object being aware of it. In Doctrine 2,
proxy objects are used to realize several features but mainly for
transparent lazy-loading.
@@ -295,7 +327,7 @@ of the objects. This is an essential property as without it there
would always be fragile partial objects at the outer edges of your
object graph.
Doctrine ORM implements a variant of the proxy pattern where it
Doctrine 2 implements a variant of the proxy pattern where it
generates classes that extend your entity classes and adds
lazy-loading capabilities to them. Doctrine can then give you an
instance of such a proxy class whenever you request an object of
@@ -306,47 +338,27 @@ Reference Proxies
The method ``EntityManager#getReference($entityName, $identifier)``
lets you obtain a reference to an entity for which the identifier
is known, without necessarily loading that entity from the database.
This is useful, for example, as a performance enhancement, when you
want to establish an association to an entity for which you have the
identifier.
Consider the following example:
is known, without loading that entity from the database. This is
useful, for example, as a performance enhancement, when you want to
establish an association to an entity for which you have the
identifier. You could simply do this:
.. code-block:: php
<?php
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference('MyProject\Model\Item', $itemId);
$item = $em->getReference(\MyProject\Model\Item::class, $itemId);
$cart->addItem($item);
Whether the object being returned from ``EntityManager#getReference()``
is a proxy or a direct instance of the entity class may depend on different
factors, including whether the entity has already been loaded into memory
or entity inheritance being used. But your code does not need to care
and in fact it **should not care**. Proxy objects should be transparent to your
code.
When using the ``EntityManager#getReference()`` method, you need to be aware
of a few peculiarities.
At the best case, the ORM can avoid querying the database at all. But, that
also means that this method will not throw an exception when an invalid value
for the ``$identifier`` parameter is passed. ``$identifier`` values are
not checked and there is no guarantee that the requested entity instance even
exists the method will still return a proxy object.
Its only when the proxy has to be fully initialized or associations cannot
be written to the database that invalid ``$identifier`` values may lead to
exceptions.
The ``EntityManager#getReference()`` is mostly useful when you only
need a reference to some entity to make an association, like in the example
above. In that case, it can save you from loading data from the database
that you don't need. But remember as soon as you read any property values
besides those making up the ID, a database request will be made to initialize
all fields.
Here, we added an ``Item`` to a ``Cart`` without loading the Item from the
database.
If you access any persistent state that isn't yet available in the ``Item``
instance, the proxying mechanism would fully initialize the object's state
transparently from the database.
Here ``$item`` is actually an instance of the proxy class that was generated
for the ``Item`` class but your code does not need to care. In fact it
**should not care**. Proxy objects should be transparent to your code.
Association proxies
~~~~~~~~~~~~~~~~~~~
@@ -354,7 +366,7 @@ Association proxies
The second most important situation where Doctrine uses proxy
objects is when querying for objects. Whenever you query for an
object that has a single-valued association to another object that
is configured LAZY, without joining that association in the same
is configured ``LAZY``, without joining that association in the same
query, Doctrine puts proxy objects in place where normally the
associated object would be. Just like other proxies it will
transparently initialize itself on first access.
@@ -366,71 +378,22 @@ transparently initialize itself on first access.
This will override the 'fetch' option specified in the mapping for
that association, but only for that query.
Generating Proxy classes
~~~~~~~~~~~~~~~~~~~~~~~~
In a production environment, it is highly recommended to use
``AUTOGENERATE_NEVER`` to allow for optimal performances.
However you will be required to generate the proxies manually
using the Doctrine Console:
.. code-block:: php
$ ./doctrine orm:generate-proxies
The other options are interesting in development environment:
- ``AUTOGENERATE_ALWAYS`` will require you to create and configure
a proxy directory. Proxies will be generated and written to file
on each request, so any modification to your code will be acknowledged.
- ``AUTOGENERATE_FILE_NOT_EXISTS`` will not overwrite an existing
proxy file. If your code changes, you will need to regenerate the
proxies manually.
- ``AUTOGENERATE_EVAL`` will regenerate each proxy on each request,
but without writing them to disk.
Autoloading Proxies
-------------------
When you deserialize proxy objects from the session or any other storage
it is necessary to have an autoloading mechanism in place for these classes.
For implementation reasons Proxy class names are not PSR-0 compliant. This
means that you have to register a special autoloader for these classes:
.. code-block:: php
<?php
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";
Autoloader::register($proxyDir, $proxyNamespace);
If you want to execute additional logic to intercept the proxy file not found
state you can pass a closure as the third argument. It will be called with
the arguments proxydir, namespace and className when the proxy file could not
be found.
Multiple Metadata Sources
-------------------------
When using different components using Doctrine ORM you may end up
When using different components using Doctrine 2 you may end up
with them using two different metadata drivers, for example XML and
PHP. You can use the MappingDriverChain Metadata implementations to
annotationsL. You can use the DriverChain Metadata implementations to
aggregate these drivers based on namespaces:
.. code-block:: php
<?php
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Doctrine\ORM\Mapping\Driver\DriverChain;
$chain = new MappingDriverChain();
$chain = new DriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($phpDriver, 'Doctrine\Tests\ORM\Mapping');
$chain->addDriver($annotationDriver, 'Doctrine\Tests\ORM\Mapping');
Based on the namespace of the entity the loading of entities is
delegated to the appropriate driver. The chain semantics come from
@@ -440,7 +403,6 @@ the entity class name against the namespace using a
correctly if sub-namespaces use different metadata driver
implementations.
Default Repository (***OPTIONAL***)
-----------------------------------
@@ -456,22 +418,22 @@ That will be available for all entities without a custom repository class.
The default value is ``Doctrine\ORM\EntityRepository``.
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
Ignoring entities (***OPTIONAL***)
-----------------------------------
Specifies the Entity FQCNs to ignore.
SchemaTool will then skip these (e.g. when comparing schemas).
.. code-block:: php
<?php
$config->setSchemaIgnoreClasses([$fqcn]);
$config->getSchemaIgnoreClasses();
Setting up the Console
----------------------
Doctrine uses the Symfony Console component for generating the command
line interface. You can take a look at the
:doc:`tools chapter <../reference/tools>` for inspiration how to setup the cli.
line interface. You can take a look at the ``vendor/bin/doctrine.php``
script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command
for inspiration how to setup the cli.
In general the required code looks like this:
.. code-block:: php
<?php
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);
Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli);
$cli->run();

File diff suppressed because it is too large Load Diff

View File

@@ -2,56 +2,49 @@ Architecture
============
This chapter gives an overview of the overall architecture,
terminology and constraints of Doctrine ORM. It is recommended to
terminology and constraints of Doctrine 2. It is recommended to
read this chapter carefully.
Using an Object-Relational Mapper
---------------------------------
As the term ORM already hints at, Doctrine ORM aims to simplify the
As the term ORM already hints at, Doctrine 2 aims to simplify the
translation between database rows and the PHP object model. The
primary use case for Doctrine are therefore applications that
utilize the Object-Oriented Programming Paradigm. For applications
that do not primarily work with objects Doctrine ORM is not suited very
that do not primarily work with objects Doctrine 2 is not suited very
well.
Requirements
------------
Doctrine ORM requires a minimum of PHP 8.1. For greatly improved
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine ORM Packages
Doctrine 2 Packages
-------------------
Doctrine ORM is divided into four main packages.
Doctrine 2 is divided into three main packages.
- `Collections <https://www.doctrine-project.org/projects/doctrine-collections/en/stable/index.html>`_
- `Event Manager <https://www.doctrine-project.org/projects/doctrine-event-manager/en/stable/index.html>`_
- `Persistence <https://www.doctrine-project.org/projects/doctrine-persistence/en/stable/index.html>`_
- `DBAL <https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/index.html>`_
- ORM (depends on DBAL+Persistence+Collections)
- Common
- DBAL (includes Common)
- ORM (includes DBAL+Common)
This manual mainly covers the ORM package, sometimes touching parts
of the underlying DBAL and Persistence packages. The Doctrine code base
of the underlying DBAL and Common packages. The Doctrine code base
is split in to these packages for a few reasons and they are to...
- ...make things more maintainable and decoupled
- ...allow you to use the code in Doctrine Persistence and Collections
without the ORM or DBAL
- ...allow you to use the code in Doctrine Common without the ORM
or DBAL
- ...allow you to use the DBAL without the ORM
Collection, Event Manager and Persistence
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Common Package
~~~~~~~~~~~~~~~~~~
The Collection, Event Manager and Persistence packages contain highly
reusable components that have no dependencies beyond the packages
themselves (and PHP, of course). The root namespace of the Persistence
package is ``Doctrine\Persistence``. The root namespace of the
Collection package is ``Doctrine\Common\Collections``, for historical
reasons. The root namespace of the Event Manager package is just
``Doctrine\Common``, also for historical reasons.
The Common package contains highly reusable components that have no
dependencies beyond the package itself (and PHP, of course). The
root namespace of the Common package is ``Doctrine\Common``.
The DBAL Package
~~~~~~~~~~~~~~~~
@@ -72,16 +65,17 @@ The root namespace of the ORM package is ``Doctrine\ORM``.
Terminology
-----------
.. _terminology_entities:
Entities
~~~~~~~~
An entity is a lightweight, persistent domain object. An entity can
be any regular PHP class observing the following restrictions:
- An entity class must not be final nor read-only but
it may contain final methods or read-only properties.
- An entity class must not be final or contain final methods.
- All persistent properties/field of any entity class should
always be private or protected, otherwise lazy-loading might not
work as expected. In case you serialize entities (for example Session)
properties should be protected (See Serialize section below).
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
@@ -100,33 +94,12 @@ classes, and non-entity classes may extend entity classes.
never calls entity constructors, thus you are free to use them as
you wish and even have it require arguments of any type.
Mapped Superclasses
~~~~~~~~~~~~~~~~~~~
A mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity.
Mapped superclasses are explained in greater detail in the chapter
on :doc:`inheritance mapping </reference/inheritance-mapping>`.
Transient Classes
~~~~~~~~~~~~~~~~~
The term "transient class" appears in some places in the mapping
drivers as well as the code dealing with metadata handling.
A transient class is a class that is neither an entity nor a mapped
superclass. From the ORM's point of view, these classes can be
completely ignored, and no class metadata is loaded for them at all.
Entity states
~~~~~~~~~~~~~
An entity instance can be characterized as being NEW, MANAGED,
DETACHED or REMOVED.
- A NEW entity instance has no persistent identity, and is not yet
associated with an EntityManager and a UnitOfWork (i.e. those just
created with the "new" operator).
@@ -163,21 +136,24 @@ subsequent access must be through the interface type.
Serializing entities
~~~~~~~~~~~~~~~~~~~~
Serializing entities can be problematic and is not really
recommended, at least not as long as an entity instance still holds
references to proxy objects or is still managed by an EntityManager.
By default, serializing proxy objects does not initialize them. On
unserialization, resulting objects are detached from the entity
manager and cannot be initialiazed anymore. You can implement the
``__serialize()`` method if you want to change that behavior, but
then you need to ensure that you won't generate large serialized
object graphs and take care of circular associations.
Serializing entities is generally to be avoided.
If you intend to serialize (and unserialize) entity
instances that still hold references to proxy objects you may run
into problems, because all proxy properties will be initialized
recursively, leading to large serialized object graphs, especially
for circular associations.
If you really must serialize entities, regardless if proxies are
involved or not, then consider implementing the ``Serializable``
interface and manually checking for cyclic dependencies in your
object graph.
The EntityManager
~~~~~~~~~~~~~~~~~
The ``EntityManager`` class is a central access point to the
functionality provided by Doctrine ORM. The ``EntityManager`` API is
The ``EntityManager`` class is a central access point to the ORM
functionality provided by Doctrine 2. The ``EntityManager`` API is
used to manage the persistence of your objects and to query for
persistent objects.
@@ -194,8 +170,6 @@ in well defined units of work. Work with your objects and modify
them as usual and when you're done call ``EntityManager#flush()``
to make your changes persistent.
.. _unit-of-work:
The Unit of Work
~~~~~~~~~~~~~~~~
@@ -205,3 +179,4 @@ typical implementation of the
to keep track of all the things that need to be done the next time
``flush`` is invoked. You usually do not directly interact with a
``UnitOfWork`` but with the ``EntityManager`` instead.

View File

@@ -18,9 +18,9 @@ This chapter is split into three different sections.
One tip for working with relations is to read the relation from left to right, where the left word refers to the current Entity. For example:
- OneToMany - One instance of the current Entity has Many instances (references) to the referred Entity.
- ManyToOne - Many instances of the current Entity refer to One instance of the referred Entity.
- OneToOne - One instance of the current Entity refers to One instance of the referred Entity.
- OneToMany - One instance of the current Entity has Many instances (references) to the refered Entity.
- ManyToOne - Many instances of the current Entity refer to One instance of the refered Entity.
- OneToOne - One instance of the current Entity refers to One instance of the refered Entity.
See below for all the possible relations.
@@ -37,20 +37,22 @@ A many-to-one association is the most common association between objects. Exampl
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class User
{
// ...
#[ManyToOne(targetEntity: Address::class)]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
private Address|null $address = null;
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
private $address;
}
#[Entity]
/** @Entity */
class Address
{
// ...
@@ -68,11 +70,9 @@ A many-to-one association is the most common association between objects. Exampl
.. note::
The above ``#[JoinColumn]`` is optional as it would default
The above ``@JoinColumn`` is optional as it would default
to ``address_id`` and ``id`` anyways. You can omit it and let it
use the defaults.
Likewise, inside the ``#[ManyToOne]`` attribute you can omit the
``targetEntity`` argument and it will default to ``Address``.
Generated MySQL Schema:
@@ -99,23 +99,25 @@ references one ``Shipment`` entity.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Product
{
// ...
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment|null $shipment = null;
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
// ...
}
#[Entity]
/** @Entity */
class Shipment
{
// ...
@@ -131,7 +133,7 @@ references one ``Shipment`` entity.
</entity>
</doctrine-mapping>
Note that the ``#[JoinColumn]`` is not really necessary in this example,
Note that the @JoinColumn is not really necessary in this example,
as the defaults would be the same.
Generated MySQL Schema:
@@ -163,30 +165,34 @@ object.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Customer
{
// ...
/** One Customer has One Cart. */
#[OneToOne(targetEntity: Cart::class, mappedBy: 'customer')]
private Cart|null $cart = null;
/**
* One Customer has One Cart.
* @OneToOne(targetEntity="Cart", mappedBy="customer")
*/
private $cart;
// ...
}
#[Entity]
/** @Entity */
class Cart
{
// ...
/** One Cart has One Customer. */
#[OneToOne(targetEntity: Customer::class, inversedBy: 'cart')]
#[JoinColumn(name: 'customer_id', referencedColumnName: 'id')]
private Customer|null $customer = null;
/**
* One Cart has One Customer.
* @OneToOne(targetEntity="Customer", inversedBy="cart")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
// ...
}
@@ -236,15 +242,17 @@ below.
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Student
{
// ...
/** One Student has One Mentor. */
#[OneToOne(targetEntity: Student::class)]
#[JoinColumn(name: 'mentor_id', referencedColumnName: 'id')]
private Student|null $mentor = null;
/**
* One Student has One Student.
* @OneToOne(targetEntity="Student")
* @JoinColumn(name="mentor_id", referencedColumnName="id")
*/
private $mentor;
// ...
}
@@ -279,21 +287,20 @@ bidirectional many-to-one.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
/** @Entity */
class Product
{
// ...
/**
* One product has many features. This is the inverse side.
* @var Collection<int, Feature>
* @OneToMany(targetEntity="Feature", mappedBy="product")
*/
#[OneToMany(targetEntity: Feature::class, mappedBy: 'product')]
private Collection $features;
private $features;
// ...
public function __construct() {
@@ -301,14 +308,16 @@ bidirectional many-to-one.
}
}
#[Entity]
/** @Entity */
class Feature
{
// ...
/** Many features have one product. This is the owning side. */
#[ManyToOne(targetEntity: Product::class, inversedBy: 'features')]
#[JoinColumn(name: 'product_id', referencedColumnName: 'id')]
private Product|null $product = null;
/**
* Many features have one product. This is the owning side.
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
// ...
}
@@ -355,33 +364,33 @@ The following example sets up such a unidirectional one-to-many association:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class User
{
// ...
/**
* Many Users have Many Phonenumbers.
* @var Collection<int, Phonenumber>
* Many User have Many Phonenumbers.
* @ManyToMany(targetEntity="Phonenumber")
* @JoinTable(name="users_phonenumbers",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
*/
#[JoinTable(name: 'users_phonenumbers')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'phonenumber_id', referencedColumnName: 'id', unique: true)]
#[ManyToMany(targetEntity: 'Phonenumber')]
private Collection $phonenumbers;
private $phonenumbers;
public function __construct()
{
$this->phonenumbers = new ArrayCollection();
$this->phonenumbers = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
#[Entity]
/** @Entity */
class Phonenumber
{
// ...
@@ -439,28 +448,29 @@ database perspective is known as an adjacency list approach.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Category
{
// ...
/**
* One Category has Many Categories.
* @var Collection<int, Category>
* @OneToMany(targetEntity="Category", mappedBy="parent")
*/
#[OneToMany(targetEntity: Category::class, mappedBy: 'parent')]
private Collection $children;
private $children;
/** Many Categories have One Category. */
#[ManyToOne(targetEntity: Category::class, inversedBy: 'children')]
#[JoinColumn(name: 'parent_id', referencedColumnName: 'id')]
private Category|null $parent = null;
/**
* Many Categories have One Category.
* @ManyToOne(targetEntity="Category", inversedBy="children")
* @JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
// ...
public function __construct() {
$this->children = new ArrayCollection();
$this->children = new \Doctrine\Common\Collections\ArrayCollection();
}
}
@@ -496,32 +506,32 @@ entities:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
private $groups;
// ...
public function __construct() {
$this->groups = new ArrayCollection();
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
}
}
#[Entity]
/** @Entity */
class Group
{
// ...
@@ -573,15 +583,6 @@ Generated MySQL Schema:
replaced by one-to-many/many-to-one associations between the 3
participating classes.
.. note::
For many-to-many associations, the ORM takes care of managing rows
in the join table connecting both sides. Due to the way it deals
with entity removals, database-level constraints may not work the
way one might intuitively assume. Thus, be sure not to miss the section
on :ref:`join table management <remove_object_many_to_many_join_tables>`.
Many-To-Many, Bidirectional
---------------------------
@@ -590,42 +591,40 @@ one is bidirectional.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups")
*/
#[ManyToMany(targetEntity: Group::class, inversedBy: 'users')]
#[JoinTable(name: 'users_groups')]
private Collection $groups;
private $groups;
public function __construct() {
$this->groups = new ArrayCollection();
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
#[Entity]
/** @Entity */
class Group
{
// ...
/**
* Many Groups have Many Users.
* @var Collection<int, User>
* @ManyToMany(targetEntity="User", mappedBy="groups")
*/
#[ManyToMany(targetEntity: User::class, mappedBy: 'groups')]
private Collection $users;
private $users;
public function __construct() {
$this->users = new ArrayCollection();
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
@@ -655,15 +654,6 @@ one is bidirectional.
The MySQL schema is exactly the same as for the Many-To-Many
uni-directional case above.
.. note::
For many-to-many associations, the ORM takes care of managing rows
in the join table connecting both sides. Due to the way it deals
with entity removals, database-level constraints may not work the
way one might intuitively assume. Thus, be sure not to miss the section
on :ref:`join table management <remove_object_many_to_many_join_tables>`.
Owning and Inverse Side on a ManyToMany Association
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -688,9 +678,9 @@ understandable:
<?php
class Article
{
private Collection $tags;
private $tags;
public function addTag(Tag $tag): void
public function addTag(Tag $tag)
{
$tag->addArticle($this); // synchronously updating inverse side
$this->tags[] = $tag;
@@ -699,9 +689,9 @@ understandable:
class Tag
{
private Collection $articles;
private $articles;
public function addArticle(Article $article): void
public function addArticle(Article $article)
{
$this->articles[] = $article;
}
@@ -729,31 +719,30 @@ field named ``$friendsWithMe`` and ``$myFriends``.
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class User
{
// ...
/**
* Many Users have Many Users.
* @var Collection<int, User>
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
*/
#[ManyToMany(targetEntity: User::class, mappedBy: 'myFriends')]
private Collection $friendsWithMe;
private $friendsWithMe;
/**
* Many Users have many Users.
* @var Collection<int, User>
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* @JoinTable(name="friends",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
*/
#[JoinTable(name: 'friends')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'friend_user_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'User', inversedBy: 'friendsWithMe')]
private Collection $myFriends;
private $myFriends;
public function __construct() {
$this->friendsWithMe = new ArrayCollection();
$this->myFriends = new ArrayCollection();
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
@@ -793,11 +782,11 @@ As an example, consider this mapping:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[OneToOne(targetEntity: Shipment::class)]
private Shipment|null $shipment = null;
/** @OneToOne(targetEntity="Shipment") */
private $shipment;
.. code-block:: xml
@@ -812,13 +801,15 @@ mapping:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment|null $shipment = null;
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
.. code-block:: xml
@@ -835,15 +826,14 @@ similar defaults. As an example, consider this mapping:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
class User
{
// ...
/** @var Collection<int, Group> */
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
/** @ManyToMany(targetEntity="Group") */
private $groups;
// ...
}
@@ -859,7 +849,7 @@ This is essentially the same as the following, more verbose, mapping:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
class User
@@ -867,13 +857,13 @@ This is essentially the same as the following, more verbose, mapping:
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="User_Group",
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
* )
*/
#[JoinTable(name: 'User_Group')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
private $groups;
// ...
}
@@ -884,10 +874,10 @@ This is essentially the same as the following, more verbose, mapping:
<many-to-many field="groups" target-entity="Group">
<join-table name="User_Group">
<join-columns>
<join-column id="user_id" referenced-column-name="id" />
<join-column id="User_id" referenced-column-name="id" />
</join-columns>
<inverse-join-columns>
<join-column id="group_id" referenced-column-name="id" />
<join-column id="Group_id" referenced-column-name="id" />
</inverse-join-columns>
</join-table>
</many-to-many>
@@ -901,59 +891,6 @@ join columns default to the simple, unqualified class name of the
targeted class followed by "\_id". The referencedColumnName always
defaults to "id", just as in one-to-one or many-to-one mappings.
Additionally, when using typed properties with Doctrine 2.9 or newer
you can skip ``targetEntity`` in ``ManyToOne`` and ``OneToOne``
associations as they will be set based on type. Also ``nullable``
attribute on ``JoinColumn`` will be inherited from PHP type. So that:
.. configuration-block::
.. code-block:: attribute
<?php
#[OneToOne]
private Shipment $shipment;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" />
</entity>
</doctrine-mapping>
Is essentially the same as following:
.. configuration-block::
.. code-block:: attribute
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
private Shipment $shipment;
.. code-block:: annotation
<?php
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id", nullable=false)
*/
private Shipment $shipment;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" nulable=false />
</one-to-one>
</entity>
</doctrine-mapping>
If you accept these defaults, you can reduce the mapping code to a
minimum.
@@ -991,19 +928,22 @@ and ``@ManyToMany`` associations in the constructor of your entities:
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
/** @Entity */
class User
{
/** Many Users have Many Groups. */
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
/**
* Many Users have Many Groups.
* @var Collection
* @ManyToMany(targetEntity="Group")
*/
private $groups;
public function __construct()
{
$this->groups = new ArrayCollection();
}
public function getGroups(): Collection
public function getGroups()
{
return $this->groups;
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,11 +14,17 @@ After working through this guide you should know:
Mapping of associations will be covered in the next chapter on
:doc:`Association Mapping <association-mapping>`.
Guide Assumptions
-----------------
You should have already :doc:`installed and configure <configuration>`
Doctrine.
Creating Classes for the Database
---------------------------------
Every PHP object that you want to save in the database using Doctrine
is called an *Entity*. The term "Entity" describes objects
is called an "Entity". The term "Entity" describes objects
that have an identity over many independent requests. This identity is
usually achieved by assigning a unique identifier to an entity.
In this tutorial the following ``Message`` PHP class will serve as the
@@ -44,18 +50,18 @@ that describes your entity.
Doctrine provides several different ways to specify object-relational
mapping metadata:
- :doc:`Attributes <attributes-reference>`
- :doc:`Docblock Annotations <annotations-reference>`
- :doc:`XML <xml-mapping>`
- :doc:`PHP code <php-mapping>`
This manual will usually show mapping metadata via attributes, though
This manual will usually show mapping metadata via docblock annotations, though
many examples also show the equivalent configuration in XML.
.. note::
All metadata drivers perform equally. Once the metadata of a class has been
read from the source (attributes, XML, etc.) it is stored in an instance
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class which are
read from the source (annotations or xml) it is stored in an instance
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class and these instances are
stored in the metadata cache. If you're not using a metadata cache (not
recommended!) then the XML driver is the fastest.
@@ -63,12 +69,10 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Entity;
#[Entity]
/** @Entity */
class Message
{
// ...
@@ -88,14 +92,13 @@ You can change this by configuring information about the table:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
#[Entity]
#[Table(name: 'message')]
/**
* @Entity
* @Table(name="message")
*/
class Message
{
// ...
@@ -114,29 +117,27 @@ Now the class ``Message`` will be saved and fetched from the table ``message``.
Property Mapping
----------------
The next step is mapping its properties to columns in the table.
The next step after marking a PHP class as an entity is mapping its properties
to columns in a table.
To configure a property use the ``Column`` attribute. The ``type``
argument specifies the :ref:`Doctrine Mapping Type
<reference-mapping-types>` to use for the field. If the type is not
specified, ``string`` is used as the default.
To configure a property use the ``@Column`` docblock annotation. The ``type``
attribute specifies the :ref:`Doctrine Mapping Type <reference-mapping-types>`
to use for the field. If the type is not specified, ``string`` is used as the
default.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Column;
use Doctrine\DBAL\Types\Types;
#[Entity]
/** @Entity */
class Message
{
#[Column(type: Types::INTEGER)]
/** @Column(type="integer") */
private $id;
#[Column(length: 140)]
/** @Column(length=140) */
private $text;
#[Column(name: 'posted_at', type: Types::DATETIME)]
/** @Column(type="datetime", name="posted_at") */
private $postedAt;
}
@@ -151,89 +152,99 @@ specified, ``string`` is used as the default.
</doctrine-mapping>
When we don't explicitly specify a column name via the ``name`` option, Doctrine
assumes the field name is also the column name. So in this example:
assumes the field name is also the column name. This means that:
* the ``id`` property will map to the column ``id`` using the type ``integer``;
* the ``text`` property will map to the column ``text`` with the default mapping type ``string``;
* the ``postedAt`` property will map to the ``posted_at`` column with the ``datetime`` type.
Here is a complete list of ``Column``s attributes (all optional):
The Column annotation has some more attributes. Here is a complete
list:
- ``type`` (default: 'string'): The mapping type to use for the column.
- ``name`` (default: name of property): The name of the column in the database.
- ``length`` (default: 255): The length of the column in the database.
Applies only if a string-valued column is used.
- ``unique`` (default: ``false``): Whether the column is a unique key.
- ``nullable`` (default: ``false``): Whether the column is nullable.
- ``insertable`` (default: ``true``): Whether the column should be inserted.
- ``updatable`` (default: ``true``): Whether the column should be updated.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
(applies only for decimal column),
- ``type``: (optional, defaults to 'string') The mapping type to
use for the column.
- ``name``: (optional, defaults to field name) The name of the
column in the database.
- ``length``: (optional, default 255) The length of the column in
the database. (Applies only if a string-valued column is used).
- ``unique``: (optional, default FALSE) Whether the column is a
unique key.
- ``nullable``: (optional, default FALSE) Whether the database
column is nullable.
- ``precision``: (optional, default 0) The precision for a decimal
(exact numeric) column (applies only for decimal column),
which is the maximum number of digits that are stored for the values.
- ``scale`` (default: 0): The scale for a decimal (exact
- ``scale``: (optional, default 0) The scale for a decimal (exact
numeric) column (applies only for decimal column), which represents
the number of digits to the right of the decimal point and must
not be greater than ``precision``.
- ``columnDefinition``: Allows to define a custom
not be greater than *precision*.
- ``columnDefinition``: (optional) Allows to define a custom
DDL snippet that is used to create the column. Warning: This normally
confuses the :doc:`SchemaTool <tools>` to always detect the column as changed.
- ``options``: Key-value pairs of options that get passed
confuses the SchemaTool to always detect the column as changed.
- ``options``: (optional) Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
.. _reference-php-mapping-types:
PHP Types Mapping
_________________
.. versionadded:: 2.9
The column types can be inferred automatically from PHP's property types.
However, when the property type is nullable this has no effect on the ``nullable`` Column attribute.
These are the "automatic" mapping rules:
+-----------------------+-------------------------------+
| PHP property type | Doctrine column type |
+=======================+===============================+
| ``DateInterval`` | ``Types::DATEINTERVAL`` |
+-----------------------+-------------------------------+
| ``DateTime`` | ``Types::DATETIME_MUTABLE`` |
+-----------------------+-------------------------------+
| ``DateTimeImmutable`` | ``Types::DATETIME_IMMUTABLE`` |
+-----------------------+-------------------------------+
| ``array`` | ``Types::JSON`` |
+-----------------------+-------------------------------+
| ``bool`` | ``Types::BOOLEAN`` |
+-----------------------+-------------------------------+
| ``float`` | ``Types::FLOAT`` |
+-----------------------+-------------------------------+
| ``int`` | ``Types::INTEGER`` |
+-----------------------+-------------------------------+
| Any other type | ``Types::STRING`` |
+-----------------------+-------------------------------+
As of version 2.11 Doctrine can also automatically map typed properties using a
PHP 8.1 enum to set the right ``type`` and ``enumType``.
.. versionadded:: 2.14
Since version 2.14 you can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
:doc:`Read more about TypedFieldMapper <typedfieldmapper>`.
.. _reference-mapping-types:
Doctrine Mapping Types
----------------------
The ``type`` option used in the ``@Column`` accepts any of the
`existing Doctrine DBAL types <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/types.html#reference>`_
or :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`. A Doctrine type defines
The ``type`` option used in the ``@Column`` accepts any of the existing
Doctrine types or even your own custom types. A Doctrine type defines
the conversion between PHP and SQL types, independent from the database vendor
you are using.
you are using. All Mapping Types that ship with Doctrine are fully portable
between the supported database systems.
As an example, the Doctrine Mapping Type ``string`` defines the
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
depending on the RDBMS brand). Here is a quick overview of the
built-in mapping types:
- ``string``: Type that maps an SQL VARCHAR to a PHP string.
- ``integer``: Type that maps an SQL INT to a PHP integer.
- ``smallint``: Type that maps a database SMALLINT to a PHP
integer.
- ``bigint``: Type that maps a database BIGINT to a PHP string.
- ``boolean``: Type that maps an SQL boolean or equivalent (TINYINT) to a PHP boolean.
- ``decimal``: Type that maps an SQL DECIMAL to a PHP string.
- ``date``: Type that maps an SQL DATETIME to a PHP DateTime
object.
- ``date_immutable``: Type that maps an SQL DATETIME to a PHP DateTimeImmutable
object.
- ``time``: Type that maps an SQL TIME to a PHP DateTime object.
- ``time_immutable``: Type that maps an SQL TIME to a PHP DateTimeImmutable object.
- ``datetime``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime
object with the current timezone.
- ``datetimetz``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime
object with the timezone specified in the value from the database.
- ``datetime_immutable``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTimeImmutable
object with the current timezone.
- ``datetimetz_immutable``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTimeImmutable
object with the timezone specified in the value from the database.
- ``dateinterval``: Type that maps an interval to a PHP DateInterval object
- ``text``: Type that maps an SQL CLOB to a PHP string.
- ``object``: Type that maps an SQL CLOB to a PHP object using
``serialize()`` and ``unserialize()``
- ``array``: Type that maps an SQL CLOB to a PHP array using
``serialize()`` and ``unserialize()``
- ``simple_array``: Type that maps an SQL CLOB to a one-dimensional PHP array using
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
Only use this type if you are sure that your values cannot contain a ",".
- ``json_array``: Type that maps an SQL CLOB to a PHP array using
``json_encode()`` and ``json_decode()``. This one has been deprecated in favor
of ``json`` type.
- ``json``: Type that maps an SQL CLOB to a PHP array using
``json_encode()`` and ``json_decode()``. An empty value is correctly represented as ``null``
- ``float``: Type that maps an SQL Float (Double Precision) to a
PHP double. *IMPORTANT*: Works only with locale settings that use
decimal points as separator.
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
varchar but uses a specific type if the platform supports it.
- ``blob``: Type that maps an SQL BLOB to a PHP resource stream
- ``binary``: Type that maps an SQL binary to a PHP resource stream
A cookbook article shows how to define :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`.
.. note::
@@ -258,19 +269,22 @@ Identifiers / Primary Keys
--------------------------
Every entity class must have an identifier/primary key. You can select
the field that serves as the identifier with the ``#[Id]`` attribute.
the field that serves as the identifier with the ``@Id``
annotation.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
class Message
{
#[Id]
#[Column(type: 'integer')]
#[GeneratedValue]
private int|null $id = null;
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
// ...
}
@@ -285,27 +299,10 @@ the field that serves as the identifier with the ``#[Id]`` attribute.
</entity>
</doctrine-mapping>
In most cases using the automatic generator strategy (``#[GeneratedValue]``) is
what you want, but for backwards-compatibility reasons it might not. It
defaults to the identifier generation mechanism your current database
vendor preferred at the time that strategy was introduced:
``AUTO_INCREMENT`` with MySQL, sequences with PostgreSQL and Oracle and
so on.
If you are using `doctrine/dbal` 4, we now recommend using ``IDENTITY``
for PostgreSQL, and ``AUTO`` resolves to it because of that.
You can stick with ``SEQUENCE`` while still using the ``AUTO``
strategy, by configuring what it defaults to.
.. code-block:: php
<?php
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Configuration;
$config = new Configuration();
$config->setIdentityGenerationPreferences([
PostgreSQLPlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
]);
In most cases using the automatic generator strategy (``@GeneratedValue``) is
what you want. It defaults to the identifier generation mechanism your current
database vendor prefers: AUTO_INCREMENT with MySQL, SERIAL with PostgreSQL,
Sequences with Oracle and so on.
.. _identifier-generation-strategies:
@@ -322,24 +319,26 @@ Here is the list of possible generation strategies:
- ``AUTO`` (default): Tells Doctrine to pick the strategy that is
preferred by the used database platform. The preferred strategies
are ``IDENTITY`` for MySQL, SQLite, MsSQL and SQL Anywhere and, for
historical reasons, ``SEQUENCE`` for Oracle and PostgreSQL. This
strategy provides full portability.
- ``IDENTITY``: Tells Doctrine to use special identity columns in
the database that generate a value on insertion of a row. This
strategy does currently not provide full portability and is
supported by the following platforms: MySQL/SQLite/SQL Anywhere
(``AUTO_INCREMENT``), MSSQL (``IDENTITY``) and PostgreSQL (``SERIAL``).
are IDENTITY for MySQL, SQLite, MsSQL and SQL Anywhere and SEQUENCE
for Oracle and PostgreSQL. This strategy provides full portability.
- ``SEQUENCE``: Tells Doctrine to use a database sequence for ID
generation. This strategy does currently not provide full
portability. Sequences are supported by Oracle, PostgreSql and
SQL Anywhere.
- ``IDENTITY``: Tells Doctrine to use special identity columns in
the database that generate a value on insertion of a row. This
strategy does currently not provide full portability and is
supported by the following platforms: MySQL/SQLite/SQL Anywhere
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
- ``TABLE``: Tells Doctrine to use a separate table for ID
generation. This strategy provides full portability.
***This strategy is not yet implemented!***
- ``NONE``: Tells Doctrine that the identifiers are assigned (and
thus generated) by your code. The assignment must take place before
a new entity is passed to ``EntityManager#persist``. NONE is the
same as leaving off the ``#[GeneratedValue]`` entirely.
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
It will allow you to pass a :ref:`class of your own to generate the identifiers. <attrref_customidgenerator>`
same as leaving off the @GeneratedValue entirely.
- ``CUSTOM``: With this option, you can use the ``@CustomIdGenerator`` annotation.
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
Sequence Generator
^^^^^^^^^^^^^^^^^^
@@ -350,15 +349,17 @@ besides specifying the sequence's name:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
class Message
{
#[Id]
#[GeneratedValue(strategy: 'SEQUENCE')]
#[SequenceGenerator(sequenceName: 'message_seq', initialValue: 1, allocationSize: 100)]
protected int|null $id = null;
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
// ...
}
@@ -381,10 +382,12 @@ performance of Doctrine. The allocationSize specifies by how much
values the sequence is incremented whenever the next value is
retrieved. If this is larger than 1 (one) Doctrine can generate
identifier values for the allocationSizes amount of entities. In
the above example with ``allocationSize=100`` Doctrine ORM would only
the above example with ``allocationSize=100`` Doctrine 2 would only
need to access the sequence once to generate the identifiers for
100 new entities.
*The default allocationSize for a @SequenceGenerator is currently 10.*
.. caution::
The allocationSize is detected by SchemaTool and
@@ -394,7 +397,6 @@ need to access the sequence once to generate the identifiers for
configuration option is never larger than the actual sequences
INCREMENT BY value, otherwise you may get duplicate keys.
.. note::
It is possible to use strategy="AUTO" and at the same time
@@ -403,16 +405,14 @@ need to access the sequence once to generate the identifiers for
of the underlying platform is SEQUENCE, such as for Oracle and
PostgreSQL.
Composite Keys
~~~~~~~~~~~~~~
With Doctrine ORM you can use composite primary keys, using ``#[Id]`` on
more than one column. Some restrictions exist opposed to using a single
identifier in this case: The use of the ``#[GeneratedValue]`` attribute
is not supported, which means you can only use composite keys if you
generate the primary key values yourself before calling
``EntityManager#persist()`` on the entity.
With Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
one column. Some restrictions exist opposed to using a single identifier in
this case: The use of the ``@GeneratedValue`` annotation is not supported,
which means you can only use composite keys if you generate the primary key
values yourself before calling ``EntityManager#persist()`` on the entity.
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
<../tutorials/composite-primary-keys>`.
@@ -428,8 +428,7 @@ needs to be done explicitly using ticks in the definition.
.. code-block:: php
<?php
#[Column(name: '`number`', type: 'integer')]
/** @Column(name="`number`", type="integer") */
private $number;
Doctrine will then quote this column name in all SQL statements
@@ -442,11 +441,15 @@ according to the used database platform.
.. _reference-basic-mapping-custom-mapping-types:
.. versionadded: 2.3
For more control over column quoting the ``Doctrine\ORM\Mapping\QuoteStrategy`` interface
was introduced in ORM. It is invoked for every column, table, alias and other
was introduced in 2.3. It is invoked for every column, table, alias and other
SQL names. You can implement the QuoteStrategy and set it by calling
``Doctrine\ORM\Configuration#setQuoteStrategy()``.
.. versionadded: 2.4
The ANSI Quote Strategy was added, which assumes quoting is not necessary for any SQL name.
You can use it with the following code:

View File

@@ -15,24 +15,6 @@ especially what the strategies presented here provide help with.
you use the tools for your particular RDBMS for these bulk
operations.
.. note::
Having an SQL logger enabled when processing batches can have a
serious impact on performance and resource usage.
To avoid that, you should use a PSR logger implementation that can be
disabled at runtime.
For example, with Monolog, you can use ``Logger::pushHandler()``
to push a ``NullHandler`` to the logger instance, and then pop it
when you need to enable logging again.
With DBAL 2, you can disable the SQL logger like below:
.. code-block:: php
<?php
$em->getConnection()->getConfiguration()->setSQLLogger(null);
Bulk Inserts
------------
@@ -83,7 +65,7 @@ Iterating results
~~~~~~~~~~~~~~~~~
An alternative solution for bulk updates is to use the
``Query#toIterable()`` facility to iterate over the query results step
``Query#iterate()`` facility to iterate over the query results step
by step instead of loading the whole result into memory at once.
The following example shows how to do this, combining the iteration
with the batching strategy that was already used for bulk inserts:
@@ -92,16 +74,18 @@ with the batching strategy that was already used for bulk inserts:
<?php
$batchSize = 20;
$i = 0;
$i = 1;
$q = $em->createQuery('select u from MyProject\Model\User u');
foreach ($q->toIterable() as $user) {
$iterableResult = $q->iterate();
foreach ($iterableResult as $row) {
$user = $row[0];
$user->increaseCredit();
$user->calculateNewBonuses();
++$i;
if (($i % $batchSize) === 0) {
$em->flush(); // Executes all updates.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
$em->flush();
@@ -117,7 +101,6 @@ with the batching strategy that was already used for bulk inserts:
additional memory not visible to the PHP process. For large sets this
may easily kill the process for no apparent reason.
Bulk Deletes
------------
@@ -143,7 +126,7 @@ Iterating results
~~~~~~~~~~~~~~~~~
An alternative solution for bulk deletes is to use the
``Query#toIterable()`` facility to iterate over the query results step
``Query#iterate()`` facility to iterate over the query results step
by step instead of loading the whole result into memory at once.
The following example shows how to do this:
@@ -151,15 +134,16 @@ The following example shows how to do this:
<?php
$batchSize = 20;
$i = 0;
$i = 1;
$q = $em->createQuery('select u from MyProject\Model\User u');
foreach($q->toIterable() as $row) {
$em->remove($row);
++$i;
$iterableResult = $q->iterate();
while (($row = $iterableResult->next()) !== false) {
$em->remove($row[0]);
if (($i % $batchSize) === 0) {
$em->flush(); // Executes all deletions.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
$em->flush();
@@ -169,24 +153,25 @@ The following example shows how to do this:
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
Iterating Large Results for Data-Processing
-------------------------------------------
You can use the ``toIterable()`` method just to iterate over a large
result and no UPDATE or DELETE intention. ``$query->toIterable()`` returns ``iterable``
so you can process a large result without memory
You can use the ``iterate()`` method just to iterate over a large
result and no UPDATE or DELETE intention. The ``IterableResult``
instance returned from ``$query->iterate()`` implements the
Iterator interface so you can process a large result without memory
problems using the following approach:
.. code-block:: php
<?php
$q = $this->_em->createQuery('select u from MyProject\Model\User u');
foreach ($q->toIterable() as $row) {
// do stuff with the data in the row
$q = $this->em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach ($iterableResult as $row) {
// do stuff with the data in the row, $row[0] is always the object
// detach from Doctrine, so that it can be Garbage-Collected immediately
$this->_em->detach($row[0]);
// detach all entities from Doctrine, so that Garbage-Collection can kick in immediately
$this->em->clear();
}
.. note::
@@ -194,3 +179,10 @@ problems using the following approach:
Iterating results is not possible with queries that
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
Packages for easing Batch Processing
------------------------------------
You can implement batch processing yourself, or use an existing
package such as `DoctrineBatchUtils <https://github.com/Ocramius/DoctrineBatchUtils>`_,
which already provides the logic described above in an encapsulated format.

View File

@@ -12,14 +12,12 @@ Constrain relationships as much as possible
It is important to constrain relationships as much as possible.
This means:
- Impose a traversal direction (avoid bidirectional associations
if possible)
- Eliminate nonessential associations
This has several benefits:
- Reduced coupling in your domain model
- Simpler code in your domain model (no need to maintain
bidirectionality properly)
@@ -43,7 +41,7 @@ should use events judiciously.
Use cascades judiciously
------------------------
Automatic cascades of the persist/remove/etc. operations are
Automatic cascades of the persist/remove/refresh/etc. operations are
very handy but should be used wisely. Do NOT simply add all
cascades to all associations. Think about which cascades actually
do make sense for you for a particular association, given the
@@ -76,10 +74,8 @@ collections in entities in the constructor. Example:
use Doctrine\Common\Collections\ArrayCollection;
class User {
/** @var Collection<int, Address> */
private Collection $addresses;
/** @var Collection<int, Article> */
private Collection $articles;
private $addresses;
private $articles;
public function __construct() {
$this->addresses = new ArrayCollection;
@@ -110,4 +106,3 @@ queries generally don't have any noticeable performance impact, it
is still preferable to use fewer, well-defined transactions that
are established through explicit transaction boundaries.

View File

@@ -1,14 +1,291 @@
Caching
=======
The Doctrine ORM package can leverage cache adapters implementing the PSR-6
standard to allow you to improve the performance of various aspects of
Doctrine by simply making some additional configurations and method calls.
Doctrine provides cache drivers in the ``Common`` package for some
of the most popular caching implementations such as APC, Memcache
and Xcache. We also provide an ``ArrayCache`` driver which stores
the data in a PHP array. Obviously, when using ``ArrayCache``, the
cache does not persist between requests, but this is useful for
testing in a development environment.
.. _types-of-caches:
Cache Drivers
-------------
Types of Caches
---------------
The cache drivers follow a simple interface that is defined in
``Doctrine\Common\Cache\Cache``. All the cache drivers extend a
base class ``Doctrine\Common\Cache\CacheProvider`` which implements
this interface.
The interface defines the following public methods for you to implement:
- fetch($id) - Fetches an entry from the cache
- contains($id) - Test if an entry exists in the cache
- save($id, $data, $lifeTime = false) - Puts data into the cache for x seconds. 0 = infinite time
- delete($id) - Deletes a cache entry
Each driver extends the ``CacheProvider`` class which defines a few
abstract protected methods that each of the drivers must
implement:
- doFetch($id)
- doContains($id)
- doSave($id, $data, $lifeTime = false)
- doDelete($id)
The public methods ``fetch()``, ``contains()`` etc. use the
above protected methods which are implemented by the drivers. The
code is organized this way so that the protected methods in the
drivers do the raw interaction with the cache implementation and
the ``CacheProvider`` can build custom functionality on top of
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>`_.
APC
~~~
In order to use the APC cache driver you must have it compiled and
enabled in your php.ini. You can read about APC
`in the PHP Documentation <https://php.net/apc>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
Below is a simple example of how you could use the APC cache driver
by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
$cacheDriver->save('cache_id', 'my_data');
APCu
~~~~
In order to use the APCu cache driver you must have it compiled and
enabled in your php.ini. You can read about APCu
`in the PHP Documentation <https://php.net/apcu>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
Below is a simple example of how you could use the APCu cache driver
by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
$cacheDriver->save('cache_id', 'my_data');
Memcache
~~~~~~~~
In order to use the Memcache cache driver you must have it compiled
and enabled in your php.ini. You can read about Memcache
`on the PHP website <https://php.net/memcache>`_. It will
give you a little background information about what it is and how
you can use it as well as how to install it.
Below is a simple example of how you could use the Memcache cache
driver by itself.
.. code-block:: php
<?php
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
$cacheDriver->setMemcache($memcache);
$cacheDriver->save('cache_id', 'my_data');
Memcached
~~~~~~~~~
Memcached is a more recent and complete alternative extension to
Memcache.
In order to use the Memcached cache driver you must have it compiled
and enabled in your php.ini. You can read about Memcached
`on the PHP website <https://php.net/memcached>`_. It will
give you a little background information about what it is and how
you can use it as well as how to install it.
Below is a simple example of how you could use the Memcached cache
driver by itself.
.. code-block:: php
<?php
$memcached = new Memcached();
$memcached->addServer('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcachedCache();
$cacheDriver->setMemcached($memcached);
$cacheDriver->save('cache_id', 'my_data');
Xcache
~~~~~~
In order to use the Xcache cache driver you must have it compiled
and enabled in your php.ini. You can read about Xcache
`here <https://xcache.lighttpd.net/>`_. It will give you a little
background information about what it is and how you can use it as
well as how to install it.
Below is a simple example of how you could use the Xcache cache
driver by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
$cacheDriver->save('cache_id', 'my_data');
Redis
~~~~~
In order to use the Redis cache driver you must have it compiled
and enabled in your php.ini. You can read about what Redis is
`from here <https://redis.io/>`_. Also check
`A PHP extension for Redis <https://github.com/nicolasff/phpredis/>`_ for how you can use
and install the Redis PHP extension.
Below is a simple example of how you could use the Redis cache
driver by itself.
.. code-block:: php
<?php
$redis = new Redis();
$redis->connect('redis_host', 6379);
$cacheDriver = new \Doctrine\Common\Cache\RedisCache();
$cacheDriver->setRedis($redis);
$cacheDriver->save('cache_id', 'my_data');
Using Cache Drivers
-------------------
In this section we'll describe how you can fully utilize the API of
the cache drivers to save data to a cache, check if some cached data
exists, fetch the cached data and delete the cached data. We'll use the
``ArrayCache`` implementation as our example here.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ArrayCache();
Saving
~~~~~~
Saving some data to the cache driver is as simple as using the
``save()`` method.
.. code-block:: php
<?php
$cacheDriver->save('cache_id', 'my_data');
The ``save()`` method accepts three arguments which are described
below:
- ``$id`` - The cache id
- ``$data`` - The cache entry/data.
- ``$lifeTime`` - The lifetime. If != false, sets a specific
lifetime for this cache entry (null => infinite lifeTime).
You can save any type of data whether it be a string, array,
object, etc.
.. code-block:: php
<?php
$array = array(
'key1' => 'value1',
'key2' => 'value2'
);
$cacheDriver->save('my_array', $array);
Checking
~~~~~~~~
Checking whether cached data exists is very simple: just use the
``contains()`` method. It accepts a single argument which is the ID
of the cache entry.
.. code-block:: php
<?php
if ($cacheDriver->contains('cache_id')) {
echo 'cache exists';
} else {
echo 'cache does not exist';
}
Fetching
~~~~~~~~
Now if you want to retrieve some cache entry you can use the
``fetch()`` method. It also accepts a single argument just like
``contains()`` which is again the ID of the cache entry.
.. code-block:: php
<?php
$array = $cacheDriver->fetch('my_array');
Deleting
~~~~~~~~
As you might guess, deleting is just as easy as saving, checking
and fetching. You can delete by an individual ID, or you can
delete all entries.
By Cache ID
^^^^^^^^^^^
.. code-block:: php
<?php
$cacheDriver->delete('my_array');
All
^^^
If you simply want to delete all cache entries you can do so with
the ``deleteAll()`` method.
.. code-block:: php
<?php
$deleted = $cacheDriver->deleteAll();
Namespaces
~~~~~~~~~~
If you heavily use caching in your application and use it in
multiple parts of your application, or use it in different
applications on the same server you may have issues with cache
naming collisions. This can be worked around by using namespaces.
You can set the namespace a cache driver should use by using the
``setNamespace()`` method.
.. code-block:: php
<?php
$cacheDriver->setNamespace('my_namespace_');
Integrating with the ORM
------------------------
The Doctrine ORM package is tightly integrated with the cache
drivers to allow you to improve the performance of various aspects of
Doctrine by simply making some additional configurations and
method calls.
Query Cache
~~~~~~~~~~~
@@ -24,9 +301,8 @@ use on your ORM configuration.
.. code-block:: php
<?php
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter('doctrine_queries');
$config = new \Doctrine\ORM\Configuration();
$config->setQueryCache($cache);
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
Result Cache
~~~~~~~~~~~~
@@ -38,13 +314,7 @@ You just need to configure the result cache implementation.
.. code-block:: php
<?php
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
'doctrine_results',
0,
'/path/to/writable/directory'
);
$config = new \Doctrine\ORM\Configuration();
$config->setResultCache($cache);
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
Now when you're executing DQL queries you can configure them to use
the result cache.
@@ -53,7 +323,7 @@ the result cache.
<?php
$query = $em->createQuery('select u from \Entities\User u');
$query->enableResultCache();
$query->useResultCache(true);
You can also configure an individual query to use a different
result cache driver.
@@ -61,24 +331,18 @@ result cache driver.
.. code-block:: php
<?php
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
'doctrine_results',
0,
'/path/to/writable/directory'
);
$query->setResultCache($cache);
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
.. note::
Setting the result cache driver on the query will
automatically enable the result cache for the query. If you want to
disable it use ``disableResultCache()``.
disable it pass false to ``useResultCache()``.
::
<?php
$query->disableResultCache();
$query->useResultCache(false);
If you want to set the time the cache has to live you can use the
``setResultCacheLifetime()`` method.
@@ -98,20 +362,19 @@ yourself with the ``setResultCacheId()`` method.
$query->setResultCacheId('my_custom_id');
You can also set the lifetime and cache ID by passing the values as
the first and second argument to ``enableResultCache()``.
the second and third argument to ``useResultCache()``.
.. code-block:: php
<?php
$query->enableResultCache(3600, 'my_custom_id');
$query->useResultCache(true, 3600, 'my_custom_id');
Metadata Cache
~~~~~~~~~~~~~~
Your class metadata can be parsed from a few different sources like
XML, Attributes, etc. Instead of parsing this
information on each request we should cache it using one of the cache
drivers.
XML, Annotations, etc. Instead of parsing this information on
each request we should cache it using one of the cache drivers.
Just like the query and result cache we need to configure it
first.
@@ -119,13 +382,7 @@ first.
.. code-block:: php
<?php
$cache = \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
'doctrine_metadata',
0,
'/path/to/writable/directory'
);
$config = new \Doctrine\ORM\Configuration();
$config->setMetadataCache($cache);
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
Now the metadata information will only be parsed once and stored in
the cache driver.
@@ -161,12 +418,6 @@ To clear the result cache use the ``orm:clear-cache:result`` task.
All these tasks accept a ``--flush`` option to flush the entire
contents of the cache instead of invalidating the entries.
.. note::
None of these tasks will work with APC, APCu, or XCache drivers
because the memory that the cache is stored in is only accessible
to the webserver.
Cache Chaining
--------------
@@ -175,15 +426,30 @@ requested many times in a single PHP request. Even though this data
may be stored in a fast memory cache, often that cache is over a
network link leading to sizable network traffic.
A chain cache class allows multiple caches to be registered at once.
For example, a per-request array cache can be used first, followed by
a (relatively) slower Memcached cache if the array cache misses.
The chain cache automatically handles pushing data up to faster caches in
The ChainCache class allows multiple caches to be registered at once.
For example, a per-request ArrayCache can be used first, followed by
a (relatively) slower MemcacheCache if the ArrayCache misses.
ChainCache automatically handles pushing data up to faster caches in
the chain and clearing data in the entire stack when it is deleted.
Symfony Cache provides such a chain cache. To find out how to use it,
please have a look at the
`Symfony Documentation <https://symfony.com/doc/current/components/cache/adapters/chain_adapter.html>`_.
A ChainCache takes a simple array of CacheProviders in the order that
they should be used.
.. code-block:: php
$arrayCache = new \Doctrine\Common\Cache\ArrayCache();
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$chainCache = new \Doctrine\Common\Cache\ChainCache([
$arrayCache,
$memcache,
]);
ChainCache itself extends the CacheProvider interface, so it is
possible to create chains of chains. While this may seem like an easy
way to build a simple high-availability cache, ChainCache does not
implement any exception handling so using it as a high-availability
mechanism is not recommended.
Cache Slams
-----------
@@ -200,3 +466,4 @@ not letting your users' requests populate the cache.
You can read more about cache slams
`in this blog post <http://notmysock.org/blog/php/user-cache-timebomb.html>`_.

View File

@@ -5,7 +5,7 @@ Change tracking is the process of determining what has changed in
managed entities since the last time they were synchronized with
the database.
Doctrine provides 2 different change tracking policies, each having
Doctrine provides 3 different change tracking policies, each having
its particular advantages and disadvantages. The change tracking
policy can be defined on a per-class basis (or more precisely,
per-hierarchy).
@@ -30,7 +30,7 @@ Deferred Explicit
The deferred explicit policy is similar to the deferred implicit
policy in that it detects changes through a property-by-property
comparison at commit time. The difference is that Doctrine ORM only
comparison at commit time. The difference is that Doctrine 2 only
considers entities that have been explicitly marked for change detection
through a call to EntityManager#persist(entity) or through a save
cascade. All other entities are skipped. This policy therefore
@@ -49,10 +49,102 @@ This policy can be configured as follows:
.. code-block:: php
<?php
#[Entity]
#[ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class User
{
// ...
}
Notify
~~~~~~
This policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that
purpose, a class that wants to use this policy needs to implement
the ``NotifyPropertyChanged`` interface from the Doctrine
namespace. As a guideline, such an implementation can look as
follows:
.. code-block:: php
<?php
use Doctrine\Common\NotifyPropertyChanged,
Doctrine\Common\PropertyChangedListener;
/**
* @Entity
* @ChangeTrackingPolicy("NOTIFY")
*/
class MyEntity implements NotifyPropertyChanged
{
// ...
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->listeners[] = $listener;
}
}
Then, in each property setter of this class or derived classes, you
need to notify all the ``PropertyChangedListener`` instances. As an
example we add a convenience method on ``MyEntity`` that shows this
behaviour:
.. code-block:: php
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function onPropertyChanged($propName, $oldValue, $newValue)
{
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
public function setData($data)
{
if ($data != $this->data) {
$this->onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
You have to invoke ``onPropertyChanged`` inside every method that
changes the persistent state of ``MyEntity``.
The check whether the new value is different from the old one is
not mandatory but recommended. That way you also have full control
over when you consider a property changed.
The negative point of this policy is obvious: You need implement an
interface and write some plumbing code. But also note that we tried
hard to keep this notification functionality abstract. Strictly
speaking, it has nothing to do with the persistence layer and the
Doctrine ORM or DBAL. You may find that property notification
events come in handy in many other scenarios as well. As mentioned
earlier, the ``Doctrine\Common`` namespace is not that evil and
consists solely of very small classes and interfaces that have
almost no external dependencies (none to the DBAL and none to the
ORM) and that you can easily take with you should you want to swap
out the persistence layer. This change tracking policy does not
introduce a dependency on the Doctrine DBAL/ORM or the persistence
layer.
The positive point and main advantage of this policy is its
effectiveness. It has the best performance characteristics of the 3
policies with larger units of work and a flush() operation is very
cheap when nothing has changed.

View File

@@ -41,58 +41,88 @@ access point to ORM functionality provided by Doctrine.
// bootstrap.php
require_once "vendor/autoload.php";
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
$paths = ['/path/to/entity-files'];
$paths = array("/path/to/entity-files");
$isDevMode = false;
// the connection configuration
$dbParams = [
$dbParams = array(
'driver' => 'pdo_mysql',
'user' => 'root',
'password' => '',
'dbname' => 'foo',
];
);
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Or if you prefer XML:
.. code-block:: php
<?php
$paths = ['/path/to/xml-mappings'];
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
$paths = array("/path/to/xml-mappings");
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Inside the ``ORMSetup`` methods several assumptions are made:
Inside the ``Setup`` methods several assumptions are made:
- If ``$isDevMode`` is true caching is done in memory with the ``ArrayAdapter``. Proxy objects are recreated on every request.
- If ``$isDevMode`` is false, check for Caches in the order APCu, Redis (127.0.0.1:6379), Memcache (127.0.0.1:11211) unless `$cache` is passed as fourth argument.
- If ``$isDevMode`` is false, set then proxy classes have to be explicitly created through the command line.
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
- If `$isDevMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
- If `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line.
- If third argument `$proxyDir` is not set, use the systems temporary directory.
.. note::
In order to have ``ORMSetup`` configure the cache automatically, the library ``symfony/cache``
has to be installed as a dependency.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration </reference/advanced-configuration>` section.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
.. note::
You can learn more about the database connection configuration in the
`Doctrine DBAL connection configuration reference <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/configuration.html>`_.
`Doctrine DBAL connection configuration reference <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
Setting up the Commandline Tool
-------------------------------
Doctrine ships with a number of command line tools that are very helpful
during development. In order to make use of them, create an executable PHP
script in your project as described in the
:doc:`tools chapter <../reference/tools>`.
during development. You can call this command from the Composer binary
directory:
.. code-block:: sh
$ php vendor/bin/doctrine
You need to register your applications EntityManager to the console tool
to make use of the tasks by creating a ``cli-config.php`` file with the
following content:
On Doctrine 2.4 and above:
.. code-block:: php
<?php
use Doctrine\ORM\Tools\Console\ConsoleRunner;
// replace with file to your own project bootstrap
require_once 'bootstrap.php';
// replace with mechanism to retrieve EntityManager in your app
$entityManager = GetEntityManager();
return ConsoleRunner::createHelperSet($entityManager);
On Doctrine 2.3 and below:
.. code-block:: php
<?php
// cli-config.php
require_once 'my_bootstrap.php';
// Any way to access the EntityManager from your application
$em = GetMyEntityManager();
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
Events
======
Doctrine ORM features a lightweight event system that is part of the
Doctrine 2 features a lightweight event system that is part of the
Common package. Doctrine uses it to dispatch system events, mainly
:ref:`lifecycle events <reference-events-lifecycle-events>`.
You can also use it for your own custom events.
@@ -70,7 +70,7 @@ method.
<?php
$evm->removeEventListener(array(self::preFoo, self::postFoo), $this);
The Doctrine ORM event system also has a simple concept of event
The Doctrine 2 event system also has a simple concept of event
subscribers. We can define a simple ``TestEventSubscriber`` class
which implements the ``\Doctrine\Common\EventSubscriber`` interface
and implements a ``getSubscribedEvents()`` method which returns an
@@ -79,9 +79,7 @@ array of events it should be subscribed to.
.. code-block:: php
<?php
use Doctrine\Common\EventSubscriber;
class TestEventSubscriber implements EventSubscriber
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
{
public $preFooInvoked = false;
@@ -123,78 +121,14 @@ Now you can test the ``$eventSubscriber`` instance to see if the
echo 'pre foo invoked!';
}
Registering Event Handlers
~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two ways to set up an event handler:
* For *all events* you can create a Lifecycle Event Listener or Subscriber class and register
it by calling ``$eventManager->addEventListener()`` or ``eventManager->addEventSubscriber()``,
see
:ref:`Listening and subscribing to Lifecycle Events <listening-and-subscribing-to-lifecycle-events>`
* For *some events* (see table below), you can create a *Lifecycle Callback* method in the
entity, see :ref:`Lifecycle Callbacks <lifecycle-callbacks>`.
.. _reference-events-lifecycle-events:
Events Overview
---------------
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| Event | Dispatched by | Lifecycle | Passed |
| | | Callback | Argument |
+==================================================================+=======================+===========+=====================================+
| :ref:`preRemove <reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postRemove <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`prePersist <reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
| | on *initial* persist | | |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postPersist <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preUpdate <reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postUpdate <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postLoad <reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`loadClassMetadata <reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
| | metadata | | |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preFlush <reference-events-pre-flush>` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onFlush <reference-events-on-flush>` | ``$em->flush()`` | No | `OnFlushEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postFlush <reference-events-post-flush>` | ``$em->flush()`` | No | `PostFlushEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onClear <reference-events-on-clear>` | ``$em->clear()`` | No | `OnClearEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
.. warning::
Making changes to entities and calling ``EntityManager::flush()`` from within
event handlers dispatched by ``EntityManager::flush()`` itself is strongly
discouraged, and might be deprecated and eventually prevented in the future.
The reason is that it causes re-entrance into ``UnitOfWork::commit()`` while a commit
is currently being processed. The ``UnitOfWork`` was never designed to support this,
and its behavior in this situation is not covered by any tests.
This may lead to entity or collection updates being missed, applied only in parts and
changes being lost at the end of the commit phase.
Naming convention
~~~~~~~~~~~~~~~~~
Events being used with the Doctrine ORM EventManager are best named
Events being used with the Doctrine 2 EventManager are best named
with camelcase and the value of the corresponding constant should
be the name of the constant itself, even with spelling. This has
several reasons:
- It is easy to read.
- Simplicity.
- Each method within an EventSubscriber is named after the
@@ -205,7 +139,97 @@ several reasons:
An example for a correct notation can be found in the example
``TestEvent`` above.
.. _lifecycle-callbacks:
.. _reference-events-lifecycle-events:
Lifecycle Events
----------------
The EntityManager and UnitOfWork trigger a bunch of events during
the life-time of their registered entities.
- preRemove - The preRemove event occurs for a given entity before
the respective EntityManager remove operation for that entity is
executed. It is not called for a DQL DELETE statement.
- postRemove - The postRemove event occurs for an entity after the
entity has been deleted. It will be invoked after the database
delete operations. It is not called for a DQL DELETE statement.
- prePersist - The prePersist event occurs for a given entity
before the respective EntityManager persist operation for that
entity is executed. It should be noted that this event is only triggered on
*initial* persist of an entity (i.e. it does not trigger on future updates).
- postPersist - The postPersist event occurs for an entity after
the entity has been made persistent. It will be invoked after the
database insert operations. Generated primary key values are
available in the postPersist event.
- preUpdate - The preUpdate event occurs before the database
update operations to entity data. It is not called for a DQL UPDATE statement
nor when the computed changeset is empty.
- postUpdate - The postUpdate event occurs after the database
update operations to entity data. It is not called for a DQL UPDATE statement.
- postLoad - The postLoad event occurs for an entity after the
entity has been loaded into the current EntityManager from the
database or after the refresh operation has been applied to it.
- loadClassMetadata - The loadClassMetadata event occurs after the
mapping metadata for a class has been loaded from a mapping source
(annotations/xml). This event is not a lifecycle callback.
- onClassMetadataNotFound - Loading class metadata for a particular
requested class name failed. Manipulating the given event args instance
allows providing fallback metadata even when no actual metadata exists
or could be found. This event is not a lifecycle callback.
- preFlush - The preFlush event occurs at the very beginning of a flush
operation.
- onFlush - The onFlush event occurs after the change-sets of all
managed entities are computed. This event is not a lifecycle
callback.
- postFlush - The postFlush event occurs at the end of a flush operation. This
event is not a lifecycle callback.
- onClear - The onClear event occurs when the EntityManager#clear() operation is
invoked, after all references to entities have been removed from the unit of
work. This event is not a lifecycle callback.
.. warning::
Note that, when using ``Doctrine\ORM\AbstractQuery#iterate()``, ``postLoad``
events will be executed immediately after objects are being hydrated, and therefore
associations are not guaranteed to be initialized. It is not safe to combine
usage of ``Doctrine\ORM\AbstractQuery#iterate()`` and ``postLoad`` event
handlers.
.. warning::
Note that the postRemove event or any events triggered after an entity removal
can receive an uninitializable proxy in case you have configured an entity to
cascade remove relations. In this case, you should load yourself the proxy in
the associated pre event.
You can access the Event constants from the ``Events`` class in the
ORM package.
.. code-block:: php
<?php
use Doctrine\ORM\Events;
echo Events::preUpdate;
These can be hooked into by two different types of event
listeners:
- Lifecycle Callbacks are methods on the entity classes that are
called when the event is triggered. As of v2.4 they receive some kind
of ``EventArgs`` instance.
- Lifecycle Event Listeners and Subscribers are classes with specific callback
methods that receives some kind of ``EventArgs`` instance.
The EventArgs instance received by the listener gives access to the entity,
EntityManager and other relevant data.
.. note::
All Lifecycle events that happen during the ``flush()`` of
an EntityManager have very specific constraints on the allowed
operations that can be executed. Please read the
:ref:`reference-events-implementing-listeners` section very carefully
to understand which operations are allowed in which lifecycle event.
Lifecycle Callbacks
-------------------
@@ -216,72 +240,121 @@ a relevant lifecycle event. More than one callback can be defined for each
lifecycle event. Lifecycle Callbacks are best used for simple operations
specific to a particular entity class's lifecycle.
.. code-block:: php
.. note::
<?php
Lifecycle Callbacks are not supported for :doc:`Embeddables </tutorials/embeddables>`.
/** @Entity @HasLifecycleCallbacks */
class User
{
// ...
.. configuration-block::
/**
* @Column(type="string", length=255)
*/
public $value;
.. code-block:: attribute
/** @Column(name="created_at", type="string", length=255) */
private $createdAt;
<?php
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Doctrine\ORM\Mapping\PrePersist;
use Doctrine\ORM\Mapping\PreUpdate;
/** @PrePersist */
public function doStuffOnPrePersist()
{
$this->createdAt = date('Y-m-d H:i:s');
}
#[Entity]
#[HasLifecycleCallbacks]
class User
/** @PrePersist */
public function doOtherStuffOnPrePersist()
{
$this->value = 'changed from prePersist callback!';
}
/** @PostPersist */
public function doStuffOnPostPersist()
{
$this->value = 'changed from postPersist callback!';
}
/** @PostLoad */
public function doStuffOnPostLoad()
{
$this->value = 'changed from postLoad callback!';
}
/** @PreUpdate */
public function doStuffOnPreUpdate()
{
$this->value = 'changed from preUpdate callback!';
}
}
Note that the methods set as lifecycle callbacks need to be public and,
when using these annotations, you have to apply the
``@HasLifecycleCallbacks`` marker annotation on the entity class.
If you want to register lifecycle callbacks from XML it would look
something like this:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="User">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
In XML the ``type`` of the lifecycle-callback entry is the event that you
are triggering on and the ``method`` is the method to call. The allowed event
types are the ones listed in the previous Lifecycle Events section.
When using XML you need to remember to create public methods to match the
callback names you defined. E.g. in these examples ``doStuffOnPrePersist()``,
``doOtherStuffOnPrePersist()`` and ``doStuffOnPostPersist()`` methods need to be
defined on your ``User`` model.
.. code-block:: php
<?php
// ...
class User
{
// ...
public function doStuffOnPrePersist()
{
// ...
#[Column(type: Types::STRING, length: 255)]
public $value;
#[PrePersist]
public function doStuffOnPrePersist(PrePersistEventArgs $eventArgs)
{
$this->createdAt = date('Y-m-d H:i:s');
}
#[PrePersist]
public function doOtherStuffOnPrePersist()
{
$this->value = 'changed from prePersist callback!';
}
#[PreUpdate]
public function doStuffOnPreUpdate(PreUpdateEventArgs $eventArgs)
{
$this->value = 'changed from preUpdate callback!';
}
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
public function doOtherStuffOnPrePersist()
{
// ...
}
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="User">
<!-- ... -->
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="prePersist" method="doOtherStuffOnPrePersist"/>
<lifecycle-callback type="preUpdate" method="doStuffOnPreUpdate"/>
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
public function doStuffOnPostPersist()
{
// ...
}
}
Lifecycle Callbacks Event Argument
----------------------------------
The triggered event is also given to the lifecycle-callback.
.. versionadded:: 2.4
Since 2.4 the triggered event is given to the lifecycle-callback.
With the additional argument you have access to the
``EntityManager`` and ``UnitOfWork`` APIs inside these callback methods.
@@ -301,8 +374,6 @@ With the additional argument you have access to the
}
}
.. _listening-and-subscribing-to-lifecycle-events:
Listening and subscribing to Lifecycle Events
---------------------------------------------
@@ -312,9 +383,9 @@ sit at a level above the entities and allow you to implement re-usable
behaviors across different entity classes.
Note that they require much more detailed knowledge about the inner
workings of the ``EntityManager`` and ``UnitOfWork`` classes. Please
read the :ref:`Implementing Event Listeners <reference-events-implementing-listeners>` section
carefully if you are trying to write your own listener.
workings of the EntityManager and UnitOfWork. Please read the
:ref:`reference-events-implementing-listeners` section carefully if you
are trying to write your own listener.
For event subscribers, there are no surprises. They declare the
lifecycle events in their ``getSubscribedEvents`` method and provide
@@ -325,11 +396,11 @@ A lifecycle event listener looks like the following:
.. code-block:: php
<?php
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class MyEventListener
{
public function preUpdate(PreUpdateEventArgs $args)
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
@@ -346,9 +417,9 @@ A lifecycle event subscriber may look like this:
.. code-block:: php
<?php
use Doctrine\ORM\Event\PostUpdateEventArgs;
use Doctrine\ORM\Events;
use Doctrine\EventSubscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class MyEventSubscriber implements EventSubscriber
{
@@ -359,7 +430,7 @@ A lifecycle event subscriber may look like this:
);
}
public function postUpdate(PostUpdateEventArgs $args)
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
@@ -382,13 +453,11 @@ EventManager that is passed to the EntityManager factory:
.. code-block:: php
<?php
use Doctrine\ORM\Events;
$eventManager = new EventManager();
$eventManager->addEventListener([Events::preUpdate], new MyEventListener());
$eventManager->addEventListener(array(Events::preUpdate), new MyEventListener());
$eventManager->addEventSubscriber(new MyEventSubscriber());
$entityManager = new EntityManager($connection, $config, $eventManager);
$entityManager = EntityManager::create($dbOpts, $config, $eventManager);
You can also retrieve the event manager instance after the
EntityManager was created:
@@ -396,9 +465,7 @@ EntityManager was created:
.. code-block:: php
<?php
use Doctrine\ORM\Events;
$entityManager->getEventManager()->addEventListener([Events::preUpdate], new MyEventListener());
$entityManager->getEventManager()->addEventListener(array(Events::preUpdate), new MyEventListener());
$entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber());
.. _reference-events-implementing-listeners:
@@ -407,37 +474,33 @@ Implementing Event Listeners
----------------------------
This section explains what is and what is not allowed during
specific lifecycle events of the ``UnitOfWork`` class. Although you get
passed the ``EntityManager`` instance in all of these events, you have
to follow these restrictions very carefully since operations in the
wrong event may produce lots of different errors, such as inconsistent
specific lifecycle events of the UnitOfWork. Although you get
passed the EntityManager in all of these events, you have to follow
these restrictions very carefully since operations in the wrong
event may produce lots of different errors, such as inconsistent
data and lost updates/persists/removes.
For the described events that are also lifecycle callback events
the restrictions apply as well, with the additional restriction
that (prior to version 2.4) you do not have access to the
``EntityManager`` or ``UnitOfWork`` APIs inside these events.
.. _reference-events-pre-persist:
EntityManager or UnitOfWork APIs inside these events.
prePersist
~~~~~~~~~~
There are two ways for the ``prePersist`` event to be triggered:
There are two ways for the ``prePersist`` event to be triggered.
One is obviously when you call ``EntityManager#persist()``. The
event is also called for all cascaded associations.
- One is when you call ``EntityManager::persist()``. The
event is also called for all :ref:`cascaded associations <transitive-persistence>`.
- The other is inside the ``flush()`` method when changes to associations are computed and
this association is marked as :ref:`cascade: persist <transitive-persistence>`. Any new entity found
during this operation is also persisted and ``prePersist`` called
on it. This is called :ref:`persistence by reachability <persistence-by-reachability>`.
There is another way for ``prePersist`` to be called, inside the
``flush()`` method when changes to associations are computed and
this association is marked as cascade persist. Any new entity found
during this operation is also persisted and ``prePersist`` called
on it. This is called "persistence by reachability".
In both cases you get passed a ``PrePersistEventArgs`` instance
In both cases you get passed a ``LifecycleEventArgs`` instance
which has access to the entity and the entity manager.
This event is only triggered on *initial* persist of an entity
(i.e. it does not trigger on future updates).
The following restrictions apply to ``prePersist``:
- If you are using a PrePersist Identity Generator such as
@@ -447,34 +510,28 @@ The following restrictions apply to ``prePersist``:
event. This includes modifications to
collections such as additions, removals or replacement.
.. _reference-events-pre-remove:
preRemove
~~~~~~~~~
The ``preRemove`` event is called on every entity immediately when it is passed
to the ``EntityManager::remove()`` method. It is cascaded for all
associations that are marked as :ref:`cascade: remove <transitive-persistence>`
It is not called for a DQL ``DELETE`` statement.
The ``preRemove`` event is called on every entity when its passed
to the ``EntityManager#remove()`` method. It is cascaded for all
associations that are marked as cascade delete.
There are no restrictions to what methods can be called inside the
``preRemove`` event, except when the remove method itself was
called during a flush operation.
.. _reference-events-pre-flush:
preFlush
~~~~~~~~
``preFlush`` is called inside ``EntityManager::flush()`` before
anything else. ``EntityManager::flush()`` must not be called inside
its listeners, since it would fire the ``preFlush`` event again, which would
result in an infinite loop.
``preFlush`` is called at ``EntityManager#flush()`` before
anything else. ``EntityManager#flush()`` can be called safely
inside its listeners.
.. code-block:: php
<?php
use Doctrine\ORM\Event\PreFlushEventArgs;
class PreFlushExampleListener
@@ -485,13 +542,11 @@ result in an infinite loop.
}
}
.. _reference-events-on-flush:
onFlush
~~~~~~~
``onFlush`` is a very powerful event. It is called inside
``EntityManager::flush()`` after the changes to all the managed
OnFlush is a very powerful event. It is called inside
``EntityManager#flush()`` after the changes to all the managed
entities and their associations have been computed. This means, the
``onFlush`` event has access to the sets of:
@@ -501,8 +556,8 @@ entities and their associations have been computed. This means, the
- Collections scheduled for update
- Collections scheduled for removal
To make use of the ``onFlush`` event you have to be familiar with the
internal :ref:`UnitOfWork <unit-of-work>` API, which grants you access to the previously
To make use of the onFlush event you have to be familiar with the
internal UnitOfWork API, which grants you access to the previously
mentioned sets. See this example:
.. code-block:: php
@@ -512,7 +567,7 @@ mentioned sets. See this example:
{
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getObjectManager();
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
@@ -537,10 +592,10 @@ mentioned sets. See this example:
}
}
The following restrictions apply to the ``onFlush`` event:
The following restrictions apply to the onFlush event:
- If you create and persist a new entity in ``onFlush``, then
calling ``EntityManager::persist()`` is not enough.
calling ``EntityManager#persist()`` is not enough.
You have to execute an additional call to
``$unitOfWork->computeChangeSet($classMetadata, $entity)``.
- Changing primitive fields or associations requires you to
@@ -548,18 +603,16 @@ The following restrictions apply to the ``onFlush`` event:
affected entity. This can be done by calling
``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``.
.. _reference-events-post-flush:
postFlush
~~~~~~~~~
``postFlush`` is called at the end of ``EntityManager::flush()``.
``EntityManager::flush()`` can **NOT** be called safely inside its listeners.
This event is not a lifecycle callback.
``postFlush`` is called at the end of ``EntityManager#flush()``.
``EntityManager#flush()`` can **NOT** be called safely inside its listeners.
.. code-block:: php
<?php
use Doctrine\ORM\Event\PostFlushEventArgs;
class PostFlushExampleListener
@@ -570,21 +623,19 @@ This event is not a lifecycle callback.
}
}
.. _reference-events-pre-update:
preUpdate
~~~~~~~~~
PreUpdate is called inside the ``EntityManager::flush()`` method,
right before an SQL ``UPDATE`` statement. This event is not
triggered when the computed changeset is empty, nor for a DQL
``UPDATE`` statement.
PreUpdate is the most restrictive to use event, since it is called
right before an update statement is called for an entity inside the
``EntityManager#flush()`` method. Note that this event is not
triggered when the computed changeset is empty.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
referential integrity at this point of the flush operation. This
event has a powerful feature however, it is executed with a
`PreUpdateEventArgs`_ instance, which contains a reference to the
``PreUpdateEventArgs`` instance, which contains a reference to the
computed change-set of this entity.
This means you have access to all the fields that have changed for
@@ -606,8 +657,6 @@ A simple example for this event looks like:
.. code-block:: php
<?php
use Doctrine\ORM\Event\PreUpdateEventArgs;
class NeverAliceOnlyBobListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
@@ -615,9 +664,6 @@ A simple example for this event looks like:
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$eventArgs->setNewValue('name', 'Bob');
// The following will only work if `status` is already present in the computed changeset.
// Otherwise it will throw an InvalidArgumentException:
$eventArgs->setNewValue('status', 'active');
}
}
}
@@ -630,8 +676,6 @@ lifecycle callback when there are expensive validations to call:
.. code-block:: php
<?php
use Doctrine\ORM\Event\PreUpdateEventArgs;
class ValidCreditCardListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
@@ -657,79 +701,31 @@ Restrictions for this event:
the flush operation anymore, use the computed change-set passed to
the event to modify primitive field values, e.g. use
``$eventArgs->setNewValue($field, $value);`` as in the Alice to Bob example above.
- Any calls to ``EntityManager::persist()`` or
``EntityManager::remove()``, even in combination with the ``UnitOfWork``
- Any calls to ``EntityManager#persist()`` or
``EntityManager#remove()``, even in combination with the UnitOfWork
API are strongly discouraged and don't work as expected outside the
flush operation.
.. _reference-events-post-update-remove-persist:
postUpdate, postRemove, postPersist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These three ``post*`` events are called inside ``EntityManager::flush()``.
The three post events are called inside ``EntityManager#flush()``.
Changes in here are not relevant to the persistence in the
database, but you can use these events to alter non-persistable items,
like non-mapped fields, logging or even associated classes that are
not directly mapped by Doctrine.
- The ``postUpdate`` event occurs after the database
update operations to entity data, but before the database transaction
has been committed. It is not called for a DQL ``UPDATE`` statement.
- The ``postPersist`` event occurs for an entity after the entity has
been made persistent. It will be invoked after all database insert
operations for new entities have been performed, but before the database
transaction has been committed. Generated primary key values will be
available for all entities at the time this event is triggered.
- The ``postRemove`` event occurs for an entity after the
entity has been deleted. It will be invoked after all database
delete operations for entity rows have been executed, but before the
database transaction has been committed. This event is not called for
a DQL ``DELETE`` statement.
.. note::
At the time ``postPersist`` is called, there may still be collection and/or
"extra" updates pending. The database may not yet be completely in
sync with the entity states in memory, not even for the new entities. Similarly,
also at the time ``postUpdate`` and ``postRemove`` are called, in-memory collections
may still be in a "dirty" state or still contain removed entities.
.. warning::
The ``postRemove`` event or any events triggered after an entity removal
can receive an uninitializable proxy in case you have configured an entity to
cascade remove relations. In this case, you should load yourself the proxy in
the associated ``pre*`` event.
.. _reference-events-post-load:
postLoad
~~~~~~~~
The postLoad event occurs after the entity has been loaded into the current
``EntityManager`` from the database or after ``refresh()`` has been applied to it.
.. warning::
When using ``Doctrine\ORM\AbstractQuery::toIterable()``, ``postLoad``
events will be executed immediately after objects are being hydrated, and therefore
associations are not guaranteed to be initialized. It is not safe to combine
usage of ``Doctrine\ORM\AbstractQuery::toIterable()`` and ``postLoad`` event
handlers.
.. _reference-events-on-clear:
onClear
~~~~~~~~
The ``onClear`` event occurs when the ``EntityManager::clear()`` operation is invoked,
after all references to entities have been removed from the unit of work.
This event is not a lifecycle callback.
This event is called after an entity is constructed by the
EntityManager.
Entity listeners
----------------
.. versionadded:: 2.4
An entity listener is a lifecycle listener class used for an entity.
- The entity listener's mapping may be applied to an entity class or mapped superclass.
@@ -737,14 +733,12 @@ An entity listener is a lifecycle listener class used for an entity.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
namespace MyProject\Entity;
use App\EventListener\UserListener;
#[Entity]
#[EntityListeners([UserListener::class])]
/** @Entity @EntityListeners({"UserListener"}) */
class User
{
// ....
@@ -777,8 +771,6 @@ An ``Entity Listener`` could be any class, by default it should be a class with
.. code-block:: php
<?php
use Doctrine\ORM\Event\PreUpdateEventArgs;
class UserListener
{
public function preUpdate(User $user, PreUpdateEventArgs $event)
@@ -792,45 +784,35 @@ you need to map the listener method using the event type mapping:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
use Doctrine\ORM\Event\PostLoadEventArgs;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PostRemoveEventArgs;
use Doctrine\ORM\Event\PostUpdateEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreRemoveEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
class UserListener
{
#[PrePersist]
public function prePersistHandler(User $user, PrePersistEventArgs $event): void { // ... }
/** @PrePersist */
public function prePersistHandler(User $user, LifecycleEventArgs $event) { // ... }
#[PostPersist]
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
/** @PostPersist */
public function postPersistHandler(User $user, LifecycleEventArgs $event) { // ... }
#[PreUpdate]
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
/** @PreUpdate */
public function preUpdateHandler(User $user, PreUpdateEventArgs $event) { // ... }
#[PostUpdate]
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
/** @PostUpdate */
public function postUpdateHandler(User $user, LifecycleEventArgs $event) { // ... }
#[PostRemove]
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
/** @PostRemove */
public function postRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
#[PreRemove]
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
/** @PreRemove */
public function preRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
#[PreFlush]
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
/** @PreFlush */
public function preFlushHandler(User $user, PreFlushEventArgs $event) { // ... }
#[PostLoad]
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
/** @PostLoad */
public function postLoadHandler(User $user, LifecycleEventArgs $event) { // ... }
}
.. code-block:: xml
<doctrine-mapping>
@@ -858,7 +840,6 @@ you need to map the listener method using the event type mapping:
The order of execution of multiple methods for the same event (e.g. multiple @PrePersist) is not guaranteed.
Entity listeners resolver
~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine invokes the listener resolver to get the listener instance.
@@ -871,12 +852,9 @@ Specifying an entity listener instance :
.. code-block:: php
<?php
use Doctrine\ORM\Event\PreUpdateEventArgs;
// User.php
#[Entity]
#[EntityListeners(["UserListener"])
/** @Entity @EntityListeners({"UserListener"}) */
class User
{
// ....
@@ -900,14 +878,12 @@ Specifying an entity listener instance :
$listener = $container->get('user_listener');
$em->getConfiguration()->getEntityListenerResolver()->register($listener);
Implementing your own resolver:
Implementing your own resolver :
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
class MyEntityListenerResolver extends DefaultEntityListenerResolver
class MyEntityListenerResolver extends \Doctrine\ORM\Mapping\DefaultEntityListenerResolver
{
public function __construct($container)
{
@@ -925,31 +901,25 @@ Implementing your own resolver:
// Configure the listener resolver only before instantiating the EntityManager
$configurations->setEntityListenerResolver(new MyEntityListenerResolver);
$entityManager = new EntityManager(.., $configurations, ..);
.. _reference-events-load-class-metadata:
EntityManager::create(.., $configurations, ..);
Load ClassMetadata Event
------------------------
``loadClassMetadata`` - The ``loadClassMetadata`` event occurs after the
mapping metadata for a class has been loaded from a mapping source
(attributes/xml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
You can hook in to this process and manipulate the instance.
This event is not a lifecycle callback.
When the mapping information for an entity is read, it is populated
in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance. You can hook in to this
process and manipulate the instance.
.. code-block:: php
<?php
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
$test = new TestEventListener();
$evm = $em->getEventManager();
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $test);
class TestEventListener
{
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$fieldMapping = array(
@@ -961,11 +931,6 @@ This event is not a lifecycle callback.
}
}
If not class metadata can be found, an ``onClassMetadataNotFound`` event is dispatched.
Manipulating the given event args instance
allows providing fallback metadata even when no actual metadata exists
or could be found. This event is not a lifecycle callback.
SchemaTool Events
-----------------
@@ -982,16 +947,13 @@ instance and class metadata.
.. code-block:: php
<?php
use Doctrine\ORM\Tools\ToolEvents;
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
$test = new TestEventListener();
$evm = $em->getEventManager();
$evm->addEventListener(ToolEvents::postGenerateSchemaTable, $test);
$evm->addEventListener(\Doctrine\ORM\Tools\ToolEvents::postGenerateSchemaTable, $test);
class TestEventListener
{
public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs)
public function postGenerateSchemaTable(\Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$schema = $eventArgs->getSchema();
@@ -1009,32 +971,15 @@ and the EntityManager.
.. code-block:: php
<?php
use Doctrine\ORM\Tools\ToolEvents;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
$test = new TestEventListener();
$evm = $em->getEventManager();
$evm->addEventListener(ToolEvents::postGenerateSchema, $test);
$evm->addEventListener(\Doctrine\ORM\Tools\ToolEvents::postGenerateSchema, $test);
class TestEventListener
{
public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs)
public function postGenerateSchema(\Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs $eventArgs)
{
$schema = $eventArgs->getSchema();
$em = $eventArgs->getEntityManager();
}
}
.. _PrePersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PrePersistEventArgs.php
.. _PreRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreRemoveEventArgs.php
.. _PreUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreUpdateEventArgs.php
.. _PostPersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostPersistEventArgs.php
.. _PostRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostRemoveEventArgs.php
.. _PostUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostUpdateEventArgs.php
.. _PostLoadEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostLoadEventArgs.php
.. _PreFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreFlushEventArgs.php
.. _PostFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostFlushEventArgs.php
.. _OnFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/OnFlushEventArgs.php
.. _OnClearEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/OnClearEventArgs.php
.. _LoadClassMetadataEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/LoadClassMetadataEventArgs.php
.. _OnClassMetadataNotFoundEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/OnClassMetadataNotFoundEventArgs.php

View File

@@ -13,10 +13,10 @@ Database Schema
How do I set the charset and collation for MySQL tables?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In your mapping configuration, the column definition (for example, the
``#[Column]`` attribute) has an ``options`` parameter where you can specify
the ``charset`` and ``collation``. The default values are ``utf8`` and
``utf8_unicode_ci``, respectively.
You can't set these values inside the annotations or xml mapping files. To make a database
work with the default charset and collation you should configure MySQL to use it as default charset,
or create the database with charset and collation details. This way they get inherited to all newly
created database tables and columns.
Entity Classes
--------------
@@ -32,12 +32,11 @@ upon insert:
class User
{
private const STATUS_DISABLED = 0;
private const STATUS_ENABLED = 1;
const STATUS_DISABLED = 0;
const STATUS_ENABLED = 1;
private string $algorithm = "sha1";
/** @var self::STATUS_* */
private int $status = self::STATUS_DISABLED;
private $algorithm = "sha1";
private $status = self:STATUS_DISABLED;
}
.
@@ -81,7 +80,7 @@ You can solve this exception by:
How can I filter an association?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You should use DQL queries to query for the filtered set of entities.
Natively you can't filter associations in 2.0 and 2.1. You should use DQL queries to query for the filtered set of entities.
I call clear() on a One-To-Many collection but the entities are not deleted
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -99,10 +98,9 @@ How can I add columns to a many-to-many table?
The many-to-many association is only supporting foreign keys in the table definition
To work with many-to-many tables containing extra columns you have to use the
foreign keys as primary keys feature of Doctrine ORM.
See :doc:`the tutorial on composite primary keys for more information <../tutorials/composite-primary-keys>`.
foreign keys as primary keys feature of Doctrine introduced in version 2.1.
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
How can i paginate fetch-joined collections?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -129,10 +127,10 @@ See the previous question for a solution to this task.
Inheritance
-----------
Can I use Inheritance with Doctrine ORM?
Can I use Inheritance with Doctrine 2?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yes, you can use Single- or Joined-Table Inheritance in ORM.
Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
See the documentation chapter on :doc:`inheritance mapping <inheritance-mapping>` for
the details.
@@ -144,23 +142,6 @@ If you set a many-to-one or one-to-one association target-entity to any parent c
an inheritance hierarchy Doctrine does not know what PHP class the foreign is actually of.
To find this out it has to execute a SQL query to look this information up in the database.
EntityGenerator
---------------
Why does the EntityGenerator not do X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The EntityGenerator is not a full fledged code-generator that solves all tasks. Code-Generation
is not a first-class priority in Doctrine 2 anymore (compared to Doctrine 1). The EntityGenerator
is supposed to kick-start you, but not towards 100%.
Why does the EntityGenerator not generate inheritance correctly?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierarchy.
This is why the generation of inherited entities does not fully work. You have to adjust some additional
code to get this one working correctly.
Performance
-----------
@@ -199,21 +180,6 @@ No, it is not supported to sort by function in DQL. If you need this functionali
use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow
starting with 1000 rows.
Is it better to write DQL or to generate it with the query builder?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The purpose of the ``QueryBuilder`` is to generate DQL dynamically,
which is useful when you have optional filters, conditional joins, etc.
But the ``QueryBuilder`` is not an alternative to DQL, it actually generates DQL
queries at runtime, which are then interpreted by Doctrine. This means that
using the ``QueryBuilder`` to build and run a query is actually always slower
than only running the corresponding DQL query.
So if you only need to generate a query and bind parameters to it,
you should use plain DQL, as this is a simpler and much more readable solution.
You should only use the ``QueryBuilder`` when you can't achieve what you want to do with a DQL query.
A Query fails, how can I debug it?
----------------------------------

View File

@@ -1,7 +1,9 @@
Filters
=======
Doctrine ORM features a filter system that allows the developer to add SQL to
.. versionadded:: 2.2
Doctrine 2.2 features a filter system that allows the developer to add SQL to
the conditional clauses of queries, regardless the place where the SQL is
generated (e.g. from a DQL query, or by loading associated entities).
@@ -14,7 +16,6 @@ By adding SQL to the conditional clauses of queries, the filter system filters
out rows belonging to the entities at the level of the SQL result set. This
means that the filtered entities are never hydrated (which can be expensive).
Example filter class
--------------------
Throughout this document the example ``MyLocaleFilter`` class will be used to
@@ -28,25 +29,24 @@ table alias of the SQL table of the entity.
In the case of joined or single table inheritance, you always get passed the ClassMetadata of the
inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters.
For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
2. The filter must be deterministic. Don't change the values base on external inputs.
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.
Parameters for the query should be set on the filter object by
``SQLFilter#setParameter()``. Only parameters set via this function can be used
in filters. The ``SQLFilter#getParameter()`` function takes care of the
proper quoting of parameters.
.. code-block:: php
<?php
namespace Example;
use Doctrine\ORM\Mapping\ClassMetadata,
use Doctrine\ORM\Mapping\ClassMetaData,
Doctrine\ORM\Query\Filter\SQLFilter;
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
// Check if the entity implements the LocalAware interface
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
if (!$targetEntity->getReflectionClass()->implementsInterface('LocaleAware')) {
return "";
}
@@ -54,10 +54,6 @@ The ``SQLFilter#getParameter()`` function takes care of the proper quoting of pa
}
}
If the parameter is an array and should be quoted as a list of values for an IN query
this is possible with the alternative ``SQLFilter#setParameterList()`` and
``SQLFilter#getParameterList()`` functions.
Configuration
-------------
Filter classes are added to the configuration as following:
@@ -67,11 +63,9 @@ Filter classes are added to the configuration as following:
<?php
$config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
The ``Configuration#addFilter()`` method takes a name for the filter and the name of the
class responsible for the actual filtering.
Disabling/Enabling Filters and Setting Parameters
---------------------------------------------------
Filters can be disabled and enabled via the ``FilterCollection`` which is
@@ -93,34 +87,3 @@ object.
want to refresh or reload an object after having modified a filter or the
FilterCollection, then you should clear the EntityManager and re-fetch your
entities, having the new rules for filtering applied.
Suspending/Restoring Filters
----------------------------
When a filter is disabled, the instance is fully deleted and all the filter
parameters previously set are lost. Then, if you enable it again, a new filter
is created without the previous filter parameters. If you want to keep a filter
(in order to use it later) but temporary disable it, you'll need to use the
``FilterCollection#suspend($name)`` and ``FilterCollection#restore($name)``
methods instead.
.. code-block:: php
<?php
$filter = $em->getFilters()->enable("locale");
$filter->setParameter('locale', 'en');
// Temporary suspend the filter
$filter = $em->getFilters()->suspend("locale");
// Do things
// Then restore it, the locale parameter will still be set
$filter = $em->getFilters()->restore("locale");
.. warning::
If you enable a previously disabled filter, doctrine will create a new
one without keeping any of the previously parameter set with
``SQLFilter#setParameter()`` or ``SQLFilter#getParameterList()``. If you
want to restore the previously disabled filter instead, you must use the
``FilterCollection#restore($name)`` method.

View File

@@ -4,7 +4,7 @@ Improving Performance
Bytecode Cache
--------------
It is highly recommended to make use of a bytecode cache like OPcache.
It is highly recommended to make use of a bytecode cache like APC.
A bytecode cache removes the need for parsing PHP code on every
request and can greatly improve performance.
@@ -14,27 +14,17 @@ request and can greatly improve performance.
*Stas Malyshev, Core Contributor to PHP and Zend Employee*
Metadata and Query caches
-------------------------
As already mentioned earlier in the chapter about configuring
Doctrine, it is strongly discouraged to use Doctrine without a
Metadata and Query cache.
Operating Doctrine without these caches means
Metadata and Query cache (preferably with APC or Memcache as the
cache driver). Operating Doctrine without these caches means
Doctrine will need to load your mapping information on every single
request and has to parse each DQL query on every single request.
This is a waste of resources.
The preferred cache adapter for metadata and query caches is a PHP file
cache like Symfony's
`PHP files adapter <https://symfony.com/doc/current/components/cache/adapters/php_files_adapter.html>`_.
This kind of cache serializes cache items and writes them to a file.
This allows for opcode caching to be used and provides high performance in most scenarios.
See :ref:`types-of-caches`
Alternative Query Result Formats
--------------------------------
@@ -45,32 +35,11 @@ in scenarios where data is loaded for read-only purposes.
Read-Only Entities
------------------
You can mark entities as read only. For details, see :ref:`attrref_entity`
This means that the entity marked as read only is never considered for updates.
During flush on the EntityManager these entities are skipped even if properties
changed.
Read-Only allows to persist new entities of a kind and remove existing ones,
they are just not considered for updates.
You can also explicitly mark individual entities read only directly on the
UnitOfWork via a call to ``markReadOnly()``:
.. code-block:: php
$user = $entityManager->find(User::class, $id);
$entityManager->getUnitOfWork()->markReadOnly($user);
Or you can set all objects that are the result of a query hydration to be
marked as read only with the following query hint:
.. code-block:: php
$query = $entityManager->createQuery('SELECT u FROM App\\Entity\\User u');
$query->setHint(Query::HINT_READ_ONLY, true);
$users = $query->getResult();
Starting with Doctrine 2.1 you can mark entities as read only (See metadata mapping
references for details). This means that the entity marked as read only is never considered
for updates, which means when you call flush on the EntityManager these entities are skipped
even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
ones, they are just not considered for updates.
Extra-Lazy Collections
----------------------
@@ -91,8 +60,6 @@ Apply Best Practices
A lot of the points mentioned in the Best Practices chapter will
also positively affect the performance of Doctrine.
See :doc:`Best Practices </reference/best-practices>`
Change Tracking policies
------------------------

View File

@@ -1,9 +1,6 @@
Inheritance Mapping
===================
This chapter explains the available options for mapping class
hierarchies.
Mapped Superclasses
-------------------
@@ -15,95 +12,56 @@ is common to multiple entity classes.
Mapped superclasses, just as regular, non-mapped classes, can
appear in the middle of an otherwise mapped inheritance hierarchy
(through Single Table Inheritance or Class Table Inheritance). They
are not query-able, and need not have an ``#[Id]`` property.
No database table will be created for a mapped superclass itself,
only for entity classes inheriting from it. That implies that a
mapped superclass cannot be the ``targetEntity`` in associations.
In other words, a mapped superclass can use unidirectional One-To-One
and Many-To-One associations where it is the owning side.
Many-To-Many associations are only possible if the mapped
superclass is only used in exactly one entity at the moment. For further
support of inheritance, the single or joined table inheritance features
have to be used.
(through Single Table Inheritance or Class Table Inheritance).
.. note::
One-To-Many associations are not generally possible on a mapped
superclass, since they require the "many" side to hold the foreign
key.
It is, however, possible to use the :doc:`ResolveTargetEntityListener </cookbook/resolve-target-entity-listener>`
to replace references to a mapped superclass with an entity class at runtime.
As long as there is only one entity subclass inheriting from the mapped
superclass and all references to the mapped superclass are resolved to that
entity class at runtime, the mapped superclass *can* use One-To-Many associations
and be named as the ``targetEntity`` on the owning sides.
.. warning::
At least when using attributes or annotations to specify your mapping,
it *seems* as if you could inherit from a base class that is neither
an entity nor a mapped superclass, but has properties with mapping configuration
on them that would also be used in the inheriting class.
This, however, is due to how the corresponding mapping
drivers work and what the PHP reflection API reports for inherited fields.
Such a configuration is explicitly not supported. To give just one example,
it will break for ``private`` properties.
.. note::
You may be tempted to use traits to mix mapped fields or relationships
into your entity classes to circumvent some of the limitations of
mapped superclasses. Before doing that, please read the section on traits
in the :doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional (with an owning side only). This means that One-To-Many
associations are not possible on a mapped superclass at all.
Furthermore Many-To-Many associations are only possible if the
mapped superclass is only used in exactly one entity at the moment.
For further support of inheritance, the single or
joined table inheritance features have to be used.
Example:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\OneToOne;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\MappedSuperclass;
use Doctrine\ORM\Mapping\Entity;
#[MappedSuperclass]
/** @MappedSuperclass */
class Person
{
#[Column(type: 'integer')]
protected int $mapped1;
#[Column(type: 'string')]
protected string $mapped2;
#[OneToOne(targetEntity: Toothbrush::class)]
#[JoinColumn(name: 'toothbrush_id', referencedColumnName: 'id')]
protected Toothbrush|null $toothbrush = null;
/** @Column(type="integer") */
protected $mapped1;
/** @Column(type="string") */
protected $mapped2;
/**
* @OneToOne(targetEntity="Toothbrush")
* @JoinColumn(name="toothbrush_id", referencedColumnName="id")
*/
protected $toothbrush;
// ... more fields and methods
}
#[Entity]
/** @Entity */
class Employee extends Person
{
#[Id, Column(type: 'integer')]
private int|null $id = null;
#[Column(type: 'string')]
private string $name;
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
// ... more fields and methods
}
#[Entity]
/** @Entity */
class Toothbrush
{
#[Id, Column(type: 'integer')]
private int|null $id = null;
/** @Id @Column(type="integer") */
private $id;
// ... more fields and methods
}
@@ -113,105 +71,67 @@ like this (this is for SQLite):
.. code-block:: sql
CREATE TABLE Employee (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, toothbrush_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
As you can see from this DDL snippet, there is only a single table
for the entity subclass. All the mappings from the mapped
superclass were inherited to the subclass as if they had been
defined on that class directly.
Entity Inheritance
------------------
As soon as one entity class inherits from another entity class, either
directly, with a mapped superclass or other unmapped (also called
"transient") classes in between, these entities form an inheritance
hierarchy. The topmost entity class in this hierarchy is called the
root entity, and the hierarchy includes all entities that are
descendants of this root entity.
On the root entity class, ``#[InheritanceType]``,
``#[DiscriminatorColumn]`` and ``#[DiscriminatorMap]`` must be specified.
``#[InheritanceType]`` specifies one of the two available inheritance
mapping strategies that are explained in the following sections.
``#[DiscriminatorColumn]`` designates the so-called discriminator column.
This is an extra column in the table that keeps information about which
type from the hierarchy applies for a particular database row.
``#[DiscriminatorMap]`` declares the possible values for the discriminator
column and maps them to class names in the hierarchy. This discriminator map
has to declare all non-abstract entity classes that exist in that particular
inheritance hierarchy. That includes the root as well as any intermediate
entity classes, given they are not abstract.
The names of the classes in the discriminator map do not need to be fully
qualified if the classes are contained in the same namespace as the entity
class on which the discriminator map is applied.
If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map contains the
lowercase short name of each class as key.
.. note::
Automatically generating the discriminator map is very expensive
computation-wise. The mapping driver has to provide all classes
for which mapping configuration exists, and those have to be
loaded and checked against the current inheritance hierarchy
to see if they are part of it. The resulting map, however, can be kept
in the metadata cache.
Performance impact on to-one associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There is a general performance consideration when using entity inheritance:
If the target-entity of a many-to-one or one-to-one association is part of
an inheritance hierarchy, it is preferable for performance reasons that it
be a leaf entity (ie. have no subclasses).
Otherwise, the ORM is currently unable to tell beforehand which entity class
will have to be used, and so no appropriate proxy instance can be created.
That means the referred-to entities will *always* be loaded eagerly, which
might even propagate to relationships of these entities (in the case of
self-referencing associations).
Single Table Inheritance
------------------------
`Single Table Inheritance <https://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
is an inheritance mapping strategy where all classes of a hierarchy are
mapped to a single database table.
is an inheritance mapping strategy where all classes of a hierarchy
are mapped to a single database table. In order to distinguish
which row represents which type in the hierarchy a so-called
discriminator column is used.
Example:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
namespace MyProject\Model;
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
#[Entity]
/**
* @Entity
*/
class Employee extends Person
{
// ...
}
Things to note:
In this example, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
``Person`` and employee" identifies a row as being of type ``Employee``.
- The @InheritanceType and @DiscriminatorColumn must be specified
on the topmost class that is part of the mapped entity hierarchy.
- The @DiscriminatorMap specifies which values of the
discriminator column identify a row as being of a certain type. In
the case above a value of "person" identifies a row as being of
type ``Person`` and "employee" identifies a row as being of type
``Employee``.
- All entity classes that is part of the mapped entity hierarchy
(including the topmost class) should be specified in the
@DiscriminatorMap. In the case above Person class included.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, an exception will be thrown.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -227,10 +147,17 @@ Performance impact
This strategy is very efficient for querying across all types in
the hierarchy or for specific types. No table joins are required,
only a ``WHERE`` clause listing the type identifiers. In particular,
only a WHERE clause listing the type identifiers. In particular,
relationships involving types that employ this mapping strategy are
very performing.
There is a general performance consideration with Single Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is an STI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -238,7 +165,7 @@ For Single-Table-Inheritance to work in scenarios where you are
using either a legacy database schema or a self-written database
schema you have to make sure that all columns that are not in the
root entity but in any of the different sub-entities has to allow
null values. Columns that have ``NOT NULL`` constraints have to be on
null values. Columns that have NOT NULL constraints have to be on
the root entity of the single-table inheritance hierarchy.
Class Table Inheritance
@@ -248,11 +175,10 @@ Class Table Inheritance
is an inheritance mapping strategy where each class in a hierarchy
is mapped to several tables: its own table and the tables of all
parent classes. The table of a child class is linked to the table
of a parent class through a foreign key constraint.
The discriminator column is placed in the topmost table of the hierarchy,
because this is the easiest way to achieve polymorphic queries with Class
Table Inheritance.
of a parent class through a foreign key constraint. Doctrine 2
implements this strategy through the use of a discriminator column
in the topmost table of the hierarchy because this is the easiest
way to achieve polymorphic queries with Class Table Inheritance.
Example:
@@ -261,24 +187,38 @@ Example:
<?php
namespace MyProject\Model;
#[Entity]
#[InheritanceType('JOINED')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
#[Entity]
/** @Entity */
class Employee extends Person
{
// ...
}
As before, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
``Person`` and "employee" identifies a row as being of type ``Employee``.
Things to note:
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
must be specified on the topmost class that is part of the mapped
entity hierarchy.
- The @DiscriminatorMap specifies which values of the
discriminator column identify a row as being of which type. In the
case above a value of "person" identifies a row as being of type
``Person`` and "employee" identifies a row as being of type
``Employee``.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, an exception will be thrown.
.. note::
@@ -288,7 +228,6 @@ discriminator column, a value of "person" identifies a row as being of type
``ON DELETE CASCADE`` in all database implementations. A failure to
implement this yourself will lead to dead rows in the database.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -309,13 +248,17 @@ perform just about any query which can have a negative impact on
performance, especially with large tables and/or large hierarchies.
When partial objects are allowed, either globally or on the
specific query, then querying for any type will not cause the
tables of subtypes to be ``OUTER JOIN``ed which can increase
tables of subtypes to be OUTER JOINed which can increase
performance but the resulting partial objects will not fully load
themselves on access of any subtype fields, so accessing fields of
subtypes after such a query is not safe.
There is also another important performance consideration that it is *not possible*
to query for the base entity without any ``LEFT JOIN``s to the sub-types.
There is a general performance consideration with Class Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is a CTI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -332,18 +275,9 @@ column and cascading on delete.
Overrides
---------
Overrides can only be applied to entities that extend a mapped superclass or
use traits. They are used to override a mapping for an entity field or
relationship defined in that mapped superclass or trait.
It is not supported to use overrides in entity inheritance scenarios.
.. note::
When using traits, make sure not to miss the warnings given in the
:doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
Used to override a mapping for an entity field or relationship.
May be applied to an entity that extends a mapped superclass
to override a relationship or field mapping defined by the mapped superclass.
Association Override
~~~~~~~~~~~~~~~~~~~~
@@ -356,47 +290,53 @@ Example:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
// user mapping
namespace MyProject\Model;
#[MappedSuperclass]
/**
* @MappedSuperclass
*/
class User
{
// other fields mapping
/** @var Collection<int, Group> */
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'Group', inversedBy: 'users')]
protected Collection $groups;
/**
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
#[ManyToOne(targetEntity: 'Address')]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
protected Address|null $address = null;
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
protected $address;
}
// admin mapping
namespace MyProject\Model;
#[Entity]
#[AssociationOverrides([
new AssociationOverride(
name: 'groups',
joinTable: new JoinTable(
name: 'users_admingroups',
),
joinColumns: [new JoinColumn(name: 'adminuser_id')],
inverseJoinColumns: [new JoinColumn(name: 'admingroup_id')]
),
new AssociationOverride(
name: 'address',
joinColumns: [new JoinColumn(name: 'adminaddress_id', referencedColumnName: 'id')]
)
])]
/**
* @Entity
* @AssociationOverrides({
* @AssociationOverride(name="groups",
* joinTable=@JoinTable(
* name="users_admingroups",
* joinColumns=@JoinColumn(name="adminuser_id"),
* inverseJoinColumns=@JoinColumn(name="admingroup_id")
* )
* ),
* @AssociationOverride(name="address",
* joinColumns=@JoinColumn(
* name="adminaddress_id", referencedColumnName="id"
* )
* )
* })
*/
class Admin extends User
{
}
@@ -410,7 +350,7 @@ Example:
<many-to-many field="groups" target-entity="Group" inversed-by="users">
<cascade>
<cascade-persist/>
<cascade-detach/>
<cascade-refresh/>
</cascade>
<join-table name="users_groups">
<join-columns>
@@ -449,11 +389,10 @@ Example:
Things to note:
- The "association override" specifies the overrides based on the property
name.
- This feature is available for all kind of associations (OneToOne, OneToMany, ManyToOne, ManyToMany).
- The association type *cannot* be changed.
- The override could redefine the ``joinTables`` or ``joinColumns`` depending on the association type.
- The "association override" specifies the overrides base on the property name.
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
- The association type *CANNOT* be changed.
- The override could redefine the joinTables or joinColumns depending on the association type.
- The override could redefine ``inversedBy`` to reference more than one extended entity.
- The override could redefine fetch to modify the fetch strategy of the extended entity.
@@ -465,46 +404,47 @@ Could be used by an entity that extends a mapped superclass to override a field
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
// user mapping
namespace MyProject\Model;
#[MappedSuperclass]
/**
* @MappedSuperclass
*/
class User
{
#[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)]
protected int|null $id = null;
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
protected $id;
#[Column(name: 'user_name', nullable: true, unique: false, length: 250)]
protected string $name;
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
protected $name;
// other fields mapping
}
// guest mapping
namespace MyProject\Model;
#[Entity]
#[AttributeOverrides([
new AttributeOverride(
name: 'id',
column: new Column(
name: 'guest_id',
type: 'integer',
length: 140
)
),
new AttributeOverride(
name: 'name',
column: new Column(
name: 'guest_name',
nullable: false,
unique: true,
length: 240
)
)
])]
/**
* @Entity
* @AttributeOverrides({
* @AttributeOverride(name="id",
* column=@Column(
* name = "guest_id",
* type = "integer",
* length = 140
* )
* ),
* @AttributeOverride(name="name",
* column=@Column(
* name = "guest_name",
* nullable = false,
* unique = true,
* length = 240
* )
* )
* })
*/
class Guest extends User
{
}
@@ -521,6 +461,7 @@ Could be used by an entity that extends a mapped superclass to override a field
<many-to-one field="address" target-entity="Address">
<cascade>
<cascade-persist/>
<cascade-refresh/>
</cascade>
<join-column name="address_id" referenced-column-name="id"/>
</many-to-one>
@@ -544,9 +485,9 @@ Could be used by an entity that extends a mapped superclass to override a field
Things to note:
- The "attribute override" specifies the overrides based on the property name.
- The column type *cannot* be changed. If the column type is not equal, you get a ``MappingException``.
- The override can redefine all the attributes except the type.
- The "attribute override" specifies the overrides base on the property name.
- The column type *CANNOT* be changed. If the column type is not equal you get a ``MappingException``
- The override can redefine all the columns except the type.
Query the Type
--------------

View File

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

View File

@@ -1,12 +1,12 @@
Limitations and Known Issues
============================
We try to make using Doctrine ORM a very pleasant experience.
We try to make using Doctrine2 a very pleasant experience.
Therefore we think it is very important to be honest about the
current limitations to our users. Much like every other piece of
software the ORM is not perfect and far from feature complete.
software Doctrine2 is not perfect and far from feature complete.
This section should give you an overview of current limitations of
Doctrine ORM as well as critical known issues that you should know
Doctrine 2 as well as critical known issues that you should know
about.
Current Limitations
@@ -39,7 +39,7 @@ possible either. See the following example:
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
@@ -98,17 +98,17 @@ to the same entity.
Behaviors
~~~~~~~~~
Doctrine ORM will **never** include a behavior system like Doctrine 1
Doctrine 2 will **never** include a behavior system like Doctrine 1
in the core library. We don't think behaviors add more value than
they cost pain and debugging hell. Please see the many different
blog posts we have written on this topics:
- `Doctrine2 "Behaviors" in a Nutshell <https://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html>`_
- `A re-usable Versionable behavior for Doctrine2 <https://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
- `Write your own ORM on top of Doctrine2 <https://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
- `Doctrine ORM Behavioral Extensions <https://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html>`_
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
Doctrine ORM has enough hooks and extension points so that **you** can
Doctrine 2 has enough hooks and extension points so that **you** can
add whatever you want on top of it. None of this will ever become
core functionality of Doctrine2 however, you will have to rely on
third party extensions for magical behaviors.
@@ -117,67 +117,13 @@ Nested Set
~~~~~~~~~~
NestedSet was offered as a behavior in Doctrine 1 and will not be
included in the core of Doctrine ORM. However there are already two
included in the core of Doctrine 2. However there are already two
extensions out there that offer support for Nested Set with
ORM:
Doctrine 2:
- `Doctrine2 Hierarchical-Structural Behavior <https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
- `Doctrine2 NestedSet <https://github.com/blt04/doctrine2-nestedset>`_
Using Traits in Entity Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The use of traits in entity or mapped superclasses, at least when they
include mapping configuration or mapped fields, is currently not
endorsed by the Doctrine project. The reasons for this are as follows.
Traits were added in PHP 5.4 more than 10 years ago, but at the same time
more than two years after the initial Doctrine 2 release and the time where
core components were designed.
In fact, this documentation mentions traits only in the context of
:doc:`overriding field association mappings in subclasses </tutorials/override-field-association-mappings-in-subclasses>`.
Coverage of traits in test cases is practically nonexistent.
Thus, you should at least be aware that when using traits in your entity and
mapped superclasses, you will be in uncharted terrain.
.. warning::
There be dragons.
From a more technical point of view, traits basically work at the language level
as if the code contained in them had been copied into the class where the trait
is used, and even private fields are accessible by the using class. In addition to
that, some precedence and conflict resolution rules apply.
When it comes to loading mapping configuration, the annotation and attribute drivers
rely on PHP reflection to inspect class properties including their docblocks.
As long as the results are consistent with what a solution *without* traits would
have produced, this is probably fine.
However, to mention known limitations, it is currently not possible to use "class"
level `annotations <https://github.com/doctrine/orm/pull/1517>`_ or
`attributes <https://github.com/doctrine/orm/issues/8868>`_ on traits, and attempts to
improve parser support for traits as `here <https://github.com/doctrine/annotations/pull/102>`_
or `there <https://github.com/doctrine/annotations/pull/63>`_ have been abandoned
due to complexity.
XML mapping configuration probably needs to completely re-configure or otherwise
copy-and-paste configuration for fields used from traits.
Mapping multiple private fields of the same name
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When two classes, say a mapped superclass and an entity inheriting from it,
both contain a ``private`` field of the same name, this will lead to a ``MappingException``.
Since the fields are ``private``, both are technically separate and can contain
different values at the same time. However, the ``ClassMetadata`` configuration used
internally by the ORM currently refers to fields by their name only, without taking the
class containing the field into consideration. This makes it impossible to keep separate
mapping configuration for both fields.
Known Issues
------------
@@ -193,10 +139,9 @@ Identifier Quoting and Legacy Databases
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For compatibility reasons between all the supported vendors and
edge case problems Doctrine ORM does **NOT** do automatic identifier
edge case problems Doctrine 2 does **NOT** do automatic identifier
quoting. This can lead to problems when trying to get
legacy-databases to work with Doctrine ORM.
legacy-databases to work with Doctrine 2.
- You can quote column-names as described in the
:doc:`Basic-Mapping <basic-mapping>` section.

View File

@@ -11,9 +11,8 @@ Core Metadata Drivers
Doctrine provides a few different ways for you to specify your
metadata:
- **XML files** (XmlDriver)
- **Attributes** (AttributeDriver)
- **Class DocBlock Annotations** (AnnotationDriver)
- **PHP Code in files or static functions** (PhpDriver)
Something important to note about the above drivers is they are all
@@ -36,8 +35,9 @@ an entity.
<?php
$em->getConfiguration()->setMetadataCacheImpl(new ApcuCache());
All the drivers are in the ``Doctrine\ORM\Mapping\Driver`` namespace:
If you want to use one of the included core metadata drivers you
just need to configure it. All the drivers are in the
``Doctrine\ORM\Mapping\Driver`` namespace:
.. code-block:: php
@@ -50,86 +50,71 @@ Implementing Metadata Drivers
In addition to the included metadata drivers you can very easily
implement your own. All you need to do is define a class which
implements the ``MappingDriver`` interface:
implements the ``Driver`` interface:
.. code-block:: php
<?php
namespace Doctrine\ORM\Mapping\Driver;
declare(strict_types=1);
use Doctrine\ORM\Mapping\ClassMetadata;
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
/**
* Contract for metadata drivers.
*/
interface MappingDriver
interface Driver
{
/**
* Loads the metadata for the specified class into the provided container.
*
* @psalm-param class-string<T> $className
* @psalm-param ClassMetadata<T> $metadata
*
* @return void
*
* @template T of object
* @param string $className
* @param ClassMetadata $metadata
*/
public function loadMetadataForClass(string $className, ClassMetadata $metadata);
function loadMetadataForClass($className, ClassMetadata $metadata);
/**
* Gets the names of all mapped classes known to this driver.
*
* @return array<int, string> The names of all mapped classes known to this driver.
* @psalm-return list<class-string>
* @return array The names of all mapped classes known to this driver.
*/
public function getAllClassNames();
function getAllClassNames();
/**
* Returns whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a MappedSuperclass.
* Whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a
* MappedSuperclass.
*
* @psalm-param class-string $className
*
* @return bool
* @param string $className
* @return boolean
*/
public function isTransient(string $className);
function isTransient($className);
}
If you want to write a metadata driver to parse information from
some file format we've made your life a little easier by providing
the ``FileDriver`` implementation for you to extend from:
the ``AbstractFileDriver`` implementation for you to extend from:
.. code-block:: php
<?php
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\Driver\FileDriver;
class MyMetadataDriver extends FileDriver
class MyMetadataDriver extends AbstractFileDriver
{
/**
* {@inheritDoc}
* {@inheritdoc}
*/
protected $_fileExtension = '.dcm.ext';
protected $fileExtension = '.dcm.ext';
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
$data = $this->_loadMappingFile($file);
$data = $this->loadMappingFile($file);
// populate ClassMetadata instance from $data
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
protected function _loadMappingFile($file)
protected function loadMappingFile($file)
{
// parse contents of $file and return php data structure
}
@@ -137,13 +122,13 @@ the ``FileDriver`` implementation for you to extend from:
.. note::
When using the ``FileDriver`` it requires that you only have one
entity defined per file and the file named after the class described
inside where namespace separators are replaced by periods. So if you
have an entity named ``Entities\User`` and you wanted to write a
mapping file for your driver above you would need to name the file
``Entities.User.dcm.ext`` for it to be recognized.
When using the ``AbstractFileDriver`` it requires that you
only have one entity defined per file and the file named after the
class described inside where namespace separators are replaced by
periods. So if you have an entity named ``Entities\User`` and you
wanted to write a mapping file for your driver above you would need
to name the file ``Entities.User.dcm.ext`` for it to be
recognized.
Now you can use your ``MyMetadataDriver`` implementation by setting
it with the ``setMetadataDriverImpl()`` method:
@@ -158,13 +143,18 @@ ClassMetadata
-------------
The last piece you need to know and understand about metadata in
Doctrine ORM is the API of the ``ClassMetadata`` classes. You need to
Doctrine 2 is the API of the ``ClassMetadata`` classes. You need to
be familiar with them in order to implement your own drivers but
more importantly to retrieve mapping information for a certain
entity when needed.
You have all the methods you need to manually specify the mapping
information instead of using some mapping file to populate it from.
The ``ClassMetadata`` class is responsible for only data storage
and is not meant for runtime use. It does not require that the
class actually exists yet so it is useful for describing some
entity before it exists and using that information to generate for
example the entities themselves.
You can read more about the API of the ``ClassMetadata`` classes in
the PHP Mapping chapter.
@@ -193,3 +183,4 @@ iterate over them:
foreach ($class->fieldMappings as $fieldMapping) {
echo $fieldMapping['fieldName'] . "\n";
}

View File

@@ -1,14 +1,12 @@
Implementing a NamingStrategy
==============================
.. versionadded:: 2.3
Using a naming strategy you can provide rules for generating database identifiers,
column or table names. This feature helps
column or table names when the column or table name is not given. This feature helps
reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``).
.. warning
The naming strategy is always overridden by entity mapping such as the `Table` attribute.
Configuring a naming strategy
-----------------------------
The default strategy used by Doctrine is quite minimal.
@@ -104,34 +102,33 @@ achieve such standards by implementing a naming strategy.
You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrategy``.
.. code-block:: php
<?php
class MyAppNamingStrategy implements NamingStrategy
{
public function classToTableName(string $className): string
public function classToTableName($className)
{
return 'MyApp_' . substr($className, strrpos($className, '\\') + 1);
}
public function propertyToColumnName(string $propertyName): string
public function propertyToColumnName($propertyName)
{
return $propertyName;
}
public function referenceColumnName(): string
public function referenceColumnName()
{
return 'id';
}
public function joinColumnName(string $propertyName, ?string $className = null): string
public function joinColumnName($propertyName, $className = null)
{
return $propertyName . '_' . $this->referenceColumnName();
}
public function joinTableName(string $sourceEntity, string $targetEntity, string $propertyName): string
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
{
return strtolower($this->classToTableName($sourceEntity) . '_' .
$this->classToTableName($targetEntity));
}
public function joinKeyColumnName(string $entityName, ?string $referencedColumnName): string
public function joinKeyColumnName($entityName, $referencedColumnName = null)
{
return strtolower($this->classToTableName($entityName) . '_' .
($referencedColumnName ?: $this->referenceColumnName()));

View File

@@ -80,7 +80,9 @@ with inheritance hierarchies.
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
The ``SELECT`` clause can be generated
.. versionadded:: 2.4
Starting with Doctrine ORM 2.4 you can generate the ``SELECT`` clause
from a ``ResultSetMappingBuilder``. You can either cast the builder
object to ``(string)`` and the DQL aliases are used as SQL table aliases
or use the ``generateSelectClause($tableAliases)`` method and pass
@@ -96,7 +98,6 @@ a mapping from DQL alias (key) to SQL alias (value)
));
$sql = "SELECT " . $selectClause . " FROM users t1 JOIN groups t2 ON t1.group_id = t2.id";
The ResultSetMapping
--------------------
@@ -104,7 +105,6 @@ Understanding the ``ResultSetMapping`` is the key to using a
``NativeQuery``. A Doctrine result can contain the following
components:
- Entity results. These represent root result elements.
- Joined entity results. These represent joined entities in
associations of root entity results.
@@ -130,7 +130,6 @@ components:
``ResultSetMapping`` that describes how the results should be
processed by the hydration routines.
We will now look at each of the result types that can appear in a
ResultSetMapping in detail.
@@ -250,40 +249,6 @@ The first parameter is the name of the column in the SQL result set
and the second parameter is the result alias under which the value
of the column will be placed in the transformed Doctrine result.
Special case: DTOs
...................
You can also use ``ResultSetMapping`` to map the results of a native SQL
query to a DTO (Data Transfer Object). This is done by adding scalar
results for each argument of the DTO's constructor, then filling the
``newObjectMappings`` property of the ``ResultSetMapping`` with
information about where to map each scalar result:
.. code-block:: php
<?php
$rsm = new ResultSetMapping();
$rsm->addScalarResult('name', 1, 'string');
$rsm->addScalarResult('email', 2, 'string');
$rsm->addScalarResult('city', 3, 'string');
$rsm->newObjectMappings['name'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0, // a result can contain many DTOs, this is the index of the DTO to map to
'argIndex' => 0, // each scalar result can be mapped to a different argument of the DTO constructor
];
$rsm->newObjectMappings['email'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0,
'argIndex' => 1,
];
$rsm->newObjectMappings['city'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0,
'argIndex' => 2,
];
Meta results
~~~~~~~~~~~~
@@ -355,7 +320,7 @@ entity.
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query = $this->em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
@@ -391,7 +356,7 @@ thus owns the foreign key.
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query = $this->em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
@@ -422,7 +387,7 @@ associations that are lazy.
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
$query = $this->_em->createNativeQuery($sql, $rsm);
$query = $this->em->createNativeQuery($sql, $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
@@ -455,7 +420,7 @@ to map the hierarchy (both use a discriminator column).
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
$rsm->setDiscriminatorColumn('u', 'discr');
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query = $this->em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
@@ -465,3 +430,354 @@ above would result in partial objects if any objects in the result
are actually a subtype of User. When using DQL, Doctrine
automatically includes the necessary joins for this mapping
strategy but with native SQL it is your responsibility.
Named Native Query
------------------
You can also map a native query using a named native query mapping.
To achieve that, you must describe the SQL resultset structure
using named native query (and sql resultset mappings if is a several resultset mappings).
Like named query, a named native query can be defined at class level or in an XML file.
A resultSetMapping parameter is defined in @NamedNativeQuery,
it represents the name of a defined @SqlResultSetMapping.
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "fetchMultipleJoinsEntityResults",
* resultSetMapping= "mappingMultipleJoinsEntityResults",
* query = "SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username"
* ),
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingMultipleJoinsEntityResults",
* entities= {
* @EntityResult(
* entityClass = "__CLASS__",
* fields = {
* @FieldResult(name = "id", column="u_id"),
* @FieldResult(name = "name", column="u_name"),
* @FieldResult(name = "status", column="u_status"),
* }
* ),
* @EntityResult(
* entityClass = "Address",
* fields = {
* @FieldResult(name = "id", column="a_id"),
* @FieldResult(name = "zip", column="a_zip"),
* @FieldResult(name = "country", column="a_country"),
* }
* )
* },
* columns = {
* @ColumnResult("numphones")
* }
* )
*})
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column(type="string", length=50, nullable=true) */
public $status;
/** @Column(type="string", length=255, unique=true) */
public $username;
/** @Column(type="string", length=255) */
public $name;
/** @OneToMany(targetEntity="Phonenumber") */
public $phonenumbers;
/** @OneToOne(targetEntity="Address") */
public $address;
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\User">
<named-native-queries>
<named-native-query name="fetchMultipleJoinsEntityResults" result-set-mapping="mappingMultipleJoinsEntityResults">
<query>SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username</query>
</named-native-query>
</named-native-queries>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingMultipleJoinsEntityResults">
<entity-result entity-class="__CLASS__">
<field-result name="id" column="u_id"/>
<field-result name="name" column="u_name"/>
<field-result name="status" column="u_status"/>
</entity-result>
<entity-result entity-class="Address">
<field-result name="id" column="a_id"/>
<field-result name="zip" column="a_zip"/>
<field-result name="country" column="a_country"/>
</entity-result>
<column-result name="numphones"/>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
Things to note:
- The resultset mapping declares the entities retrieved by this native query.
- Each field of the entity is bound to a SQL alias (or column name).
- All fields of the entity including the ones of subclasses
and the foreign key columns of related entities have to be present in the SQL query.
- Field definitions are optional provided that they map to the same
column name as the one declared on the class property.
- ``__CLASS__`` is an alias for the mapped class
In the above example,
the ``fetchJoinedAddress`` named query use the joinMapping result set mapping.
This mapping returns 2 entities, User and Address, each property is declared and associated to a column name,
actually the column name retrieved by the query.
Let's now see an implicit declaration of the property / column.
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "findAll",
* resultSetMapping = "mappingFindAll",
* query = "SELECT * FROM addresses"
* ),
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingFindAll",
* entities= {
* @EntityResult(
* entityClass = "Address"
* )
* }
* )
* })
*/
class Address
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column() */
public $country;
/** @Column() */
public $zip;
/** @Column()*/
public $city;
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Address">
<named-native-queries>
<named-native-query name="findAll" result-set-mapping="mappingFindAll">
<query>SELECT * FROM addresses</query>
</named-native-query>
</named-native-queries>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingFindAll">
<entity-result entity-class="Address"/>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
In this example, we only describe the entity member of the result set mapping.
The property / column mappings is done using the entity mapping values.
In this case the model property is bound to the model_txt column.
If the association to a related entity involve a composite primary key,
a @FieldResult element should be used for each foreign key column.
The @FieldResult name is composed of the property name for the relationship,
followed by a dot ("."), followed by the name or the field or property of the primary key.
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "fetchJoinedAddress",
* resultSetMapping= "mappingJoinedAddress",
* query = "SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?"
* ),
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingJoinedAddress",
* entities= {
* @EntityResult(
* entityClass = "__CLASS__",
* fields = {
* @FieldResult(name = "id"),
* @FieldResult(name = "name"),
* @FieldResult(name = "status"),
* @FieldResult(name = "address.id", column = "a_id"),
* @FieldResult(name = "address.zip", column = "a_zip"),
* @FieldResult(name = "address.city", column = "a_city"),
* @FieldResult(name = "address.country", column = "a_country"),
* }
* )
* }
* )
* })
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column(type="string", length=50, nullable=true) */
public $status;
/** @Column(type="string", length=255, unique=true) */
public $username;
/** @Column(type="string", length=255) */
public $name;
/** @OneToOne(targetEntity="Address") */
public $address;
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\User">
<named-native-queries>
<named-native-query name="fetchJoinedAddress" result-set-mapping="mappingJoinedAddress">
<query>SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?</query>
</named-native-query>
</named-native-queries>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingJoinedAddress">
<entity-result entity-class="__CLASS__">
<field-result name="id"/>
<field-result name="name"/>
<field-result name="status"/>
<field-result name="address.id" column="a_id"/>
<field-result name="address.zip" column="a_zip"/>
<field-result name="address.city" column="a_city"/>
<field-result name="address.country" column="a_country"/>
</entity-result>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
If you retrieve a single entity and if you use the default mapping,
you can use the resultClass attribute instead of resultSetMapping:
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "find-by-id",
* resultClass = "Address",
* query = "SELECT * FROM addresses"
* ),
* })
*/
class Address
{
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Address">
<named-native-queries>
<named-native-query name="find-by-id" result-class="Address">
<query>SELECT * FROM addresses WHERE id = ?</query>
</named-native-query>
</named-native-queries>
</entity>
</doctrine-mapping>
In some of your native queries, you'll have to return scalar values,
for example when building report queries.
You can map them in the @SqlResultsetMapping through @ColumnResult.
You actually can even mix, entities and scalar returns in the same native query (this is probably not that common though).
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "count",
* resultSetMapping= "mappingCount",
* query = "SELECT COUNT(*) AS count FROM addresses"
* )
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingCount",
* columns = {
* @ColumnResult(
* name = "count"
* )
* }
* )
* })
*/
class Address
{
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Address">
<named-native-query name="count" result-set-mapping="mappingCount">
<query>SELECT COUNT(*) AS count FROM addresses</query>
</named-native-query>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingCount">
<column-result name="count"/>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>

View File

@@ -0,0 +1,88 @@
Partial Objects
===============
A partial object is an object whose state is not fully initialized
after being reconstituted from the database and that is
disconnected from the rest of its data. The following section will
describe why partial objects are problematic and what the approach
of Doctrine2 to this problem is.
.. note::
The partial object problem in general does not apply to
methods or queries where you do not retrieve the query result as
objects. Examples are: ``Query#getArrayResult()``,
``Query#getScalarResult()``, ``Query#getSingleScalarResult()``,
etc.
.. warning::
Use of partial objects is tricky. Fields that are not retrieved
from the database will not be updated by the UnitOfWork even if they
get changed in your objects. You can only promote a partial object
to a fully-loaded object by calling ``EntityManager#refresh()``
or a DQL query with the refresh flag.
What is the problem?
--------------------
In short, partial objects are problematic because they are usually
objects with broken invariants. As such, code that uses these
partial objects tends to be very fragile and either needs to "know"
which fields or methods can be safely accessed or add checks around
every field access or method invocation. The same holds true for
the internals, i.e. the method implementations, of such objects.
You usually simply assume the state you need in the method is
available, after all you properly constructed this object before
you pushed it into the database, right? These blind assumptions can
quickly lead to null reference errors when working with such
partial objects.
It gets worse with the scenario of an optional association (0..1 to
1). When the associated field is NULL, you don't know whether this
object does not have an associated object or whether it was simply
not loaded when the owning object was loaded from the database.
These are reasons why many ORMs do not allow partial objects at all
and instead you always have to load an object with all its fields
(associations being proxied). One secure way to allow partial
objects is if the programming language/platform allows the ORM tool
to hook deeply into the object and instrument it in such a way that
individual fields (not only associations) can be loaded lazily on
first access. This is possible in Java, for example, through
bytecode instrumentation. In PHP though this is not possible, so
there is no way to have "secure" partial objects in an ORM with
transparent persistence.
Doctrine, by default, does not allow partial objects. That means,
any query that only selects partial object data and wants to
retrieve the result as objects (i.e. ``Query#getResult()``) will
raise an exception telling you that partial objects are dangerous.
If you want to force a query to return you partial objects,
possibly as a performance tweak, you can use the ``partial``
keyword as follows:
.. code-block:: php
<?php
$q = $em->createQuery("select partial u.{id,name} from MyApp\Domain\User u");
You can also get a partial reference instead of a proxy reference by
calling:
.. code-block:: php
<?php
$reference = $em->getPartialReference('MyApp\Domain\User', 1);
Partial references are objects with only the identifiers set as they
are passed to the second argument of the ``getPartialReference()`` method.
All other fields are null.
When should I force partial objects?
------------------------------------
Mainly for optimization purposes, but be careful of premature
optimization as partial objects lead to potentially more fragile
code.

View File

@@ -1,26 +1,96 @@
PHP Mapping
===========
Doctrine ORM also allows you to provide the ORM metadata in the form of plain
PHP code using the ``ClassMetadata`` API. You can write the code in inside of a
static function named ``loadMetadata($class)`` on the entity class itself.
Doctrine 2 also allows you to provide the ORM metadata in the form
of plain PHP code using the ``ClassMetadata`` API. You can write
the code in PHP files or inside of a static function named
``loadMetadata($class)`` on the entity class itself.
Static Function
---------------
PHP Files
---------
In addition to other drivers using configuration languages you can also
programatically specify your mapping information inside of a static function
defined on the entity class itself.
This is useful for cases where you want to keep your entity and mapping
information together but don't want to use attributes. For this you just
need to use the ``StaticPHPDriver``:
If you wish to write your mapping information inside PHP files that
are named after the entity and included to populate the metadata
for an entity you can do so by using the ``PHPDriver``:
.. code-block:: php
<?php
use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver;
$driver = new PHPDriver('/path/to/php/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Now imagine we had an entity named ``Entities\User`` and we wanted
to write a mapping file for it using the above configured
``PHPDriver`` instance:
.. code-block:: php
<?php
namespace Entities;
class User
{
private $id;
private $username;
}
To write the mapping information you just need to create a file
named ``Entities.User.php`` inside of the
``/path/to/php/mapping/files`` folder:
.. code-block:: php
<?php
// /path/to/php/mapping/files/Entities.User.php
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string',
'options' => array(
'fixed' => true,
'comment' => "User's login name"
)
));
$metadata->mapField(array(
'fieldName' => 'login_count',
'type' => 'integer',
'nullable' => false,
'options' => array(
'unsigned' => true,
'default' => 0
)
));
Now we can easily retrieve the populated ``ClassMetadata`` instance
where the ``PHPDriver`` includes the file and the
``ClassMetadataFactory`` caches it for later retrieval:
.. code-block:: php
<?php
$class = $em->getClassMetadata('Entities\User');
// or
$class = $em->getMetadataFactory()->getMetadataFor('Entities\User');
Static Function
---------------
In addition to the PHP files you can also specify your mapping
information inside of a static function defined on the entity class
itself. This is useful for cases where you want to keep your entity
and mapping information together but don't want to use annotations.
For this you just need to use the ``StaticPHPDriver``:
.. code-block:: php
<?php
$driver = new StaticPHPDriver('/path/to/entities');
$em->getConfiguration()->setMetadataDriverImpl($driver);
@@ -87,11 +157,13 @@ The API of the ClassMetadataBuilder has the following methods with a fluent inte
- ``setTable($name)``
- ``addIndex(array $columns, $indexName)``
- ``addUniqueConstraint(array $columns, $constraintName)``
- ``addNamedQuery($name, $dqlQuery)``
- ``setJoinedTableInheritance()``
- ``setSingleTableInheritance()``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255, $columnDefinition = null, $enumType = null, $options = [])``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
- ``addDiscriminatorMapClass($name, $class)``
- ``setChangeTrackingPolicyDeferredExplicit()``
- ``setChangeTrackingPolicyNotify()``
- ``addLifecycleEvent($methodName, $event)``
- ``addManyToOne($name, $targetEntity, $inversedBy = null)``
- ``addInverseOneToOne($name, $targetEntity, $mappedBy)``
@@ -109,16 +181,28 @@ It also has several methods that create builders (which are necessary for advanc
- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance
ClassMetadata API
-----------------
---------------------
The ``ClassMetadata`` class is the data object for storing the mapping
metadata for a single entity. It contains all the getters and setters
you need populate and retrieve information for an entity.
The ``ClassMetadata`` class is the base data object for storing
the mapping metadata for a single entity. It contains all the
getters and setters you need populate and retrieve information for
an entity.
Internal
~~~~~~~~
- ``getReflectionClass()``
- ``getReflectionProperties()``
- ``getReflectionProperty($name)``
- ``getSingleIdReflectionProperty()``
- ``getIdentifierValues($entity)``
- ``assignIdentifier($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``
General Setters
~~~~~~~~~~~~~~~
- ``setTableName($tableName)``
- ``setPrimaryTable(array $primaryTableDefinition)``
- ``setCustomRepositoryClass($repositoryClassName)``
@@ -131,7 +215,6 @@ General Setters
Inheritance Setters
~~~~~~~~~~~~~~~~~~~
- ``setInheritanceType($type)``
- ``setSubclasses(array $subclasses)``
- ``setParentClasses(array $classNames)``
@@ -141,24 +224,18 @@ Inheritance Setters
Field Mapping Setters
~~~~~~~~~~~~~~~~~~~~~
- ``mapField(array $mapping)``
- ``mapOneToOne(array $mapping)``
- ``mapOneToMany(array $mapping)``
- ``mapManyToOne(array $mapping)``
- ``mapManyToMany(array $mapping)``
- ``addProperty(Property $property)``
- ``addAssociation(AssociationMetadata $property)``
Lifecycle Callback Setters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``addLifecycleCallback($callback, $event)``
- ``setLifecycleCallbacks(array $callbacks)``
Versioning Setters
~~~~~~~~~~~~~~~~~~
- ``setVersionMapping(array &$mapping)``
- ``setVersioned($bool)``
- ``setVersionField()``
@@ -166,7 +243,6 @@ Versioning Setters
General Getters
~~~~~~~~~~~~~~~
- ``getTableName()``
- ``getSchemaName()``
- ``getTemporaryIdTableName()``
@@ -174,14 +250,8 @@ General Getters
Identifier Getters
~~~~~~~~~~~~~~~~~~
- ``getIdentifierColumnNames()``
- ``usesIdGenerator()``
- ``isIdentifier($fieldName)``
- ``isIdGeneratorIdentity()``
- ``isIdGeneratorSequence()``
- ``isIdGeneratorTable()``
- ``isIdentifierNatural()``
- ``getIdentifierFieldNames()``
- ``getSingleIdentifierFieldName()``
- ``getSingleIdentifierColumnName()``
@@ -189,33 +259,18 @@ Identifier Getters
Inheritance Getters
~~~~~~~~~~~~~~~~~~~
- ``isInheritanceTypeNone()``
- ``isInheritanceTypeJoined()``
- ``isInheritanceTypeSingleTable()``
- ``isInheritedField($fieldName)``
- ``isInheritedAssociation($fieldName)``
Change Tracking Getters
~~~~~~~~~~~~~~~~~~~~~~~
- ``isChangeTrackingDeferredExplicit()``
- ``isChangeTrackingDeferredImplicit()``
Field & Association Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``isUniqueField($fieldName)``
- ``isNullable($fieldName)``
- ``getColumnName($fieldName)``
- ``getFieldMapping($fieldName)``
- ``getAssociationMapping($fieldName)``
- ``getAssociationMappings()``
- ``getFieldName($columnName)``
- ``hasField($fieldName)``
- ``getColumnNames(array $fieldNames = null)``
- ``getTypeOfField($fieldName)``
- ``getTypeOfColumn($columnName)``
- ``hasAssociation($fieldName)``
@@ -225,22 +280,6 @@ Field & Association Getters
Lifecycle Callback Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``hasLifecycleCallbacks($lifecycleEvent)``
- ``getLifecycleCallbacks($event)``
Runtime reflection methods
~~~~~~~~~~~~~~~~~~~~~~~~~~
These are methods related to runtime reflection for working with the
entities themselves.
- ``getReflectionClass()``
- ``getReflectionProperties()``
- ``getReflectionProperty($name)``
- ``getSingleIdReflectionProperty()``
- ``getIdentifierValues($entity)``
- ``setIdentifierValues($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``

View File

@@ -9,12 +9,6 @@ programmatically build queries, and also provides a fluent API.
This means that you can change between one methodology to the other
as you want, or just pick a preferred one.
.. note::
The ``QueryBuilder`` is not an abstraction of DQL, but merely a tool to dynamically build it.
You should still use plain DQL when you can, as it is simpler and more readable.
More about this in the :doc:`FAQ <faq>`.
Constructing a new QueryBuilder object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -44,7 +38,6 @@ good example is to inspect what type of object the
There're currently 3 possible return values for ``getType()``:
- ``QueryBuilder::SELECT``, which returns value 0
- ``QueryBuilder::DELETE``, returning value 1
- ``QueryBuilder::UPDATE``, which returns value 2
@@ -72,7 +65,6 @@ performance. Any changes that may affect the generated DQL actually
modifies the state of ``QueryBuilder`` to a stage we call
STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
- ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been
altered since last retrieval or nothing were added since its
instantiation
@@ -82,11 +74,10 @@ STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
Working with QueryBuilder
~~~~~~~~~~~~~~~~~~~~~~~~~
High level API methods
^^^^^^^^^^^^^^^^^^^^^^
The most straightforward way to build a dynamic query with the ``QueryBuilder`` is by taking
To simplify even more the way you build a query in Doctrine, you can take
advantage of Helper methods. For all base code, there is a set of
useful methods to simplify a programmer's life. To illustrate how
to work with them, here is the same example 6 re-written using
@@ -103,9 +94,10 @@ to work with them, here is the same example 6 re-written using
->orderBy('u.name', 'ASC');
``QueryBuilder`` helper methods are considered the standard way to
use the ``QueryBuilder``. The ``$qb->expr()->*`` methods can help you
build conditional expressions dynamically. Here is a converted example 8 to
suggested way to build queries with dynamic conditions:
build DQL queries. Although it is supported, using string-based
queries should be avoided. You are greatly encouraged to use
``$qb->expr()->*`` methods. Here is a converted example 8 to
suggested standard way to build queries:
.. code-block:: php
@@ -253,22 +245,7 @@ Calling ``setParameter()`` automatically infers which type you are setting as
value. This works for integers, arrays of strings/integers, DateTime instances
and for managed entities. If you want to set a type explicitly you can call
the third argument to ``setParameter()`` explicitly. It accepts either a DBAL
``Doctrine\DBAL\ParameterType::*`` or a DBAL Type name for conversion.
.. note::
Even though passing DateTime instance is allowed, it impacts performance
as by default there is an attempt to load metadata for object, and if it's not found,
type is inferred from the original value.
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Types;
// prevents attempt to load metadata for date time class, improving performance
$qb->setParameter('date', new \DateTimeImmutable(), Types::DATETIME_IMMUTABLE)
Doctrine\DBAL\ParameterType::* or a DBAL Type name for conversion.
If you've got several parameters to bind to your query, you can
also use setParameters() instead of setParameter() with the
@@ -277,17 +254,10 @@ following syntax:
.. code-block:: php
<?php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Query\Parameter;
// $qb instanceof QueryBuilder
// Query here...
$qb->setParameters(new ArrayCollection([
new Parameter('1', 'value for ?1'),
new Parameter('2', 'value for ?2')
]));
$qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2'));
Getting already bound parameters is easy - simply use the above
mentioned syntax with "getParameter()" or "getParameters()":
@@ -361,7 +331,6 @@ a querybuilder instance into a Query object:
// Execute Query
$result = $query->getResult();
$iterableResult = $query->toIterable();
$single = $query->getSingleResult();
$array = $query->getArrayResult();
$scalar = $query->getScalarResult();
@@ -407,7 +376,6 @@ complete list of supported helper methods available:
// Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->...
public function orX($x = null); // Returns Expr\OrX instance
/** Comparison objects **/
// Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1
@@ -434,13 +402,6 @@ complete list of supported helper methods available:
// Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
public function isNotNull($x); // Returns string
// Example - $qb->expr()->isMemberOf('?1', 'u.groups') => ?1 MEMBER OF u.groups
public function isMemberOf($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->isInstanceOf('u', Employee::class) => u INSTANCE OF Employee
public function isInstanceOf($x, $y); // Returns Expr\Comparison instance
/** Arithmetic objects **/
// Example - $qb->expr()->prod('u.id', '2') => u.id * 2
@@ -455,7 +416,6 @@ complete list of supported helper methods available:
// Example - $qb->expr()->quot('u.id', '2') => u.id / 2
public function quot($x, $y); // Returns Expr\Math instance
/** Pseudo-function objects **/
// Example - $qb->expr()->exists($qb2->getDql())
@@ -490,7 +450,6 @@ complete list of supported helper methods available:
// Example - $qb->expr()->between('u.id', '1', '10')
public function between($val, $x, $y); // Returns Expr\Func
/** Function objects **/
// Example - $qb->expr()->trim('u.firstname')
@@ -526,9 +485,6 @@ complete list of supported helper methods available:
// Example - $qb->expr()->sqrt('u.currentBalance')
public function sqrt($x); // Returns Expr\Func
// Example - $qb->expr()->mod('u.currentBalance', '10')
public function mod($x); // Returns Expr\Func
// Example - $qb->expr()->count('u.firstname')
public function count($x); // Returns Expr\Func
@@ -568,7 +524,6 @@ one: ``add()``. This method is responsible of building every piece
of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
``$append`` (default=false)
- ``$dqlPartName``: Where the ``$dqlPart`` should be placed.
Possible values: select, from, where, groupBy, having, orderBy
- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts
@@ -578,6 +533,8 @@ of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
not (no effect on the ``where`` and ``having`` DQL query parts,
which always override all previously defined items)
-
.. code-block:: php
<?php
@@ -611,3 +568,4 @@ same query of example 6 written using
->add('from', new Expr\From('User', 'u'))
->add('where', new Expr\Comparison('u.id', '=', '?1'))
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));

View File

@@ -18,7 +18,6 @@ There are some flavors of caching available, but is better to cache read-only da
Be aware that caches are not aware of changes made to the persistent store by another application.
They can, however, be configured to regularly expire cached data.
Caching Regions
---------------
@@ -31,31 +30,30 @@ Each cache region resides in a specific cache namespace and has its own lifetime
Notice that when caching collection and queries only identifiers are stored.
The entity values will be stored in its own region
Something like below for an entity region:
Something like below for an entity region :
.. code-block:: php
<?php
[
'region_name:entity_1_hash' => ['id' => 1, 'name' => 'FooBar', 'associationName' => null],
'region_name:entity_2_hash' => ['id' => 2, 'name' => 'Foo', 'associationName' => ['id' => 11]],
'region_name:entity_3_hash' => ['id' => 3, 'name' => 'Bar', 'associationName' => ['id' => 22]]
'region_name:entity_1_hash' => ['id'=> 1, 'name' => 'FooBar', 'associationName'=>null],
'region_name:entity_2_hash' => ['id'=> 2, 'name' => 'Foo', 'associationName'=>['id'=>11]],
'region_name:entity_3_hash' => ['id'=> 3, 'name' => 'Bar', 'associationName'=>['id'=>22]]
];
If the entity holds a collection that also needs to be cached.
An collection region could look something like:
An collection region could look something like :
.. code-block:: php
<?php
[
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId' => 1, 'list' => [1, 2, 3]],
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId' => 2, 'list' => [2, 3]],
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId' => 3, 'list' => [2, 4]]
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId'=> 1, 'list' => [1, 2, 3]],
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId'=> 2, 'list' => [2, 3]],
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId'=> 3, 'list' => [2, 4]]
];
A query region might be something like:
A query region might be something like :
.. code-block:: php
@@ -66,32 +64,35 @@ A query region might be something like:
'region_name:query_3_hash' => ['list' => [2, 4]]
];
.. note::
The following data structures represents now the cache will looks like, this is not actual cached data.
.. _reference-second-level-cache-regions:
Cache Regions
-------------
``Doctrine\ORM\Cache\Region\DefaultRegion`` is the default implementation.
``Doctrine\ORM\Cache\Region\DefaultRegion`` It's the default implementation.
A simplest cache region compatible with all doctrine-cache drivers but does not support locking.
``Doctrine\ORM\Cache\Region`` and ``Doctrine\ORM\Cache\ConcurrentRegion``
define contracts that should be implemented by a cache provider.
Defines contracts that should be implemented by a cache provider.
It allows you to provide your own cache implementation that might take advantage of specific cache driver.
If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegion``; ``CacheRegion`` otherwise.
Cache region
~~~~~~~~~~~~
``Doctrine\ORM\Cache\Region`` defines a contract for accessing a particular
cache region.
Defines a contract for accessing a particular region.
``Doctrine\ORM\Cache\Region``
Defines a contract for accessing a particular cache region.
`See API Doc <https://www.doctrine-project.org/api/orm/latest/Doctrine/ORM/Cache/Region.html>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
@@ -101,7 +102,11 @@ By default, Doctrine provides a very simple implementation based on file locks `
If you want to use an ``READ_WRITE`` cache, you should consider providing your own cache region.
``Doctrine\ORM\Cache\ConcurrentRegion`` defines a contract for concurrently managed data region.
``Doctrine\ORM\Cache\ConcurrentRegion``
Defines contract for concurrently managed data region.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
Timestamp region
~~~~~~~~~~~~~~~~
@@ -110,6 +115,8 @@ Timestamp region
Tracks the timestamps of the most recent updates to particular entity.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
.. _reference-second-level-cache-mode:
Caching mode
@@ -127,7 +134,6 @@ Caching mode
* Read Write Cache doesnt employ any locks but can do reads, inserts, updates and deletes.
* Good if the application needs to update data rarely.
* ``READ_WRITE``
* Read Write cache employs locks before update/delete.
@@ -135,44 +141,42 @@ Caching mode
* Slowest strategy.
* To use it a the cache region implementation must support locking.
Built-in cached persisters
~~~~~~~~~~~~~~~~~~~~~~~~~~
Cached persisters are responsible to access cache regions.
+-----------------------+------------------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+==========================================================================================+
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
+-----------------------+-------------------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+===========================================================================================+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadOnlyCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\NonStrictReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadOnlyCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\NonStrictReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
Configuration
-------------
Doctrine allows you to specify configurations and some points of extension for the second-level-cache
Enable Second Level Cache
~~~~~~~~~~~~~~~~~~~~~~~~~
To enable the second-level-cache, you should provide a cache factory.
``Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $cacheConfig */
/** @var \Psr\Cache\CacheItemPoolInterface $cache */
/** @var \Doctrine\Common\Cache\Cache $cache */
/** @var \Doctrine\ORM\Configuration $config */
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($cacheConfig, $cache);
@@ -184,24 +188,20 @@ To enable the second-level-cache, you should provide a cache factory.
$config->getSecondLevelCacheConfiguration()
->setCacheFactory($factory);
Cache Factory
~~~~~~~~~~~~~
Cache Factory is the main point of extension.
It allows you to provide a specific implementation of the following components:
It allows you to provide a specific implementation of the following components :
``QueryCache``
stores and retrieves query cache results.
``CachedEntityPersister``
stores and retrieves entity results.
``CachedCollectionPersister``
stores and retrieves query results.
``EntityHydrator``
transforms entities into a cache entries and cache entries into entities
``CollectionHydrator``
transforms collections into cache entries and cache entries into collections
* ``QueryCache`` Store and retrieve query cache results.
* ``CachedEntityPersister`` Store and retrieve entity results.
* ``CachedCollectionPersister`` Store and retrieve query results.
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/DefaultCacheFactory.html>`_.
Region Lifetime
~~~~~~~~~~~~~~~
@@ -218,15 +218,14 @@ To specify a default lifetime for all regions or specify a different lifetime fo
$regionConfig = $cacheConfig->getRegionsConfiguration();
// Cache Region lifetime
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region (in seconds)
$regionConfig->setDefaultLifetime(7200); // Default time to live (in seconds)
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region; In seconds
$regionConfig->setDefaultLifetime(7200); // Default time to live; In seconds
Cache Log
~~~~~~~~~
By providing a cache logger you should be able to get information about all cache operations such as hits, misses and puts.
``Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
``\Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
.. code-block:: php
@@ -239,7 +238,6 @@ By providing a cache logger you should be able to get information about all cach
$config->getSecondLevelCacheConfiguration()
->setCacheLogger($logger);
// Collect cache statistics
// Get the number of entries successfully retrieved from a specific region.
@@ -260,37 +258,41 @@ By providing a cache logger you should be able to get information about all cach
// Get the total number of cached entries *not* found in all regions.
$logger->getMissCount();
If you want to get more information you should implement
``Doctrine\ORM\Cache\Logging\CacheLogger`` and collect
all the information you want.
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
and collect all information you want.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Logging/CacheLogger.html>`_.
Entity cache definition
-----------------------
* Entity cache configuration allows you to define the caching strategy and region for an entity.
* ``usage`` specifies the caching strategy: ``READ_ONLY``,
``NONSTRICT_READ_WRITE``, ``READ_WRITE``.
See :ref:`reference-second-level-cache-mode`.
* ``region`` is an optional value that specifies the name of the second
level cache region.
* ``usage`` Specifies the caching strategy: ``READ_ONLY``, ``NONSTRICT_READ_WRITE``, ``READ_WRITE``. see :ref:`reference-second-level-cache-mode`
* ``region`` Optional value that specifies the name of the second level cache region.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
#[Cache(usage: 'READ_ONLY', region: 'my_entity_region')]
/**
* @Entity
* @Cache(usage="READ_ONLY", region="my_entity_region")
*/
class Country
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
#[Column(unique: true)]
protected string $name;
/**
* @Column(unique=true)
*/
protected $name;
// other properties and methods
}
@@ -298,10 +300,7 @@ level cache region.
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Country">
<cache usage="READ_ONLY" region="my_entity_region" />
<id name="id" type="integer" column="id">
@@ -316,33 +315,41 @@ Association cache definition
The most common use case is to cache entities. But we can also cache relationships.
It caches the primary keys of association and cache each element will be cached into its region.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
/**
* @Entity
* @Cache("NONSTRICT_READ_WRITE")
*/
class State
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
#[Column(unique: true)]
protected string $name;
/**
* @Column(unique=true)
*/
protected $name;
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[ManyToOne(targetEntity: Country::class)]
#[JoinColumn(name: 'country_id', referencedColumnName: 'id')]
protected Country|null $country = null;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country_id", referencedColumnName="id")
*/
protected $country;
/** @var Collection<int, City> */
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[OneToMany(targetEntity: City::class, mappedBy: 'state')]
protected Collection $cities;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
*/
protected $cities;
// other properties and methods
}
@@ -350,10 +357,7 @@ It caches the primary keys of association and cache each element will be cached
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="State">
<cache usage="NONSTRICT_READ_WRITE" />
@@ -378,9 +382,7 @@ It caches the primary keys of association and cache each element will be cached
</entity>
</doctrine-mapping>
.. note::
for this to work, the target entity must also be marked as cacheable.
> Note: for this to work, the target entity must also be marked as cacheable.
Cache usage
~~~~~~~~~~~
@@ -397,8 +399,8 @@ Basic entity cache
$country1 = $em->find('Country', 1); // Retrieve item from cache
$country1->setName('New Name');
$country1->setName("New Name");
$em->flush(); // Hit database to update the row and update cache
$em->clear(); // Clear entity manager
@@ -406,7 +408,6 @@ Basic entity cache
$country2 = $em->find('Country', 1); // Retrieve item from cache
// Notice that $country1 and $country2 are not the same instance.
Association cache
.. code-block:: php
@@ -423,7 +424,7 @@ Association cache
$state = $em->find('State', 1);
// Hit database to update the row and update cache entry
$state->setName('New Name');
$state->setName("New Name");
$em->persist($state);
$em->flush();
@@ -481,7 +482,7 @@ The query cache stores the results of the query but as identifiers, entity value
->setCacheable(true)
->getResult();
$em->clear();
$em->clear()
// Check if query result is valid and load entities from cache
$result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
@@ -504,53 +505,49 @@ The Cache Mode controls how a particular query interacts with the second-level c
/** @var \Doctrine\ORM\EntityManager $em */
// Will refresh the query cache and all entities the cache as it reads from the database.
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheMode(\Doctrine\ORM\Cache::MODE_GET)
->setCacheMode(Cache::MODE_GET)
->setCacheable(true)
->getResult();
.. note::
The default query cache mode is ```Cache::MODE_NORMAL```
The the default query cache mode is ```Cache::MODE_NORMAL```
DELETE / UPDATE queries
~~~~~~~~~~~~~~~~~~~~~~~
DQL UPDATE / DELETE statements are ported directly into a database and bypass
the second-level cache.
DQL UPDATE / DELETE statements are ported directly into a database and bypass the second-level cache,
Entities that are already cached will NOT be invalidated.
However the cached data could be evicted using the cache API or an special query hint.
Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``
.. code-block:: php
<?php
// Execute and invalidate
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->setHint(\Doctrine\ORM\Query::HINT_CACHE_EVICT, true)
$this->em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->setHint(Query::HINT_CACHE_EVICT, true)
->execute();
Execute the ``UPDATE`` and invalidate ``all cache entries`` using the cache API
.. code-block:: php
<?php
// Execute
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
$this->em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->execute();
// Invoke Cache API
$em->getCache()->evictEntityRegion('Entity\Country');
Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache API
.. code-block:: php
<?php
// Execute
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
$this->em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->execute();
// Invoke Cache API
$em->getCache()->evictEntity('Entity\Country', 1);
@@ -559,7 +556,7 @@ Using the repository query cache
--------------------------------
As well as ``Query Cache`` all persister queries store only identifier values for an individual query.
All persisters use a single timestamp cache region to keep track of the last update for each persister,
All persister use a single timestamps cache region keeps track of the last update for each persister,
When a query is loaded from cache, the timestamp region is checked for the last update for that persister.
Using the last update timestamps as part of the query key invalidate the cache key when an update occurs.
@@ -578,7 +575,7 @@ Using the last update timestamps as part of the query key invalidate the cache k
$em->clear();
// Reload from database.
// At this point the query cache key is no longer valid, the select goes straight to the database
// At this point the query cache key if not logger valid, the select goes straight
$entities = $em->getRepository('Entity\Country')->findAll();
Cache API
@@ -614,18 +611,23 @@ For performance reasons the cache API does not extract from composite primary ke
.. code-block:: php
<?php
#[Entity]
/**
* @Entity
*/
class Reference
{
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'source_id', referencedColumnName: 'article_id')]
private Article|null $source = null;
/**
* @Id
* @ManyToOne(targetEntity="Article", inversedBy="references")
* @JoinColumn(name="source_id", referencedColumnName="article_id")
*/
private $source;
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'target_id', referencedColumnName: 'article_id')]
/**
* @Id
* @ManyToOne(targetEntity="Article")
* @JoinColumn(name="target_id", referencedColumnName="article_id")
*/
private $target;
}
@@ -638,11 +640,11 @@ For performance reasons the cache API does not extract from composite primary ke
$article = $em->find('Article', $article);
// Supported
$id = ['source' => 1, 'target' => 2];
$id = array('source' => 1, 'target' => 2);
$reference = $em->find('Reference', $id);
// NOT Supported
$id = ['source' => new Article(1), 'target' => new Article(2)];
$id = array('source' => new Article(1), 'target' => new Article(2));
$reference = $em->find('Reference', $id);
Distributed environments
@@ -655,10 +657,8 @@ should be used in conjunction with distributed caching system such as memcached,
Caches should be used with care when using a load-balancer if you don't share the cache.
While using APC or any file based cache update occurred in a specific machine would not reflect to the cache in other machines.
Paginator
~~~~~~~~~
Count queries generated by ``Doctrine\ORM\Tools\Pagination\Paginator`` are not cached by second-level cache.
Although entities and query result are cached, count queries will hit the
database every time.
Although entities and query result are cached count queries will hit the database every time.

View File

@@ -10,10 +10,11 @@ we cannot protect you from SQL injection.
Please also read the documentation chapter on Security in Doctrine DBAL. This
page only handles Security issues in the ORM.
- `DBAL Security Page <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
- `DBAL Security Page <http://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
If you find a Security bug in Doctrine, please follow our
`Security reporting guidelines <https://www.doctrine-project.org/policies/security.html#reporting>`_.
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
developers and you only.
User input and Doctrine ORM
---------------------------
@@ -80,7 +81,6 @@ this is technically impossible. The correct way is:
$query = $entityManager->createQuery($dql);
$query->setParameter(1, $_GET['status']);
Preventing Mass Assignment Vulnerabilities
------------------------------------------
@@ -97,20 +97,19 @@ entity might look like this:
<?php
#[Entity]
/**
* @Entity
*/
class InsecureEntity
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column */
private $email;
/** @Column(type="boolean") */
private $isAdmin;
#[Column]
private string $email;
#[Column]
private bool $isAdmin;
/** @param array<string, mixed> $userInput */
public function fromArray(array $userInput): void
public function fromArray(array $userInput)
{
foreach ($userInput as $key => $value) {
$this->$key = $value;
@@ -118,7 +117,7 @@ entity might look like this:
}
}
Now the possiblity of mass-assignment exists on this entity and can
Now the possibility of mass-assignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:

View File

@@ -5,48 +5,83 @@ Doctrine Console
----------------
The Doctrine Console is a Command Line Interface tool for simplifying common
administration tasks during the development of a project that uses ORM.
administration tasks during the development of a project that uses Doctrine 2.
For the following examples, we will set up the CLI as ``bin/doctrine``.
Take a look at the :doc:`Installation and Configuration <configuration>`
chapter for more information how to setup the console command.
Setting Up the Console
~~~~~~~~~~~~~~~~~~~~~~
Display Help Information
~~~~~~~~~~~~~~~~~~~~~~~~
Type ``php vendor/bin/doctrine`` on the command line and you should see an
overview of the available commands or use the --help flag to get
information on the available commands. If you want to know more
about the use of generate entities for example, you can call:
.. code-block:: php
$> php vendor/bin/doctrine orm:generate-entities --help
Configuration
~~~~~~~~~~~~~
Whenever the ``doctrine`` command line tool is invoked, it can
access all Commands that were registered by a developer. There is no
access all Commands that were registered by developer. There is no
auto-detection mechanism at work. The Doctrine binary
already registers all the commands that currently ship with
Doctrine DBAL and ORM. If you want to use additional commands you
have to register them yourself.
All the commands of the Doctrine Console require access to the
``EntityManager``. You have to inject it into the console application.
All the commands of the Doctrine Console require access to the ``EntityManager``
or ``DBAL`` Connection. You have to inject them into the console application
using so called Helper-Sets. This requires either the ``db``
or the ``em`` helpers to be defined in order to work correctly.
Here is an example of a the project-specific ``bin/doctrine`` binary.
Whenever you invoke the Doctrine binary the current folder is searched for a
``cli-config.php`` file. This file contains the project specific configuration:
.. code-block:: php
#!/usr/bin/env php
<?php
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn)
));
$cli->setHelperSet($helperSet);
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
When dealing with the ORM package, the EntityManagerHelper is
required:
// replace with path to your own project bootstrap file
require_once 'bootstrap.php';
.. code-block:: php
// replace with mechanism to retrieve EntityManager in your app
$entityManager = GetEntityManager();
<?php
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));
$cli->setHelperSet($helperSet);
$commands = [
// If you want to add your own custom console commands,
// you can do so here.
];
The HelperSet instance has to be generated in a separate file (i.e.
``cli-config.php``) that contains typical Doctrine bootstrap code
and predefines the needed HelperSet attributes mentioned above. A
sample ``cli-config.php`` file looks as follows:
ConsoleRunner::run(
new SingleManagerProvider($entityManager),
$commands
);
.. code-block:: php
<?php
// cli-config.php
require_once 'my_bootstrap.php';
// Any way to access the EntityManager from your application
$em = GetMyEntityManager();
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));
It is important to define a correct HelperSet that Doctrine binary
script will ultimately use. The Doctrine Binary will automatically
find the first instance of HelperSet in the global variable
namespace and use this.
.. note::
@@ -54,24 +89,11 @@ Here is an example of a the project-specific ``bin/doctrine`` binary.
and use their facilities to access the Doctrine EntityManager and
Connection Resources.
Display Help Information
~~~~~~~~~~~~~~~~~~~~~~~~
Type ``php bin/doctrine`` on the command line and you should see an
overview of the available commands or use the ``--help`` flag to get
information on the available commands. If you want to know more
about the use of generate entities for example, you can call:
::
$> php bin/doctrine orm:generate-entities --help
Command Overview
~~~~~~~~~~~~~~~~
The following Commands are currently available:
- ``help`` Displays help for a command (?)
- ``list`` Lists commands
- ``dbal:import`` Import SQL file(s) directly to Database.
@@ -83,6 +105,10 @@ The following Commands are currently available:
cache drivers.
- ``orm:clear-cache:result`` Clear result cache of the various
cache drivers.
- ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a
Doctrine 2.X schema.
- ``orm:ensure-production-settings`` Verify that Doctrine is
properly configured for a production environment.
- ``orm:generate-proxies`` Generates proxy classes for entity
classes.
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
@@ -97,9 +123,9 @@ The following Commands are currently available:
update the database schema of EntityManager Storage Connection or
generate the SQL output.
The following alias is defined:
For these commands are also available aliases:
- ``orm:convert:d1-schema`` is alias for ``orm:convert-d1-schema``.
- ``orm:generate:proxies`` is alias for ``orm:generate-proxies``.
.. note::
@@ -126,7 +152,6 @@ Database Schema Generation
they are not related to the current project that is using Doctrine.
Please be careful!
To generate your database schema from your Doctrine mapping files
you can use the ``SchemaTool`` class or the ``schema-tool`` Console
Command.
@@ -181,40 +206,52 @@ To create the schema use the ``create`` command:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:create
$ php doctrine orm:schema-tool:create
To drop the schema use the ``drop`` command:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:drop
$ php doctrine orm:schema-tool:drop
If you want to drop and then recreate the schema then use both
options:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:drop
$ php bin/doctrine orm:schema-tool:create
$ php doctrine orm:schema-tool:drop
$ php doctrine orm:schema-tool:create
As you would think, if you want to update your schema use the
``update`` command:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:update
$ php doctrine orm:schema-tool:update
All of the above commands also accept a ``--dump-sql`` option that
will output the SQL for the ran operation.
.. code-block:: php
$ php bin/doctrine orm:schema-tool:create --dump-sql
$ php doctrine orm:schema-tool:create --dump-sql
Before using the orm:schema-tool commands, remember to configure
your cli-config.php properly.
.. note::
When using the Annotation Mapping Driver you have to either setup
your autoloader in the cli-config.php correctly to find all the
entities, or you can use the second argument of the
``EntityManagerHelper`` to specify all the paths of your entities
(or mapping files), i.e.
``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);``
Runtime vs Development Mapping Validation
-----------------------------------------
For performance reasons Doctrine ORM has to skip some of the
For performance reasons Doctrine 2 has to skip some of the
necessary validation of metadata mappings. You have to execute
this validation in your development workflow to verify the
associations are correctly defined.
@@ -225,11 +262,6 @@ You can either use the Doctrine Command Line Tool:
doctrine orm:validate-schema
If the validation fails, you can change the verbosity level to
check the detected errors:
doctrine orm:validate-schema -v
Or you can trigger the validation manually:
.. code-block:: php
@@ -261,7 +293,6 @@ number of elements with error messages.
prefix backslash. PHP does this with ``get_class()`` or Reflection
methods for backwards compatibility reasons.
Adding own commands
-------------------
@@ -307,7 +338,6 @@ defined ones) is possible through the command:
new \MyProject\Tools\Console\Commands\OneMoreCommand(),
));
Re-use console application
--------------------------
@@ -324,3 +354,4 @@ HelperSet, like it is described in the configuration section.
// Runs console application
$cli->run();

View File

@@ -16,13 +16,13 @@ transaction. Without any explicit transaction demarcation from your
side, this quickly results in poor performance because transactions
are not cheap.
For the most part, Doctrine ORM already takes care of proper
For the most part, Doctrine 2 already takes care of proper
transaction demarcation for you: All the write operations
(INSERT/UPDATE/DELETE) are queued until ``EntityManager#flush()``
is invoked which wraps all of these changes in a single
transaction.
However, Doctrine ORM also allows (and encourages) you to take over
However, Doctrine 2 also allows (and encourages) you to take over
and control transaction demarcation yourself.
These are two ways to deal with transactions when using the
@@ -88,7 +88,7 @@ requirement.
A more convenient alternative for explicit transaction demarcation is the use
of provided control abstractions in the form of
``Connection#transactional($func)`` and ``EntityManager#wrapInTransaction($func)``.
``Connection#transactional($func)`` and ``EntityManager#transactional($func)``.
When used, these control abstractions ensure that you never forget to rollback
the transaction, in addition to the obvious code reduction. An example that is
functionally equivalent to the previously shown code looks as follows:
@@ -96,17 +96,8 @@ functionally equivalent to the previously shown code looks as follows:
.. code-block:: php
<?php
// transactional with Connection instance
// $conn instanceof Connection
$conn->transactional(function($conn) {
// ... do some work
$user = new User;
$user->setName('George');
});
// transactional with EntityManager instance
// $em instanceof EntityManager
$em->wrapInTransaction(function($em) {
$em->transactional(function($em) {
// ... do some work
$user = new User;
$user->setName('George');
@@ -116,8 +107,7 @@ functionally equivalent to the previously shown code looks as follows:
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction
commit and in case of an exception the ``EntityManager`` gets closed
in addition to the transaction rollback.
commit.
.. _transactions-and-concurrency_exception-handling:
@@ -156,7 +146,7 @@ occurred you should do that with a new ``EntityManager``.
Locking Support
---------------
Doctrine ORM offers support for Pessimistic- and Optimistic-locking
Doctrine 2 offers support for Pessimistic- and Optimistic-locking
strategies natively. This allows to take very fine-grained control
over what kind of locking is required for your Entities in your
application.
@@ -191,14 +181,14 @@ example we'll use an integer.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
class User
{
// ...
#[Version, Column(type: 'integer')]
private int $version;
/** @Version @Column(type="integer") */
private $version;
// ...
}
@@ -215,14 +205,14 @@ timestamp or datetime):
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
class User
{
// ...
#[Version, Column(type: 'datetime')]
private DateTime $version;
/** @Version @Column(type="datetime") */
private $version;
// ...
}
@@ -351,7 +341,7 @@ And the change headline action (POST Request):
Pessimistic Locking
~~~~~~~~~~~~~~~~~~~
Doctrine ORM supports Pessimistic Locking at the database level. No
Doctrine 2 supports Pessimistic Locking at the database level. No
attempt is being made to implement pessimistic locking inside
Doctrine, rather vendor-specific and ANSI-SQL commands are used to
acquire row-level locks. Every Entity can be part of a pessimistic
@@ -360,12 +350,11 @@ lock, there is no special metadata required to use this feature.
However for Pessimistic Locking to work you have to disable the
Auto-Commit Mode of your Database and start a transaction around
your pessimistic lock use-case using the "Approach 2: Explicit
Transaction Demarcation" described above. Doctrine ORM will throw an
Transaction Demarcation" described above. Doctrine 2 will throw an
Exception if you attempt to acquire an pessimistic lock and no
transaction is running.
Doctrine ORM currently supports two pessimistic lock modes:
Doctrine 2 currently supports two pessimistic lock modes:
- Pessimistic Write
(``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the
@@ -374,8 +363,7 @@ Doctrine ORM currently supports two pessimistic lock modes:
locks other concurrent requests that attempt to update or lock rows
in write mode.
You can use pessimistic locks in four different scenarios:
You can use pessimistic locks in three different scenarios:
1. Using
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
@@ -386,10 +374,7 @@ You can use pessimistic locks in four different scenarios:
or
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
3. Using
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
4. Using
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``

View File

@@ -1,164 +0,0 @@
Implementing a TypedFieldMapper
===============================
.. versionadded:: 2.14
You can specify custom typed field mapping between PHP type and DBAL type using ``Doctrine\ORM\Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
.. code-block:: php
<?php
$configuration->setTypedFieldMapper(new CustomTypedFieldMapper());
DefaultTypedFieldMapper
-----------------------
By default the ``Doctrine\ORM\Mapping\DefaultTypedFieldMapper`` is used, and you can pass an array of
PHP type => DBAL type mappings into its constructor to override the default behavior or add new mappings.
.. code-block:: php
<?php
use App\CustomIds\CustomIdObject;
use App\DBAL\Type\CustomIdObjectType;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
CustomIdObject::class => CustomIdObjectType::class,
]));
Then, an entity using the ``CustomIdObject`` typed field will be correctly assigned its DBAL type
(``CustomIdObjectType``) without the need of explicit declaration.
.. configuration-block::
.. code-block:: attribute
<?php
#[ORM\Entity]
#[ORM\Table(name: 'cms_users_typed_with_custom_typed_field')]
class UserTypedWithCustomTypedField
{
#[ORM\Column]
public CustomIdObject $customId;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="UserTypedWithCustomTypedField">
<field name="customId"/>
<!-- -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
UserTypedWithCustomTypedField:
type: entity
fields:
customId: ~
It is perfectly valid to override even the "automatic" mapping rules mentioned above:
.. code-block:: php
<?php
use App\DBAL\Type\CustomIntType;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
'int' => CustomIntType::class,
]));
.. note::
If chained, once the first ``TypedFieldMapper`` assigns a type to a field, the ``DefaultTypedFieldMapper`` will
ignore its mapping and not override it anymore (if it is later in the chain). See below for chaining type mappers.
TypedFieldMapper interface
-------------------------
The interface ``Doctrine\ORM\Mapping\TypedFieldMapper`` allows you to implement your own
typed field mapping logic. It consists of just one function
.. code-block:: php
<?php
/**
* Validates & completes the given field mapping based on typed property.
*
* @param array{fieldName: string, enumType?: string, type?: mixed} $mapping The field mapping to validate & complete.
* @param \ReflectionProperty $field
*
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array;
ChainTypedFieldMapper
---------------------
The class ``Doctrine\ORM\Mapping\ChainTypedFieldMapper`` allows you to chain multiple ``TypedFieldMapper`` instances.
When being evaluated, the ``TypedFieldMapper::validateAndComplete`` is called in the order in which
the instances were supplied to the ``ChainTypedFieldMapper`` constructor.
.. code-block:: php
<?php
use App\DBAL\Type\CustomIntType;
use Doctrine\ORM\Mapping\ChainTypedFieldMapper;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(
new ChainTypedFieldMapper(
new DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
new CustomTypedFieldMapper()
)
);
Implementing a TypedFieldMapper
-------------------------------
If you want to assign all ``BackedEnum`` fields to your custom ``BackedEnumDBALType`` or you want to use different
DBAL types based on whether the entity field is nullable or not, you can achieve this by implementing your own
typed field mapper.
You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMapper``.
.. code-block:: php
<?php
final class CustomEnumTypedFieldMapper implements TypedFieldMapper
{
/**
* {@inheritDoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
$type = $field->getType();
if (
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
if (! $type->isBuiltin() && enum_exists($type->getName())) {
$mapping['type'] = BackedEnumDBALType::class;
}
}
return $mapping;
}
}
.. note::
Note that this case checks whether the mapping is already assigned, and if yes, it skips it. This is up to your
implementation. You can make a "greedy" mapper which will always override the mapping with its own type, or one
that behaves like the ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.

View File

@@ -39,7 +39,7 @@ side of the association and these 2 references both represent the
same association but can change independently of one another. Of
course, in a correct application the semantics of the bidirectional
association are properly maintained by the application developer
(that's their responsibility). Doctrine needs to know which of these
(that's his responsibility). Doctrine needs to know which of these
two in-memory references is the one that should be persisted and
which not. This is what the owning/inverse concept is mainly used
for.

View File

@@ -17,7 +17,7 @@ ask for an entity with a specific ID twice, it will return the same instance:
.. code-block:: php
public function testIdentityMap(): void
public function testIdentityMap()
{
$objectA = $this->entityManager->find('EntityName', 1);
$objectB = $this->entityManager->find('EntityName', 1);
@@ -34,15 +34,17 @@ will still end up with the same reference:
.. code-block:: php
public function testIdentityMapReference(): void
public function testIdentityMapReference()
{
$objectA = $this->entityManager->getReference('EntityName', 1);
// check entity is not initialized
$this->assertTrue($this->entityManager->isUninitializedObject($objectA));
/** @var EntityName|\ProxyManager\Proxy\GhostObjectInterface $objectA */
$objectA = $this->entityManager->getReference(EntityName::class, 1);
$objectB = $this->entityManager->find('EntityName', 1);
self::assertInstanceOf(\ProxyManager\Proxy\GhostObjectInterface::class, $objectA);
self::assertFalse($objectA->isProxyInitialized());
$this->assertSame($objectA, $objectB)
$objectB = $this->entityManager->find(EntityName::class, 1);
self::assertSame($objectA, $objectB)
}
The identity map being indexed by primary keys only allows shortcuts when you
@@ -146,15 +148,15 @@ Hydration
~~~~~~~~~
Responsible for creating a final result from a raw database statement and a
result-set mapping object. The developer can choose which kind of result they
wish to be hydrated. Default result-types include:
result-set mapping object. The developer can choose which kind of result he
wishes to be hydrated. Default result-types include:
- SQL to Entities
- SQL to structured Arrays
- SQL to simple scalar result arrays
- SQL to a single result variable
Hydration to entities and arrays is one of the most complex parts of Doctrine
Hydration to entities and arrays is one of most complex parts of Doctrine
algorithm-wise. It can build results with for example:
- Single table selects

View File

@@ -32,62 +32,62 @@ information about its type and if it's the owning or inverse side.
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class User
{
#[Id, GeneratedValue, Column]
private int|null $id = null;
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
*
* @var Collection<int, Comment>
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
* @JoinTable(name="user_favorite_comments")
*/
#[ManyToMany(targetEntity: Comment::class, inversedBy: 'userFavorites')]
#[JoinTable(name: 'user_favorite_comments')]
private Collection $favorites;
private $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @var Collection<int, Comment>
* @ManyToMany(targetEntity="Comment")
* @JoinTable(name="user_read_comments")
*/
#[ManyToMany(targetEntity: Comment::class)]
#[JoinTable(name: 'user_read_comments')]
private Collection $commentsRead;
private $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @var Collection<int, Comment>
* @OneToMany(targetEntity="Comment", mappedBy="author")
*/
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author')]
private Collection $commentsAuthored;
private $commentsAuthored;
/** Unidirectional - Many-To-One */
#[ManyToOne(targetEntity: Comment::class)]
private Comment|null $firstComment = null;
/**
* Unidirectional - Many-To-One
*
* @ManyToOne(targetEntity="Comment")
*/
private $firstComment;
}
#[Entity]
/** @Entity */
class Comment
{
#[Id, GeneratedValue, Column]
private string $id;
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @var Collection<int, User>
* @ManyToMany(targetEntity="User", mappedBy="favorites")
*/
#[ManyToMany(targetEntity: User::class, mappedBy: 'favorites')]
private Collection $userFavorites;
private $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
* @ManyToOne(targetEntity="User", inversedBy="commentsAuthored")
*/
#[ManyToOne(targetEntity: User::class, inversedBy: 'commentsAuthored')]
private User|null $author = null;
private $author;
}
This two entities generate the following MySQL Schema (Foreign Key
@@ -132,12 +132,11 @@ relations of the ``User``:
class User
{
// ...
/** @return Collection<int, Comment> */
public function getReadComments(): Collection {
public function getReadComments() {
return $this->commentsRead;
}
public function setFirstComment(Comment $c): void {
public function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
}
@@ -173,13 +172,11 @@ fields on both sides:
{
// ..
/** @return Collection<int, Comment> */
public function getAuthoredComments(): Collection {
public function getAuthoredComments() {
return $this->commentsAuthored;
}
/** @return Collection<int, Comment> */
public function getFavoriteComments(): Collection {
public function getFavoriteComments() {
return $this->favorites;
}
}
@@ -188,12 +185,11 @@ fields on both sides:
{
// ...
/** @return Collection<int, User> */
public function getUserFavorites(): Collection {
public function getUserFavorites() {
return $this->userFavorites;
}
public function setAuthor(User|null $author = null): void {
public function setAuthor(User $author = null) {
$this->author = $author;
}
}
@@ -266,7 +262,6 @@ where n is the size of the map.
can often be used to improve performance by avoiding the loading of
the inverse collection.
You can also clear the contents of a whole collection using the
``Collections::clear()`` method. You should be aware that using
this method can lead to a straight and optimized database delete or
@@ -296,12 +291,12 @@ example that encapsulate much of the association management code:
class User
{
// ...
public function markCommentRead(Comment $comment): void {
public function markCommentRead(Comment $comment) {
// Collections implement ArrayAccess
$this->commentsRead[] = $comment;
}
public function addComment(Comment $comment): void {
public function addComment(Comment $comment) {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
}
@@ -309,16 +304,16 @@ example that encapsulate much of the association management code:
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c): void {
private function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment): void {
public function addFavorite(Comment $comment) {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment): void {
public function removeFavorite(Comment $comment) {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
@@ -328,11 +323,11 @@ example that encapsulate much of the association management code:
{
// ..
public function addUserFavorite(User $user): void {
public function addUserFavorite(User $user) {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user): void {
public function removeUserFavorite(User $user) {
$this->userFavorites->removeElement($user);
}
}
@@ -355,13 +350,11 @@ the details inside the classes can be challenging.
entity cannot circumvent the logic you implement on your entity for
association management. For example:
.. code-block:: php
<?php
class User {
/** @return array<int, Comment> */
public function getReadComments(): array {
public function getReadComments() {
return $this->commentsRead->toArray();
}
}
@@ -398,7 +391,6 @@ can show the possible caveats you can encounter:
There are two approaches to handle this problem in your code:
1. Ignore updating the inverse side of bidirectional collections,
BUT never read from them in requests that changed their state. In
the next request Doctrine hydrates the consistent collection state
@@ -412,10 +404,10 @@ There are two approaches to handle this problem in your code:
Transitive persistence / Cascade Operations
-------------------------------------------
Doctrine ORM provides a mechanism for transitive persistence through cascading of certain operations.
Doctrine 2 provides a mechanism for transitive persistence through cascading of certain operations.
Each association to another entity or a collection of
entities can be configured to automatically cascade the following operations to the associated entities:
``persist``, ``remove``, ``detach``, ``refresh`` or ``all``.
``persist``, ``remove``, ``refresh`` or ``all``.
The main use case for ``cascade: persist`` is to avoid "exposing" associated entities to your PHP application.
Continuing with the User-Comment example of this chapter, this is how the creation of a new user and a new
@@ -442,10 +434,8 @@ only accessing it through the User entity:
// User entity
class User
{
private int $id;
/** @var Collection<int, Comment> */
private Collection $comments;
private $id;
private $comments;
public function __construct()
{
@@ -471,8 +461,11 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
class User
{
// ...
/** Bidirectional - One-To-Many (INVERSE SIDE) */
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author', cascade: ['persist', 'remove'])]
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
// ...
}
@@ -525,8 +518,6 @@ For each cascade operation that gets activated, Doctrine also
applies that operation to the association, be it single or
collection valued.
.. _persistence-by-reachability:
Persistence by Reachability: Cascade Persist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -535,7 +526,6 @@ operation. During each ``flush()`` operation Doctrine detects if there
are new entities in any collection and three possible cases can
happen:
1. New entities in a collection marked as ``cascade: persist`` will be
directly persisted by Doctrine.
2. New entities in a collection not marked as ``cascade: persist`` will
@@ -563,13 +553,6 @@ OrphanRemoval works with one-to-one, one-to-many and many-to-many associations.
If you neglect this assumption your entities will get deleted by Doctrine even if
you assigned the orphaned entity to another one.
.. note::
``orphanRemoval=true`` option should be used in combination with ``cascade=["persist"]`` option
as the child entity, that is manually persisted, will not be deleted automatically by Doctrine
when a collection is still an instance of ArrayCollection (before first flush / hydration).
This is a Doctrine limitation since ArrayCollection does not have access to a UnitOfWork.
As a better example consider an Addressbook application where you have Contacts, Addresses
and StandingData:
@@ -581,30 +564,31 @@ and StandingData:
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
/**
* @Entity
*/
class Contact
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[OneToOne(targetEntity: StandingData::class, cascade: ['persist'], orphanRemoval: true)]
private StandingData|null $standingData = null;
/** @OneToOne(targetEntity="StandingData", orphanRemoval=true) */
private $standingData;
/** @var Collection<int, Address> */
#[OneToMany(targetEntity: Address::class, mappedBy: 'contact', cascade: ['persist'], orphanRemoval: true)]
private Collection $addresses;
/** @OneToMany(targetEntity="Address", mappedBy="contact", orphanRemoval=true) */
private $addresses;
public function __construct()
{
$this->addresses = new ArrayCollection();
}
public function newStandingData(StandingData $sd): void
public function newStandingData(StandingData $sd)
{
$this->standingData = $sd;
}
public function removeAddress(int $pos): void
public function removeAddress($pos)
{
unset($this->addresses[$pos]);
}
@@ -718,7 +702,6 @@ methods:
* ``andX($arg1, $arg2, ...)``
* ``orX($arg1, $arg2, ...)``
* ``not($expression)``
* ``eq($field, $value)``
* ``gt($field, $value)``
* ``lt($field, $value)``
@@ -729,11 +712,9 @@ methods:
* ``in($field, array $values)``
* ``notIn($field, array $values)``
* ``contains($field, $value)``
* ``memberOf($value, $field)``
* ``startsWith($field, $value)``
* ``endsWith($field, $value)``
.. note::
There is a limitation on the compatibility of Criteria comparisons.

View File

@@ -27,7 +27,7 @@ Work that have not yet been persisted are lost.
.. note::
Doctrine NEVER touches the public API of methods in your entity
Doctrine does NEVER touch the public API of methods in your entity
classes (like getters and setters) nor the constructor method.
Instead, it uses reflection to get/set data from/to your entity objects.
When Doctrine fetches data from DB and saves it back,
@@ -53,7 +53,7 @@ headline "Hello World" with the ID 1234:
echo $article2->getHeadline();
In this case the Article is accessed from the entity manager twice,
but modified in between. Doctrine ORM realizes this and will only
but modified in between. Doctrine 2 realizes this and will only
ever give you access to one instance of the Article with ID 1234,
no matter how often do you retrieve it from the EntityManager and
even no matter what kind of Query method you are using (find,
@@ -95,29 +95,28 @@ from newly opened EntityManager.
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Article
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Column(type: 'string')]
private string $headline;
/** @Column(type="string") */
private $headline;
#[ManyToOne(targetEntity: User::class)]
private User|null $author = null;
/** @ManyToOne(targetEntity="User") */
private $author;
/** @var Collection<int, Comment> */
#[OneToMany(targetEntity: Comment::class, mappedBy: 'article')]
private Collection $comments;
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getAuthor(): User|null { return $this->author; }
public function getComments(): Collection { return $this->comments; }
public function getAuthor() { return $this->author; }
public function getComments() { return $this->comments; }
}
$article = $em->find('Article', 1);
@@ -162,6 +161,31 @@ your code. See the following code:
echo "This will always be true!";
}
A slice of the generated proxy classes code looks like the
following piece of code. A real proxy class override all non-identity
non-transient object state at instantiation time in order to
enable lazy-loading mechanisms:
.. code-block:: php
<?php
class UserProxyHASH extends User implements GhostObjectInterface
{
// ... generated code
public static function staticProxyConstructor($initializer)
{
// ... generated code
}
private function callInitializerHASH($methodName, array $parameters)
{
// ... generated code
}
// ... generated code
}
.. warning::
Traversing the object graph for parts that are lazy-loaded will
@@ -169,7 +193,6 @@ your code. See the following code:
to heavily. Make sure to use DQL to fetch-join all the parts of the
object-graph that you need as efficiently as possible.
Persisting entities
-------------------
@@ -192,12 +215,6 @@ be properly synchronized with the database when
database in the most efficient way and a single, short transaction,
taking care of maintaining referential integrity.
.. note::
Do not make any assumptions in your code about the number of queries
it takes to flush changes, about the ordering of ``INSERT``, ``UPDATE``
and ``DELETE`` queries or the order in which entities will be processed.
Example:
.. code-block:: php
@@ -218,11 +235,9 @@ Example:
generated identifier being not available after a failed flush
operation.
The semantics of the persist operation, applied on an entity X, are
as follows:
- If X is a new entity, it becomes managed. The entity X will be
entered into the database as a result of the flush operation.
- If X is a preexisting managed entity, it is ignored by the
@@ -234,12 +249,6 @@ as follows:
- If X is a detached entity, an exception will be thrown on
flush.
.. caution::
Do not pass detached entities to the persist operation. The persist operation always
considers entities that are not yet known to the ``EntityManager`` as new entities
(refer to the ``STATE_NEW`` constant inside the ``UnitOfWork``).
Removing entities
-----------------
@@ -260,7 +269,6 @@ which means that its persistent state will be deleted once
the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>`
for more information.
Example:
.. code-block:: php
@@ -272,7 +280,6 @@ Example:
The semantics of the remove operation, applied to an entity X are
as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
@@ -288,61 +295,23 @@ as follows:
- A removed entity X will be removed from the database as a result
of the flush operation.
After an entity has been removed, its in-memory state is the same as
After an entity has been removed its in-memory state is the same as
before the removal, except for generated identifiers.
During the ``EntityManager#flush()`` operation, the removed entity
will also be removed from all collections in entities currently
loaded into memory.
.. _remove_object_many_to_many_join_tables:
Join-table management when removing from many-to-many collections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Regarding existing rows in many-to-many join tables that refer to
an entity being removed, the following applies.
When the entity being removed does not declare the many-to-many association
itself (that is, the many-to-many association is unidirectional and
the entity is on the inverse side), the ORM has no reasonable way to
detect associations targeting the entity's class. Thus, no ORM-level handling
of join-table rows is attempted and database-level constraints apply.
In case of database-level ``ON DELETE RESTRICT`` constraints, the
``EntityManager#flush()`` operation may abort and a ``ConstraintViolationException``
may be thrown. No in-memory collections will be modified in this case.
With ``ON DELETE CASCADE``, the RDBMS will take care of removing rows
from join tables.
When the entity being removed is part of bi-directional many-to-many
association, either as the owning or inverse side, the ORM will
delete rows from join tables before removing the entity itself. That means
database-level ``ON DELETE RESTRICT`` constraints on join tables are not
effective, since the join table rows are removed first. Removal of join table
rows happens through specialized methods in entity and collection persister
classes and take one query per entity and join table. In case the association
uses a ``@JoinColumn`` configuration with ``onDelete="CASCADE"``, instead
of using a dedicated ``DELETE`` query the database-level operation will be
relied upon.
.. note::
In case you rely on database-level ``ON DELETE RESTRICT`` constraints,
be aware that by making many-to-many associations bidirectional the
assumed protection may be lost.
Performance of different deletion strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Removing an entity will also automatically delete any existing
records in many-to-many join tables that link this entity. The
action taken depends on the value of the ``@joinColumn`` mapping
attribute "onDelete". Either Doctrine issues a dedicated ``DELETE``
statement for records of each join table or it depends on the
foreign key semantics of onDelete="CASCADE".
Deleting an object with all its associated objects can be achieved
in multiple ways with very different performance impacts.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM will
fetch this association. If it's a Single association it will pass
this entity to ``EntityManager#remove()``. If the association is a
collection, Doctrine will loop over all its elements and pass them to
``EntityManager#remove()``.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2
will fetch this association. If it is a Single association it will
pass this entity to
´EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to ``EntityManager#remove()\`.
In both cases the cascade remove semantics are applied recursively.
For large object graphs this removal strategy can be very costly.
2. Using a DQL ``DELETE`` statement allows you to delete multiple
@@ -357,64 +326,43 @@ in multiple ways with very different performance impacts.
because Doctrine will fetch and remove all associated entities
explicitly nevertheless.
.. note::
Calling ``remove`` on an entity will remove the object from the identity
map and therefore detach it. Querying the same entity again, for example
via a lazy loaded relation, will return a new object.
Detaching entities
------------------
An entity is detached from an EntityManager and thus no longer
managed by invoking the ``EntityManager#detach($entity)`` method on
it or by cascading the detach operation to it. Changes made to the
detached entity, if any (including removal of the entity), will not
be synchronized to the database after the entity has been
All entities are detached from an EntityManager and thus no longer
managed by it after invoking the ``EntityManager#clear()`` method.
Changes made to the detached entities, if any (including their removal),
will not be synchronized to the database after they have been
detached.
Doctrine will not hold on to any references to a detached entity.
Doctrine will not hold on to any references to detached entities.
Example:
.. code-block:: php
<?php
$em->detach($entity);
$em->clear();
The semantics of the detach operation, applied to an entity X are
as follows:
- If X is a managed entity, the detach operation causes it to
become detached. The detach operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`transitive-persistence`"). Entities which previously referenced X
- If X is a managed entity, the ``clear`` operation causes it to
become detached. Entities which previously referenced X
will continue to reference X.
- If X is a new or detached entity, it is ignored by the detach
operation.
- If X is a removed entity, the detach operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`transitive-persistence`"). Entities which previously referenced X
will continue to reference X.
- If X is a removed entity, it will become detached, and therefore
no longer scheduled to be removed. Entities which previously
referenced X will continue to reference X.
There are several situations in which an entity is detached
automatically without invoking the ``detach`` method:
automatically:
- When ``EntityManager#clear()`` is invoked, all entities that are
currently managed by the EntityManager instance become detached.
- When serializing an entity. The entity retrieved upon subsequent
unserialization will be detached (This is the case for all entities
unserialization will be detached (this is the case for all entities
that are serialized and stored in some cache).
The ``detach`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``.
Synchronization with the Database
---------------------------------
@@ -456,7 +404,6 @@ Synchronizing New and Managed Entities
The flush operation applies to a managed entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
UPDATE statement, only if at least one persistent field has
changed.
@@ -465,14 +412,12 @@ semantics:
The flush operation applies to a new entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
INSERT statement.
For all (initialized) relationships of the new or managed entity
the following semantics apply to each associated entity X:
- If X is new and persist operations are configured to cascade on
the relationship, X will be persisted.
- If X is new and no persist operations are configured to cascade
@@ -504,7 +449,6 @@ The cost of flushing
How costly a flush operation is, mainly depends on two factors:
- The size of the EntityManager's current UnitOfWork.
- The configured change tracking policies
@@ -524,14 +468,13 @@ during development.
.. note::
Do not invoke ``flush`` after every change to an entity
or every single invocation of persist/remove/... This is an
or every single invocation of persist/remove/refresh/... This is an
anti-pattern and unnecessarily reduces the performance of your
application. Instead, form units of work that operate on your
objects and call ``flush`` when you are done. While serving a
single HTTP request there should be usually no need for invoking
``flush`` more than 0-2 times.
Direct access to a Unit of Work
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -551,7 +494,6 @@ instance the EntityManager is currently using.
marked as INTERNAL by not using them and carefully read the API
documentation.
Entity State
~~~~~~~~~~~~
@@ -587,14 +529,14 @@ An entity is in DETACHED state if it has persistent state and
identity but is currently not associated with an
``EntityManager``.
An entity is in NEW state if has no persistent state and identity
An entity is in NEW state if it has no persistent state and identity
and is not associated with an ``EntityManager`` (for example those
just created via the "new" operator).
Querying
--------
Doctrine ORM provides the following ways, in increasing level of
Doctrine 2 provides the following ways, in increasing level of
power and flexibility, to query for persistent objects. You should
always start with the simplest one that suits your needs.
@@ -698,8 +640,10 @@ Additionally, you can just count the result of the provided conditions when you
By Criteria
~~~~~~~~~~~
The Repository implement the ``Doctrine\Common\Collections\Selectable``
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
.. versionadded:: 2.3
The Repository implements the ``Doctrine\Common\Collections\Selectable``
interface. It means you can build ``Doctrine\Common\Collections\Criteria``
and pass them to the ``matching($criteria)`` method.
See section `Filtering collections` of chapter :doc:`Working with Associations <working-with-associations>`
@@ -709,26 +653,9 @@ By Eager Loading
Whenever you query for an entity that has persistent associations
and these associations are mapped as EAGER, they will automatically
be loaded together with the entity being queried and is thus
be loaded together with the entity being queried and are thus
immediately available to your application.
Eager Loading can also be configured at runtime through
``AbstractQuery::setFetchMode`` in DQL or Native Queries.
Eager loading for many-to-one and one-to-one associations is using either a
LEFT JOIN or a second query for fetching the related entity eagerly.
Eager loading for many-to-one associations uses a second query to load
the collections for several entities at the same time.
When many-to-many, one-to-one or one-to-many associations are eagerly loaded,
then the global batch size configuration is used to avoid IN(?) queries with
too many arguments. The default batch size is 100 and can be changed with
``Configuration::setEagerFetchBatchSize()``.
For eagerly loaded Many-To-Many associations one query has to be made for each
collection.
By Lazy Loading
~~~~~~~~~~~~~~~
@@ -769,9 +696,7 @@ DQL and its syntax as well as the Doctrine class can be found in
:doc:`the dedicated chapter <dql-doctrine-query-language>`.
For programmatically building up queries based on conditions that
are only known at runtime, Doctrine provides the special
``Doctrine\ORM\QueryBuilder`` class. While this a powerful tool,
it also brings more complexity to your code compared to plain DQL,
so you should only use it when you need it. More information on
``Doctrine\ORM\QueryBuilder`` class. More information on
constructing queries with a QueryBuilder can be found
:doc:`in Query Builder chapter <query-builder>`.
@@ -792,7 +717,7 @@ By default the EntityManager returns a default implementation of
``Doctrine\ORM\EntityRepository`` when you call
``EntityManager#getRepository($entityClass)``. You can overwrite
this behaviour by specifying the class name of your own Entity
Repository in the Attribute or XML metadata. In large
Repository in the Annotation or XML metadata. In large
applications that require lots of specialized DQL queries using a
custom repository is one recommended way of grouping these queries
in a central location.
@@ -802,11 +727,12 @@ in a central location.
<?php
namespace MyDomain\Model;
use MyDomain\Model\UserRepository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserRepository::class)]
/**
* @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/
class User
{
@@ -814,10 +740,9 @@ in a central location.
class UserRepository extends EntityRepository
{
/** @return Collection<User> */
public function getAllAdminUsers(): Collection
public function getAllAdminUsers()
{
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
return $this->em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
}
}
@@ -830,3 +755,4 @@ You can access your repository now by calling:
// $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();

View File

@@ -2,14 +2,15 @@ XML Mapping
===========
The XML mapping driver enables you to provide the ORM metadata in
form of XML documents. It requires the ``dom`` extension in order to be
able to validate your mapping documents against its XML Schema.
form of XML documents.
The XML driver is backed by an XML Schema document that describes
the structure of a mapping document. The most recent version of the
XML Schema document is available online at
`https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
The most convenient way to work with
In order to point to the latest version of the document of a
particular stable release branch, just append the release number,
i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with
XML mapping files is to use an IDE/editor that can provide
code-completion based on such an XML Schema document. The following
is an outline of a XML mapping document with the proper xmlns/xsi
@@ -18,7 +19,7 @@ setup for the latest code in trunk.
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -30,7 +31,6 @@ The XML mapping document of a class is loaded on-demand the first
time it is requested and subsequently stored in the metadata cache.
In order to work, this requires certain conventions:
- Each entity/mapped superclass must get its own dedicated XML
mapping document.
- The name of the mapping document must consist of the fully
@@ -104,7 +104,7 @@ of several common elements:
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -193,12 +193,10 @@ specified as the ``<entity />`` element as a direct child of the
Required attributes:
- name - The fully qualified class-name of the entity.
Optional attributes:
- **table** - The Table-Name to be used for this entity. Otherwise the
Unqualified Class-Name is used by default.
- **repository-class** - The fully qualified class-name of an
@@ -207,10 +205,10 @@ Optional attributes:
- **inheritance-type** - The type of inheritance, defaults to none. A
more detailed description follows in the
*Defining Inheritance Mappings* section.
- **read-only** - Specifies that this entity is marked as read only and not
- **read-only** - (>= 2.1) Specifies that this entity is marked as read only and not
considered for change-tracking. Entities of this type can be persisted
and removed though.
- **schema** - The schema the table lies in, for platforms that support schemas
- **schema** - (>= 2.5) The schema the table lies in, for platforms that support schemas
Defining Fields
~~~~~~~~~~~~~~~
@@ -240,13 +238,11 @@ entity. For the ID mapping you have to use the ``<id />`` element.
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
Optional attributes:
- type - The ``Doctrine\DBAL\Types\Type`` name, defaults to
"string"
- column - Name of the column in the database, defaults to the
@@ -257,11 +253,6 @@ Optional attributes:
table? Defaults to false.
- nullable - Should this field allow NULL as a value? Defaults to
false.
- insertable - Should this field be inserted? Defaults to true.
- updatable - Should this field be updated? Defaults to true.
- generated - Enum of the values ALWAYS, INSERT, NEVER that determines if
generated value must be fetched from database after INSERT or UPDATE.
Defaults to "NEVER".
- version - Should this field be used for optimistic locking? Only
works on fields with type integer or datetime.
- scale - Scale of a decimal type.
@@ -297,7 +288,7 @@ Defining Identity and Generator Strategies
An entity has to have at least one ``<id />`` element. For
composite keys you can specify more than one id-element, however
surrogate keys are recommended for use with Doctrine ORM. The Id
surrogate keys are recommended for use with Doctrine 2. The Id
field allows to define properties of the identifier and allows a
subset of the ``<field />`` element attributes:
@@ -309,7 +300,6 @@ subset of the ``<field />`` element attributes:
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
- type - The ``Doctrine\DBAL\Types\Type`` name, preferably
@@ -317,7 +307,6 @@ Required attributes:
Optional attributes:
- column - Name of the column in the database, defaults to the
field name.
@@ -343,7 +332,6 @@ have to use the ``NONE`` strategy.
The following values are allowed for the ``<generator />`` strategy
attribute:
- AUTO - Automatic detection of the identifier strategy based on
the preferred solution of the database vendor.
- IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs
@@ -366,12 +354,10 @@ element to describe the sequence:
Required attributes for ``<sequence-generator />``:
- sequence-name - The name of the sequence
Optional attributes for ``<sequence-generator />``:
- allocation-size - By how much steps should the sequence be
incremented when a value is retrieved. Defaults to 1
- initial-value - What should the initial value of the sequence
@@ -384,7 +370,6 @@ Optional attributes for ``<sequence-generator />``:
element, if Doctrine chooses the sequence strategy for a
platform.
Defining a Mapped Superclass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -404,7 +389,6 @@ can define it in XML using the ``<mapped-superclass />`` tag.
Required attributes:
- name - Class name of the mapped superclass.
You can nest any number of ``<field />`` and unidirectional
@@ -444,7 +428,6 @@ The allowed values for inheritance-type attribute are ``JOINED`` or
All inheritance related definitions have to be defined on the root
entity of the hierarchy.
Defining Lifecycle Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -477,7 +460,6 @@ For the inverse side the mapping is as simple as:
Required attributes for inverse One-To-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -495,7 +477,6 @@ For the owning side this mapping would look like:
Required attributes for owning One-to-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -503,7 +484,6 @@ Required attributes for owning One-to-One:
Optional attributes for owning One-to-One:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
@@ -546,7 +526,6 @@ like:
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -554,7 +533,6 @@ Required attributes:
Optional attributes:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
@@ -597,7 +575,6 @@ exists for bi-directional associations.
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -607,7 +584,6 @@ Required attributes:
Optional attributes:
- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
- index-by: Index the collection by a field on the target entity.
@@ -626,7 +602,6 @@ definitions and rely on their implicit values.
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -634,7 +609,6 @@ Required attributes:
Optional attributes:
- mapped-by - Name of the field on the owning side that contains
the owning side association if the defined many-to-many association
is on the inverse side.
@@ -690,11 +664,9 @@ tags.
Besides ``<cascade-all />`` the following operations can be
specified by their respective tags:
- ``<cascade-persist />``
- ``<cascade-remove />``
- ``<cascade-refresh />``
- ``<cascade-detach />``
Join Column Element
~~~~~~~~~~~~~~~~~~~
@@ -705,14 +677,12 @@ key names are called that are used for joining two entities.
Required attributes:
- name - The column name of the foreign key.
- referenced-column-name - The column name of the associated
entities primary key
Optional attributes:
- unique - If the join column should contain a UNIQUE constraint.
This makes sense for Many-To-Many join-columns only to simulate a
one-to-many unidirectional using a join-table.
@@ -770,7 +740,7 @@ entity relationship. You can define this in XML with the "association-key" attri
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

View File

@@ -31,7 +31,6 @@
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/typedfieldmapper
reference/events
reference/unitofwork
reference/unitofwork-associations
@@ -41,8 +40,9 @@
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/attributes-reference
reference/partial-objects
reference/xml-mapping
reference/annotations-reference
reference/php-mapping
reference/caching
reference/improving-performance
@@ -70,6 +70,8 @@
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction

View File

@@ -1,9 +1,11 @@
Composite and Foreign Keys as Primary Key
=========================================
Doctrine ORM supports composite primary keys natively. Composite keys are a very powerful relational database concept
and we took good care to make sure Doctrine ORM supports as many of the composite primary key use-cases.
For Doctrine ORM composite keys of primitive data-types are supported, even foreign keys as
.. versionadded:: 2.1
Doctrine 2 supports composite primary keys natively. Composite keys are a very powerful relational database concept
and we took good care to make sure Doctrine 2 supports as many of the composite primary key use-cases.
For Doctrine 2.0 composite keys of primitive data-types are supported, for Doctrine 2.1 even foreign keys as
primary keys are supported.
This tutorial shows how the semantics of composite primary keys work and how they map to the database.
@@ -17,34 +19,42 @@ the ID fields have to have their values set before you call ``EntityManager#pers
Primitive Types only
~~~~~~~~~~~~~~~~~~~~
You can have composite keys as long as they only consist of the primitive types
Even in version 2.0 you can have composite keys as long as they only consist of the primitive types
``integer`` and ``string``. Suppose you want to create a database of cars and use the model-name
and year of production as primary keys:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
namespace VehicleCatalogue\Model;
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
*/
class Car
{
public function __construct(
#[Id, Column]
private string $name,
#[Id, Column]
private int $year,
) {
/** @ORM\Id @ORM\Column(type="string") */
private $name;
/** @ORM\Id @ORM\Column(type="integer") */
private $year;
public function __construct($name, $year)
{
$this->name = $name;
$this->year = $year;
}
public function getModelName(): string
public function getModelName()
{
return $this->name;
}
public function getYearOfProduction(): int
public function getYearOfProduction()
{
return $this->year;
}
@@ -54,7 +64,7 @@ and year of production as primary keys:
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -89,7 +99,7 @@ And for querying you can use arrays to both DQL and EntityRepositories:
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1";
$audi = $em->createQuery($dql)
->setParameter(1, ["name" => "Audi A8", "year" => 2010])
->setParameter(1, array("name" => "Audi A8", "year" => 2010))
->getSingleResult();
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
@@ -103,6 +113,10 @@ and to ``year`` to the related entities.
Identity through foreign Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note::
Identity through foreign entities is only supported with Doctrine 2.1
There are tons of use-cases where the identity of an Entity should be determined by the entity
of one or many parent entities.
@@ -116,7 +130,7 @@ of one or many parent entities.
The semantics of mapping identity through foreign entities are easy:
- Only allowed on Many-To-One or One-To-One associations.
- Plug an ``#[Id]`` attribute onto every association.
- Plug an ``@ORM\Id`` annotation onto every association.
- Set an attribute ``association-key`` with the field name of the association in XML.
Use-Case 1: Dynamic Attributes
@@ -126,44 +140,51 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Annotation as ORM;
#[Entity]
/**
* @ORM\Entity
*/
class Article
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
#[Column]
private string $title;
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
private $id;
/** @var ArrayCollection<string, ArticleAttribute> */
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
/** @ORM\Column(type="string") */
private $title;
public function addAttribute(string $name, string $value): void
/**
* @ORM\OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
*/
private $attributes;
public function addAttribute($name, $value)
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
#[Entity]
/**
* @ORM\Entity
*/
class ArticleAttribute
{
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
private Article $article;
/** @ORM\Id @ORM\ManyToOne(targetEntity="Article", inversedBy="attributes") */
private $article;
#[Id, Column]
private string $attribute;
/** @ORM\Id @ORM\Column(type="string") */
private $attribute;
#[Column]
private string $value;
/** @ORM\Column(type="string") */
private $value;
public function __construct(string $name, string $value, Article $article)
public function __construct($name, $value, $article)
{
$this->attribute = $name;
$this->value = $value;
@@ -174,7 +195,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -198,22 +219,28 @@ One good example for this is a user-address relationship:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
*/
class User
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
private $id;
}
#[Entity]
/**
* @ORM\Entity
*/
class Address
{
#[Id, OneToOne(targetEntity: User::class)]
private User|null $user = null;
/** @ORM\Id @ORM\OneToOne(targetEntity="User") */
private $user;
}
Use-Case 3: Join-Table with Metadata
@@ -226,70 +253,72 @@ of products purchased and maybe even the current price.
.. code-block:: php
<?php
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Annotation as ORM;
#[Entity]
/** @ORM\Entity */
class Order
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
private $id;
/** @var ArrayCollection<int, OrderItem> */
#[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')]
private Collection $items;
/** @ORM\ManyToOne(targetEntity="Customer") */
private $customer;
#[Column]
private bool $paid = false;
#[Column]
private bool $shipped = false;
#[Column]
private DateTime $created;
/** @ORM\OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
public function __construct(
#[ManyToOne(targetEntity: Customer::class)]
private Customer $customer,
) {
/** @ORM\Column(type="boolean") */
private $paid = false;
/** @ORM\Column(type="boolean") */
private $shipped = false;
/** @ORM\Column(type="datetime") */
private $created;
public function __construct(Customer $customer)
{
$this->customer = $customer;
$this->items = new ArrayCollection();
$this->created = new DateTime("now");
$this->created = new \DateTime("now");
}
}
#[Entity]
/** @ORM\Entity */
class Product
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
private $id;
#[Column]
private string $name;
/** @ORM\Column(type="string") */
private $name;
#[Column]
private int $currentPrice;
/** @ORM\Column(type="decimal") */
private $currentPrice;
public function getCurrentPrice(): int
public function getCurrentPrice()
{
return $this->currentPrice;
}
}
#[Entity]
/** @ORM\Entity */
class OrderItem
{
#[Id, ManyToOne(targetEntity: Order::class)]
private Order|null $order = null;
/** @ORM\Id @ORM\ManyToOne(targetEntity="Order") */
private $order;
#[Id, ManyToOne(targetEntity: Product::class)]
private Product|null $product = null;
/** @ORM\Id @ORM\ManyToOne(targetEntity="Product") */
private $product;
#[Column]
private int $amount = 1;
/** @ORM\Column(type="integer") */
private $amount = 1;
#[Column]
private int $offeredPrice;
/** @ORM\Column(type="decimal") */
private $offeredPrice;
public function __construct(Order $order, Product $product, int $amount = 1)
public function __construct(Order $order, Product $product, $amount = 1)
{
$this->order = $order;
$this->product = $product;
@@ -297,7 +326,6 @@ of products purchased and maybe even the current price.
}
}
Performance Considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -1,5 +1,5 @@
Separating Concerns using Embeddables
=====================================
-------------------------------------
Embeddables are classes which are not entities themselves, but are embedded
in entities and can also be queried in DQL. You'll mostly want to use them
@@ -17,31 +17,33 @@ instead of simply adding the respective columns to the ``User`` class.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
class User
{
#[Embedded(class: Address::class)]
private Address $address;
/** @ORM\Embedded(class = "Address") */
private $address;
}
#[Embeddable]
/** @ORM\Embeddable */
class Address
{
#[Column(type: "string")]
private string $street;
/** @ORM\Column(type = "string") */
private $street;
#[Column(type: "string")]
private string $postalCode;
/** @ORM\Column(type = "string") */
private $postalCode;
#[Column(type: "string")]
private string $city;
/** @ORM\Column(type = "string") */
private $city;
#[Column(type: "string")]
private string $country;
/** @ORM\Column(type = "string") */
private $country;
}
.. code-block:: xml
@@ -93,15 +95,17 @@ The following example shows you how to set your prefix to ``myPrefix_``:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
class User
{
#[Embedded(class: Address::class, columnPrefix: "myPrefix_")]
private Address $address;
/** @ORM\Embedded(class = "Address", columnPrefix = "myPrefix_") */
private $address;
}
.. code-block:: xml
@@ -115,15 +119,17 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
class User
{
#[Embedded(class: Address::class, columnPrefix: false)]
private Address $address;
/** @ORM\Embedded(class = "Address", columnPrefix = false) */
private $address;
}
.. code-block:: xml
@@ -132,7 +138,6 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
<embedded name="address" class="Address" use-column-prefix="false" />
</entity>
DQL
---
@@ -142,3 +147,4 @@ as if they were declared in the ``User`` class:
.. code-block:: sql
SELECT u FROM User u WHERE u.address.city = :myCity

View File

@@ -5,19 +5,19 @@ Extra Lazy Associations
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog.
where posts can be commented, you always have to assume that a post draws hundreds of comments.
In Doctrine ORM if you accessed an association it would always get loaded completely into memory. This
In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This
can lead to pretty serious performance problems, if your associations contain several hundreds or thousands
of entities.
Doctrine ORM includes a feature called **Extra Lazy** for associations. Associations
With Doctrine 2.1 a feature called **Extra Lazy** is introduced for associations. Associations
are marked as **Lazy** by default, which means the whole collection object for an association is populated
the first time its accessed. If you mark an association as extra lazy the following methods on collections
can be called without triggering a full load of the collection:
- ``Collection#contains($entity)``
- ``Collection#containsKey($key)``
- ``Collection#containsKey($key)`` (available with Doctrine 2.5)
- ``Collection#count()``
- ``Collection#get($key)``
- ``Collection#get($key)`` (available with Doctrine 2.4)
- ``Collection#slice($offset, $length = null)``
For each of the above methods the following semantics apply:
@@ -25,7 +25,7 @@ For each of the above methods the following semantics apply:
- For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database.
- For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed.
Additionally even with Doctrine ORM the following methods do not trigger the collection load:
Additionally even with Doctrine 2.0 the following methods do not trigger the collection load:
- ``Collection#add($entity)``
- ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity``, it does
@@ -34,16 +34,6 @@ Additionally even with Doctrine ORM the following methods do not trigger the col
With extra lazy collections you can now not only add entities to large collections but also paginate them
easily using a combination of ``count`` and ``slice``.
.. warning::
``removeElement`` directly issued DELETE queries to the database from
version 2.4.0 to 2.7.0. This circumvents the flush operation and might run
outside a transactional boundary if you don't create one yourself. We
consider this a critical bug in the assumption of how the ORM works and
reverted ``removeElement`` EXTRA_LAZY behavior in 2.7.1.
Enabling Extra-Lazy Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -52,24 +42,30 @@ switch to extra lazy as shown in these examples:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
namespace Doctrine\Tests\Models\CMS;
#[Entity]
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
*/
class CmsGroup
{
/** @var Collection<int, CmsUser> */
#[ManyToMany(targetEntity: CmsUser::class, mappedBy: 'groups', fetch: 'EXTRA_LAZY')]
public Collection $users;
/**
* @ORM\ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

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