Compare commits

...

436 Commits

Author SHA1 Message Date
Grégoire Paris
e5fb1a4a8f Merge pull request #10604 from greg0ire/psalm-5.9.0 2023-03-30 17:18:54 +02:00
Grégoire Paris
8cb7a5e212 Upgrade to Psalm 5.9.0
It looks like some issues are now represented by different classes.
2023-03-30 17:05:25 +02:00
Grégoire Paris
da9b9de590 Merge pull request #10589 from e2palmes/patch-1 2023-03-22 12:12:27 +01:00
Emmanuel DE PALMÈS
9ec697db7d Added missing ';'
missing ';' on Obtaining the EntityManager section, bootstrap code line 32
2023-03-22 11:52:36 +01:00
Mikael Peigney
a50a611bee docs: Remove incorrect @SequenceGenerator info (#10583)
The default allocationSize for @SequenceGenerator has actually been set to 1 ever since 434325e (back in 2010!)
This was done to remove confusion, but the docs were never updated to reflect the change
2023-03-16 10:32:42 +01:00
David Maicher
abb30093ed add $isXsdValidationEnabled to SimplifiedXmlDriver constructor 2023-03-14 09:38:23 +01:00
Alexander M. Turek
df559d8ac0 PHPStan 1.10.6, Psalm 5.8.0 (#10575) 2023-03-14 07:03:59 +01:00
Cyril Beslay
a6de4b9663 docs: Update Logger removal in Batch Processing documentation (#10572)
Current documented way is using Doctrine DBAL2.
Since DBAL3, logging is done with Middlewares, so I updated the recommendation in documentation.
2023-03-12 00:09:30 +01:00
Grégoire Paris
6db296cd5c Merge pull request #10539 from mpdude/performance-to-one-inheritance
More precisely document the performance impact of to-one associations towards inheritance hierarchies
2023-03-08 23:00:08 +01:00
Matthias Pigulla
7c2adde6f2 More precisely document the performance impact of to-one associations towards inheritance hierarchies
This puts the remarks that apply to both JTI/STI in a common section at the beginning, and tries to explain a bit more in detail why it is that way.

It was sparked off by the discussion in #10538.
2023-03-08 17:27:44 +00:00
Grégoire Paris
69543ba1bf Skip test instead of commenting it out (#10563)
Skipping makes the test more discoverable.
2023-03-06 09:27:35 +01:00
Alexander M. Turek
a33885575f Skip test instead of commenting it out (#10560) 2023-03-05 22:24:44 +01:00
Gabriel Ostrolucký
e28dee0742 Add missing return statements to Command:configure methods 2023-03-05 21:31:46 +01:00
Alexander M. Turek
a28e2d8277 Ignore the cache dir of PHPUnit 10 (#10546) 2023-02-28 13:35:35 +01:00
Alexander M. Turek
4759a1bf75 Make data providers static (#10544) 2023-02-28 08:29:28 +01:00
Alexander M. Turek
e0ad7ac506 Bump dev tools (#10541)
* phpstan/phpstan (1.9.14 => 1.10.3)
* squizlabs/php_codesniffer (3.7.1 => 3.7.2)
* vimeo/psalm (5.6.0 => 5.7.7)
2023-02-28 08:28:45 +01:00
Christophe Coevoet
9485d4d835 Mark SqlWalker methods as not deprecated (#10540)
phpstan treats implementations of deprecated methods of an interface as being deprecated themselves by default.
However, SqlWalker does not intend to deprecate all those methods that are deprecated in TreeWalker, as they are
moved down. Marking them as not deprecated will avoid reporting usages of deprecated APIs when implementing
custom DQL functions for instance.
2023-02-26 15:21:47 +01:00
Matthieu Lempereur
9a6e1b3505 docs: consistency order for docblock in association mapping (#10534) 2023-02-22 14:00:30 +01:00
Grégoire Paris
c286742814 Merge pull request #10529 from joshpme/patch-1
Correct use of PHP attribute
2023-02-20 08:00:20 +01:00
Josh P
052887765b Correct use of PHP attribute
Incorrect syntax used in php 8 attribute. This should be key: value, rather than key=value
2023-02-20 15:37:30 +11:00
Al Zee
5464e21022 fix typo in faq.rst (#10526) 2023-02-17 08:25:10 +01:00
Grégoire Paris
c26c55926f Merge pull request #8797 from mpdude/query_count_hint 2023-02-15 17:15:45 +01:00
Simon Podlipsky
5369e4f425 fix: use executeStatement in SchemaTool (#10516) 2023-02-14 19:14:28 +01:00
Matthias Pigulla
29bc6cc955 Write a test in a more specific way
... so we can be sure that in fact the second result has a different size.

Co-authored-by: Luís Cobucci <lcobucci@gmail.com>
2023-02-14 08:04:56 +00:00
Matthias Pigulla
31ff969628 Put up a warning sign that mapping may not be inherited from transient classes (#10392)
This _seems_ to work, but...

To my understanding, that is only because `ReflectionClass::getProperties` will report `protected` properties also for subclasses, including DocBlocks etc. The mapping drivers are unable to tell where a field comes from, so they pick up the mapping and treat it as originating from the inheriting class.

If that is indeed outside of what the ORM was designed for (sombody please confirm?), then we should explicitly discourage it.

Yes, I have seen examples of this in the wild.
2023-02-09 00:19:38 +01:00
Alexander M. Turek
01f139d76c Run tests with ext-pgsql (#10480) 2023-02-08 09:33:05 +01:00
Matthias Pigulla
660197ea71 Avoid unnecessary information in query hints to improve query cache hit ratio
I've noticed that over time my query caches fill up with redundant queries, i. e. different cache entries for the DQL -> SQL translation that are exactly the same. For me, it's an issue because the cache entries fill up precious OPcache memory.

Further investigation revealed that the queries themselves do not differ, but only the query hints – that are part of the computed cache key – do.

In particular, only the value for the `WhereInWalker::HINT_PAGINATOR_ID_COUNT` query hint are different. Since `WhereInWalker` only needs to know _if_ there are matching IDs but not _how many_, we could avoid such cache misses by using just a boolean value as cache hint.
2023-02-08 08:19:25 +01:00
Alexander M. Turek
ab06e07b53 Baseline Psalm errors for DBAL 3.6 (#10507) 2023-02-08 07:53:21 +01:00
Grégoire Paris
022b945ed5 Merge pull request #10444 from mpdude/paginator-dql-cacheable
Make Paginator-internal query cacheable in the query cache
2023-02-08 07:53:12 +01:00
Sebastian Busch
7203d05539 Clarify difference between transactional() methods of Connection and EntityManager (#10133)
One could interpret the old description as if `Connection#transactional()` would not rollback the transaction. Also, the fact that the `EntityManager` gets closed in case of an exception was not mentioned.
2023-02-07 23:38:04 +01:00
Alexander M. Turek
0bd5fbf215 Remove calls to assertObjectHasAttribute() (#10502) 2023-02-07 00:13:13 +01:00
Alexander M. Turek
6a713dd39e Remove calls to withConsecutive() (#10501) 2023-02-06 23:23:05 +01:00
Grégoire Paris
5f169d9325 Merge pull request #10420 from mpdude/fix-9095
Fix #9095 by re-applying #9096
2023-02-06 08:23:15 +01:00
Grégoire Paris
cf4680d0e6 Merge pull request #10498 from greg0ire/fix-invalid-test
Use recognized array key
2023-02-06 08:22:26 +01:00
Matthias Pigulla
cc5775c3f4 Make class final (as suggested in GH review) 2023-02-05 21:38:03 +00:00
Grégoire Paris
c5cf6a046b Use recognized array key
"joinColumn" has no meaning for the static PHP driver. That's because it
performs no translation, unlike other drivers: we're using the
ClassMetadata API directly.

Before this change, changing the value of the "name" key did not break
the test suite. After this change, it does.
2023-02-05 14:35:06 +01:00
Matthias Pigulla
77df5db3b9 Make Paginator-internal query cacheable in the query cache
Make the `Paginator`-internal query (`... WHERE ... IN (id, id2,
id3...)`) cacheable in the query cache again.

When the Paginator creates the internal subquery that does the actual
result limiting, it has to take DBAL type conversions for the identifier
column of the paginated root entity into account (#7820, fixed in
 #7821).

In order to perform this type conversion, we need to know the DBAL type
class for the root entity's `#[Id]`, and we have to figure it out based
on a given (arbitrary) DQL query. This requires DQL parsing and
inspecting the AST, so #7821 placed the conversion code in the
`WhereInWalker` where all the necessary information is available.

The problem is that type conversion has to happen every time the
paginator is run, but the query that results from running
`WhereInWalker` would be kept in the query cache. This was reported in
 #7837 and fixed by #7865, by making this particular query expire every
time. The query must not be cached, since the necessary ID type
conversion happens as a side-effect of running the `WhereInWalker`.

The Paginator internal query that uses `WhereInWalker` has its DQL
re-parsed and transformed in every request.

This PR moves the code that determines the DBAL type out of
`WhereInWalker` into a dedicated SQL walker class, `RootTypeWalker`.

`RootTypeWalker` uses a ~hack~  clever trick to report the type back: It
sets the type as the resulting "SQL" string. The benefit is that
`RootTypeWalker` results can be cached in the query cache themselves.
Only the first time a given DQL query has to be paginated, we need to
run this walker to find out the root entity's ID type. After that, the
type will be returned from the query cache.

With the type information being provided, `Paginator` can take care of
the necessary conversions by itself. This happens every time the
Paginator is used.

The internal query that uses `WhereInWalker` can be cached again since
it no longer has side effects.
2023-02-05 11:17:38 +01:00
Matthias Pigulla
ee8269ea55 Fix #9095 by re-applying #9096
Since #10411 has been merged, no more need to specify all intermediate
abstract entity classes.

Thus, we can relax the schema validator check as requested in #9095. The
reasons given in #9142 no longer apply.

Fixes #9095
2023-02-05 11:13:21 +01:00
Grégoire Paris
d038f23570 Use linebreaks 2023-02-05 11:11:21 +01:00
Alexander M. Turek
3843d7e0cc Make all data providers static (#10493) 2023-02-04 08:35:56 +00:00
Jan Nedbal
d50ba2e248 Fix invalid phpdocs missing null (#10490) 2023-02-03 22:36:32 +00:00
Jan Nedbal
8debb92d78 Add forgotten exception throws (#10489) 2023-02-03 21:32:13 +00:00
Grégoire Paris
8ff7938e31 Hunt down invalid docblocks (#10476)
It is OK to ignore some of the errors we get, but not this one.
2023-01-31 21:12:00 +01:00
Grégoire Paris
d6c0031d44 Merge pull request #10453 from mpdude/mapped-superclass-association
Add regression test for a to-many relationship on a base class & mapped superclass in the hierarchy
2023-01-28 11:12:16 +01:00
Alexander M. Turek
c78f933e57 Psalm 5.6.0, PHPStan 1.9.14 (#10468) 2023-01-26 19:05:45 +01:00
Matthias Pigulla
80eb85beaa Add regression test for a to-many relationship on a base class & mapped superclass in the hierarchy
This picks the test case from #9517 and rebases it onto 2.14.x.

The problem has been covered in #8415, so this PR closes #9517 and fixes #9516.

Co-authored-by: Robert D'Ercole <bobdercole@gmail.com>
2023-01-24 20:47:41 +00:00
Alexander M. Turek
ed56f42cd5 Psalm 5.5.0 (#10445) 2023-01-23 17:33:19 +01:00
Matthias Pigulla
8b28543939 Avoid wasting Opcache memory with Paginator queries (#10434)
This PR prevents the Paginator from causing OpCache "wasted memory" to increase _on every request_ when used with Symfony's `PhpFilesAdapter` as the cache implementation for the query cache.

Depending on configured thresholds, wasted memory this will either cause periodic opcache restarts or running out of memory and not being able to cache additional scripts ([Details](https://tideways.com/profiler/blog/fine-tune-your-opcache-configuration-to-avoid-caching-suprises)).

Fixes #9917, closes #10095.

There is a long story (#7820, #7821, #7837, #7865) behind how the Paginator can take care of DBAL type conversions when creating the pagination query. This conversion has to transform identifier values before they will be used as a query parameter, so it has to happen every time the Paginator is used.

For reasons, this conversion happens inside `WhereInWalker`. Tree walkers like this are used only during the DQL parsing/AST processing steps. Having a DQL query in the query cache short-cuts this step by fetching the parsing/processing result from the cache.

So, to make sure the conversion happens also with the query cache being enabled, this line

1753d03500/lib/Doctrine/ORM/Tools/Pagination/Paginator.php (L165)

was added in #7837. It causes `\Doctrine\ORM\Query::parse()` to re-parse the query every time, but will also put the result into the query cache afterwards.

At this point, the setup described in #9917 – which, to my knowledge, is the default in Symfony + DoctrineBundle projects – will ultimately bring us to this code:

4b3391725f/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php (L248-L249)

When writing a cache item with an already existing key, the driver has to make sure the opcache will honor the changed PHP file. This is what causes _wasted memory_ to increase.

Instead of using `\Doctrine\ORM\Query::expireQueryCache()`, which will force `\Doctrine\ORM\Query::parse()` to parse the query again before putting it into the cache, use `\Doctrine\ORM\Query::useQueryCache(false)`. The subtle difference is the latter will not place the processed query in the cache in the first place.

A test case is added to check that repeated use of the paginator does not call the cache to update existing keys. That should suffice to make sure we're not running into the issue, while at the same time not complicating tests by using the `PhpFilesAdapter` directly.

Note that in order to observe the described issue in tests, you will need to use the `PhpFilesDriver` and also make sure that OpCache is enabled and also activated for `php-cli` (which is running the unit tests).

This particular subquery generated/used by the Paginator is not put into the query cache. The DQL parsing/to-SQL conversion has to happen _every time_ the Paginator is used.

This, however, was already the case before this PR. In other words, this PR only changes that we do not store/update the cached result every time, but instead completely omit caching the query.
2023-01-23 13:14:46 +01:00
Javier Spagnoletti
eec3c42494 Replace hardcoded name with Command::getName() in output message from UpdateCommand (#10443) 2023-01-23 12:51:16 +01:00
Grégoire Paris
bc394877bc Use the right property (#10441) 2023-01-22 14:04:12 +07:00
Grégoire Paris
1753d03500 Merge pull request #10433 from mpdude/re-enable-tests-7820
Make sure tests from #7837 are actually run
2023-01-21 10:02:34 +01:00
Grégoire Paris
7ce6d8d427 Merge pull request #10436 from greg0ire/update-baseline
Update Psalm baseline
2023-01-20 11:43:52 +01:00
Grégoire Paris
a48d95c71d Update Psalm baseline 2023-01-20 11:42:11 +01:00
Matthias Pigulla
aee1d33042 Review the documentation regarding entity inheritance (#10429)
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-01-19 19:59:28 +01:00
Matthias Pigulla
8da741ad75 Make sure tests from 7820 are actually run 2023-01-19 12:05:32 +00:00
Matthias Pigulla
ba7387fd8c Fixup GH8127 test case (#10424)
* Fixup GH8127 test case

This removes the unnecessary "middle2" class and puts back in the effect of the data provider.

Both changes were accidentally committed when I was working on #10411 and just meant as experiments during debugging.

* Fix CS
2023-01-18 14:42:26 +01:00
Grégoire Paris
a83e4f7978 Merge pull request #10418 from greg0ire/unique-bool
Use correct type for FieldMapping#unique
2023-01-18 07:37:15 +01:00
Grégoire Paris
4f335ab565 Merge pull request #10415 from greg0ire/maintain-psalm-xml
Remove ignore rules for fixed issues
2023-01-18 07:36:27 +01:00
Grégoire Paris
f88b0032ad Use correct type for FieldMapping#unique
Looking at usages in the codebase, it is supposed to be at least
bool|string, but not string.
When dumping the value inside `getFieldMapping`, it is always a boolean.
2023-01-17 16:29:07 +01:00
Grégoire Paris
69c7791ba2 Merge pull request #8415 from mpdude/mapped-superclass-association-inheritance
Fix association handling when there is a MappedSuperclass in the middle of an inheritance hierarchy
2023-01-17 16:25:20 +01:00
Grégoire Paris
1090dbe9be Merge pull request #10411 from mpdude/discover-missing-subclasses
Fill in missing subclasses when loading ClassMetadata
2023-01-17 16:13:53 +01:00
Matthias Pigulla
c46b604ed7 Fix CS 2023-01-17 14:40:55 +00:00
Matthias Pigulla
8d9ebeded8 Fix association handling when there is a MappedSuperclass in the middle of an inheritance hierarchy
This fixes two closely related bugs.

1. When inheriting a to-one association from a mapped superclass, update the `sourceEntity` class name to the current class only when the association is actually _declared_ in the mapped superclass.
2. Reject association types that are not allowed on mapped superclasses only when they are actually _declared_ in a mapped superclass, not when inherited from parent classes.

Currently, when a many-to-one association is inherited from a `MappedSuperclass`, mapping information will be updated so that the association has the current (inheriting) class as the source entity.

2138cc9383/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php (L384-L393)

This was added in 7dc8ef1db9 for [DDC-671](https://github.com/doctrine/orm/issues/5181).

The reason for this is that a mapped superclass is not an entity itself and has no table.

So, in the database, associations can only be from the inheriting entities' tables towards the referred-to target. This is also the reason for the limitation that only to-one associations may be added in mapped superclasses, since for those the database foreign key can be placed on the table(s) of the inheriting entities (and there may be more than one child class).

Neither the decision to update the `sourceEntity` nor the validation check should be based on `$parent->isMappedSuperclass`.

This only works in the simple case where the class hierarchy is `Mapped Superclass → Entity`.

The check is wrong when we have an inheritance hierarchy set up and the class hierarchy is `Base Entity → Mapped Superclass → Child Entity`.

Bug 1: The association should keep the root entity as the source. After all, in a JTI, the root table will contain the foreign key, and we need to base joins on that table when traversing `FROM LeafClass l JOIN l.target`.

Bug 2: Do not reject the to-many association declared in the base class. It is ok to have the reverse (owning) side point back to the base entity, as it would be if there were no mapped superclasses at all. The mapped superclass does not declare, add or otherwise interfere with the to-many association at all.

Base the decision to change the `sourceEntity` on `$mapping['inherited']` being set. This field points to the topmost _parent entity_ class in the ancestry tree where the relationship mapping appears for the first time.

When it is not set, the current class is the first _entity_ class in the hierarchy with that association. Since we are inheriting the relation, it must have been added in a mapped superclass above, but was not yet present in the nearest parent entity class.

In that case, it may only be a to-one association and the source entity needs to be updated.

(See #10396 for a clarification of the semantics of `inherited`.)

Here is a simplified example of the class hierarchy.

See the two tests added for more details – one is for checking the correct usage of a to-one association against/with the base class in JTI. The other is to test that a to-many association on the base class is not rejected.

I am sure that there are other tests that (still) cover the update of `sourceEntity` is happening.

```php
/**
 * @Entity
 */
class AssociationTarget
{
    /**
     * @Column(type="integer")
     * @Id
     * @GeneratedValue
     */
    public $id;
}

/**
 * @Entity
 * @InheritanceType("JOINED")
 * @DiscriminatorColumn(name="discriminator", type="string")
 * @DiscriminatorMap({"1" = "BaseClass", "2" = "LeafClass"})
 */
class BaseClass
{
    /**
     * @Column(type="integer")
     * @Id
     * @GeneratedValue
     */
    public $id;

    /**
     * @ManyToOne(targetEntity="AssociationTarget")
     */
    public $target;
}

/**
 * @MappedSuperclass
 */
class MediumSuperclass extends BaseClass
{
}

/**
 * @Entity
 */
class LeafClass extends MediumSuperclass
{
}
```

When querying `FROM LeafClass l`, it should be possible to `JOIN l.target`. This currently leads to an SQL error because the SQL join will be made via `LeafClass.target_id` instead of `BaseClass.target_id`. `LeafClass` is considered the `sourceEntity` for the association – which is wrong–, and so the foreign key field is expected to be in the `LeafClass` table (using JTI here).

Fixes #5998, fixes #7825.

I have removed the abstract entity class, since it is not relevant for the issue and took the discussion off course. Also, the discriminator map now contains all classes.

Added the second variant of the bug, namely that a to-many association would wrongly be rejected in the same situation.
2023-01-17 14:37:40 +00:00
Grégoire Paris
55f9178e84 Remove ignore rules for fixed issues
The ArgumentTypeCoercion one is hiding issues we could address.
Addressing them will require changes that should go in the next minor,
so I'm moving them to the baseline instead. That way, we cannot
introduce more issues of this type in this file.
2023-01-17 15:34:10 +01:00
Matthias Pigulla
60955755e0 Remove outdated todo 2023-01-17 08:49:36 +00:00
Matthias Pigulla
a8e979819a Include a test for DDC-6558 2023-01-16 21:13:30 +00:00
Matthias Pigulla
4e8e3ef30b Discover entity subclasses that need not be declared in the discriminator map 2023-01-16 20:33:25 +00:00
Grégoire Paris
de7eee5ed7 Merge pull request #10385 from nicolas-grekas/uninitialized-prop-reproducer
Fix initializing lazy objects and get rid of "Typed property must not be accessed before initialization" errors
2023-01-16 19:36:59 +01:00
Grégoire Paris
4051937fda Merge pull request #10412 from ThomasLandauer/patch-8
Adding link to Attributes reference
2023-01-16 19:35:20 +01:00
Thomas Landauer
b2e42dc92d Adding link to Attributes reference
In fact, I moved it upwards, and updated it to new target :-)
2023-01-16 17:19:29 +01:00
Grégoire Paris
f219b87870 Merge pull request #10393 from mpdude/traits-warning
Place a warning about the uses of traits in the documentation
2023-01-16 08:56:11 +01:00
Grégoire Paris
87fefbac34 Merge pull request #10396 from mpdude/document-inherited-declared-meaning
Document the meanings of 'inherited' and 'declared' in field mapping information
2023-01-15 15:40:38 +01:00
Matthias Pigulla
853e80ca98 Reword text 2023-01-14 22:56:23 +00:00
Grégoire Paris
d68baef880 Merge pull request #10404 from greg0ire/stable-phpbench
Stop allowing phpbench's master branch
2023-01-14 20:44:08 +01:00
Grégoire Paris
fdccfbd120 Stop allowing phpbench's master branch
A stable version has been published, it allows doctrine/annotations 2
2023-01-14 16:41:18 +01:00
Matthias Pigulla
180afa8c8f Write down what "transient" means (#10394)
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-01-14 10:33:58 +01:00
Grégoire Paris
dbbf5c7279 Merge pull request #10390 from greg0ire/wrong-phpdoc-exception
Use more accurate phpdoc for OptimisticLockException
2023-01-14 10:29:13 +01:00
Grégoire Paris
5d9b8f0ea8 Merge pull request #10399 from mpdude/fix-toothbrush-example
Fix DDL example for Mapped Superclasses
2023-01-14 10:28:24 +01:00
Matthias Pigulla
174947155d Fix DDL example for Mapped Superclasses
This was not updated to reflect the changes made when the example was improved in b3ee7141eb.
2023-01-14 08:14:04 +00:00
Grégoire Paris
2138cc9383 Sync variable name with class name (#10395) 2023-01-14 00:16:41 +01:00
Matthias Pigulla
39a434914d Be more vague about the Entity Generator 2023-01-13 23:04:40 +00:00
Matthias Pigulla
227f60c832 Update lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-01-13 23:51:39 +01:00
Matthias Pigulla
f72f6b199b Document the meanings of 'inherited' and 'declared' in field mapping information 2023-01-13 22:35:36 +00:00
Matthias Pigulla
92e63ca4f9 Place a warning about the uses of traits in the documentation 2023-01-13 13:33:53 +00:00
Grégoire Paris
3c98973ab3 Use more accurate phpdoc for OptimisticLockException
While working on migrating this part of the codebase to PHP 8, I found
phpdoc that is wrong.
2023-01-12 18:01:17 +01:00
Kevin Bond
3b8692fa4a add reproducer 2023-01-09 16:09:03 +01:00
Nicolas Grekas
c9efc1cdee Fix initializing lazy objects and get rid of "Typed property must not be accessed before initialization" errors 2023-01-09 15:58:20 +01:00
Alexander M. Turek
c5e4e41e05 PHPStan 1.9.8, Psalm 5.4.0 (#10382) 2023-01-09 11:16:44 +07:00
fauVictor
9431b2ea45 fix typo for missing a comma (#10377) 2023-01-06 16:31:05 +01:00
Thomas Landauer
036ea713a5 Docs: Removing type: 'integer' from mappings (#10368)
Yet another micro-PR ;-) - as requested at https://github.com/doctrine/orm/pull/10364#issuecomment-1370155521

I also changed `$currentPrice` from `float` to `int`, since IMO it's better to store prices as `int` (=cents).

Question: Is there a reason why most typehints are `int|null`, instead of `?int`? Should I change them?
2023-01-04 00:16:22 +01:00
Thomas Landauer
0852847659 Docs: Moving *attributes* mapping to first position (#10364) 2023-01-03 22:42:12 +01:00
Thomas Landauer
1e2625a82f Docs: Deleting duplicate mapping example (#10363)
I'm guessing this was forgotten to delete some time ago...
2023-01-03 20:43:26 +01:00
Alexander M. Turek
3010fd1680 PHPStan 1.9.5 (#10359) 2023-01-02 23:12:40 +01:00
Alexander M. Turek
85ac2769a9 Shorter deprecation message (#10357) 2022-12-31 17:43:12 +01:00
Axel Venet
28e98b3475 Add Fully-Qualified class name in UnrecognizedField exception to ease debugging (#10342) 2022-12-31 15:20:29 +01:00
Alexander M. Turek
99a37d864e Include parameter types in hydration cache key generation (#10355) 2022-12-31 00:44:23 +01:00
Alexander M. Turek
27df173971 Fix Psalm errors with Collection 2.1.2 (#10343) 2022-12-28 17:19:21 +01:00
Antonio Norman
0aa45dd607 Added warning about query cache in relation to parameters (#10276)
* Added warning about query cache in relation to parameters

* Updated warning about query cache in relation to parameters

* Update docs/en/reference/filters.rst

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>

* Update docs/en/reference/filters.rst

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2022-12-24 23:01:28 +01:00
Grégoire Paris
f8bf84d1aa Merge pull request #10088 from HypeMC/enums-in-simpleobjecthydrator 2022-12-20 16:50:24 +01:00
michnovka
c825e34f8d Improve and fix TypedFieldMapper docs (#10327) 2022-12-20 13:09:59 +01:00
Grégoire Paris
ff6bad486b Require dev version of phpbench (#10328)
It is important to have the same version of all dependencies in dev and
in the CI, otherwise it makes it hard to have the right static analysis
baseline for every environment.
2022-12-20 09:18:23 +01:00
Grégoire Paris
30a2680bfd Merge pull request #10325 from greg0ire/update-branch-metadata
Update branch metadata
2022-12-19 23:48:49 +01:00
Grégoire Paris
a460a4d054 Update branch metadata 2022-12-19 23:35:07 +01:00
Alexander M. Turek
f82485e651 Support doctrine/annotations 2 (#10320) 2022-12-19 22:51:58 +01:00
Grégoire Paris
c4835cca0d Drop forceful loading of annotations (#10321)
Since doctrine/annotations 1.10, autoloading is used when everything
else has failed. Using registerFile() has been deprecated in favor of
that later on.
2022-12-19 20:12:23 +01:00
Alexander M. Turek
82a406332e Document stdClass structures used by CommitOrderCalculator (#10315) 2022-12-19 15:43:46 +01:00
Alexander M. Turek
8093c2eef6 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Psalm 5.3.0 (#10317)
  PHPStan 1.9.4 (#10318)
2022-12-19 15:22:24 +01:00
Alexander M. Turek
099e51d899 Psalm 5.3.0 (#10317) 2022-12-19 15:04:28 +01:00
Alexander M. Turek
5df84d4ec0 PHPStan 1.9.4 (#10318) 2022-12-19 12:16:41 +01:00
Alexander M. Turek
f94cb9a5e6 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  add apcu enabled check if apcu extension loaded (#10310) (#10311)
2022-12-19 00:23:32 +01:00
aleksejs1
c23220b68a add apcu enabled check if apcu extension loaded (#10310) (#10311)
* add apcu enabled check if apcu extension loaded (#10310)

* Apply suggestions from code review

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>

* add use function apcu_enabled

* enable apcu_cli for cache tests

Co-authored-by: Aleksejs Kovalovs <kovalovs@co.inbox.lv>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2022-12-18 21:21:27 +01:00
michnovka
6255461b84 Add TypedFieldMapper for automatic mapping of typed PHP fields to DBAL types (#10313)
Previously, only a predefined set of automatic mappings was allowed, such as int, float, boolean, DateTime etc.
With this extension, it is possible to supply custom TypedFieldMapper implementation which takes as parameter the ReflectionProperty of a given field and decides the appropriate mapping.
A new configuration option was added to set and get the TypedFieldMapper.
The old logic was moved into a class DefaultTypedFieldMapper which is used by default when no mapper is supplied.
The selected TypedFieldMapper is passed into ClassMetadataInfo constructor. If empty, the DefaultTypedFieldMapper is used.
There is also ChainTypedFieldMapper class which allows chaining multiple TypedFieldMappers and apply them in a cascade (always if a field gets type assigned by the earlier mapper in the list, it will not be changed later).
2022-12-18 21:08:30 +01:00
Grégoire Paris
0aa5946286 Merge pull request #10301 from greg0ire/allow-lexer-2
Allow lexer 2
2022-12-14 20:29:27 +01:00
Grégoire Paris
eda7558674 Allow doctrine/lexer 2 2022-12-14 13:49:51 +01:00
Grégoire Paris
13bab31da6 Merge pull request #10302 from greg0ire/non-nullability-assertions
Add assertions about non nullability
2022-12-13 23:22:19 +01:00
Grégoire Paris
f960bc2c11 Add assertions about non nullability 2022-12-13 21:13:38 +01:00
Grégoire Paris
15058ca83e Merge pull request #10288 from michnovka/2.13.x-enum-discriminator-columns
Allow enum discriminator columns
2022-12-13 20:01:31 +01:00
Alexander M. Turek
9b14786738 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Bump coding standard to v11 (#10295)
  PHPStan 1.9.3 (#10298)
  Rename AbstractCommandTest (#10294)
2022-12-13 19:09:05 +01:00
Alexander M. Turek
83f6356f25 Bump coding standard to v11 (#10295) 2022-12-13 19:08:04 +01:00
Alexander M. Turek
4e6cb908f6 PHPStan 1.9.3 (#10298) 2022-12-13 18:51:58 +01:00
Tomas
497ee166bd Add support for enum discriminator columns
This commit adds enumType option to DiscriminatorColumn as well as support for custom DBAL types which use PHP enums for PHP values.
Previously, the enumType option was completely missing, but also even using custom types that used PHP enums would end up in exception because ObjectHydrator would try to convert enums to string using (string) explicit conversion.
Apart from hydrators, ClassMetadataBuilder was extended to support specifying enumType.
Documentation was updated.
2022-12-13 11:15:39 +01:00
Alexander M. Turek
9b723a88aa Rename AbstractCommandTest (#10294) 2022-12-12 16:36:16 +01:00
Alexander M. Turek
db18161a1a Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Fix changeset computation for enum arrays (#10277)
  Psalm 5.2.0 (#10291)
  Run tools on PHP 8.2 (#10287)
2022-12-12 13:59:45 +01:00
michnovka
bd11475615 Fix changeset computation for enum arrays (#10277)
Previously, array of enums were incorrectly compared in UoW::computeChangeSet() resulting always in false positive, since the original data for comparison is fetched using ReflectionEnumProperty::getValue(), which returns the enum values, not enum objects.
This fix ensures comparing the individual enum array members' values.
2022-12-12 13:58:21 +01:00
Alexander M. Turek
90f1f54e73 Psalm 5.2.0 (#10291) 2022-12-12 13:54:50 +01:00
Alexander M. Turek
b25561ad96 Run tools on PHP 8.2 (#10287) 2022-12-11 11:58:09 +01:00
Alexander M. Turek
284e81403b Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Fix association mapping with enum fields
  Correct spelling errors
2022-12-10 18:32:48 +01:00
Alexander M. Turek
3ea8550ca6 Control proxy implementation via env (#10282) 2022-12-10 18:11:02 +01:00
Grégoire Paris
aa4b62ce78 Merge pull request #10187 from nicolas-grekas/ve-proxy-2
Leverage LazyGhostTrait when possible
2022-12-09 14:14:42 +01:00
Nicolas Grekas
e5e674c686 Leverage LazyGhostTrait when possible 2022-12-08 22:09:29 +01:00
Grégoire Paris
b391431a0e Merge pull request #10274 from michnovka/2.13.x-complex-enum-ids
Fix enum IDs in association mappings
2022-12-08 08:59:52 +01:00
Tomas
d5a6b36e6f Fix association mapping with enum fields
Enum fields as ID have worked for some time, but referencing these fields from other entities in association mappings such as OneToOne, ManyToOne and ManyToMany resulted in fatal error as there was an attempt to convert enum value to string improperly.
2022-12-07 16:56:35 +01:00
HypeMC
9d5ab4ce76 Ensure consistent original data with enums
Previously different hydrators would store the original data for enum
fields in different ways, the SimpleObjectHydrator would keep them as
strings while other hydrators would convert then to native php enums.

This would make the data in the internal UnitOfWork::$originalEntityData
array inconsistent which could've caused problems in the long run.

Now, all hydrators convert enum fields to native php enums ensuring the
original data is always consistent regardless of the hydrator used.
2022-12-07 04:54:22 +01:00
Alexander M. Turek
a2b5bae923 Fix deprecation message (#10270) 2022-12-05 21:45:50 +01:00
Grégoire Paris
aeed977d6e Merge pull request #10271 from kalifg/patch-1
Correct spelling errors
2022-12-05 19:43:18 +01:00
Michael Dwyer
f66b008b8b Correct spelling errors 2022-12-05 12:31:10 -06:00
Alexander M. Turek
5a8541b450 Add $not constructor parameter to AST classes (#10267) 2022-12-05 11:44:53 +01:00
Grégoire Paris
fa18e130cb Merge pull request #10265 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-12-04 00:12:25 +01:00
Grégoire Paris
2ed3f55c01 Merge pull request #10264 from greg0ire/psalm-5.1
Upgrade to Psalm 5.1.0
2022-12-04 00:00:18 +01:00
Grégoire Paris
2dfb4f44e2 Upgrade to Psalm 5.1.0 2022-12-03 10:58:00 +01:00
Alexander M. Turek
24bf06725b Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Bump Psalm to 5.0.0 and fix errors for Symfony 6.2 (#10261)
  Leverage Lexer's Token type (follow up)
2022-11-30 22:01:47 +01:00
Alexander M. Turek
4b577e7a18 Bump Psalm to 5.0.0 and fix errors for Symfony 6.2 (#10261) 2022-11-30 22:00:40 +01:00
Grégoire Paris
d5ef6be4cc Merge pull request #10256 from greg0ire/parser-consistent-style
Make use statements redundant
2022-11-27 21:59:00 +01:00
Grégoire Paris
65da1fe8cb Merge pull request #10257 from greg0ire/fix-phpdoc-parser
Leverage Lexer's Token type (follow up)
2022-11-27 21:53:55 +01:00
Grégoire Paris
54336840e6 Make use statements redundant
In 68bc00b6c6, while fixing coding style,
I introduced many, many use statements in that file. Using
half-qualified names sometimes, and unqualified names some other times
makes no sense.

If AST\ is used at least once, use it always.
2022-11-26 17:09:39 +01:00
Grégoire Paris
74986f1d53 Merge pull request #10253 from greg0ire/2.14.x
Merge 2.13.x up into 2.14.x
2022-11-26 14:35:02 +01:00
Grégoire Paris
1df221860f Leverage Lexer's Token type (follow up)
Follow-up of f82db6a894.
The previous value introduced in
dc37c2cd2f was too restrictive, hence the
changes to the Psalm baseline.
2022-11-26 14:24:02 +01:00
Grégoire Paris
16cd49ba89 Merge remote-tracking branch 'origin/2.13.x' into 2.14.x 2022-11-26 14:18:14 +01:00
Grégoire Paris
57ac275137 Merge pull request #10252 from greg0ire/psalm-5
Upgrade to Psalm v5
2022-11-26 14:11:21 +01:00
Grégoire Paris
e958046c4a Upgrade to Psalm v5
It is not stable yet, but should be good enough, and this will help
taking care of #10118

Let us baseline all the new issues, just because they are new does not
mean they are more important than already-baselined errors. Besides, it
is more important to have higher standards for new code than to get an
increased baseline.
2022-11-26 13:41:02 +01:00
Grégoire Paris
3038f6aeef Psalm 5 fixes (#10248)
* Account for ClassMetadata::inheritanceType always being truthy

* Remove redundant cast to array
2022-11-24 21:28:23 +01:00
Grégoire Paris
28bf6cb1fa Merge pull request #10247 from derrabus/sa/paginator
Fix types for Paginator
2022-11-23 21:49:53 +01:00
Alexander M. Turek
e864c4cbc2 Fix types for Paginator 2022-11-23 21:40:17 +01:00
Grégoire Paris
f9d5a89a39 Merge pull request #10242 from VincentLanglet/staticAnalysis
Solve some PHPStan baseline errors
2022-11-21 08:09:27 +01:00
Vincent Langlet
db7333cc84 Update baseline 2022-11-20 23:55:47 +01:00
Vincent Langlet
47b4ccc4e6 Solve some baseline errors 2022-11-20 22:49:51 +01:00
Grégoire Paris
5a7fce12b8 Merge pull request #10238 from VincentLanglet/lockMode
Use more precise phpdoc
2022-11-20 22:20:08 +01:00
Vincent Langlet
cc9e456ed8 Use more precise phpdoc 2022-11-20 21:56:59 +01:00
Alexander M. Turek
da356316c1 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Remove fragile assertions (#10239)
2022-11-20 20:57:47 +01:00
Grégoire Paris
a5a6cc6630 Remove fragile assertions (#10239)
RuntimePublicReflectionProperty has been deprecated in favor of
RuntimeReflectionProperty.

Let us use a more robust assertion.
2022-11-20 19:53:31 +01:00
Grégoire Paris
1ce806fcb7 Merge pull request #10231 from greg0ire/static-analysis-improvements
Make the code easier to statically analyse
2022-11-16 08:08:49 +01:00
David Maicher
958d0b6193 update help for RunDqlCommand (#10233) 2022-11-15 17:38:56 +01:00
Grégoire Paris
843f3c3b23 Make the code easier to statically analyse 2022-11-14 23:00:25 +01:00
Grégoire Paris
82e4c644f9 Merge pull request #10230 from greg0ire/2.14.x
Merge 2.13.x up into 2.14.x
2022-11-14 22:50:32 +01:00
Grégoire Paris
9399f1f3a8 Merge remote-tracking branch 'origin/2.13.x' into 2.14.x 2022-11-14 22:43:56 +01:00
Grégoire Paris
fc3201bded Merge pull request #10229 from greg0ire/fix-phpdoc-exception
Widen parameter type
2022-11-14 22:15:29 +01:00
Grégoire Paris
ad69810775 Merge pull request #10224 from greg0ire/fix-wrong-phpdoc
Document property as non-nullable
2022-11-13 22:11:54 +01:00
Grégoire Paris
3178b4ec4f Widen parameter type
This exception is used in two places, one where $generatedMode is
clearly a string, and another where typing it as a string will cause a
type error, because $generatedMode is supposed to be an int there.
2022-11-13 19:13:40 +01:00
Grégoire Paris
6c7a5e6faa Document property as non-nullable
The constructor forbids it.
2022-11-11 18:39:25 +01:00
Grégoire Paris
dcc1c26826 Merge pull request #10222 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-11-11 16:31:42 +01:00
Grégoire Paris
465c02fe68 Reverse-engineer actual type from code (#10221)
The code contains tests for is_array(), is object(), and an else clause.
The type is wrong, and the variable name misleading.
2022-11-11 16:29:36 +01:00
Grégoire Paris
8afb644a18 Ignore PropertyNotSetInConstructor (#10218)
Inside the Query\AST namespace, many classes use public properties that
are supposed to be set from outside the class. Let us ignore
PropertyNotSetInConstructor for that entire namespace instead of doing
it on a case by case basis.
2022-11-11 14:56:31 +01:00
Alexander M. Turek
953e42d059 Add a constructor to CacheKey (#10212) 2022-11-11 10:50:07 +01:00
Alexander M. Turek
318af0a666 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Psalm 4.30.0, PHPStan 1.9.2 (#10213)
2022-11-11 10:16:45 +01:00
Alexander M. Turek
7e45ad935c Psalm 4.30.0, PHPStan 1.9.2 (#10213) 2022-11-10 22:29:50 +01:00
Willem Verspyck
90ececcc85 Allow "Expr\Func" as condition in join (#10202) 2022-11-10 14:43:40 +01:00
Simon Podlipsky
88b36e07e1 refactor: use list type in SchemaTool (#10199) 2022-11-10 14:22:22 +01:00
Grégoire Paris
a37c2cc05f Merge pull request #10206 from greg0ire/rename-variable
Use a more accurate name for $annotationName
2022-11-06 22:13:19 +01:00
Grégoire Paris
40b34b03c1 Use a more accurate name for $annotationName 2022-11-06 21:44:29 +01:00
Grégoire Paris
8d9ab72613 Merge pull request #10204 from greg0ire/rename-internal-methods
Rename internal methods
2022-11-06 21:26:20 +01:00
Grégoire Paris
12f0674b1a Deprecate AttributeDriver::$entityAnnotationClasses 2022-11-06 21:04:05 +01:00
Grégoire Paris
069206ba14 Refer to attributes in comments and variable names 2022-11-06 21:04:05 +01:00
Grégoire Paris
e8a4d2e91b Remove useless comment
Cannot tell what this ignores.
2022-11-06 21:04:05 +01:00
Grégoire Paris
284baf890e Improve phpdoc 2022-11-06 21:04:05 +01:00
Grégoire Paris
a1f9b28cdc Replace Annotations with Attributes in method names
These are internal, so it should be fine.
2022-11-06 21:04:04 +01:00
Grégoire Paris
2b7485af97 Merge pull request #10205 from greg0ire/avoid-references-to-annotations
Avoid references to annotations
2022-11-06 20:56:45 +01:00
Grégoire Paris
edb6332359 Avoid $annotation as a parameter name 2022-11-06 14:58:38 +01:00
Grégoire Paris
48c1eef1b8 Migrate comments to attributes 2022-11-06 14:51:02 +01:00
Alexander M. Turek
5d88dc9be4 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  PHPStan 1.9.0 (#10200)
2022-11-04 13:08:38 +01:00
Alexander M. Turek
0b14c01b93 PHPStan 1.9.0 (#10200) 2022-11-03 21:04:02 +01:00
michnovka
c97f0a1078 Backport NonProxyLoadingEntityManager::refresh() signature change (#10197) 2022-11-02 16:49:13 +01:00
Alexander M. Turek
474f76fc8b Remove Doctrine\Persistence\ObjectManager::refresh from Psalm baseline 2022-11-02 00:15:00 +01:00
michnovka
25ce9b9101 Add lockMode to EntityManager#refresh() (#10040) 2022-11-01 23:59:11 +01:00
Alexander M. Turek
75340b68b2 Merge 2.13.x into 2.14.x (#10190) 2022-10-31 09:20:30 +01:00
Alexander M. Turek
543be3fe35 Deprecate the Annotation interface (#10178) 2022-10-26 21:51:46 +02:00
Alexander M. Turek
552d98d554 Bump CI to PHP 8.2 and latest database versions (#10180) 2022-10-26 15:12:52 +02:00
Alexander M. Turek
8f605c652a Remove reference to deprecated DriverChain from docs (#10179) 2022-10-26 15:09:12 +02:00
Alexander M. Turek
1edfa91714 Merge 2.13.x into 2.14.x (#10181) 2022-10-26 11:53:28 +02:00
Alexander M. Turek
75e42abfdf PHPStan 1.8.11 (#10182) 2022-10-26 11:36:22 +02:00
Romain Monteil
3133bf06c2 Add isMemberOf and isInstanceOf to Expr helper list (#10104)
* Add isMemberOf and isInstanceOf to Expr helper list

* Apply suggestions from code review

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-10-26 11:04:17 +02:00
Grégoire Paris
7cf4074d3a Migrate more references to annotations (#10176) 2022-10-26 10:42:42 +02:00
Jonny Eom
bb1deba510 Fix grammer in working-with-objects (#10120) 2022-10-26 10:39:05 +02:00
HypeMC
c828a3814b Automap events in AttachEntityListenersListener (#10122) 2022-10-26 10:36:06 +02:00
Grégoire Paris
a5553a0786 Adapt use statements to the code (#10174) 2022-10-25 23:25:36 +02:00
Grégoire Paris
cf91ce63d3 Merge pull request #10153 from greg0ire/address-dbal-deprecations
Address dbal deprecations
2022-10-24 08:53:38 +02:00
Alexander M. Turek
f3f453286f Deprecate EntityManager::create() (#9961) 2022-10-24 00:15:56 +02:00
Grégoire Paris
7cb96fcf0e Address deprecation of SchemaDiff::toSaveSql()
This implies deprecating a feature relying on that method.
2022-10-23 23:29:36 +02:00
Grégoire Paris
ac94d826dc Address deprecation of SchemaDiff::toSql()
The new method AbstractPlatform::getAlterSchemaSQL() should be preferred
when available.
2022-10-23 22:58:31 +02:00
Grégoire Paris
f33919d7d6 Merge pull request #10162 from greg0ire/stderr-for-humans
Use error style for notifications
2022-10-23 22:56:18 +02:00
Grégoire Paris
f256d996cc Use error style for notifications
stderr is not a great name for something that is not meant to be
processed (piped into) a program, but to be read by humans.
Most commands should use stderr, and some of them should partially use
stdout, typically when dumping SQL requests.
2022-10-23 21:51:12 +02:00
Grégoire Paris
d617323a48 Merge pull request #10161 from greg0ire/deprecate-mapping-drivers
Make the mapping driver deprecations more obvious
2022-10-23 10:30:43 +02:00
Alexander M. Turek
6b61e26238 Fix calls to AbstractSchemaManager::createSchema() (#10165) 2022-10-22 17:04:53 -07:00
Alexander M. Turek
edad800711 Merge 2.13.x into 2.14.x (#10164) 2022-10-23 01:19:54 +02:00
Alexander M. Turek
0b9c949590 Fix build with DBAL 3.5 (#10163) 2022-10-23 01:11:03 +02:00
Grégoire Paris
3ee7d96179 Adjust comments (#10160) 2022-10-22 15:34:10 +02:00
Grégoire Paris
fba05675b6 Deprecate methods related to the annotation driver 2022-10-22 15:23:57 +02:00
Grégoire Paris
8160e89c5a Use correct link 2022-10-22 15:05:19 +02:00
Grégoire Paris
a76b776802 Deprecate annotations 2022-10-22 14:54:12 +02:00
Grégoire Paris
ad1d1ca942 Remove trailing whitespace 2022-10-22 14:54:12 +02:00
Grégoire Paris
be835bb8e2 Merge remote-tracking branch 'origin/2.13.x' into 2.14.x 2022-10-21 21:28:17 +02:00
Grégoire Paris
9f926f04ba Merge pull request #10157 from greg0ire/followup-migrate-docs-to-attributes
Migrate more documentation towards attributes
2022-10-21 21:25:08 +02:00
Grégoire Paris
4e445feb6c Migrate more documentation towards attributes
The previous rework missed some details / left the documentation in an
inconsistent state. Searching for "annotation" case insensitively allows
to find discrepancies and forgotten things.
2022-10-21 12:31:10 +02:00
Grégoire Paris
d79e61f8d9 Remove wrong sentence 2022-10-21 12:31:10 +02:00
Simon Podlipsky
06eafd82ac Do not export phpstan stuff (#10154) 2022-10-19 13:36:12 +02:00
Alexander M. Turek
bac784c9ba Merge 2.13.x up into 2.14.x (#10149) 2022-10-17 23:19:27 +02:00
Grégoire Paris
5f4b76b88f Merge pull request #10126 from greg0ire/migrate-docs-to-attributes
Modernize documentation code
2022-10-17 23:11:33 +02:00
Grégoire Paris
794777b21f Modernize documentation code
- migrate to attributes;
- add helpful phpdoc annotations;
- use typed properties;
- add type declarations.

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-10-17 22:56:34 +02:00
Alexander M. Turek
119c378a3a Add CI jobs for SQLite3 driver (#10141) 2022-10-17 22:48:07 +02:00
Grégoire Paris
90bc6dc300 Merge pull request #10136 from greg0ire/trigger-sa-workflows-less
Stop triggering static analysis workflows on tests
2022-10-17 22:45:22 +02:00
Alexander M. Turek
1ad936a448 Fix type doc blocks in annotation classes (#10145) 2022-10-17 22:20:08 +02:00
Ondřej Mirtes
e93e8e0bdf Fix FieldMapping for generated key (#10144) 2022-10-17 15:37:06 +02:00
Alexander M. Turek
7e75807918 Merge 2.13.x into 2.14.x (#10139) 2022-10-17 10:14:22 +02:00
Grégoire Paris
bdf067b58a Stop triggering static analysis workflows on tests
In my opinion it is not great that we do not run static analysis tools on
tests, but since we do not, let us stop triggering extra jobs for no reason.
2022-10-15 23:59:57 +02:00
Grégoire Paris
f08b67f0cc Merge pull request #10135 from greg0ire/remove-extra-file
Remove file suffixed with singular
2022-10-15 11:59:05 +02:00
Grégoire Paris
5f8504b5cf Remove file suffixed with singular
It was wrongly preserved after a conflict that followed a rename.
2022-10-15 11:57:22 +02:00
Grégoire Paris
16e25656d9 Merge pull request #10134 from greg0ire/lighter-builds-💚🌍
Run only relevant workflows
2022-10-15 11:48:21 +02:00
Grégoire Paris
cacdc56b6e Run only relevant workflows
The downside of this is that we will have to tweak the settings so that
no job is required anymore.
The upside is that builds should be faster, and less resource-intensive.
2022-10-15 11:44:34 +02:00
Alexander M. Turek
90f82202a8 Merge 2.13.x into 2.14.x (#10129) 2022-10-13 15:21:22 +02:00
Alexander M. Turek
97aa5b37e6 Allow doctrine/event-manager 2 (#10123) 2022-10-13 13:15:37 +02:00
Alexander M. Turek
13d1c7b286 Psalm 4.29 (#10128) 2022-10-13 10:03:12 +02:00
Grégoire Paris
1a9f40c785 Address deprecation of Table::changeColumn() (#10121)
It is deprecated in favor of Table::modifyColumn().
2022-10-11 19:38:01 +02:00
Pierre du Plessis
06c77cebb5 Make code blocks consistent (#10103) 2022-10-10 23:31:20 +02:00
HypeMC
1ed0057276 Fix change set computation with enums (#10074)
* add failing testcase test enum Change sets

* Fix change set computation with enums

Co-authored-by: Olda Salek <mzk@mozektevidi.net>
2022-10-10 11:59:04 +02:00
Grégoire Paris
7ce9a6fe5c Merge pull request #10116 from greg0ire/address-dbal-deprecations
Address deprecations form DBAL
2022-10-10 08:42:54 +02:00
Grégoire Paris
8e062955d5 Address deprecations from DBAL
See https://github.com/doctrine/dbal/pull/5731
2022-10-10 08:42:21 +02:00
Alexander M. Turek
5b6f3cd598 PHPStan 1.8.8, Psalm 4.28.0 (#10115) 2022-10-09 23:31:52 +02:00
Grégoire Paris
38271d4aeb Merge pull request #10110 from davidromani/2.13.x
Fix deprecated trigger help comment
2022-10-08 22:42:56 +02:00
David Romaní
84213b9f05 fix deprecated trigger help comment 2022-10-07 18:03:42 +02:00
Grégoire Paris
0e65b0c3dc Merge pull request #10108 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-10-07 08:52:39 +02:00
Grégoire Paris
e750360bd5 Merge pull request #10101 from greg0ire/followup-9488
Assert that serialization leaves PersistentCollection usable
2022-10-07 08:37:17 +02:00
Alexander M. Turek
e14e9bebcc Merge pull request #10105 from greg0ire/backport-dbal-fixes
Backport DBAL-related fixes
2022-10-06 19:57:45 +02:00
Grégoire Paris
e8ac1169ad Bump CI workflows (#10106) 2022-10-06 16:57:00 +02:00
Grégoire Paris
9422260efd Specify precision for decimal columns
doctrine/dbal 4 no longer provides a default value for that column
option.
2022-10-06 11:32:28 +02:00
Grégoire Paris
d46512332c Address AbstractSchemaManager::createSchema() removal 2022-10-06 10:56:38 +02:00
Grégoire Paris
0d56ffc261 Address method rename
See https://github.com/doctrine/dbal/pull/5724
2022-10-06 10:46:22 +02:00
Grégoire Paris
dd8c7003b8 Assert that serialization leaves PersistentCollection usable
That feature is no longer covered since support for merging entities in
the entity manager was removed.
2022-10-06 10:40:06 +02:00
Grégoire Paris
9bec416bb0 Merge pull request #10100 from greg0ire/forward-compat-attributes
Forward compat attributes
2022-10-05 22:44:49 +02:00
Grégoire Paris
021722acc7 Remove ignored annotation
Making it a separate, valid annotation breaks the tests
2022-10-05 22:15:04 +02:00
Grégoire Paris
5a528bef5d Use short class name
It is more friendly with conversion to attributes
2022-10-05 22:00:44 +02:00
Grégoire Paris
5fbcb18334 Spell cities properly 2022-10-05 21:55:24 +02:00
Grégoire Paris
8280f41fa5 Merge pull request #10099 from greg0ire/fix-attribute-docs
Fix attribute reference
2022-10-05 21:48:46 +02:00
Grégoire Paris
d95d8d0a19 Fix broken links 2022-10-05 21:40:09 +02:00
Grégoire Paris
d36004f825 Remove trailing whitespace 2022-10-05 21:39:31 +02:00
Grégoire Paris
18366db578 Remove reference to JoinColumns
While that class can be an annotation, it cannot be an attribute because
it is not necessary when using attributes.

See https://github.com/doctrine/orm/issues/9986
2022-10-05 21:38:38 +02:00
Grégoire Paris
95c818928e Merge pull request #10098 from greg0ire/deprecate-annotations 2022-10-05 13:37:57 +02:00
Grégoire Paris
f27034880a Remove trailing whitespace 2022-10-05 12:24:53 +02:00
Grégoire Paris
3735822662 Deprecate annotation driver
No new project should use that driver.
2022-10-05 12:24:53 +02:00
Grégoire Paris
52f7ddc680 Use appropriate directive 2022-10-05 09:46:43 +02:00
Grégoire Paris
de32d8239f Merge pull request #10089 from greg0ire/remove-unneeded-checks
Document properties as possibly null
2022-10-04 23:28:01 +02:00
Grégoire Paris
1b5bef3d4d Merge pull request #10094 from doctrine/fix_doc_toc 2022-10-04 15:55:16 +02:00
Christophe Coevoet
880b622fe6 Fix heading levels for the embeddable tutorial 2022-10-04 14:13:44 +02:00
Grégoire Paris
397751ee65 Update Psalm baseline 2022-10-03 21:41:04 +02:00
Grégoire Paris
b0038eeea6 Document properties as possibly null
__sleep() does not list these properties as suposed to be serialized.
This means we have to assert that it is not in the many scenarios where
we resort to these properties. Introducing a private method allows us to
centralize the assertions.
2022-10-03 21:41:03 +02:00
Grégoire Paris
f06f9f843e Merge pull request #10093 from doctrine/2.13.x
Merge 2.13.x up in 2.14.x
2022-10-03 21:40:34 +02:00
Grégoire Paris
cbf141f7ed Add missing variable name in param phpdoc and switch to psalm-assert (#10092)
* Add missing variable name in param phpdoc

* Use psalm-assert instead of psalm-param

The parameter can in fact be any string, but if an exception is not
thrown, then it was a valid value.
2022-10-03 21:17:21 +02:00
Grégoire Paris
5d3fc62023 Merge pull request #10086 from franmomu/create_specific_event_classes
Create dedicated event argument classes
2022-10-02 21:49:09 +02:00
Grégoire Paris
154e7130f5 Merge pull request #10091 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-10-02 16:54:10 +02:00
Grégoire Paris
536bb4f678 Merge pull request #10090 from greg0ire/update-psalm-baseline
Update Psalm baseline
2022-10-02 16:23:14 +02:00
Grégoire Paris
3f2f42277e Update Psalm baseline 2022-10-02 16:02:57 +02:00
Fran Moreno
99f044cbc7 Deprecate LifecycleEventArgs 2022-10-01 14:39:31 +02:00
Fran Moreno
e8ca7b4abf Add dedicated classes for events 2022-10-01 14:39:31 +02:00
Grégoire Paris
b05fb96ad3 Merge pull request #10070 from greg0ire/preview-coll-2
Add support for doctrine/collection 2
2022-09-29 23:30:59 +02:00
Grégoire Paris
bb020320ca Allow collections 2 2022-09-29 23:08:43 +02:00
Grégoire Paris
1e0ac8899c Merge pull request #10079 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-09-29 23:07:20 +02:00
Grégoire Paris
757d950128 Merge pull request #10080 from greg0ire/add-missing-template-annotation
Address changes from doctrine/collections 1.8.0
2022-09-29 22:58:03 +02:00
Grégoire Paris
f07840b10c Address changes from doctrine/collections 1.8.0
Not quite sure why this annotation is not inherited.
2022-09-29 17:07:57 +02:00
Grégoire Paris
6e8afeeb60 Merge pull request #10072 from greg0ire/fix-return-type 2022-09-27 13:50:49 +02:00
Grégoire Paris
9130b27aca Remove return type override
In the parent class, all 3 methods are documented to return mixed.
This was not forward compatible with the addition of return types.
2022-09-27 13:41:09 +02:00
Grégoire Paris
2e7c2bb385 Merge pull request #10058 from HypeMC/enum-with-query-builder-fix
Fix using enums with the QueryBuilder
2022-09-23 22:08:15 +02:00
HypeMC
d69a0fa2cf Fix using enums with the QueryBuilder 2022-09-22 22:39:52 +02:00
Grégoire Paris
4a04c8c2db Merge pull request #10055 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-09-22 15:49:21 +02:00
Grégoire Paris
a8b02fd70f Merge pull request #10054 from greg0ire/sa-fixes 2022-09-22 15:36:43 +02:00
Alexander M. Turek
60adc6b7d9 Fix CS 2022-09-22 12:06:40 +02:00
Alexander M. Turek
c65ff6651c Merge branch '2.13.x-enum-arrayhydration' into 2.13.x
* 2.13.x-enum-arrayhydration:
  Fix ArrayHydration of enums
2022-09-22 12:04:22 +02:00
Alexander M. Turek
5f29fcdea2 Revert "Fix EnumType not being hydrated with HYDRATE_ARRAY (#9995)"
This reverts commit bb3ce7e802.
2022-09-22 12:04:09 +02:00
Grégoire Paris
470e2ddd05 Merge pull request #10045 from nicolas-grekas/proxy-common-less 2022-09-22 11:29:02 +02:00
Nicolas Grekas
6ec5ab9145 Deprecate Doctrine\ORM\Proxy\Proxy and decouple a bit more from Doctrine\Common\Proxy 2022-09-22 09:56:30 +02:00
Grégoire Paris
2b8ac15813 Mark ClassMetadataInfo::sequenceGeneratorDefinition as nullable 2022-09-22 09:12:50 +02:00
Alexander M. Turek
86000d9c70 Merge 2.13.x into 2.14.x (#10051) 2022-09-22 08:41:04 +02:00
Grégoire Paris
6dd07e4c76 Merge pull request #9983 from VincentLanglet/discriminatorColumn
Add phpdoc for ClassMetadataInfo::discriminatorColumn property
2022-09-21 23:17:54 +02:00
Alexander M. Turek
328bf707cc Merge 2.13.x into 2.14.x (#10046) 2022-09-18 15:20:51 +02:00
Alexander M. Turek
0d043059b9 PHPStan 1.8.5, Psalm 4.27.0 (#10033) 2022-09-18 15:06:51 +02:00
Ilya Shashilov
bb3ce7e802 Fix EnumType not being hydrated with HYDRATE_ARRAY (#9995)
Co-authored-by: Ilya Shashilov <kvushiha@gmail.com>
2022-09-14 14:33:42 +02:00
Tomas
bc7e252f00 Fix ArrayHydration of enums 2022-09-12 17:01:40 +02:00
Carlos Buenosvinos
498da2ff98 "Strange" return lines in documentation of inheritance-mapping.rst (#10027) 2022-09-04 18:16:02 +02:00
Carlos Buenosvinos
73e1e42ab5 More strange break lines in inheritance-mapping.rst (#10028) 2022-09-03 21:40:53 +02:00
Alexander M. Turek
7a9037e9d7 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Bump Ubuntu version and shared workflows (#10020)
  Fail gracefully if EM could not be constructed in tests (#10008)
2022-08-30 21:22:58 +02:00
Alexander M. Turek
5d11648767 Bump Ubuntu version and shared workflows (#10020) 2022-08-30 21:10:38 +02:00
Vincent Langlet
8d03f8f089 Make paginator covariant (#10002) 2022-08-30 13:43:14 +02:00
Alexander M. Turek
0ecac1b255 Fail gracefully if EM could not be constructed in tests (#10008) 2022-08-30 13:40:54 +02:00
Grégoire Paris
8dfe8b8782 Merge pull request #10010 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-08-27 21:07:59 +02:00
Grégoire Paris
b2a4fac40b Merge pull request #10009 from greg0ire/cs-update
Upgrade to doctrine/coding-standard 10.0.0
2022-08-27 20:53:59 +02:00
Grégoire Paris
beeba93a53 Upgrade to doctrine/coding-standard 10.0.0 2022-08-27 19:04:47 +02:00
Alexander M. Turek
4c253f2403 Merge pull request #10006 from derrabus/fix/build
Fix build
2022-08-26 13:07:34 +02:00
Alexander M. Turek
46ec86557e Bump coding standard to 9.0.2 2022-08-26 12:27:20 +02:00
Alexander M. Turek
f287b74470 Fix tests for doctrine/common 3.4 2022-08-26 00:34:50 +02:00
Alexander M. Turek
12d086551e Fix static analysis errors for Collections 1.7 2022-08-26 00:09:37 +02:00
Alberto Acha
5283e1441c Fix type in docs (#9994) 2022-08-16 20:40:01 +02:00
Michael Olšavský
18be6d2218 Improve orphan removal documentation - recommend using cascade=persist (#9848)
* Improve orphanRemoval documentation

* Wording improvement

* Update docs/en/reference/working-with-associations.rst

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2022-08-09 21:39:43 +02:00
Vincent Langlet
2fda625dba Add phpdoc for discriminatorColumn 2022-08-08 22:34:18 +02:00
Alexander M. Turek
35c44a5667 Backport type fixes for EntityListenerResolver (#9977) 2022-08-08 11:00:16 +02:00
Fran Moreno
0215b6b9fb Undeprecate LifecycleEventArgs (#9980)
Deprecating this class means that users using SA tools have to
update their code specifying the ObjectManager implementation.

Instead of this, dedicated classes for each event should be
created before deprecating this class.
2022-08-08 10:59:03 +02:00
Fran Moreno
cc9272f53f Update documentation to not use deprecated method (#9979) 2022-08-08 07:46:05 +00:00
Alexander M. Turek
e3e7f3c209 Update branch metadata (#9971) 2022-08-07 19:56:19 +02:00
Alexander M. Turek
c9870a3d82 Remove calls to deprecated Type::getName() (#9972) 2022-08-07 19:55:23 +02:00
Alexander M. Turek
827cb0c10b Address DBAL 3.4 deprecations (#9969) 2022-08-07 18:11:43 +02:00
Vincent Langlet
4d19c0ea71 Improve phpdoc for ClassMetadataInfo (#9965) 2022-08-07 16:00:01 +00:00
Alexander M. Turek
17cfb944f2 Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  Fix build (#9964)
  fix: class normalisation test (#9966)
2022-08-07 17:39:22 +02:00
Romain Canon
33f4db8405 Support native enum hydration when using NEW operator (#9936)
Using the `NEW` operator with the query builder now properly converts
scalar values to native enums inside data transfer objects.
2022-08-04 00:33:46 +02:00
Alexander M. Turek
fa63a395cc Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  Update branch info in README and .doctrine-project.json (#9943)
2022-08-02 22:35:33 +02:00
Alexander M. Turek
2ec2c585b0 Deprecate QueryBuilder APIs exposing its internal state (#9945)
Co-authored-by: Sergei Morozov <morozov@tut.by>
2022-08-01 11:42:24 +02:00
Alexander M. Turek
cd95b2a9e5 Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  Psalm 4.25.0, PHPStan 1.8.2 (#9941)
  Use a more precise phpdoc for ClassMetadataInfo::versionField than mixed (#9937)
2022-07-28 19:38:58 +02:00
Grégoire Paris
38a9a1c795 Stop passing event manager to constructor (#9938)
Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-07-28 16:47:04 +00:00
Grégoire Paris
2ebe18a822 Merge pull request #9915 from nicolas-grekas/pub-em 2022-07-28 14:55:34 +02:00
Nicolas Grekas
518d7f2ef1 Make EntityManager @final and its constructor public 2022-07-27 17:15:40 +02:00
Alexander M. Turek
7684cea8ef Add helper function TreeWalkerAdapter::getMetadataForDqlAlias() (#9932) 2022-07-25 20:54:31 +02:00
Alexander M. Turek
5085dbe94b Merge 2.12.x into 2.13.x (#9931) 2022-07-25 18:49:29 +02:00
Grégoire Paris
2da28703e3 Merge pull request #9903 from glaszig/fix/dump-sql
schema tool: remove useless text from --dump-sql output
2022-07-19 23:13:51 +02:00
glaszig
20cec8ed79 schema tool: remove useless text from --dump-sql output
the description and semantics of the `--dump-sql` switch
indicate and sql dump. without that useless line

  The following SQL statements will be executed:

the output can actually be used to generate a plain
dump.sql file that can be fed into sql command-
consuming programs.

resolves #8263 and #7186.
2022-07-19 19:33:48 +00:00
Grégoire Paris
c125a856d0 Merge pull request #9914 from greg0ire/fix-build
Avoid supportsCreateDropDatabase()
2022-07-18 23:00:38 +02:00
Grégoire Paris
d7db596cb4 Avoid supportsCreateDropDatabase()
It has been deprecated.
2022-07-18 22:42:13 +02:00
Grégoire Paris
3a9aa5b8c6 Merge pull request #9910 from doctrine/2.12.x
Merge 2.12.x up into 2.13.x
2022-07-18 21:31:50 +02:00
Grégoire Paris
61d405162f Merge pull request #9906 from franmomu/deprecate_lifecycleevent
Deprecate `LifecycleEventArgs`
2022-07-17 17:35:37 +02:00
Fran Moreno
b0f15e070d Deprecate LifecycleEventArgs
Use LifecycleEventArgs from doctrine/persistence instead.
2022-07-17 12:06:10 +02:00
Grégoire Paris
fbb7e24594 Merge pull request #9895 from greg0ire/avoid-sql-assertions
Avoid SQL assertions
2022-07-14 19:09:32 +02:00
Grégoire Paris
888a4a8eff Avoid SQL assertions
doctrine/dbal is the component responsible for generating the queries.
Let us make the test suite more robust by asserting that things work
from a functional point of view.
2022-07-12 08:01:19 +02:00
Grégoire Paris
48b4f63f61 Merge pull request #9893 from greg0ire/2.13.x
Merge 2.12.x up into 2.13.x
2022-07-10 23:18:39 +02:00
Grégoire Paris
4a62b661a5 Merge remote-tracking branch 'origin/2.12.x' into 2.13.x 2022-07-10 23:07:44 +02:00
Alexander M. Turek
1538d70bb9 Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  PHPStan 1.8.0 (#9887)
  Fix typo in AbstractQuery
  ObjectHydrator: defer initialization of potentially empty collections
  Migrate more usages of SchemaTool::createSchema()
  preUpdate: Add restriction that changed field needs to be in computed changeset (#9871)
2022-07-08 13:54:43 +02:00
Grégoire Paris
68405f3e5b Merge pull request #9876 from franmomu/extend_event_manager_args
Change parent classes in some events
2022-07-04 19:21:56 +02:00
Fran Moreno
eea53397c5 Change parent classes
*FlushEventArgs classes should extend ManagerEventArgs from
doctrine/persistence to be able to use ManagerEventArgs for any
persistance implementation.

OnClearEventArgs should extend from OnClearEventArgs from
doctrine/persistence.
2022-07-02 14:27:36 +02:00
Alexander M. Turek
dc8ddfd3e6 Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  Psalm 4.24.0, PHPStan 1.7.15 (#9865)
  PHP CodeSniffer 3.7.1, PHPStan 1.7.14 (#9858)
2022-06-28 13:35:26 +02:00
Hans Mackowiak
b931a59ebc Deprecate omitting the alias in QueryBuilder (#9765)
… methods update() and delete()

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-06-17 10:33:11 +02:00
Alexander M. Turek
d15eef9051 Merge release 2.12.3 into 2.13.x (#9853) 2022-06-17 10:22:16 +02:00
Grégoire Paris
5ccb59fa02 Merge pull request #9839 from morozov/list-tables
Use proper API for introspection of tables
2022-06-11 22:41:41 +02:00
Sergei Morozov
2e927970ca Use proper API for introspection of tables 2022-06-11 12:13:25 -07:00
Grégoire Paris
0366a5796f Merge pull request #9837 from greg0ire/address-getdbplatform-deprecation
Address deprecation of getDatabasePlatform()
2022-06-10 20:14:06 +02:00
Grégoire Paris
93f7e78a14 Address deprecation of getDatabasePlatform() 2022-06-10 19:57:50 +02:00
Grégoire Paris
d99e64c05e Merge pull request #9833 from greg0ire/deprecate-sequence-based-ig 2022-06-10 10:09:33 +02:00
Grégoire Paris
9efeefb913 Merge pull request #9826 from greg0ire/improve-phpdoc-configuration
Improve phpdoc for Configuration
2022-06-09 23:53:12 +02:00
Grégoire Paris
3f8430459c Deprecate reliance on sequence-emulated identity columns
Sequence-based identity values have been deprecated.
2022-06-09 23:46:50 +02:00
Grégoire Paris
5f12b8f7de Remove uneeded rule form baseline 2022-06-09 08:13:54 +02:00
Grégoire Paris
f949b9d212 Improve phpdoc for Configuration 2022-06-09 08:09:41 +02:00
Grégoire Paris
2bc0be6fa9 Merge remote-tracking branch 'origin/2.12.x' into 2.13.x 2022-06-09 07:47:32 +02:00
Alexander M. Turek
ddede4064c Merge 2.12.x into 2.13.x (#9811) 2022-06-03 13:22:41 +02:00
Grégoire Paris
1d02289481 Merge pull request #9809 from greg0ire/disallow-null-setFirstResult
Deprecate passing null to Query::setFirstResult()
2022-06-03 07:51:04 +02:00
Alexander M. Turek
4bd0f974ab Remove calls to deprecated MockBuilder::setMethods() (#9808) 2022-06-02 23:48:47 +02:00
Grégoire Paris
d0c582ca48 Deprecate passing null to Query::setFirstResult()
The argument is cast to an integer, so the user might as well pass 0
instead, and we can require an integer.
2022-06-02 22:29:01 +02:00
Alexander M. Turek
cc6cc26f18 Rename Abstract*Test to *TestCase (#9806) 2022-06-02 16:33:33 +02:00
Grégoire Paris
deb5f49413 Merge pull request #9804 from greg0ire/2.13.x
Merge 2.12.x up into 2.13.x
2022-06-02 07:14:42 +02:00
Grégoire Paris
52ce39f595 Merge pull request #9801 from greg0ire/widen-types
Widen types
2022-06-02 07:13:58 +02:00
Grégoire Paris
f84ecb2842 Merge pull request #9794 from VincentLanglet/associationMapping
Add type for AssociationMapping
2022-06-01 23:37:00 +02:00
Grégoire Paris
b2fedaef9e Merge remote-tracking branch 'origin/2.12.x' into 2.13.x 2022-06-01 22:49:10 +02:00
Grégoire Paris
6fb88e1496 Widen return type
This type is so complex that it is not going to bring much value to the
consumer of this method. Let us widen it to mixed.
2022-06-01 19:56:25 +02:00
Sergei Morozov
3ac5f119aa Merge pull request #9799 from morozov/sqlite-fk
Prep work for enabling support for foreign keys on SQLite
2022-06-01 06:19:47 -07:00
Sergei Morozov
01fb82b497 Prep work for enabling support for foreign keys on SQLite 2022-05-30 18:45:43 -07:00
Alexander M. Turek
4f1072e1ac Add missing import (#9796) 2022-05-31 00:42:03 +02:00
Alexander M. Turek
a559563682 Deprecate calling setters without arguments (#9791) 2022-05-30 20:39:06 +02:00
Vincent Langlet
0f9cc194ae Update baseline 2022-05-29 20:27:26 +02:00
Vincent Langlet
2513a1e2b1 Fix 2022-05-29 20:16:12 +02:00
Vincent Langlet
4230214ced Add type for AssociationMapping 2022-05-29 13:17:04 +02:00
Alexander M. Turek
fb1f258736 Move duplicate fixture into dedicated file (#9789) 2022-05-27 15:08:21 +02:00
Alexander M. Turek
e66fbc434d MockTreeWalker should be an SqlWalker (#9790) 2022-05-27 00:15:30 +02:00
Grégoire Paris
0f6f752887 Merge pull request #9777 from greg0ire/improve-phpdoc-abstract-query
Make phpdoc more precise for AbstractQuery
2022-05-24 20:42:40 +02:00
Grégoire Paris
c1dd1cfc2c Make phpdoc more precise 2022-05-24 20:42:10 +02:00
Grégoire Paris
3684d236f6 Deprecate setting fetch mode to random integers 2022-05-24 20:42:08 +02:00
Alexander M. Turek
bba6c696f5 Prepare split of output walkers and tree walkers (#9786) 2022-05-24 18:34:40 +00:00
Alexander M. Turek
e02e6f481b Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  PHPStan 1.7.0 (#9785)
2022-05-24 00:43:35 +02:00
Grégoire Paris
507bc514ce Merge pull request #9784 from greg0ire/deprecate-no-op
Deprecate passing null to Query::setDQL()
2022-05-23 22:31:50 +02:00
Grégoire Paris
1ae5de5409 Deprecate passing null to Query::setDQL()
It is a no-op.
2022-05-23 22:11:16 +02:00
Alexander M. Turek
82508956fe Kill call_user_func(_array) (#9780) 2022-05-23 16:28:32 +02:00
Alexander M. Turek
a131878814 Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  Fix wrong types for AbstractQuery and child classes (#9774)
  Document callable as possible
  Add use statement (#9769)
2022-05-23 11:29:26 +02:00
Grégoire Paris
5601c2ce4b Merge pull request #9775 from greg0ire/no-override
Remove override phpdoc tag
2022-05-23 07:48:01 +02:00
Grégoire Paris
1141fe106f Remove override phpdoc tag
Given how little occurrences there are, signalling method overrides with
this tag is probably not something we do everywhere. Besides, it does
not seem to be standard.

See https://docs.phpdoc.org/3.0/guide/references/phpdoc/tags/index.html#tag-reference
2022-05-22 16:28:41 +02:00
Grégoire Paris
779f9c36fa Merge pull request #9766 from greg0ire/non-nullable-arg-ns
Document future argument better
2022-05-18 08:00:17 +02:00
Grégoire Paris
0908f92629 Document future argument better
That argument is always provided, so the tests should provide it and the
commented out argument should reflect the future.
2022-05-17 12:09:31 +02:00
Grégoire Paris
24badd60fb Merge pull request #9761 from greg0ire/fix-phpdoc-ns
Fix phpdoc and tests for NamingStrategy
2022-05-16 22:00:41 +02:00
Sergei Morozov
f2d794f8bc Merge pull request #9762 from morozov/schema-manager-database-platform
Do not call AbstractSchemaManager::getDatabasePlatform()
2022-05-16 08:31:45 -07:00
Sergei Morozov
7311f77dfe Do not call AbstractSchemaManager::getDatabasePlatform() 2022-05-15 19:25:51 -07:00
Grégoire Paris
16afa45abf Make tests more realistic
These tests were using the fact that some arguments of some methods of
the naming strategy interface are optional or nullable for now to avoid
providing some. In practice, these arguments are always provided, and
that should also be the case in tests.
2022-05-14 17:31:45 +02:00
Grégoire Paris
8b4d25e94f Handle self-refencing entities
When computing a foreign key column name, the referenced column name
may be null in the case of a self referencing entity with join columns
defined in the mapping.
2022-05-14 16:56:19 +02:00
Grégoire Paris
70087782e8 Merge pull request #9756 from greg0ire/more-precise-phpdoc-ns
Document types as they are passed
2022-05-12 09:05:51 +02:00
Grégoire Paris
dbc5a818e0 Document types as they are passed
Some arguments have been added afterwards which was a BC break for
implementing classes. I do not think they should have been introduced as
optional.
2022-05-11 21:36:44 +02:00
Alexander M. Turek
31a9c9c49b Update Psalm baseline (#9751) 2022-05-10 16:00:54 +02:00
Alexander M. Turek
125afb8e39 Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  Omit version number in README (#9749)
2022-05-10 15:08:24 +02:00
Grégoire Paris
2c30fe6e5b Merge pull request #9748 from doctrine/2.12.x
Merge 2.12.x up into 2.13.x
2022-05-10 08:21:34 +02:00
Grégoire Paris
6757bdf8c6 Deprecate omitting second argument to joinColumnName() (#9747)
* fix headings

* Deprecate omitting second argument to joinColumnName()
2022-05-10 00:37:44 +02:00
Sergei Morozov
636712a928 Merge pull request #9737 from morozov/dbal-4-compatibility
Improve compatibility with DBAL 4 for MySQL, MariaDB and PostgreSQL
2022-05-07 07:42:14 -07:00
Sergei Morozov
0aa91c7140 Pass sequence name to AbstractPlatform::getDropSequenceSQL()
See https://github.com/doctrine/dbal/pull/4797
2022-05-06 19:15:06 -07:00
Sergei Morozov
c2f3831b85 Specify length for string columns
See https://github.com/doctrine/dbal/pull/3586
2022-05-06 19:15:06 -07:00
Sergei Morozov
2af52f6a18 Inherit parent column length regardless of the type
See https://github.com/doctrine/dbal/pull/3586
2022-05-06 19:15:05 -07:00
Sergei Morozov
0a79ddf344 Cast column length to int in XML annotation driver
See https://github.com/doctrine/dbal/pull/3511
2022-05-06 19:15:05 -07:00
Sergei Morozov
165c8bd6dd Do not use AbstractSchemaManager::dropAndCreateTable()
See https://github.com/doctrine/dbal/pull/4933
2022-05-06 19:15:03 -07:00
Sergei Morozov
e6bda4afda Merge pull request #9730 from morozov/dbal-4-compatibility
Forward compatibility with DBAL 4
2022-05-05 14:06:25 -07:00
Sergei Morozov
51b4e02873 Obtain database platform from the connection, not from the driver
See https://github.com/doctrine/dbal/pull/4764
2022-05-05 11:30:36 -07:00
Sergei Morozov
1915dcd1e8 Use zero as the default query offset
See https://github.com/doctrine/dbal/pull/3574
2022-05-05 11:30:35 -07:00
Sergei Morozov
480d99b107 Use getStringTypeDeclarationSQL() instead of getVarcharTypeDeclarationSQL()
See https://github.com/doctrine/dbal/pull/3586
2022-05-05 11:30:35 -07:00
Sergei Morozov
c6661caaed Do not extend a type with a different PHP return type
See https://github.com/doctrine/dbal/pull/5043
2022-05-05 11:30:34 -07:00
Sergei Morozov
9e27370f15 Drop and create test database manually
See https://github.com/doctrine/dbal/pull/4933
2022-05-05 11:30:32 -07:00
Sergei Morozov
1a3fbcb145 Lookup type name in the registry
See https://github.com/doctrine/dbal/pull/5208
2022-05-05 09:32:38 -07:00
Sergei Morozov
c950e72628 Do not use null column definition 2022-05-05 09:32:37 -07:00
Alexander M. Turek
05560f260c Merge release 2.12.2 into 2.13.x (#9725) 2022-05-03 02:15:35 +02:00
Ruud Kamphuis
d7d6b9d2c7 Allow setting column options like charset and collation everywhere (#9655)
This makes it possible to set custom options on the following:
* `JoinTable`
* `JoinColumn`
* `InverseJoinColumn`
2022-05-02 19:01:24 +02:00
Alexander M. Turek
a02642e3e6 Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  Psalm 4.23, PHPStan 1.6.3 (#9718)
2022-05-02 11:09:12 +02:00
Grégoire Paris
b4da0ece41 Merge pull request #9704 from greg0ire/reference-cmi-less-and-less
Reference ClassMetadataInfo less and less
2022-05-01 22:06:19 +02:00
Sergei Morozov
3980d58b80 Merge pull request #9710 from morozov/2.13.x
Merge 2.12.x into 2.13.x
2022-05-01 12:46:13 -07:00
Sergei Morozov
10cbb24649 Merge branch '2.12.x' into 2.13.x 2022-05-01 12:24:28 -07:00
Grégoire Paris
476a02075f Deprecate classes and methods removed in 3.0 2022-04-30 19:47:20 +02:00
Grégoire Paris
98e10906f8 Change class metadata type declarations
There is a guarantee in the call sites that we are only passing
ClassMetadata instances.
2022-04-30 19:47:20 +02:00
Grégoire Paris
7241b4d2e0 Reference constants from ClassMetadata 2022-04-30 19:47:20 +02:00
Grégoire Paris
b8db858784 Deprecate not passing ClassMetadata instances 2022-04-30 19:47:20 +02:00
Grégoire Paris
d8f3198ef8 Merge pull request #9693 from doctrine/2.12.x
Merge 2.12.x up into 2.13.x
2022-04-29 08:13:20 +02:00
Alexander M. Turek
825e9641fd Merge branch '2.12.x' into 2.13.x
* 2.12.x:
  PHPStan 1.6.1 (#9688)
  Drop SymfonyStyle[listing] for sqls (#9679)
2022-04-27 09:53:50 +02:00
Alexander M. Turek
5fbe5ebef4 Merge 2.12.x into 2.13.x (#9684) 2022-04-26 11:32:14 +02:00
Grégoire Paris
f4585b954f Merge pull request #6728 from greg0ire/validate_xml_against_xsd
Validate XML mapping against XSD file
2022-04-25 23:04:26 +02:00
Grégoire Paris
ab3a255440 Validate XML mapping against XSD file
Co-Authored-By: Axel Venet <avenet@wamiz.com>
Co-authored-by: Luís Cobucci <lcobucci@gmail.com>
2022-04-25 22:55:00 +02:00
1061 changed files with 15385 additions and 12291 deletions

View File

@@ -12,21 +12,33 @@
"upcoming": true
},
{
"name": "2.13",
"branchName": "2.13.x",
"slug": "2.13",
"name": "2.15",
"branchName": "2.15.x",
"slug": "2.15",
"upcoming": true
},
{
"name": "2.12",
"branchName": "2.12.x",
"slug": "2.12",
"name": "2.14",
"branchName": "2.14.x",
"slug": "2.14",
"current": true,
"aliases": [
"current",
"stable"
]
},
{
"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",

3
.gitattributes vendored
View File

@@ -16,5 +16,8 @@ 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

View File

@@ -1,15 +0,0 @@
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
push:
branches:
- "*.x"
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@1.4.1"
with:
php-version: "8.1"

27
.github/workflows/coding-standards.yml vendored Normal file
View File

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

View File

@@ -4,9 +4,23 @@ on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- lib/**
- phpunit.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- lib/**
- phpunit.xml.dist
- tests/**
env:
fail-fast: true
@@ -14,7 +28,7 @@ env:
jobs:
phpunit-smoke-check:
name: "PHPUnit with SQLite"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
matrix:
@@ -24,19 +38,31 @@ jobs:
- "7.4"
- "8.0"
- "8.1"
- "8.2"
dbal-version:
- "default"
extension:
- "pdo_sqlite"
proxy:
- "common"
include:
- php-version: "8.0"
dbal-version: "2.13"
- php-version: "8.1"
dbal-version: "3@dev"
extension: "pdo_sqlite"
- php-version: "8.2"
dbal-version: "3@dev"
extension: "pdo_sqlite"
- php-version: "8.2"
dbal-version: "default"
extension: "sqlite3"
- php-version: "8.1"
dbal-version: "default"
proxy: "lazy-ghost"
extension: "pdo_sqlite"
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -44,57 +70,64 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "apcu, pdo, pdo_sqlite"
extensions: "apcu, pdo, ${{ matrix.extension }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
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@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --coverage-clover=coverage-no-cache.xml"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
- name: "Run PHPUnit with Second Level Cache"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
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
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "phpunit-sqlite-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
phpunit-postgres:
name: "PHPUnit with PostgreSQL"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
dbal-version:
- "default"
- "3@dev"
postgres-version:
- "9.6"
- "14"
- "15"
extension:
- pdo_pgsql
- pgsql
include:
- php-version: "8.0"
dbal-version: "2.13"
postgres-version: "14"
extension: pdo_pgsql
- php-version: "8.2"
dbal-version: "3@dev"
postgres-version: "14"
dbal-version: "default"
postgres-version: "9.6"
extension: pdo_pgsql
services:
postgres:
@@ -110,7 +143,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -118,15 +151,16 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "pgsql pdo_pgsql"
coverage: "pcov"
ini-values: "zend.assertions=1"
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@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
@@ -134,7 +168,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
@@ -142,17 +176,18 @@ jobs:
phpunit-mariadb:
name: "PHPUnit with MariaDB"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
dbal-version:
- "default"
- "3@dev"
mariadb-version:
- "10.6"
- "10.9"
extension:
- "mysqli"
- "pdo_mysql"
@@ -161,14 +196,6 @@ jobs:
dbal-version: "2.13"
mariadb-version: "10.6"
extension: "pdo_mysql"
- php-version: "8.2"
dbal-version: "3@dev"
mariadb-version: "10.6"
extension: "pdo_mysql"
- php-version: "8.2"
dbal-version: "3@dev"
mariadb-version: "10.6"
extension: "mysqli"
services:
mariadb:
@@ -185,7 +212,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -198,11 +225,11 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
@@ -210,7 +237,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
@@ -218,15 +245,16 @@ jobs:
phpunit-mysql:
name: "PHPUnit with MySQL"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
dbal-version:
- "default"
- "3@dev"
mysql-version:
- "5.7"
- "8.0"
@@ -238,14 +266,6 @@ jobs:
dbal-version: "2.13"
mysql-version: "8.0"
extension: "pdo_mysql"
- php-version: "8.2"
dbal-version: "3@dev"
mysql-version: "8.0"
extension: "mysqli"
- php-version: "8.2"
dbal-version: "3@dev"
mysql-version: "8.0"
extension: "pdo_mysql"
services:
mysql:
@@ -261,7 +281,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -270,7 +290,7 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Require specific DBAL version"
@@ -278,7 +298,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
@@ -293,7 +313,7 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
@@ -301,7 +321,7 @@ jobs:
phpunit-lower-php-versions:
name: "PHPUnit with SQLite"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
matrix:
@@ -313,7 +333,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -321,20 +341,20 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml"
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_sqlite.xml"
upload_coverage:
name: "Upload coverage to Codecov"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
needs:
- "phpunit-smoke-check"
- "phpunit-postgres"
@@ -343,16 +363,16 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v2"
uses: "actions/download-artifact@v3"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v1"
uses: "codecov/codecov-action@v3"
with:
directory: reports

View File

@@ -5,9 +5,21 @@ on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- lib/**
- phpbench.json
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- lib/**
- phpbench.json
- tests/**
env:
fail-fast: true
@@ -15,7 +27,7 @@ env:
jobs:
phpbench:
name: "PHPBench"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
matrix:
@@ -24,7 +36,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -33,10 +45,10 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v2"
uses: "actions/cache@v3"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"

View File

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

View File

@@ -1,56 +1,64 @@
name: "Static Analysis"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- lib/**
- phpstan*
- psalm*
- tests/Doctrine/StaticAnalysis/**
push:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- lib/**
- phpstan*
- psalm*
- tests/Doctrine/StaticAnalysis/**
jobs:
static-analysis-phpstan:
name: "Static Analysis with PHPStan"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
fail-fast: false
matrix:
php-version:
- "8.1"
dbal-version:
- "default"
persistence-version:
- "default"
include:
- php-version: "8.1"
dbal-version: "2.13"
- dbal-version: "2.13"
persistence-version: "default"
- php-version: "8.1"
dbal-version: "default"
- dbal-version: "default"
persistence-version: "2.5"
steps:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
php-version: "8.2"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^${{ matrix.persistence-version }} --no-update"
if: "${{ matrix.persistence-version != 'default' }}"
run: "composer require doctrine/persistence ^$([ ${{ matrix.persistence-version }} = default ] && echo '3.1' || echo ${{ matrix.persistence-version }}) --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"
@@ -68,26 +76,26 @@ jobs:
static-analysis-psalm:
name: "Static Analysis with Psalm"
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
strategy:
fail-fast: false
matrix:
php-version:
- "8.1"
steps:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
php-version: "8.2"
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^3.1 --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"

1
.gitignore vendored
View File

@@ -15,5 +15,6 @@ vendor/
/tests/Doctrine/Performance/history.db
/.phpcs-cache
composer.lock
.phpunit.cache
.phpunit.result.cache
/*.phpunit.xml

View File

@@ -1,7 +1,7 @@
| [3.0.x][3.0] | [2.13.x][2.13] | [2.12.x][2.12] |
| [3.0.x][3.0] | [2.14.x][2.14] | [2.13.x][2.13] |
|:----------------:|:----------------:|:----------:|
| [![Build status][3.0 image]][3.0] | [![Build status][2.13 image]][2.13] | [![Build status][2.12 image]][2.12] |
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.13 coverage image]][2.13 coverage] | [![Coverage Status][2.12 coverage image]][2.12 coverage] |
| [![Build status][3.0 image]][3.0] | [![Build status][2.14 image]][2.14] | [![Build status][2.13 image]][2.13] |
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.14 coverage image]][2.14 coverage] | [![Coverage Status][2.13 coverage image]][2.13 coverage] |
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
@@ -22,11 +22,11 @@ without requiring unnecessary code duplication.
[3.0]: https://github.com/doctrine/orm/tree/3.0.x
[3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg
[3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x
[2.14 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.14.x
[2.14]: https://github.com/doctrine/orm/tree/2.14.x
[2.14 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.14.x/graph/badge.svg
[2.14 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.14.x
[2.13 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.13.x
[2.13]: https://github.com/doctrine/orm/tree/2.13.x
[2.13 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.13.x/graph/badge.svg
[2.13 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.13.x
[2.12 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.12.x
[2.12]: https://github.com/doctrine/orm/tree/2.12.x
[2.12 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.12.x/graph/badge.svg
[2.12 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.12.x

View File

@@ -1,3 +1,295 @@
# Upgrade to 2.14
## Deprecated `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byName($field)` method.
Use `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byFullyQualifiedName($className, $field)` instead.
## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator`
The following public constants have been deprecated:
* `CommitOrderCalculator::NOT_VISITED`
* `CommitOrderCalculator::IN_PROGRESS`
* `CommitOrderCalculator::VISITED`
These constants were used for internal purposes. Relying on them is discouraged.
## Deprecated `Doctrine\ORM\Query\AST\InExpression`
The AST parser will create a `InListExpression` or a `InSubselectExpression` when
encountering an `IN ()` DQL expression instead of a generic `InExpression`.
As a consequence, `SqlWalker::walkInExpression()` has been deprecated in favor of
`SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`.
## Deprecated constructing a `CacheKey` without `$hash`
The `Doctrine\ORM\Cache\CacheKey` class has an explicit constructor now with
an optional parameter `$hash`. That parameter will become mandatory in 3.0.
## Deprecated `AttributeDriver::$entityAnnotationClasses`
If you need to change the behavior of `AttributeDriver::isTransient()`,
override that method instead.
## Deprecated incomplete schema updates
Using `orm:schema-tool:update` without passing the `--complete` flag is
deprecated. Use schema asset filtering if you need to preserve assets not
managed by DBAL.
Likewise, calling `SchemaTool::updateSchema()` or
`SchemaTool::getUpdateSchemaSql()` with a second argument is deprecated.
## Deprecated annotation mapping driver.
Please switch to one of the other mapping drivers. Native attributes which PHP
supports since version 8.0 are probably your best option.
As a consequence, the following methods are deprecated:
- `ORMSetup::createAnnotationMetadataConfiguration`
- `ORMSetup::createDefaultAnnotationDriver`
The marker interface `Doctrine\ORM\Mapping\Annotation` is deprecated as well.
All annotation/attribute classes implement
`Doctrine\ORM\Mapping\MappingAttribute` now.
## Deprecated `Doctrine\ORM\Proxy\Proxy` interface.
Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized.
## Deprecated `Doctrine\ORM\Event\LifecycleEventArgs` class.
It will be removed in 3.0. Use one of the dedicated event classes instead:
* `Doctrine\ORM\Event\PrePersistEventArgs`
* `Doctrine\ORM\Event\PreUpdateEventArgs`
* `Doctrine\ORM\Event\PreRemoveEventArgs`
* `Doctrine\ORM\Event\PostPersistEventArgs`
* `Doctrine\ORM\Event\PostUpdateEventArgs`
* `Doctrine\ORM\Event\PostRemoveEventArgs`
* `Doctrine\ORM\Event\PostLoadEventArgs`
# Upgrade to 2.13
## Deprecated `EntityManager::create()`
The constructor of `EntityManager` is now public and should be used instead of the `create()` method.
However, the constructor expects a `Connection` while `create()` accepted an array with connection parameters.
You can pass that array to DBAL's `Doctrine\DBAL\DriverManager::getConnection()` method to bootstrap the
connection.
## Deprecated `QueryBuilder` methods and constants.
1. The `QueryBuilder::getState()` method has been deprecated as the builder state is an internal concern.
2. Relying on the type of the query being built by using `QueryBuilder::getType()` has been deprecated.
If necessary, track the type of the query being built outside of the builder.
The following `QueryBuilder` constants related to the above methods have been deprecated:
1. `SELECT`,
2. `DELETE`,
3. `UPDATE`,
4. `STATE_DIRTY`,
5. `STATE_CLEAN`.
## Deprecated omitting only the alias argument for `QueryBuilder::update` and `QueryBuilder::delete`
When building an UPDATE or DELETE query and when passing a class/type to the function, the alias argument must not be omitted.
### Before
```php
$qb = $em->createQueryBuilder()
->delete('User u')
->where('u.id = :user_id')
->setParameter('user_id', 1);
```
### After
```php
$qb = $em->createQueryBuilder()
->delete('User', 'u')
->where('u.id = :user_id')
->setParameter('user_id', 1);
```
## Deprecated using the `IDENTITY` identifier strategy on platform that do not support identity columns
If identity columns are emulated with sequences on the platform you are using,
you should switch to the `SEQUENCE` strategy.
## Deprecated passing `null` to `Doctrine\ORM\Query::setFirstResult()`
`$query->setFirstResult(null);` is equivalent to `$query->setFirstResult(0)`.
## Deprecated calling setters without arguments
The following methods will require an argument in 3.0. Pass `null` instead of
omitting the argument.
* `Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs::setFoundMetadata()`
* `Doctrine\ORM\AbstractQuery::setHydrationCacheProfile()`
* `Doctrine\ORM\AbstractQuery::setResultCache()`
* `Doctrine\ORM\AbstractQuery::setResultCacheProfile()`
## Deprecated passing invalid fetch modes to `AbstractQuery::setFetchMode()`
Calling `AbstractQuery::setFetchMode()` with anything else than
`Doctrine\ORM\Mapping::FETCH_EAGER` results in
`Doctrine\ORM\Mapping::FETCH_LAZY` being used. Relying on that behavior is
deprecated and will result in an exception in 3.0.
## Deprecated `getEntityManager()` in `Doctrine\ORM\Event\OnClearEventArgs` and `Doctrine\ORM\Event\*FlushEventArgs`
This method has been deprecated in:
* `Doctrine\ORM\Event\OnClearEventArgs`
* `Doctrine\ORM\Event\OnFlushEventArgs`
* `Doctrine\ORM\Event\PostFlushEventArgs`
* `Doctrine\ORM\Event\PreFlushEventArgs`
It will be removed in 3.0. Use `getObjectManager()` instead.
## Prepare split of output walkers and tree walkers
In 3.0, `SqlWalker` and its child classes won't implement the `TreeWalker`
interface anymore. Relying on that inheritance is deprecated.
The following methods of the `TreeWalker` interface have been deprecated:
* `setQueryComponent()`
* `walkSelectClause()`
* `walkFromClause()`
* `walkFunction()`
* `walkOrderByClause()`
* `walkOrderByItem()`
* `walkHavingClause()`
* `walkJoin()`
* `walkSelectExpression()`
* `walkQuantifiedExpression()`
* `walkSubselect()`
* `walkSubselectFromClause()`
* `walkSimpleSelectClause()`
* `walkSimpleSelectExpression()`
* `walkAggregateExpression()`
* `walkGroupByClause()`
* `walkGroupByItem()`
* `walkDeleteClause()`
* `walkUpdateClause()`
* `walkUpdateItem()`
* `walkWhereClause()`
* `walkConditionalExpression()`
* `walkConditionalTerm()`
* `walkConditionalFactor()`
* `walkConditionalPrimary()`
* `walkExistsExpression()`
* `walkCollectionMemberExpression()`
* `walkEmptyCollectionComparisonExpression()`
* `walkNullComparisonExpression()`
* `walkInExpression()`
* `walkInstanceOfExpression()`
* `walkLiteral()`
* `walkBetweenExpression()`
* `walkLikeExpression()`
* `walkStateFieldPathExpression()`
* `walkComparisonExpression()`
* `walkInputParameter()`
* `walkArithmeticExpression()`
* `walkArithmeticTerm()`
* `walkStringPrimary()`
* `walkArithmeticFactor()`
* `walkSimpleArithmeticExpression()`
* `walkPathExpression()`
* `walkResultVariable()`
* `getExecutor()`
The following changes have been made to the abstract `TreeWalkerAdapter` class:
* All implementations of now-deprecated `TreeWalker` methods have been
deprecated as well.
* The method `setQueryComponent()` will become protected in 3.0. Calling it
publicly is deprecated.
* The method `_getQueryComponents()` is deprecated, call `getQueryComponents()`
instead.
On the `TreeWalkerChain` class, all implementations of now-deprecated
`TreeWalker` methods have been deprecated as well. However, `SqlWalker` is
unaffected by those deprecations and will continue to implement all of those
methods.
## Deprecated passing `null` to `Doctrine\ORM\Query::setDQL()`
Doing `$query->setDQL(null);` achieves nothing.
## Deprecated omitting second argument to `NamingStrategy::joinColumnName`
When implementing `NamingStrategy`, it is deprecated to implement
`joinColumnName()` with only one argument.
### Before
```php
<?php
class MyStrategy implements NamingStrategy
{
/**
* @param string $propertyName A property name.
*/
public function joinColumnName($propertyName): string
{
// …
}
}
```
### After
For backward-compatibility reasons, the parameter has to be optional, but can
be documented as guaranteed to be a `class-string`.
```php
<?php
class MyStrategy implements NamingStrategy
{
/**
* @param string $propertyName A property name.
* @param class-string $className
*/
public function joinColumnName($propertyName, $className = null): string
{
// …
}
}
```
## Deprecated methods related to named queries
The following methods have been deprecated:
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryResultClassMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryResultSetMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryEntityResultMapping()`
## Deprecated classes related to Doctrine 1 and reverse engineering
The following classes have been deprecated:
- `Doctrine\ORM\Tools\ConvertDoctrine1Schema`
- `Doctrine\ORM\Tools\DisconnectedClassMetadataFactory`
## Deprecate `ClassMetadataInfo` usage
It is deprecated to pass `Doctrine\ORM\Mapping\ClassMetadataInfo` instances
that are not also instances of `Doctrine\ORM\ClassMetadata` to the following
methods:
- `Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder::__construct()`
- `Doctrine\ORM\Mapping\Driver\DatabaseDriver::loadMetadataForClass()`
- `Doctrine\ORM\Tools\SchemaValidator::validateClass()`
# Upgrade to 2.12
## Deprecated the `doctrine` binary.
@@ -181,7 +473,7 @@ function foo(EntityManagerInterface $entityManager, callable $func) {
if (method_exists($entityManager, 'wrapInTransaction')) {
return $entityManager->wrapInTransaction($func);
}
return $entityManager->transactional($func);
}
```
@@ -247,7 +539,7 @@ implementation. To work around this:
* As a quick workaround, you can lock the doctrine/cache dependency to work around this: `composer require doctrine/cache ^1.11`.
Note that this is only recommended as a bandaid fix, as future versions of ORM will no longer work with doctrine/cache
1.11.
## Deprecated: doctrine/cache for metadata caching
The `Doctrine\ORM\Configuration#setMetadataCacheImpl()` method is deprecated and should no longer be used. Please use
@@ -272,12 +564,12 @@ Note that `toIterable()` yields results of the query, unlike `iterate()` which y
# Upgrade to 2.7
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for both enabling and disabling the cache
(depending on passed flag) was split into two.
(depending on passed flag) was split into two.
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to
perform the pagination with join collections when max results isn't set in the query.
@@ -296,7 +588,7 @@ In the last patch of the `v2.6.x` series, we fixed a bug that was not converting
In order to not break BC we've introduced a way to enable the fixed behavior using a boolean constructor argument. This
argument will be removed in 3.0 and the default behavior will be the fixed one.
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
Method `Doctrine\ORM\AbstractQuery#useResultCache()` is deprecated because it is split into `enableResultCache()`
and `disableResultCache()`. It will be removed in 3.0.
@@ -326,7 +618,7 @@ These related classes have been deprecated:
* `Doctrine\ORM\Proxy\ProxyFactory`
* `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead
These methods have been deprecated:
* `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()`
@@ -375,7 +667,7 @@ If your code relies on single entity flushing optimisations via
Said API was affected by multiple data integrity bugs due to the fact
that change tracking was being restricted upon a subset of the managed
entities. The ORM cannot support committing subsets of the managed
entities. The ORM cannot support committing subsets of the managed
entities while also guaranteeing data integrity, therefore this
utility was removed.
@@ -476,8 +768,8 @@ either:
- map those classes as `MappedSuperclass`
## Minor BC BREAK: ``EntityManagerInterface`` instead of ``EntityManager`` in type-hints
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
an ``EntityManagerInterface`` instead.
If you are extending any of the following classes, then you need to check following
signatures:
@@ -570,7 +862,7 @@ the `Doctrine\ORM\Repository\DefaultRepositoryFactory`.
When executing DQL queries with new object expressions, instead of returning DTOs numerically indexes, it will now respect user provided aliases. Consider the following query:
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
Previously, your result would be similar to this:
array(

View File

@@ -0,0 +1,40 @@
<?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"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
convertDeprecationsToExceptions="true"
>
<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>
<filter>
<whitelist>
<directory suffix=".php">../../../lib/Doctrine</directory>
</whitelist>
</filter>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@@ -0,0 +1,38 @@
<?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"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
convertDeprecationsToExceptions="true"
>
<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>
<filter>
<whitelist>
<directory suffix=".php">../../../lib/Doctrine</directory>
</whitelist>
</filter>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@@ -24,36 +24,38 @@
"composer-runtime-api": "^2",
"ext-ctype": "*",
"doctrine/cache": "^1.12.1 || ^2.1.1",
"doctrine/collections": "^1.5",
"doctrine/collections": "^1.5 || ^2.0",
"doctrine/common": "^3.0.3",
"doctrine/dbal": "^2.13.1 || ^3.2",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3",
"doctrine/lexer": "^1.2.3",
"doctrine/lexer": "^1.2.3 || ^2",
"doctrine/persistence": "^2.4 || ^3",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0",
"symfony/console": "^4.2 || ^5.0 || ^6.0",
"symfony/polyfill-php72": "^1.23",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"doctrine/annotations": "^1.13",
"doctrine/coding-standard": "^9.0",
"doctrine/annotations": "^1.13 || ^2",
"doctrine/coding-standard": "^9.0.2 || ^11.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.8.2",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"phpstan/phpstan": "~1.4.10 || 1.10.6",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.1",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.26.0"
"vimeo/psalm": "4.30.0 || 5.9.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 2.0"
"doctrine/annotations": "<1.13 || >= 3.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",
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},

View File

@@ -1,74 +0,0 @@
Accessing private/protected properties/methods of the same class from different instance
========================================================================================
.. sectionauthor:: Michael Olsavsky (olsavmic)
As explained in the :doc:`restrictions for entity classes in the manual <../reference/architecture>`,
it is dangerous to access private/protected properties of different entity instance of the same class because of lazy loading.
The proxy instance that's injected instead of the real entity may not be initialized yet
and therefore not contain expected data which may result in unexpected behavior.
That's a limitation of current proxy implementation - only public methods automatically initialize proxies.
It is usually preferable to use a public interface to manipulate the object from outside the `$this`
context but it may not be convenient in some cases. The following example shows how to do it safely.
Safely accessing private properties from different instance of the same class
-----------------------------------------------------------------------------
To safely access private property of different instance of the same class, make sure to initialise
the proxy before use manually as follows:
.. code-block:: php
<?php
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Entity
{
// ...
/**
* @ORM\ManyToOne(targetEntity="Entity")
* @ORM\JoinColumn(nullable=false)
*/
private self $parent;
/**
* @ORM\Column(type="string", nullable=false)
*/
private string $name;
// ...
public function doSomethingWithParent()
{
// Always initializing the proxy before use
if ($this->parent instanceof Proxy) {
$this->parent->__load();
}
// Accessing the `$this->parent->name` property without loading the proxy first
// may throw error in case the Proxy has not been initialized yet.
$this->parent->name;
}
public function doSomethingWithAnotherInstance(self $instance)
{
// Always initializing the proxy before use
if ($instance instanceof Proxy) {
$instance->__load();
}
// Accessing the `$instance->name` property without loading the proxy first
// may throw error in case the Proxy has not been initialized yet.
$instance->name;
}
// ...
}

View File

@@ -32,59 +32,39 @@ The entity class:
namespace Geo\Entity;
/**
* @Entity
*/
use Geo\ValueObject\Point;
#[Entity]
class Location
{
/**
* @Column(type="point")
*
* @var \Geo\ValueObject\Point
*/
private $point;
#[Column(type: 'point')]
private Point $point;
/**
* @Column(type="string")
*
* @var string
*/
private $address;
#[Column]
private string $address;
/**
* @param \Geo\ValueObject\Point $point
*/
public function setPoint(\Geo\ValueObject\Point $point)
public function setPoint(Point $point): void
{
$this->point = $point;
}
/**
* @return \Geo\ValueObject\Point
*/
public function getPoint()
public function getPoint(): Point
{
return $this->point;
}
/**
* @param string $address
*/
public function setAddress($address)
public function setAddress(string $address): void
{
$this->address = $address;
}
/**
* @return string
*/
public function getAddress()
public function getAddress(): string
{
return $this->address;
}
}
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
We use the custom type ``point`` in the ``#[Column]`` attribute of the
``$point`` field. We will create this custom mapping type in the next chapter.
The point class:
@@ -97,29 +77,18 @@ The point class:
class Point
{
/**
* @param float $latitude
* @param float $longitude
*/
public function __construct($latitude, $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
public function __construct(
private float $latitude,
private float $longitude,
) {
}
/**
* @return float
*/
public function getLatitude()
public function getLatitude(): float
{
return $this->latitude;
}
/**
* @return float
*/
public function getLongitude()
public function getLongitude(): float
{
return $this->longitude;
}
@@ -227,7 +196,7 @@ Example usage
<?php
// Bootstrapping stuff...
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
// $em = new \Doctrine\ORM\EntityManager($connection, $config);
// Setup custom mapping type
use Doctrine\DBAL\Types\Type;

View File

@@ -23,48 +23,32 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
namespace Test;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
"cd" = "Test\Decorator\ConcreteDecorator"})
*/
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['cc' => Component\ConcreteComponent::class,
'cd' => Decorator\ConcreteDecorator::class])]
abstract class Component
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
#[Id, Column]
#[GeneratedValue(strategy: 'AUTO')]
protected int|null $id = null;
/** @Column(type="string", nullable=true) */
#[Column(type: 'string', nullable: true)]
protected $name;
/**
* Get id
* @return integer $id
*/
public function getId()
public function getId(): int|null
{
return $this->id;
}
/**
* Set name
* @param string $name
*/
public function setName($name)
public function setName(string $name): void
{
$this->name = $name;
}
/**
* Get name
* @return string $name
*/
public function getName()
public function getName(): string
{
return $this->name;
}
@@ -86,7 +70,7 @@ purpose of keeping this example simple).
use Test\Component;
/** @Entity */
#[Entity]
class ConcreteComponent extends Component
{}
@@ -103,14 +87,11 @@ use a ``MappedSuperclass`` for this.
namespace Test;
/** @MappedSuperclass */
#[MappedSuperclass]
abstract class Decorator extends Component
{
/**
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
* @JoinColumn(name="decorates", referencedColumnName="id")
*/
#[OneToOne(targetEntity: Component::class, cascade: ['all'])]
#[JoinColumn(name: 'decorates', referencedColumnName: 'id')]
protected $decorates;
/**
@@ -126,25 +107,19 @@ use a ``MappedSuperclass`` for this.
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName()
public function getName(): string
{
return 'Decorated ' . $this->getDecorates()->getName();
}
/**
* the component being decorated
* @return Component
*/
protected function getDecorates()
/** the component being decorated */
protected function getDecorates(): Component
{
return $this->decorates;
}
/**
* sets the component being decorated
* @param Component $c
*/
protected function setDecorates(Component $c)
/** sets the component being decorated */
protected function setDecorates(Component $c): void
{
$this->decorates = $c;
}
@@ -187,27 +162,19 @@ of the getSpecial() method to its return value.
use Test\Decorator;
/** @Entity */
#[Entity]
class ConcreteDecorator extends Decorator
{
/** @Column(type="string", nullable=true) */
protected $special;
#[Column(type: 'string', nullable: true)]
protected string|null $special = null;
/**
* Set special
* @param string $special
*/
public function setSpecial($special)
public function setSpecial(string|null $special): void
{
$this->special = $special;
}
/**
* Get special
* @return string $special
*/
public function getSpecial()
public function getSpecial(): string|null
{
return $this->special;
}
@@ -216,7 +183,7 @@ of the getSpecial() method to its return value.
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName()
public function getName(): string
{
return '[' . $this->getSpecial()
. '] ' . parent::getName();
@@ -270,4 +237,3 @@ objects
echo $d->getName();
// prints: [Really] Decorated Test Component 2

View File

@@ -46,7 +46,7 @@ configuration:
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = EntityManager::create($dbParams, $config);
$em = new EntityManager($connection, $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
@@ -247,5 +247,3 @@ vendor sql functions and extend the DQL languages scope.
Code for this Extension to DQL and other Doctrine Extensions can be
found
`in the GitHub DoctrineExtensions repository <https://github.com/beberlei/DoctrineExtensions>`_.

View File

@@ -29,15 +29,15 @@ implement the ``NotifyPropertyChanged`` interface from the
<?php
use Doctrine\Persistence\NotifyPropertyChanged;
use Doctrine\Persistence\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) {
@@ -55,12 +55,12 @@ listeners:
.. code-block:: php
<?php
// Mapping not shown, either in annotations, xml or yaml as usual
// Mapping not shown, either in attributes, annotations, xml or yaml 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);
@@ -73,5 +73,3 @@ 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,78 +0,0 @@
Implementing Wakeup or Clone
============================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the :ref:`restrictions for entity classes in the manual
<terminology_entities>`,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe
way by guarding the custom wakeup or clone code with an entity
identity check, as demonstrated in the following sections.
Safely implementing __wakeup
----------------------------
To safely implement ``__wakeup``, simply enclose your
implementation code in an identity check as follows:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
// ...
public function __wakeup()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
// ...
}
Safely implementing __clone
---------------------------
Safely implementing ``__clone`` is pretty much the same:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
// ...
public function __clone()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
// ...
}
Summary
-------
As you have seen, it is quite easy to safely make use of
``__wakeup`` and ``__clone`` in your entities without adding any
really Doctrine-specific or Doctrine-dependant code.
These implementations are possible and safe because when Doctrine
invokes these methods, the entities never have an identity (yet).
Furthermore, it is possibly a good idea to check for the identity
in your code anyway, since it's rarely the case that you want to
unserialize or clone an entity with no identity.

View File

@@ -127,7 +127,8 @@ the targetEntity resolution will occur reliably:
// Add the ResolveTargetEntityListener
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
$connection = \Doctrine\DBAL\DriverManager::createConnection($connectionOptions, $config, $evm);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
Final Thoughts
--------------
@@ -136,5 +137,3 @@ 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

@@ -81,6 +81,4 @@ before the prefix has been set.
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);

View File

@@ -87,12 +87,12 @@ Such an interface could look like this:
* @return \Zend_View_Helper_Interface
*/
public function setView(\Zend_View_Interface $view);
/**
* @return \Zend_View_Interface
*/
public function getView();
/**
* Renders this strategy. This method will be called when the user
* displays the site.
@@ -100,7 +100,7 @@ Such an interface could look like this:
* @return string
*/
public function renderFrontend();
/**
* Renders the backend of this block. This method will be called when
* a user tries to reconfigure this block instance.
@@ -118,21 +118,21 @@ Such an interface could look like this:
* @return array
*/
public function getRequiredPanelTypes();
/**
* Determines whether a Block is able to use a given type or not
* @param string $typeName The typename
* @return boolean
*/
public function canUsePanelType($typeName);
public function setBlockEntity(AbstractBlock $block);
public function getBlockEntity();
}
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
.. code-block:: php
<?php
@@ -154,8 +154,8 @@ 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)
*
* This is a doctrine field, so make sure that you use an @column annotation or setup your
* yaml or xml files correctly
* This is a doctrine field, so make sure that you use a
#[Column] attribute or setup your yaml or xml files correctly
* @var string
*/
protected $strategyClassName;
@@ -177,7 +177,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
public function getStrategyClassName() {
return $this->strategyClassName;
}
/**
* Returns the instantiated strategy
*
@@ -186,7 +186,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
public function getStrategyInstance() {
return $this->strategyInstance;
}
/**
* Sets the strategy this block / panel should work as. Make sure that you've used
* this method before persisting the block!
@@ -213,28 +213,29 @@ This might look like this:
.. code-block:: php
<?php
use \Doctrine\ORM,
\Doctrine\Common;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
/**
* The BlockStrategyEventListener will initialize a strategy after the
* block itself was loaded.
*/
class BlockStrategyEventListener implements Common\EventSubscriber {
class BlockStrategyEventListener implements EventSubscriber {
protected $view;
public function __construct(\Zend_View_Interface $view) {
$this->view = $view;
}
public function getSubscribedEvents() {
return array(ORM\Events::postLoad);
return array(Events::postLoad);
}
public function postLoad(ORM\Event\LifecycleEventArgs $args) {
$blockItem = $args->getEntity();
public function postLoad(LifecycleEventArgs $args) {
$blockItem = $args->getObject();
// Both blocks and panels are instances of Block\AbstractBlock
if ($blockItem instanceof Block\AbstractBlock) {
$strategy = $blockItem->getStrategyClassName();
@@ -250,5 +251,3 @@ This might look like this:
In this example, even some variables are set - like a view object
or a specific configuration object.

View File

@@ -36,12 +36,12 @@ are allowed to:
public function assertCustomerAllowedBuying()
{
$orderLimit = $this->customer->getOrderLimit();
$amount = 0;
foreach ($this->orderLines as $line) {
$amount += $line->getAmount();
}
if ($amount > $orderLimit) {
throw new CustomerOrderLimitExceededException();
}
@@ -53,7 +53,21 @@ 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 Annotations:
First Attributes:
.. code-block:: php
<?php
#[Entity]
#[HasLifecycleCallbacks]
class Order
{
#[PrePersist, PreUpdate]
public function assertCustomerAllowedBuying() {}
}
As Annotations:
.. code-block:: php
@@ -83,9 +97,6 @@ In XML Mappings:
</entity>
</doctrine-mapping>
YAML needs some little change yet, to allow multiple lifecycle
events for one method, this will happen before Beta 1 though.
Now validation is performed whenever you call
``EntityManager#persist($order)`` or when you call
``EntityManager#flush()`` and an order is about to be updated. Any
@@ -101,19 +112,17 @@ validation callbacks.
<?php
class Order
{
/**
* @PrePersist @PreUpdate
*/
#[PrePersist, PreUpdate]
public function validate()
{
if (!($this->plannedShipDate instanceof DateTime)) {
throw new ValidateException();
}
if ($this->plannedShipDate->format('U') < time()) {
throw new ValidateException();
}
if ($this->customer == null) {
throw new OrderRequiresCustomerException();
}

View File

@@ -15,13 +15,16 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
.. code-block:: php
<?php
/** @Entity */
use DateTime;
#[Entity]
class Article
{
/** @Column(type="datetime") */
private $updated;
#[Column(type: 'datetime')]
private DateTime $updated;
public function setUpdated()
public function setUpdated(): void
{
// will NOT be saved in the database
$this->updated->modify("now");
@@ -33,12 +36,14 @@ The way to go would be:
.. code-block:: php
<?php
use DateTime;
class Article
{
public function setUpdated()
public function setUpdated(): void
{
// WILL be saved in the database
$this->updated = new \DateTime("now");
$this->updated = new DateTime("now");
}
}
@@ -84,16 +89,14 @@ 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
{
/**
* @var \DateTimeZone
*/
private static $utc;
private static DateTimeZone $utc;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
@@ -126,10 +129,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(): DateTimeZone
{
return self::$utc ?: self::$utc = new \DateTimeZone('UTC');
return self::$utc ??= new DateTimeZone('UTC');
}
}

View File

@@ -72,6 +72,7 @@ Advanced Topics
* :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:`Partial Objects <reference/partial-objects>`
@@ -112,7 +113,6 @@ Cookbook
* **Implementation**:
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
:doc:`Using Wakeup Or Clone <cookbook/implementing-wakeup-or-clone>` |
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
:doc:`Validation <cookbook/validation-of-entities>` |
:doc:`Entities in the Session <cookbook/entities-in-session>` |

View File

@@ -12,6 +12,7 @@ steps of configuration.
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;
@@ -28,7 +29,7 @@ steps of configuration.
$config = new Configuration;
$config->setMetadataCache($metadataCache);
$driverImpl = ORMSetup::createDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
@@ -40,12 +41,12 @@ steps of configuration.
$config->setAutoGenerateProxyClasses(false);
}
$connectionOptions = array(
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);
'path' => 'database.sqlite',
], $config);
$em = EntityManager::create($connectionOptions, $config);
$em = new EntityManager($connection, $config);
Doctrine and Caching
--------------------
@@ -113,29 +114,30 @@ classes.
There are currently 5 available implementations:
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
- ``Doctrine\ORM\Mapping\Driver\AttributeDriver``
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver`` (deprecated and will
be removed in ``doctrine/orm`` 3.0)
- ``Doctrine\ORM\Mapping\Driver\YamlDriver`` (deprecated and will be
removed in ``doctrine/orm`` 3.0)
Throughout the most part of this manual the AnnotationDriver is
used in the examples. For information on the usage of the XmlDriver
or YamlDriver please refer to the dedicated chapters
``XML Mapping`` and ``YAML Mapping``.
Throughout the most part of this manual the AttributeDriver is
used in the examples. For information on the usage of the
AnnotationDriver, XmlDriver or YamlDriver please refer to the dedicated
chapters ``Annotation Reference``, ``XML Mapping`` and ``YAML Mapping``.
The annotation driver can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
.. code-block:: php
<?php
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
$driverImpl = ORMSetup::createDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the annotation
The path information to the entities is required for the attribute
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
@@ -152,7 +154,7 @@ Metadata Cache (***RECOMMENDED***)
$config->getMetadataCache();
Gets or sets the cache adapter to use for caching metadata
information, that is, all the information you supply via
information, that is, all the information you supply via attributes,
annotations, xml or yaml, 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
@@ -214,7 +216,7 @@ option that controls this behavior is:
Possible values for ``$mode`` are:
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_NEVER``
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
Never autogenerate a proxy. You will need to generate the proxies
manually, for this use the Doctrine Console like so:
@@ -230,17 +232,17 @@ 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.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_ALWAYS``
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
Always generates a new proxy in every request and writes it to disk.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
- ``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\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_EVAL``
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via eval(),
avoiding writing the proxies to disk.
@@ -274,15 +276,13 @@ proxy sets an exclusive file lock which can cause serious
performance bottlenecks in systems with regular concurrent
requests.
Connection Options
------------------
Connection
----------
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
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
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
Proxy Objects
@@ -325,8 +325,9 @@ identifier. You could simply do this:
$cart->addItem($item);
Here, we added an Item to a Cart without loading the Item from the
database. If you invoke any method on the Item instance, it would
fully initialize its state transparently from the database. Here
database. If you access any 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
@@ -404,15 +405,15 @@ Multiple Metadata Sources
When using different components using Doctrine ORM you may end up
with them using two different metadata drivers, for example XML and
YAML. You can use the DriverChain Metadata implementations to
YAML. You can use the MappingDriverChain Metadata implementations to
aggregate these drivers based on namespaces:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\DriverChain;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
$chain = new DriverChain();
$chain = new MappingDriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');

View File

@@ -1,6 +1,11 @@
Annotations Reference
=====================
.. warning::
The annotation driver is deprecated and will be removed in version
3.0. It is strongly recommended to switch to one of the other
mapping drivers.
.. note::
To be able to use annotations, you will have to install an extra
@@ -124,7 +129,7 @@ Optional attributes:
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
included when inserting a new row into the underlying entities table.
If not specified, default value is true.
- **updatable**: Boolean value to determine if the column should be
@@ -1382,4 +1387,3 @@ Example:
* @Version
*/
protected $version;

View File

@@ -74,32 +74,13 @@ 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 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).
- An entity class must not implement ``__clone`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
- An entity class must not implement ``__wakeup`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
You can also consider implementing
`Serializable <https://php.net/manual/en/class.serializable.php>`_,
but be aware that it is deprecated since PHP 8.1. We do not recommend its usage.
- PHP 7.4 introduces :doc:`the new magic method <https://php.net/manual/en/language.oop5.magic.php#object.unserialize>`
``__unserialize``, which changes the execution priority between
``__wakeup`` and itself when used. This can cause unexpected behaviour in
an Entity.
- An entity class must not be final nor read-only but
it may contain final methods or read-only properties.
- 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
must not have a mapped field with the same name as an already
mapped field that is inherited from A.
- An entity cannot make use of func_get_args() to implement variable parameters.
Generated proxies do not support this for performance reasons and your code might
actually fail to work when violating this restriction.
- Entity cannot access private/protected properties/methods of another entity of the same class or :doc:`do so safely <../cookbook/accessing-private-properties-of-the-same-class-from-different-instance>`.
Entities support inheritance, polymorphic associations, and
polymorphic queries. Both abstract and concrete classes can be
@@ -113,6 +94,25 @@ 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
~~~~~~~~~~~~~
@@ -159,17 +159,13 @@ 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. If you intend to serialize (and unserialize) entity
instances that still hold references to proxy objects you may run
into problems with private properties because of technical
limitations. Proxy objects implement ``__sleep`` and it is not
possible for ``__sleep`` to return names of private properties in
parent classes. On the other hand it is not a solution for proxy
objects to implement ``Serializable`` because Serializable does not
work well with any potential cyclic object references (at least we
did not find a way yet, if you did, please contact us). The
``Serializable`` interface is also deprecated beginning with PHP 8.1.
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.
The EntityManager
~~~~~~~~~~~~~~~~~

View File

@@ -37,7 +37,26 @@ A many-to-one association is the most common association between objects. Exampl
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
#[ManyToOne(targetEntity: Address::class)]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
private Address|null $address = null;
}
#[Entity]
class Address
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -49,7 +68,7 @@ A many-to-one association is the most common association between objects. Exampl
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
private $address;
private Address|null $address = null;
}
/** @Entity */
@@ -82,9 +101,11 @@ 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:
@@ -111,7 +132,29 @@ references one ``Shipment`` entity.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class Product
{
// ...
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment|null $shipment = null;
// ...
}
#[Entity]
class Shipment
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -124,7 +167,7 @@ references one ``Shipment`` entity.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
private Shipment|null $shipment = null;
// ...
}
@@ -156,7 +199,7 @@ references one ``Shipment`` entity.
name: shipment_id
referencedColumnName: id
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:
@@ -188,7 +231,35 @@ object.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class Customer
{
// ...
/** One Customer has One Cart. */
#[OneToOne(targetEntity: Cart::class, mappedBy: 'customer')]
private Cart|null $cart = null;
// ...
}
#[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;
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -200,7 +271,7 @@ object.
* One Customer has One Cart.
* @OneToOne(targetEntity="Cart", mappedBy="customer")
*/
private $cart;
private Cart|null $cart = null;
// ...
}
@@ -215,7 +286,7 @@ object.
* @OneToOne(targetEntity="Customer", inversedBy="cart")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
private Customer|null $customer = null;
// ...
}
@@ -281,17 +352,15 @@ below.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class Student
{
// ...
/**
* One Student has One Mentor.
* @OneToOne(targetEntity="Student")
* @JoinColumn(name="mentor_id", referencedColumnName="id")
*/
private $mentor;
/** One Student has One Mentor. */
#[OneToOne(targetEntity: Student::class)]
#[JoinColumn(name: 'mentor_id', referencedColumnName: 'id')]
private Student|null $mentor = null;
// ...
}
@@ -326,7 +395,40 @@ bidirectional many-to-one.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Product
{
// ...
/**
* One product has many features. This is the inverse side.
* @var Collection<int, Feature>
*/
#[OneToMany(targetEntity: Feature::class, mappedBy: 'product')]
private Collection $features;
// ...
public function __construct() {
$this->features = new ArrayCollection();
}
}
#[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;
// ...
}
.. code-block:: annotation
<?php
use Doctrine\Common\Collections\ArrayCollection;
@@ -337,9 +439,10 @@ bidirectional many-to-one.
// ...
/**
* One product has many features. This is the inverse side.
* @var Collection<int, Feature>
* @OneToMany(targetEntity="Feature", mappedBy="product")
*/
private $features;
private Collection $features;
// ...
public function __construct() {
@@ -356,7 +459,7 @@ bidirectional many-to-one.
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
private Product|null $product = null;
// ...
}
@@ -421,7 +524,39 @@ The following example sets up such a unidirectional one-to-many association:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
/**
* Many Users have Many Phonenumbers.
* @var Collection<int, Phonenumber>
*/
#[JoinTable(name: 'users_phonenumbers')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'phonenumber_id', referencedColumnName: 'id', unique: true)]
#[ManyToMany(targetEntity: 'Phonenumber')]
private Collection $phonenumbers;
public function __construct()
{
$this->phonenumbers = new ArrayCollection();
}
// ...
}
#[Entity]
class Phonenumber
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -436,8 +571,9 @@ The following example sets up such a unidirectional one-to-many association:
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
* @var Collection<int, Phonenumber>
*/
private $phonenumbers;
private Collection $phonenumbers;
public function __construct()
{
@@ -523,7 +659,32 @@ database perspective is known as an adjacency list approach.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class Category
{
// ...
/**
* One Category has Many Categories.
* @var Collection<int, Category>
*/
#[OneToMany(targetEntity: Category::class, mappedBy: 'parent')]
private Collection $children;
/** Many Categories have One Category. */
#[ManyToOne(targetEntity: Category::class, inversedBy: 'children')]
#[JoinColumn(name: 'parent_id', referencedColumnName: 'id')]
private Category|null $parent = null;
// ...
public function __construct() {
$this->children = new ArrayCollection();
}
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -533,15 +694,16 @@ database perspective is known as an adjacency list approach.
/**
* One Category has Many Categories.
* @OneToMany(targetEntity="Category", mappedBy="parent")
* @var Collection<int, Category>
*/
private $children;
private Collection $children;
/**
* Many Categories have One Category.
* @ManyToOne(targetEntity="Category", inversedBy="children")
* @JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
private Category|null $parent = null;
// ...
public function __construct() {
@@ -594,7 +756,38 @@ entities:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
*/
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
public function __construct() {
$this->groups = new ArrayCollection();
}
}
#[Entity]
class Group
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -609,8 +802,9 @@ entities:
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
private $groups;
private Collection $groups;
// ...
@@ -695,7 +889,48 @@ one is bidirectional.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
*/
#[ManyToMany(targetEntity: Group::class, inversedBy: 'users')]
#[JoinTable(name: 'users_groups')]
private Collection $groups;
public function __construct() {
$this->groups = new ArrayCollection();
}
// ...
}
#[Entity]
class Group
{
// ...
/**
* Many Groups have Many Users.
* @var Collection<int, User>
*/
#[ManyToMany(targetEntity: User::class, mappedBy: 'groups')]
private Collection $users;
public function __construct() {
$this->users = new ArrayCollection();
}
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -707,8 +942,9 @@ one is bidirectional.
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups")
* @var Collection<int, Group>
*/
private $groups;
private Collection $groups;
public function __construct() {
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
@@ -724,8 +960,9 @@ one is bidirectional.
/**
* Many Groups have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="groups")
* @var Collection<int, User>
*/
private $users;
private Collection $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
@@ -806,9 +1043,9 @@ understandable:
<?php
class Article
{
private $tags;
private Collection $tags;
public function addTag(Tag $tag)
public function addTag(Tag $tag): void
{
$tag->addArticle($this); // synchronously updating inverse side
$this->tags[] = $tag;
@@ -817,9 +1054,9 @@ understandable:
class Tag
{
private $articles;
private Collection $articles;
public function addArticle(Article $article)
public function addArticle(Article $article): void
{
$this->articles[] = $article;
}
@@ -847,30 +1084,31 @@ field named ``$friendsWithMe`` and ``$myFriends``.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class User
{
// ...
/**
* Many Users have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
* @var Collection<int, User>
*/
private $friendsWithMe;
#[ManyToMany(targetEntity: User::class, mappedBy: 'myFriends')]
private Collection $friendsWithMe;
/**
* Many Users have many Users.
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* @JoinTable(name="friends",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
* @var Collection<int, User>
*/
private $myFriends;
#[JoinTable(name: 'friends')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'friend_user_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'User', inversedBy: 'friendsWithMe')]
private Collection $myFriends;
public function __construct() {
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
$this->friendsWithMe = new ArrayCollection();
$this->myFriends = new ArrayCollection();
}
// ...
@@ -910,11 +1148,17 @@ As an example, consider this mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[OneToOne(targetEntity: Shipment::class)]
private Shipment|null $shipment = null;
.. code-block:: annotation
<?php
/** @OneToOne(targetEntity="Shipment") */
private $shipment;
private Shipment|null $shipment = null;
.. code-block:: xml
@@ -937,7 +1181,15 @@ mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment|null $shipment = null;
.. code-block:: annotation
<?php
/**
@@ -945,7 +1197,7 @@ mapping:
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
private Shipment|null $shipment = null;
.. code-block:: xml
@@ -973,14 +1225,29 @@ similar defaults. As an example, consider this mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
/** @ManyToMany(targetEntity="Group") */
private $groups;
/** @var Collection<int, Group> */
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @var Collection<int, Group>
*/
private Collection $groups;
// ...
}
@@ -1004,7 +1271,25 @@ This is essentially the same as the following, more verbose, mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
*/
#[JoinTable(name: 'User_Group')]
#[JoinColumn(name: 'User_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'Group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
}
.. code-block:: annotation
<?php
class User
@@ -1017,8 +1302,9 @@ This is essentially the same as the following, more verbose, mapping:
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
private $groups;
private Collection $groups;
// ...
}
@@ -1069,7 +1355,13 @@ attribute on ``JoinColumn`` will be inherited from PHP type. So that:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[OneToOne]
private Shipment $shipment;
.. code-block:: annotation
<?php
/** @OneToOne */
@@ -1094,7 +1386,15 @@ Is essentially the same as following:
.. configuration-block::
.. code-block:: php
.. 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
/**
@@ -1163,22 +1463,19 @@ 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.
* @var Collection
* @ManyToMany(targetEntity="Group")
*/
private $groups;
/** Many Users have Many Groups. */
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
public function __construct()
{
$this->groups = new ArrayCollection();
}
public function getGroups()
public function getGroups(): Collection
{
return $this->groups;
}

View File

@@ -10,8 +10,8 @@ annotation metadata supported since the first version 2.0.
Index
-----
- :ref:`#[AssociationOverride] <attrref_associationoverride]`
- :ref:`#[AttributeOverride] <attrref_attributeoverride]`
- :ref:`#[AssociationOverride] <attrref_associationoverride>`
- :ref:`#[AttributeOverride] <attrref_attributeoverride>`
- :ref:`#[Column] <attrref_column>`
- :ref:`#[Cache] <attrref_cache>`
- :ref:`#[ChangeTrackingPolicy <attrref_changetrackingpolicy>`
@@ -27,7 +27,6 @@ Index
- :ref:`#[Id] <attrref_id>`
- :ref:`#[InheritanceType] <attrref_inheritancetype>`
- :ref:`#[JoinColumn] <attrref_joincolumn>`
- :ref:`#[JoinColumns] <attrref_joincolumns>`
- :ref:`#[JoinTable] <attrref_jointable>`
- :ref:`#[ManyToOne] <attrref_manytoone>`
- :ref:`#[ManyToMany] <attrref_manytomany>`
@@ -179,7 +178,7 @@ Optional parameters:
If not specified, default value is ``false``.
- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
included when inserting a new row into the underlying entities table.
If not specified, default value is true.
- **updatable**: Boolean value to determine if the column should be
@@ -207,6 +206,8 @@ Optional parameters:
- ``comment``: The comment of the column in the schema (might not
be supported by all vendors).
- ``charset``: The charset of the column (only supported by Mysql, PostgreSQL, Sqlite and SQLServer).
- ``collation``: The collation of the column (only supported by Mysql, PostgreSQL, Sqlite and SQLServer).
- ``check``: Adds a check constraint type to the column (might not
@@ -365,6 +366,8 @@ Optional parameters:
- **type**: By default this is string.
- **length**: By default this is 255.
- **columnDefinition**: By default this is null the definition according to the type will be used. This option allows to override it.
- **enumType**: By default this is `null`. Allows to map discriminatorColumn value to PHP enum
.. _attrref_discriminatormap:
@@ -681,6 +684,8 @@ Optional parameters:
"columnDefinition" attribute on :ref:`#[Column] <attrref_column>` also sets
the related ``#[JoinColumn]``'s columnDefinition. This is necessary to
make foreign keys work.
- **options**:
See "options" attribute on :ref:`#[Column] <attrref_column>`.
Example:

View File

@@ -45,13 +45,15 @@ 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>`
- :doc:`Docblock Annotations <annotations-reference>` (deprecated and
will be removed in ``doctrine/orm`` 3.0)
- :doc:`YAML <yaml-mapping>` (deprecated and will be removed in ``doctrine/orm`` 3.0.)
This manual will usually show mapping metadata via docblock annotations, though
many examples also show the equivalent configuration in YAML and XML.
This manual will usually show mapping metadata via attributes, though
many examples also show the equivalent configuration in annotations,
YAML and XML.
.. note::
@@ -157,10 +159,10 @@ Property Mapping
The next step is mapping its properties to columns in the table.
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.
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.
.. configuration-block::
@@ -287,6 +289,13 @@ These are the "automatic" mapping rules:
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
@@ -360,12 +369,23 @@ 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``
annotation.
the field that serves as the identifier with the ``#[Id]`` attribute.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class Message
{
#[Id]
#[Column(type: 'integer')]
#[GeneratedValue]
private int|null $id = null;
// ...
}
.. code-block:: annotation
<?php
class Message
@@ -375,7 +395,7 @@ annotation.
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
private int|null $id = null;
// ...
}
@@ -402,7 +422,7 @@ annotation.
fields:
# fields here
In most cases using the automatic generator strategy (``@GeneratedValue``) is
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, sequences with PostgreSQL
and Oracle and so on.
@@ -438,8 +458,8 @@ Here is the list of possible generation strategies:
- ``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`` annotation.
same as leaving off the ``#[GeneratedValue]`` entirely.
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
Sequence Generator
@@ -451,7 +471,19 @@ besides specifying the sequence's name:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class Message
{
#[Id]
#[GeneratedValue(strategy: 'SEQUENCE')]
#[SequenceGenerator(sequenceName: 'message_seq', initialValue: 1, allocationSize: 100)]
protected int|null $id = null;
// ...
}
.. code-block:: annotation
<?php
class Message
@@ -461,7 +493,7 @@ besides specifying the sequence's name:
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
protected int|null $id = null;
// ...
}
@@ -502,8 +534,6 @@ the above example with ``allocationSize=100`` Doctrine ORM 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
@@ -526,11 +556,12 @@ need to access the sequence once to generate the identifiers for
Composite Keys
~~~~~~~~~~~~~~
With Doctrine ORM 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.
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.
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
<../tutorials/composite-primary-keys>`.
@@ -546,7 +577,8 @@ 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

View File

@@ -19,11 +19,13 @@ especially what the strategies presented here provide help with.
.. note::
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
To avoid that you should disable it in the DBAL configuration:
To avoid that you should remove the corresponding middleware.
To remove all middlewares, you can use this line:
.. code-block:: php
<?php
$em->getConnection()->getConfiguration()->setSQLLogger(null);
$em->getConnection()->getConfiguration()->setMiddlewares([]); // DBAL 3
$em->getConnection()->getConfiguration()->setSQLLogger(null); // DBAL 2
Bulk Inserts
------------

View File

@@ -74,11 +74,13 @@ collections in entities in the constructor. Example:
<?php
namespace MyProject\Model;
use Doctrine\Common\Collections\ArrayCollection;
class User {
private $addresses;
private $articles;
/** @var Collection<int, Address> */
private Collection $addresses;
/** @var Collection<int, Article> */
private Collection $articles;
public function __construct() {
$this->addresses = new ArrayCollection;
$this->articles = new ArrayCollection;

View File

@@ -109,8 +109,9 @@ Metadata Cache
~~~~~~~~~~~~~~
Your class metadata can be parsed from a few different sources like
YAML, XML, Annotations, etc. Instead of parsing this information on
each request we should cache it using one of the cache drivers.
YAML, XML, Attributes, 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.
@@ -199,5 +200,3 @@ 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

@@ -49,10 +49,9 @@ This policy can be configured as follows:
.. code-block:: php
<?php
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
#[Entity]
#[ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
class User
{
// ...
@@ -78,18 +77,16 @@ follows:
<?php
use Doctrine\Persistence\NotifyPropertyChanged,
Doctrine\Persistence\PropertyChangedListener;
/**
* @Entity
* @ChangeTrackingPolicy("NOTIFY")
*/
#[Entity]
#[ChangeTrackingPolicy('NOTIFY')]
class MyEntity implements NotifyPropertyChanged
{
// ...
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
private array $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener): void
{
$this->_listeners[] = $listener;
}
@@ -104,12 +101,12 @@ behaviour:
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue)
protected function _onPropertyChanged($propName, $oldValue, $newValue): void
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
@@ -117,8 +114,8 @@ behaviour:
}
}
}
public function setData($data)
public function setData($data): void
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
@@ -134,18 +131,18 @@ 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.
If your entity contains an embeddable, you will need to notify
separately for each property in the embeddable when it changes
If your entity contains an embeddable, you will need to notify
separately for each property in the embeddable when it changes
for example:
.. code-block:: php
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
public function setEmbeddable(MyValueObject $embeddable)
public function setEmbeddable(MyValueObject $embeddable): void
{
if (!$embeddable->equals($this->embeddable)) {
// notice the entityField.embeddableField notation for referencing the property
@@ -178,5 +175,3 @@ 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,22 +41,24 @@ access point to ORM functionality provided by Doctrine.
// bootstrap.php
require_once "vendor/autoload.php";
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
$paths = array("/path/to/entity-files");
$paths = ['/path/to/entity-files'];
$isDevMode = false;
// the connection configuration
$dbParams = array(
$dbParams = [
'driver' => 'pdo_mysql',
'user' => 'root',
'password' => '',
'dbname' => 'foo',
);
];
$config = ORMSetup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
.. note::
@@ -68,24 +70,26 @@ Or if you prefer XML:
.. code-block:: php
<?php
$paths = array("/path/to/xml-mappings");
$paths = ['/path/to/xml-mappings'];
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
Or if you prefer YAML:
.. code-block:: php
<?php
$paths = array("/path/to/yml-mappings");
$paths = ['/path/to/yml-mappings'];
$config = ORMSetup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
.. note::
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
::
"symfony/yaml": "*"
Inside the ``ORMSetup`` methods several assumptions are made:

View File

@@ -180,10 +180,10 @@ not need to lazy load the association with another query.
Doctrine allows you to walk all the associations between
all the objects in your domain model. Objects that were not already
loaded from the database are replaced with lazy load proxy
instances. Non-loaded Collections are also replaced by lazy-load
loaded from the database are replaced with lazy-loading proxy
instances. Non-loaded Collections are also replaced by lazy-loading
instances that fetch all the contained objects upon first access.
However relying on the lazy-load mechanism leads to many small
However relying on the lazy-loading mechanism leads to many small
queries executed against the database, which can significantly
affect the performance of your application. **Fetch Joins** are the
solution to hydrate most or all of the entities that you need in a
@@ -319,11 +319,11 @@ With Nested Conditions in WHERE Clause:
<?php
$query = $em->createQuery('SELECT u FROM ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
$query->setParameters(array(
$query->setParameters([
'name' => 'Bob',
'name2' => 'Alice',
'id' => 321,
));
]);
$users = $query->getResult(); // array of ForumUser objects
With COUNT DISTINCT:
@@ -672,18 +672,18 @@ The same restrictions apply for the reference of related entities.
DQL DELETE statements are ported directly into an SQL DELETE statement.
Therefore, some limitations apply:
- Lifecycle events for the affected entities are not executed.
- A cascading ``remove`` operation (as indicated e. g. by ``cascade={"remove"}``
or ``cascade={"all"}`` in the mapping configuration) is not being performed
- A cascading ``remove`` operation (as indicated e. g. by ``cascade: ['remove']``
or ``cascade: ['all']`` in the mapping configuration) is not being performed
for associated entities. You can rely on database level cascade operations by
configuring each join column with the ``onDelete`` option.
- Checks for the version column are bypassed if they are not explicitly added
to the WHERE clause of the query.
When you rely on one of these features, one option is to use the
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
It means collections and related entities are fetched into memory
When you rely on one of these features, one option is to use the
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
It means collections and related entities are fetched into memory
(even if they are marked as lazy). Pulling object graphs into memory on cascade
can cause considerable performance overhead, especially when the cascaded collections
are large. Make sure to weigh the benefits and downsides.
@@ -794,7 +794,7 @@ You can register custom DQL functions in your ORM Configuration:
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = EntityManager::create($dbParams, $config);
$em = new EntityManager($connection, $config);
The functions have to return either a string, numeric or datetime
value depending on the registered function type. As an example we
@@ -806,8 +806,8 @@ classes have to implement the base class :
<?php
namespace MyProject\Query\AST;
use \Doctrine\ORM\Query\AST\Functions\FunctionNode;
use \Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
class MysqlFloor extends FunctionNode
{
@@ -864,36 +864,26 @@ scenario it is a generic Person and Employee example:
<?php
namespace Entities;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])]
class Person
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
protected $id;
#[Id, Column(type: 'integer')]
#[GeneratedValue]
protected int|null $id = null;
/**
* @Column(type="string", length=50)
*/
protected $name;
#[Column(type: 'string', length: 50)]
protected string $name;
// ...
}
/**
* @Entity
*/
#[Entity]
class Employee extends Person
{
/**
* @Column(type="string", length=50)
*/
#[Column(type: 'string', length: 50)]
private $department;
// ...
@@ -959,12 +949,11 @@ table, you just need to change the inheritance type from
.. code-block:: php
<?php
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
#[Entity]
#[InheritanceType('JOINED')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])]
class Person
{
// ...
@@ -1068,7 +1057,7 @@ the Query class. Here they are:
Instead of using these methods, you can alternatively use the
general-purpose method
``Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)``.
``Query#execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``.
Using this method you can directly supply the hydration mode as the
second parameter via one of the Query constants. In fact, the
methods mentioned earlier are just convenient shortcuts for the
@@ -1841,5 +1830,3 @@ Functions
"LOWER" "(" StringPrimary ")" |
"UPPER" "(" StringPrimary ")" |
"IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"

View File

@@ -144,20 +144,20 @@ Events Overview
| Event | Dispatched by | Lifecycle | Passed |
| | | Callback | Argument |
+=================================================================+=======================+===========+=====================================+
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `LifecycleEventArgs`_ |
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `LifecycleEventArgs`_ |
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
| | on *initial* persist | | |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
| :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 | `LifecycleEventArgs`_ |
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `LifecycleEventArgs`_ |
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
| | metadata | | |
@@ -214,7 +214,7 @@ specific to a particular entity class's lifecycle.
<?php
use Doctrine\DBAL\Types\Types;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
#[Entity]
#[HasLifecycleCallbacks]
@@ -226,7 +226,7 @@ specific to a particular entity class's lifecycle.
public $value;
#[PrePersist]
public function doStuffOnPrePersist(LifecycleEventArgs $eventArgs)
public function doStuffOnPrePersist(PrePersistEventArgs $eventArgs)
{
$this->createdAt = date('Y-m-d H:i:s');
}
@@ -246,7 +246,7 @@ specific to a particular entity class's lifecycle.
.. code-block:: annotation
<?php
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
/**
* @Entity
@@ -260,7 +260,7 @@ specific to a particular entity class's lifecycle.
public $value;
/** @PrePersist */
public function doStuffOnPrePersist(LifecycleEventArgs $eventArgs)
public function doStuffOnPrePersist(PrePersistEventArgs $eventArgs)
{
$this->createdAt = date('Y-m-d H:i:s');
}
@@ -353,11 +353,11 @@ A lifecycle event listener looks like the following:
.. code-block:: php
<?php
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
class MyEventListener
{
public function preUpdate(LifecycleEventArgs $args)
public function preUpdate(PreUpdateEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
@@ -374,9 +374,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\Persistence\Event\LifecycleEventArgs;
class MyEventSubscriber implements EventSubscriber
{
@@ -387,7 +387,7 @@ A lifecycle event subscriber may look like this:
);
}
public function postUpdate(LifecycleEventArgs $args)
public function postUpdate(PostUpdateEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
@@ -416,7 +416,7 @@ EventManager that is passed to the EntityManager factory:
$eventManager->addEventListener([Events::preUpdate], new MyEventListener());
$eventManager->addEventSubscriber(new MyEventSubscriber());
$entityManager = EntityManager::create($dbOpts, $config, $eventManager);
$entityManager = new EntityManager($connection, $config, $eventManager);
You can also retrieve the event manager instance after the
EntityManager was created:
@@ -461,7 +461,7 @@ this association is marked as :ref:`cascade: persist<transitive-persistence>`. A
during this operation is also persisted and ``prePersist`` called
on it. This is called :ref:`persistence by reachability<persistence-by-reachability>`.
In both cases you get passed a ``LifecycleEventArgs`` instance
In both cases you get passed a ``PrePersistEventArgs`` instance
which has access to the entity and the entity manager.
This event is only triggered on *initial* persist of an entity
@@ -541,7 +541,7 @@ mentioned sets. See this example:
{
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$em = $eventArgs->getObjectManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
@@ -756,7 +756,7 @@ An entity listener is a lifecycle listener class used for an entity.
.. configuration-block::
.. code-block:: attribute
<?php
namespace MyProject\Entity;
use App\EventListener\UserListener;
@@ -827,39 +827,84 @@ you need to map the listener method using the event type mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
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 { // ... }
#[PostPersist]
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
#[PreUpdate]
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
#[PostUpdate]
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
#[PostRemove]
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
#[PreRemove]
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
#[PreFlush]
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
#[PostLoad]
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
}
.. code-block:: annotation
<?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, LifecycleEventArgs $event) { // ... }
public function prePersistHandler(User $user, PrePersistEventArgs $event): void { // ... }
/** @PostPersist */
public function postPersistHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
/** @PreUpdate */
public function preUpdateHandler(User $user, PreUpdateEventArgs $event) { // ... }
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
/** @PostUpdate */
public function postUpdateHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
/** @PostRemove */
public function postRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
/** @PreRemove */
public function preRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
/** @PreFlush */
public function preFlushHandler(User $user, PreFlushEventArgs $event) { // ... }
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
/** @PostLoad */
public function postLoadHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
}
.. code-block:: xml
<doctrine-mapping>
@@ -971,7 +1016,7 @@ Implementing your own resolver:
// Configure the listener resolver only before instantiating the EntityManager
$configurations->setEntityListenerResolver(new MyEntityListenerResolver);
EntityManager::create(.., $configurations, ..);
$entityManager = new EntityManager(.., $configurations, ..);
.. _reference-events-load-class-metadata:
@@ -980,7 +1025,7 @@ Load ClassMetadata Event
``loadClassMetadata`` - The ``loadClassMetadata`` event occurs after the
mapping metadata for a class has been loaded from a mapping source
(annotations/xml/yaml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
(attributes/annotations/xml/yaml) 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.
@@ -1071,8 +1116,13 @@ and the EntityManager.
}
}
.. _LifecycleEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/LifecycleEventArgs.php
.. _PrePersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PrePersistEventArgs.php
.. _PreRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreRemoveEventArgs.php
.. _PreUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php
.. _PostPersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostPersistEventArgs.php
.. _PostRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostRemoveEventArgs.php
.. _PostUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostUpdateEventArgs.php
.. _PostLoadEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostLoadEventArgs.php
.. _PreFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreFlushEventArgs.php
.. _PostFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostFlushEventArgs.php
.. _OnFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/OnFlushEventArgs.php

View File

@@ -13,10 +13,11 @@ Database Schema
How do I set the charset and collation for MySQL tables?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can't set these values inside the annotations, yml 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.
You can't set these values with attributes, annotations or inside yml 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,11 +33,12 @@ upon insert:
class User
{
const STATUS_DISABLED = 0;
const STATUS_ENABLED = 1;
private const STATUS_DISABLED = 0;
private const STATUS_ENABLED = 1;
private $algorithm = "sha1";
private $status = self:STATUS_DISABLED;
private string $algorithm = "sha1";
/** @var self::STATUS_* */
private int $status = self::STATUS_DISABLED;
}
.

View File

@@ -28,10 +28,11 @@ 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.
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.
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.
.. code-block:: php
@@ -42,7 +43,7 @@ proper quoting of parameters.
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
// Check if the entity implements the LocalAware interface
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {

View File

@@ -45,8 +45,7 @@ in scenarios where data is loaded for read-only purposes.
Read-Only Entities
------------------
You can mark entities as read only (See metadata mapping
references for details).
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
@@ -55,8 +54,6 @@ changed.
Read-Only allows to persist new entities of a kind and remove existing ones,
they are just not considered for updates.
See :ref:`annref_entity`
You can also explicitly mark individual entities read only directly on the
UnitOfWork via a call to ``markReadOnly()``:

View File

@@ -1,6 +1,9 @@
Inheritance Mapping
===================
This chapter explains the available options for mapping class
hierarchies.
Mapped Superclasses
-------------------
@@ -14,6 +17,10 @@ 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).
No database table will be created for a mapped superclass itself,
only for entity classes inheriting from it. Also, a mapped superclass
need not have an ``#[Id]`` property.
.. note::
A mapped superclass cannot be an entity, it is not query-able and
@@ -25,6 +32,25 @@ appear in the middle of an otherwise mapped inheritance hierarchy
For further support of inheritance, the single or
joined table inheritance features have to be used.
.. 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.
Example:
@@ -38,38 +64,36 @@ Example:
use Doctrine\ORM\Mapping\MappedSuperclass;
use Doctrine\ORM\Mapping\Entity;
/** @MappedSuperclass */
#[MappedSuperclass]
class Person
{
/** @Column(type="integer") */
protected $mapped1;
/** @Column(type="string") */
protected $mapped2;
/**
* @OneToOne(targetEntity="Toothbrush")
* @JoinColumn(name="toothbrush_id", referencedColumnName="id")
*/
protected $toothbrush;
#[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;
// ... more fields and methods
}
/** @Entity */
#[Entity]
class Employee extends Person
{
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
#[Id, Column(type: 'integer')]
private int|null $id = null;
#[Column(type: 'string')]
private string $name;
// ... more fields and methods
}
/** @Entity */
#[Entity]
class Toothbrush
{
/** @Id @Column(type="integer") */
private $id;
#[Id, Column(type: 'integer')]
private int|null $id = null;
// ... more fields and methods
}
@@ -79,31 +103,106 @@ like this (this is for SQLite):
.. code-block:: sql
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))
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))
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. In order to distinguish
which row represents which type in the hierarchy a so-called
discriminator column is used.
is an inheritance mapping strategy where all classes of a hierarchy are
mapped to a single database table.
Example:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace MyProject\Model;
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
class Person
{
// ...
}
#[Entity]
class Employee extends Person
{
// ...
}
.. code-block:: annotation
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
@@ -114,7 +213,7 @@ Example:
{
// ...
}
/**
* @Entity
*/
@@ -124,7 +223,7 @@ Example:
}
.. code-block:: yaml
MyProject\Model\Person:
type: entity
inheritanceType: SINGLE_TABLE
@@ -134,30 +233,13 @@ Example:
discriminatorMap:
person: Person
employee: Employee
MyProject\Model\Employee:
type: entity
Things to note:
- 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, then the map is generated
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
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``.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -173,17 +255,10 @@ 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
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -191,7 +266,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
@@ -201,10 +276,11 @@ 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. Doctrine ORM
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.
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.
Example:
@@ -212,42 +288,25 @@ Example:
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
#[Entity]
#[InheritanceType('JOINED')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
class Person
{
// ...
}
/** @Entity */
#[Entity]
class Employee extends Person
{
// ...
}
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, then the map is generated
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
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``.
.. note::
@@ -278,20 +337,13 @@ 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 JOINed which can increase
tables of subtypes to be ``OUTER JOIN``ed 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 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.
There is also another important performance consideration that it is *NOT POSSIBLE*
to query for the base entity without any LEFT JOINs to the sub-types.
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.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -309,14 +361,16 @@ column and cascading on delete.
Overrides
---------
Used to override a mapping for an entity field or relationship. Can only be
applied to an entity that extends a mapped superclass or uses a trait to
override a relationship or field mapping defined by the mapped superclass or
trait.
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 possible to override attributes or associations in entity to entity
inheritance scenarios, because this can cause unforseen edge case behavior and
increases complexity in ORM internal classes.
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.
Association Override
@@ -330,7 +384,52 @@ Example:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// user mapping
namespace MyProject\Model;
#[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;
#[ManyToOne(targetEntity: 'Address')]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
protected Address|null $address = null;
}
// 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')]
)
])]
class Admin extends User
{
}
.. code-block:: annotation
<?php
// user mapping
@@ -348,14 +447,15 @@ Example:
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
protected $groups;
protected Collection $groups;
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
protected $address;
protected Address|null $address = null;
}
// admin mapping
@@ -475,10 +575,11 @@ Example:
Things to note:
- 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 "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 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.
@@ -490,7 +591,51 @@ Could be used by an entity that extends a mapped superclass to override a field
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// user mapping
namespace MyProject\Model;
#[MappedSuperclass]
class User
{
#[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)]
protected int|null $id = null;
#[Column(name: 'user_name', nullable: true, unique: false, length: 250)]
protected string $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
)
)
])]
class Guest extends User
{
}
.. code-block:: annotation
<?php
// user mapping
@@ -501,10 +646,10 @@ Could be used by an entity that extends a mapped superclass to override a field
class User
{
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
protected $id;
protected int|null $id = null;
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
protected $name;
protected string $name;
// other fields mapping
}
@@ -607,8 +752,8 @@ Could be used by an entity that extends a mapped superclass to override a field
Things to note:
- 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 "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.
Query the Type

View File

@@ -130,10 +130,51 @@ included in the core of Doctrine ORM. However there are already two
extensions out there that offer support for Nested Set with
ORM:
- `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.
Known Issues
------------
@@ -177,27 +218,3 @@ MySQL with MyISAM tables
Doctrine cannot provide atomic operations when calling ``EntityManager#flush()`` if one
of the tables involved uses the storage engine MyISAM. You must use InnoDB or
other storage engines that support transactions if you need integrity.
Entities, Proxies and Reflection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using methods for Reflection on entities can be prone to error, when the entity
is actually a proxy the following methods will not work correctly:
- ``new ReflectionClass``
- ``new ReflectionObject``
- ``get_class()``
- ``get_parent_class()``
This is why ``Doctrine\Common\Util\ClassUtils`` class exists that has similar
methods, which resolve the proxy problem beforehand.
.. code-block:: php
<?php
use Doctrine\Common\Util\ClassUtils;
$bookProxy = $entityManager->getReference('Acme\Book');
$reflection = ClassUtils::newReflectionClass($bookProxy);
$class = ClassUtils::getClass($bookProxy)¸

View File

@@ -13,11 +13,16 @@ metadata:
- **XML files** (XmlDriver)
- **Class DocBlock Annotations** (AnnotationDriver)
- **Attributes** (AttributeDriver)
- **YAML files** (YamlDriver)
- **PHP Code in files or static functions** (PhpDriver)
There are also two deprecated ways to do this:
- **Class DocBlock Annotations** (AnnotationDriver)
- **YAML files** (YamlDriver)
They will be removed in 3.0, make sure to avoid them.
Something important to note about the above drivers is they are all
an intermediate step to the same end result. The mapping
information is populated to ``Doctrine\ORM\Mapping\ClassMetadata``
@@ -40,8 +45,9 @@ an entity.
If you want to use one of the included core metadata drivers you need to
configure it. If you pick the annotation driver, you will additionally
need to install ``doctrine/annotations``. All the drivers are in the
configure it. If you pick the annotation driver despite it being
deprecated, you will additionally need to install
``doctrine/annotations``. All the drivers are in the
``Doctrine\ORM\Mapping\Driver`` namespace:
.. code-block:: php
@@ -120,17 +126,17 @@ the ``FileDriver`` implementation for you to extend from:
* {@inheritdoc}
*/
protected $_fileExtension = '.dcm.ext';
/**
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
$data = $this->_loadMappingFile($file);
// populate ClassMetadata instance from $data
}
/**
* {@inheritdoc}
*/
@@ -198,5 +204,3 @@ iterate over them:
foreach ($class->fieldMappings as $fieldMapping) {
echo $fieldMapping['fieldName'] . "\n";
}

View File

@@ -7,7 +7,7 @@ reduce the verbosity of the mapping document, eliminating repetitive noise (eg:
.. warning
The naming strategy is always overridden by entity mapping such as the `Table` annotation.
The naming strategy is always overridden by entity mapping such as the `Table` attribute.
Configuring a naming strategy
-----------------------------

View File

@@ -90,8 +90,8 @@ 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``:
and mapping information together but don't want to use attributes or
annotations. For this you just need to use the ``StaticPHPDriver``:
.. code-block:: php
@@ -167,7 +167,7 @@ The API of the ClassMetadataBuilder has the following methods with a fluent inte
- ``addNamedQuery($name, $dqlQuery)``
- ``setJoinedTableInheritance()``
- ``setSingleTableInheritance()``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255, $columnDefinition = null, $enumType = null)``
- ``addDiscriminatorMapClass($name, $class)``
- ``setChangeTrackingPolicyDeferredExplicit()``
- ``setChangeTrackingPolicyNotify()``
@@ -325,5 +325,3 @@ entities themselves.
- ``setIdentifierValues($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``

View File

@@ -268,7 +268,7 @@ Doctrine\DBAL\ParameterType::* or a DBAL Type name for conversion.
use Doctrine\DBAL\Types\Types;
// prevents attempt to load metadata for date time class, improving performance
$qb->setParameter('date', new \DateTimeImmutable(), Types::DATE_IMMUTABLE)
$qb->setParameter('date', new \DateTimeImmutable(), Types::DATETIME_IMMUTABLE)
If you've got several parameters to bind to your query, you can
also use setParameters() instead of setParameter() with the
@@ -434,6 +434,12 @@ 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 **/

View File

@@ -277,26 +277,44 @@ level cache region.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
#[Cache(usage: 'READ_ONLY', region: 'my_entity_region')]
class Country
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
#[Column(unique: true)]
protected string $name;
// other properties and methods
}
.. code-block:: annotation
<?php
/**
* @Entity
* @Cache(usage="READ_ONLY", region="my_entity_region")
* @Cache("NONSTRICT_READ_WRITE")
*/
class Country
class State
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
protected int|null $id = null;
/**
* @Column(unique=true)
*/
protected $name;
protected string $name;
// other properties and methods
}
@@ -340,7 +358,35 @@ It caches the primary keys of association and cache each element will be cached
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
class State
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
#[Column(unique: true)]
protected string $name;
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[ManyToOne(targetEntity: Country::class)]
#[JoinColumn(name: 'country_id', referencedColumnName: 'id')]
protected Country|null $country = null;
/** @var Collection<int, City> */
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[OneToMany(targetEntity: City::class, mappedBy: 'state')]
protected Collection $cities;
// other properties and methods
}
.. code-block:: annotation
<?php
/**
@@ -354,25 +400,26 @@ It caches the primary keys of association and cache each element will be cached
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
protected int|null $id = null;
/**
* @Column(unique=true)
*/
protected $name;
protected string $name;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country_id", referencedColumnName="id")
*/
protected $country;
protected Country|null $country;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
* @var Collection<int, City>
*/
protected $cities;
protected Collection $cities;
// other properties and methods
}
@@ -673,23 +720,18 @@ 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", inversedBy="references")
* @JoinColumn(name="source_id", referencedColumnName="article_id")
*/
private $source;
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'source_id', referencedColumnName: 'article_id')]
private Article|null $source = null;
/**
* @Id
* @ManyToOne(targetEntity="Article")
* @JoinColumn(name="target_id", referencedColumnName="article_id")
*/
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'target_id', referencedColumnName: 'article_id')]
private $target;
}

View File

@@ -98,19 +98,20 @@ entity might look like this:
<?php
/**
* @Entity
*/
#[Entity]
class InsecureEntity
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column */
private $email;
/** @Column(type="boolean") */
private $isAdmin;
#[Id, Column, GeneratedValue]
private int|null $id = null;
public function fromArray(array $userInput)
#[Column]
private string $email;
#[Column]
private bool $isAdmin;
/** @param array<string, mixed> $userInput */
public function fromArray(array $userInput): void
{
foreach ($userInput as $key => $value) {
$this->$key = $value;
@@ -118,7 +119,7 @@ entity might look like this:
}
}
Now the possiblity of mass-asignment exists on this entity and can
Now the possiblity 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

@@ -114,8 +114,8 @@ 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 rolls back the transaction when an
exception occurs.
commit and in case of an exception the ``EntityManager`` gets closed
in addition to the transaction rollback.
.. _transactions-and-concurrency_exception-handling:
@@ -189,14 +189,25 @@ example we'll use an integer.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
#[Version, Column(type: 'integer')]
private int $version;
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
private int $version;
// ...
}
@@ -222,14 +233,25 @@ timestamp or datetime):
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
#[Version, Column(type: 'datetime')]
private DateTime $version;
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
private DateTime $version;
// ...
}
@@ -279,15 +301,15 @@ either when calling ``EntityManager#find()``:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
@@ -300,16 +322,16 @@ Or you can use ``EntityManager#lock()`` to find out:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
$entity = $em->find('User', $theEntityId);
try {
// assert version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
@@ -348,7 +370,7 @@ See the example code, The form (GET Request):
<?php
$post = $em->find('BlogPost', 123456);
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
@@ -359,7 +381,7 @@ And the change headline action (POST Request):
<?php
$postId = (int)$_GET['id'];
$postVersion = (int)$_GET['version'];
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
.. _transactions-and-concurrency_pessimistic-locking:
@@ -390,7 +412,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 three different scenarios:
You can use pessimistic locks in four different scenarios:
1. Using
@@ -402,8 +424,10 @@ You can use pessimistic locks in three 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

@@ -0,0 +1,179 @@
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:: annotation
<?php
/**
* @Entity
* @Table(name="cms_users_typed_with_custom_typed_field")
*/
class UserTypedWithCustomTypedField
{
/** @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

@@ -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()
public function testIdentityMap(): void
{
$objectA = $this->entityManager->find('EntityName', 1);
$objectB = $this->entityManager->find('EntityName', 1);
@@ -34,11 +34,11 @@ will still end up with the same reference:
.. code-block:: php
public function testIdentityMapReference()
public function testIdentityMapReference(): void
{
$objectA = $this->entityManager->getReference('EntityName', 1);
// check for proxyinterface
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
$this->assertInstanceOf('Doctrine\Persistence\Proxy', $objectA);
$objectB = $this->entityManager->find('EntityName', 1);
@@ -104,7 +104,7 @@ How Doctrine Detects Changes
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
This means you map php objects into a relational database that don't
necessarily know about the database at all. A natural question would now be,
"how does Doctrine even detect objects have changed?".
"how does Doctrine even detect objects have changed?".
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
an object from the database Doctrine will keep a copy of all the properties and
@@ -202,4 +202,3 @@ ClassMetadataFactory
~~~~~~~~~~~~~~~~~~~~
tbr

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(type="string") */
private $id;
#[Id, GeneratedValue, Column]
private int|null $id = null;
/**
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
*
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
* @JoinTable(name="user_favorite_comments")
* @var Collection<int, Comment>
*/
private $favorites;
#[ManyToMany(targetEntity: Comment::class, inversedBy: 'userFavorites')]
#[JoinTable(name: 'user_favorite_comments')]
private Collection $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @ManyToMany(targetEntity="Comment")
* @JoinTable(name="user_read_comments")
* @var Collection<int, Comment>
*/
private $commentsRead;
#[ManyToMany(targetEntity: Comment::class)]
#[JoinTable(name: 'user_read_comments')]
private Collection $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author")
* @var Collection<int, Comment>
*/
private $commentsAuthored;
/**
* Unidirectional - Many-To-One
*
* @ManyToOne(targetEntity="Comment")
*/
private $firstComment;
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author')]
private Collection $commentsAuthored;
/** Unidirectional - Many-To-One */
#[ManyToOne(targetEntity: Comment::class)]
private Comment|null $firstComment = null;
}
/** @Entity */
#[Entity]
class Comment
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
#[Id, GeneratedValue, Column]
private string $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @ManyToMany(targetEntity="User", mappedBy="favorites")
* @var Collection<int, User>
*/
private $userFavorites;
#[ManyToMany(targetEntity: User::class, mappedBy: 'favorites')]
private Collection $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
* @ManyToOne(targetEntity="User", inversedBy="commentsAuthored")
*/
private $author;
#[ManyToOne(targetEntity: User::class, inversedBy: 'commentsAuthored')]
private User|null $author = null;
}
This two entities generate the following MySQL Schema (Foreign Key
@@ -100,19 +100,19 @@ definitions omitted):
firstComment_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Comment (
id VARCHAR(255) NOT NULL,
author_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE user_favorite_comments (
user_id VARCHAR(255) NOT NULL,
favorite_comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, favorite_comment_id)
) ENGINE = InnoDB;
CREATE TABLE user_read_comments (
user_id VARCHAR(255) NOT NULL,
comment_id VARCHAR(255) NOT NULL,
@@ -132,11 +132,12 @@ relations of the ``User``:
class User
{
// ...
public function getReadComments() {
/** @return Collection<int, Comment> */
public function getReadComments(): Collection {
return $this->commentsRead;
}
public function setFirstComment(Comment $c) {
public function setFirstComment(Comment $c): void {
$this->firstComment = $c;
}
}
@@ -148,17 +149,17 @@ The interaction code would then look like in the following snippet
<?php
$user = $em->find('User', $userId);
// unidirectional many to many
$comment = $em->find('Comment', $readCommentId);
$user->getReadComments()->add($comment);
$em->flush();
// unidirectional many to one
$myFirstComment = new Comment();
$user->setFirstComment($myFirstComment);
$em->persist($myFirstComment);
$em->flush();
@@ -171,40 +172,43 @@ fields on both sides:
class User
{
// ..
public function getAuthoredComments() {
/** @return Collection<int, Comment> */
public function getAuthoredComments(): Collection {
return $this->commentsAuthored;
}
public function getFavoriteComments() {
/** @return Collection<int, Comment> */
public function getFavoriteComments(): Collection {
return $this->favorites;
}
}
class Comment
{
// ...
public function getUserFavorites() {
/** @return Collection<int, User> */
public function getUserFavorites(): Collection {
return $this->userFavorites;
}
public function setAuthor(User $author = null) {
public function setAuthor(User|null $author = null): void {
$this->author = $author;
}
}
// Many-to-Many
$user->getFavorites()->add($favoriteComment);
$favoriteComment->getUserFavorites()->add($user);
$em->flush();
// Many-To-One / One-To-Many Bidirectional
$newComment = new Comment();
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
$em->persist($newComment);
$em->flush();
@@ -225,10 +229,10 @@ element. Here are some examples:
// Remove by Elements
$user->getComments()->removeElement($comment);
$comment->setAuthor(null);
$user->getFavorites()->removeElement($comment);
$comment->getUserFavorites()->removeElement($user);
// Remove by Key
$user->getComments()->remove($ithComment);
$comment->setAuthor(null);
@@ -240,7 +244,7 @@ Notice how both sides of the bidirectional association are always
updated. Unidirectional associations are consequently simpler to
handle.
Also note that if you use type-hinting in your methods, you will
Also note that if you use type-hinting in your methods, you will
have to specify a nullable type, i.e. ``setAddress(?Address $address)``,
otherwise ``setAddress(null)`` will fail to remove the association.
Another way to deal with this is to provide a special method, like
@@ -271,8 +275,8 @@ entities that have been re-added to the collection.
Say you clear a collection of tags by calling
``$post->getTags()->clear();`` and then call
``$post->getTags()->add($tag)``. This will not recognize the tag having
already been added previously and will consequently issue two separate database
``$post->getTags()->add($tag)``. This will not recognize the tag having
already been added previously and will consequently issue two separate database
calls.
Association Management Methods
@@ -292,43 +296,43 @@ example that encapsulate much of the association management code:
class User
{
// ...
public function markCommentRead(Comment $comment) {
public function markCommentRead(Comment $comment): void {
// Collections implement ArrayAccess
$this->commentsRead[] = $comment;
}
public function addComment(Comment $comment) {
public function addComment(Comment $comment): void {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
}
$this->comments[] = $comment;
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c) {
private function setFirstComment(Comment $c): void {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment) {
public function addFavorite(Comment $comment): void {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment) {
public function removeFavorite(Comment $comment): void {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
}
class Comment
{
// ..
public function addUserFavorite(User $user) {
public function addUserFavorite(User $user): void {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user) {
public function removeUserFavorite(User $user): void {
$this->userFavorites->removeElement($user);
}
}
@@ -356,7 +360,8 @@ the details inside the classes can be challenging.
<?php
class User {
public function getReadComments() {
/** @return array<int, Comment> */
public function getReadComments(): array {
return $this->commentsRead->toArray();
}
}
@@ -373,7 +378,7 @@ as your preferences.
Synchronizing Bidirectional Collections
---------------------------------------
In the case of Many-To-Many associations you as the developer have the
In the case of Many-To-Many associations you as the developer have the
responsibility of keeping the collections on the owning and inverse side
in sync when you apply changes to them. Doctrine can only
guarantee a consistent state for the hydration, not for your client
@@ -387,7 +392,7 @@ can show the possible caveats you can encounter:
<?php
$user->getFavorites()->add($favoriteComment);
// not calling $favoriteComment->getUserFavorites()->add($user);
$user->getFavorites()->contains($favoriteComment); // TRUE
$favoriteComment->getUserFavorites()->contains($user); // FALSE
@@ -422,7 +427,7 @@ comment might look like in your controller (without ``cascade: persist``):
$user = new User();
$myFirstComment = new Comment();
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment); // required, if `cascade: persist` is not set
$em->flush();
@@ -437,8 +442,10 @@ only accessing it through the User entity:
// User entity
class User
{
private $id;
private $comments;
private int $id;
/** @var Collection<int, Comment> */
private Collection $comments;
public function __construct()
{
@@ -464,11 +471,8 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
class User
{
// ...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
/** Bidirectional - One-To-Many (INVERSE SIDE) */
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author', cascade: ['persist', 'remove'])]
private $commentsAuthored;
// ...
}
@@ -480,7 +484,7 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
<?php
$user = new User();
$user->comment('Lorem ipsum', new DateTime());
$em->persist($user);
$em->flush();
@@ -559,6 +563,13 @@ 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:
@@ -570,31 +581,30 @@ and StandingData:
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
*/
#[Entity]
class Contact
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @OneToOne(targetEntity="StandingData", orphanRemoval=true) */
private $standingData;
#[OneToOne(targetEntity: StandingData::class, cascade: ['persist'], orphanRemoval: true)]
private StandingData|null $standingData = null;
/** @OneToMany(targetEntity="Address", mappedBy="contact", orphanRemoval=true) */
private $addresses;
/** @var Collection<int, Address> */
#[OneToMany(targetEntity: Address::class, mappedBy: 'contact', cascade: ['persist'], orphanRemoval: true)]
private Collection $addresses;
public function __construct()
{
$this->addresses = new ArrayCollection();
}
public function newStandingData(StandingData $sd)
public function newStandingData(StandingData $sd): void
{
$this->standingData = $sd;
}
public function removeAddress($pos)
public function removeAddress(int $pos): void
{
unset($this->addresses[$pos]);
}
@@ -612,10 +622,10 @@ Now two examples of what happens when you remove the references:
$em->flush();
In this case you have not only changed the ``Contact`` entity itself but
you have also removed the references for standing data and as well as one
address reference. When flush is called not only are the references removed
but both the old standing data and the one address entity are also deleted
In this case you have not only changed the ``Contact`` entity itself but
you have also removed the references for standing data and as well as one
address reference. When flush is called not only are the references removed
but both the old standing data and the one address entity are also deleted
from the database.
.. _filtering-collections:

View File

@@ -95,28 +95,29 @@ from newly opened EntityManager.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @Column(type="string") */
private $headline;
#[Column(type: 'string')]
private string $headline;
/** @ManyToOne(targetEntity="User") */
private $author;
#[ManyToOne(targetEntity: User::class)]
private User|null $author = null;
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments;
/** @var Collection<int, Comment> */
#[OneToMany(targetEntity: Comment::class, mappedBy: 'article')]
private Collection $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getAuthor() { return $this->author; }
public function getComments() { return $this->comments; }
public function getAuthor(): User|null { return $this->author; }
public function getComments(): Collection { return $this->comments; }
}
$article = $em->find('Article', 1);
@@ -161,28 +162,6 @@ 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 public
methods along the lines of the ``getName()`` method shown below:
.. code-block:: php
<?php
class UserProxy extends User implements Proxy
{
private function _load()
{
// lazy loading code
}
public function getName()
{
$this->_load();
return parent::getName();
}
// .. other public methods of User
}
.. warning::
Traversing the object graph for parts that are lazy-loaded will
@@ -304,7 +283,7 @@ 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.
Removing an entity will also automatically delete any existing
@@ -339,8 +318,8 @@ in multiple ways with very different performance impacts.
.. 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.
map and therefore detach it. Querying the same entity again, for example
via a lazy loaded relation, will return a new object.
Detaching entities
@@ -413,14 +392,6 @@ Example:
// $entity now refers to the fully managed copy returned by the merge operation.
// The EntityManager $em now manages the persistence of $entity as usual.
.. note::
When you want to serialize/unserialize entities you
have to make all entity properties protected, never private. The
reason for this is, if you serialize a class that was a proxy
instance before, the private variables won't be serialized and a
PHP Notice is thrown.
The semantics of the merge operation, applied to an entity X, are
as follows:
@@ -833,7 +804,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 Annotation, XML or YAML metadata. In large
Repository in the Attribute, Annotation, XML or YAML 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.
@@ -843,12 +814,11 @@ 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="MyDomain\Model\UserRepository")
*/
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
@@ -856,7 +826,8 @@ in a central location.
class UserRepository extends EntityRepository
{
public function getAllAdminUsers()
/** @return Collection<User> */
public function getAllAdminUsers(): Collection
{
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
@@ -871,5 +842,3 @@ You can access your repository now by calling:
// $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();

View File

@@ -1,7 +1,7 @@
YAML Mapping
============
.. note::
.. warning::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.

View File

@@ -73,7 +73,6 @@
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

@@ -75,7 +75,6 @@ Cookbook
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

@@ -23,7 +23,34 @@ and year of production as primary keys:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace VehicleCatalogue\Model;
#[Entity]
class Car
{
public function __construct(
#[Id, Column]
private string $name,
#[Id, Column]
private int $year,
) {
}
public function getModelName(): string
{
return $this->name;
}
public function getYearOfProduction(): int
{
return $this->year;
}
}
.. code-block:: annotation
<?php
namespace VehicleCatalogue\Model;
@@ -34,9 +61,9 @@ and year of production as primary keys:
class Car
{
/** @Id @Column(type="string") */
private $name;
private string $name;
/** @Id @Column(type="integer") */
private $year;
private int $year;
public function __construct($name, $year)
{
@@ -44,12 +71,12 @@ and year of production as primary keys:
$this->year = $year;
}
public function getModelName()
public function getModelName(): string
{
return $this->name;
}
public function getYearOfProduction()
public function getYearOfProduction(): int
{
return $this->year;
}
@@ -104,7 +131,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, array("name" => "Audi A8", "year" => 2010))
->setParameter(1, ["name" => "Audi A8", "year" => 2010])
->getSingleResult();
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
@@ -131,7 +158,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`` annotation onto every association.
- Plug an ``#[Id]`` attribute onto every association.
- Set an attribute ``association-key`` with the field name of the association in XML.
- Set a key ``associationKey:`` with the field name of the association in YAML.
@@ -142,7 +169,52 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Article
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
#[Column]
private string $title;
/** @var ArrayCollection<string, ArticleAttribute> */
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
public function addAttribute(string $name, ArticleAttribute $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
#[Entity]
class ArticleAttribute
{
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
private Article $article;
#[Id, Column]
private string $attribute;
#[Column]
private string $value;
public function __construct(string $name, string $value, Article $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: annotation
<?php
namespace Application\Model;
@@ -155,16 +227,17 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
private int|null $id = null;
/** @Column(type="string") */
private $title;
private string $title;
/**
* @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
* @var Collection<int, ArticleAttribute>
*/
private $attributes;
private Collection $attributes;
public function addAttribute($name, $value)
public function addAttribute($name, $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
@@ -176,13 +249,13 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
class ArticleAttribute
{
/** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
private $article;
private Article|null $article;
/** @Id @Column(type="string") */
private $attribute;
private string $attribute;
/** @Column(type="string") */
private $value;
private string $value;
public function __construct($name, $value, $article)
{
@@ -202,7 +275,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
<entity name="Application\Model\ArticleAttribute">
<id name="article" association-key="true" />
<id name="attribute" type="string" />
<field name="value" type="string" />
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
@@ -237,25 +310,22 @@ One good example for this is a user-address relationship:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
/**
* @Entity
*/
#[Entity]
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column, GeneratedValue]
private int|null $id = null;
}
/**
* @Entity
*/
#[Entity]
class Address
{
/** @Id @OneToOne(targetEntity="User") */
private $user;
#[Id, OneToOne(targetEntity: User::class)]
private User|null $user = null;
}
.. code-block:: yaml
@@ -288,68 +358,70 @@ of products purchased and maybe even the current price.
.. code-block:: php
<?php
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
#[Entity]
class Order
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column, GeneratedValue]
private int|null $id = null;
/** @ManyToOne(targetEntity="Customer") */
private $customer;
/** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
/** @var ArrayCollection<int, OrderItem> */
#[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')]
private Collection $items;
/** @Column(type="boolean") */
private $paid = false;
/** @Column(type="boolean") */
private $shipped = false;
/** @Column(type="datetime") */
private $created;
#[Column]
private bool $paid = false;
#[Column]
private bool $shipped = false;
#[Column]
private DateTime $created;
public function __construct(Customer $customer)
{
$this->customer = $customer;
public function __construct(
#[ManyToOne(targetEntity: Customer::class)]
private Customer $customer,
) {
$this->items = new ArrayCollection();
$this->created = new \DateTime("now");
$this->created = new DateTime("now");
}
}
/** @Entity */
#[Entity]
class Product
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column, GeneratedValue]
private int|null $id = null;
/** @Column(type="string") */
private $name;
#[Column]
private string $name;
/** @Column(type="decimal") */
private $currentPrice;
#[Column]
private int $currentPrice;
public function getCurrentPrice()
public function getCurrentPrice(): int
{
return $this->currentPrice;
}
}
/** @Entity */
#[Entity]
class OrderItem
{
/** @Id @ManyToOne(targetEntity="Order") */
private $order;
#[Id, ManyToOne(targetEntity: Order::class)]
private Order|null $order = null;
/** @Id @ManyToOne(targetEntity="Product") */
private $product;
#[Id, ManyToOne(targetEntity: Product::class)]
private Product|null $product = null;
/** @Column(type="integer") */
private $amount = 1;
#[Column]
private int $amount = 1;
/** @Column(type="decimal") */
private $offeredPrice;
#[Column]
private int $offeredPrice;
public function __construct(Order $order, Product $product, $amount = 1)
public function __construct(Order $order, Product $product, int $amount = 1)
{
$this->order = $order;
$this->product = $product;

View File

@@ -1,10 +1,10 @@
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
to reduce duplication or separating concerns. Value objects such as date range
or address are the primary use case for this feature.
or address are the primary use case for this feature.
.. note::
@@ -17,7 +17,34 @@ instead of simply adding the respective columns to the ``User`` class.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
#[Embedded(class: Address::class)]
private Address $address;
}
#[Embeddable]
class Address
{
#[Column(type: "string")]
private string $street;
#[Column(type: "string")]
private string $postalCode;
#[Column(type: "string")]
private string $city;
#[Column(type: "string")]
private string $country;
}
.. code-block:: annotation
<?php
@@ -25,23 +52,23 @@ instead of simply adding the respective columns to the ``User`` class.
class User
{
/** @Embedded(class = "Address") */
private $address;
private Address $address;
}
/** @Embeddable */
class Address
{
/** @Column(type = "string") */
private $street;
private string $street;
/** @Column(type = "string") */
private $postalCode;
private string $postalCode;
/** @Column(type = "string") */
private $city;
private string $city;
/** @Column(type = "string") */
private $country;
private string $country;
}
.. code-block:: xml
@@ -109,7 +136,18 @@ The following example shows you how to set your prefix to ``myPrefix_``:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
#[Embedded(class: Address::class, columnPrefix: "myPrefix_")]
private Address $address;
}
.. code-block:: annotation
<?php
@@ -140,7 +178,18 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
#[Embedded(class: Address::class, columnPrefix: false)]
private Address $address;
}
.. code-block:: annotation
<?php
@@ -148,9 +197,15 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
class User
{
/** @Embedded(class = "Address", columnPrefix = false) */
private $address;
private Address $address;
}
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" use-column-prefix="false" />
</entity>
.. code-block:: yaml
User:
@@ -160,12 +215,6 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
class: Address
columnPrefix: false
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" use-column-prefix="false" />
</entity>
DQL
---
@@ -176,4 +225,3 @@ as if they were declared in the ``User`` class:
.. code-block:: sql
SELECT u FROM User u WHERE u.address.city = :myCity

View File

@@ -52,7 +52,20 @@ switch to extra lazy as shown in these examples:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Doctrine\Tests\Models\CMS;
#[Entity]
class CmsGroup
{
/** @var Collection<int, CmsUser> */
#[ManyToMany(targetEntity: CmsUser::class, mappedBy: 'groups', fetch: 'EXTRA_LAZY')]
public Collection $users;
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\CMS;
@@ -64,8 +77,9 @@ switch to extra lazy as shown in these examples:
{
/**
* @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
* @var Collection<int, CmsUser>
*/
public $users;
public Collection $users;
}
.. code-block:: xml
@@ -92,4 +106,3 @@ switch to extra lazy as shown in these examples:
targetEntity: CmsUser
mappedBy: groups
fetch: EXTRA_LAZY

View File

@@ -43,14 +43,15 @@ What are Entities?
Entities are PHP Objects that can be identified over many requests
by a unique identifier or primary key. These classes don't need to extend any
abstract base class or interface. An entity class must not be final
or contain final methods. Additionally it must not implement
**clone** nor **wakeup**, unless it :doc:`does so safely <../cookbook/implementing-wakeup-or-clone>`.
abstract base class or interface.
An entity contains persistable properties. A persistable property
is an instance variable of the entity that is saved into and retrieved from the database
by Doctrine's data mapping capabilities.
An entity class must not be final nor read-only, although
it can contain final methods or read-only properties.
An Example Model: Bug Tracker
-----------------------------
@@ -83,7 +84,6 @@ that directory with the following contents:
"require": {
"doctrine/orm": "^2.11.0",
"doctrine/dbal": "^3.2",
"doctrine/annotations": "1.13.2",
"symfony/yaml": "^5.4",
"symfony/cache": "^5.4"
},
@@ -137,38 +137,44 @@ step:
<?php
// bootstrap.php
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
require_once "vendor/autoload.php";
// Create a simple "default" Doctrine ORM configuration for Annotations
$isDevMode = true;
$proxyDir = null;
$cache = null;
$useSimpleAnnotationReader = false;
$config = ORMSetup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
// or if you prefer YAML or XML
// $config = ORMSetup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
// $config = ORMSetup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
// Create a simple "default" Doctrine ORM configuration for Attributes
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: array(__DIR__."/src"),
isDevMode: true,
);
// or if you prefer annotation, YAML or XML
// $config = ORMSetup::createAnnotationMetadataConfiguration(
// paths: array(__DIR__."/src"),
// isDevMode: true,
// );
// $config = ORMSetup::createXMLMetadataConfiguration(
// paths: array(__DIR__."/config/xml"),
// isDevMode: true,
//);
// $config = ORMSetup::createYAMLMetadataConfiguration(
// paths: array(__DIR__."/config/yaml"),
// isDevMode: true,
// );
// database configuration parameters
$conn = array(
// configuring the database connection
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'path' => __DIR__ . '/db.sqlite',
);
], $config);
// obtaining the entity manager
$entityManager = EntityManager::create($conn, $config);
$entityManager = new EntityManager($connection, $config);
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
.. note::
It is recommended not to use the SimpleAnnotationReader because its
usage will be removed for version 3.0.
The ``require_once`` statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
@@ -256,14 +262,8 @@ entity definition:
// src/Product.php
class Product
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $name;
private int|null $id = null;
private string $name;
}
When creating entity classes, all of the fields should be ``private``.
@@ -500,14 +500,35 @@ the ``Product`` entity to Doctrine using a metadata language. The metadata
language describes how entities, their properties and references should be
persisted and what constraints should be applied to them.
Metadata for an Entity can be configured using DocBlock annotations directly
in the Entity class itself, or in an external XML or YAML file. This Getting
Started guide will demonstrate metadata mappings using all three methods,
but you only need to choose one.
Metadata for an Entity can be configured using attributes directly in
the Entity class itself, or in an external XML or YAML file. This
Getting Started guide will demonstrate metadata mappings using all three
methods, but you only need to choose one.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// src/Product.php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'products')]
class Product
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $name;
// .. (other code)
}
.. code-block:: annotation
<?php
// src/Product.php
@@ -525,11 +546,11 @@ but you only need to choose one.
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
private $id;
private int|null $id = null;
/**
* @ORM\Column(type="string")
*/
private $name;
private string $name;
// .. (other code)
}
@@ -708,49 +729,35 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="bugs")
*/
#[ORM\Entity]
#[ORM\Table(name: 'bugs')]
class Bug
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
* @var int
*/
private $id;
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int $id;
/**
* @ORM\Column(type="string")
* @var string
*/
private $description;
#[ORM\Column(type: 'string')]
private string $description;
/**
* @ORM\Column(type="datetime")
* @var DateTime
*/
private $created;
#[ORM\Column(type: 'datetime')]
private DateTime $created;
/**
* @ORM\Column(type="string")
* @var string
*/
private $status;
#[ORM\Column(type: 'string')]
private string $status;
public function getId()
public function getId(): int|null
{
return $this->id;
}
public function getDescription()
public function getDescription(): string
{
return $this->description;
}
public function setDescription($description)
public function setDescription(string $description): void
{
$this->description = $description;
}
@@ -760,17 +767,17 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
$this->created = $created;
}
public function getCreated()
public function getCreated(): DateTime
{
return $this->created;
}
public function setStatus($status)
public function setStatus($status): void
{
$this->status = $status;
}
public function getStatus()
public function getStatus():string
{
return $this->status;
}
@@ -783,37 +790,31 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="users")
*/
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @var int
*/
private $id;
/** @var int */
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int|null $id = null;
/**
* @ORM\Column(type="string")
* @var string
*/
private $name;
/** @var string */
#[ORM\Column(type: 'string')]
private string $name;
public function getId()
public function getId(): int|null
{
return $this->id;
}
public function getName()
public function getName(): string
{
return $this->name;
}
public function setName($name)
public function setName(string $name): void
{
$this->name = $name;
}
@@ -842,13 +843,16 @@ domain model to match the requirements:
<?php
// src/Bug.php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
class Bug
{
// ... (previous code)
private $products;
/** @var Collection<int, Product> */
private Collection $products;
public function __construct()
{
@@ -866,8 +870,10 @@ domain model to match the requirements:
{
// ... (previous code)
private $reportedBugs;
private $assignedBugs;
/** @var Collection<int, Bug> */
private Collection $reportedBugs;
/** @var Collection<int, Bug> */
private Collection $assignedBugs;
public function __construct()
{
@@ -884,18 +890,6 @@ domain model to match the requirements:
understand the changes that have happened to the collection that are
noteworthy for persistence.
.. warning::
Lazy load proxies always contain an instance of
Doctrine's EntityManager and all its dependencies. Therefore a
``var_dump()`` will possibly dump a very large recursive structure
which is impossible to render and read. You have to use
``Doctrine\Common\Util\Debug::dump()`` to restrict the dumping to a
human readable level. Additionally you should be aware that dumping
the EntityManager to a Browser may take several minutes, and the
``Debug::dump()`` method just ignores any occurrences of it in Proxy
instances.
Because we only work with collections for the references we must be
careful to implement a bidirectional reference in the domain model.
The concept of owning or inverse side of a relation is central to
@@ -942,27 +936,27 @@ the bi-directional reference:
{
// ... (previous code)
private $engineer;
private $reporter;
private User $engineer;
private User $reporter;
public function setEngineer(User $engineer)
public function setEngineer(User $engineer): void
{
$engineer->assignedToBug($this);
$this->engineer = $engineer;
}
public function setReporter(User $reporter)
public function setReporter(User $reporter): void
{
$reporter->addReportedBug($this);
$this->reporter = $reporter;
}
public function getEngineer()
public function getEngineer(): User
{
return $this->engineer;
}
public function getReporter()
public function getReporter(): User
{
return $this->reporter;
}
@@ -976,15 +970,17 @@ the bi-directional reference:
{
// ... (previous code)
private $reportedBugs;
private $assignedBugs;
/** @var Collection<int, Bug> */
private Collection $reportedBugs;
/** @var Collection<int, Bug> */
private Collection $assignedBugs;
public function addReportedBug(Bug $bug)
public function addReportedBug(Bug $bug): void
{
$this->reportedBugs[] = $bug;
}
public function assignedToBug(Bug $bug)
public function assignedToBug(Bug $bug): void
{
$this->assignedBugs[] = $bug;
}
@@ -1028,14 +1024,16 @@ the database that points from Bugs to Products.
{
// ... (previous code)
private $products;
/** @var Collection<int, Product> */
private Collection $products;
public function assignToProduct(Product $product)
public function assignToProduct(Product $product): void
{
$this->products[] = $product;
}
public function getProducts()
/** @return Collection<int, Product> */
public function getProducts(): Collection
{
return $this->products;
}
@@ -1046,7 +1044,46 @@ Lets add metadata mappings for the ``Bug`` entity, as we did for
the ``Product`` before:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// src/Bug.php
use DateTime;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'bugs')]
class Bug
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $description;
#[ORM\Column(type: 'datetime')]
private DateTime $created;
#[ORM\Column(type: 'string')]
private string $status;
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'assignedBugs')]
private User|null $engineer = null;
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'reportedBugs')]
private User|null $reporter;
/** @var Collection<int, Product> */
#[ORM\ManyToMany(targetEntity: Product::class)]
private Collection $products;
// ... (other code)
}
.. code-block:: annotation
<?php
// src/Bug.php
@@ -1064,37 +1101,37 @@ the ``Product`` before:
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
private $id;
private int|null $id = null;
/**
* @ORM\Column(type="string")
*/
private $description;
private string $description;
/**
* @ORM\Column(type="datetime")
*/
private $created;
private DateTime $created;
/**
* @ORM\Column(type="string")
*/
private $status;
private string $status;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
*/
private $engineer;
private User|null $engineer;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
*/
private $reporter;
private User|null $reporter;
/**
* @ORM\ManyToMany(targetEntity="Product")
*/
private $products;
private Collection $products;
// ... (other code)
}
@@ -1183,7 +1220,37 @@ Finally, we'll add metadata mappings for the ``User`` entity.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $name;
/** @var Collection<int, Bug> An ArrayCollection of Bug objects. */
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'reporter')]
private Collection $reportedBugs;
/** @var Collection<int,Bug> An ArrayCollection of Bug objects. */
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'engineer')]
private $assignedBugs;
// .. (other code)
}
.. code-block:: annotation
<?php
// src/User.php
@@ -1202,29 +1269,28 @@ Finally, we'll add metadata mappings for the ``User`` entity.
* @ORM\Column(type="integer")
* @var int
*/
private $id;
private int|null $id = null;
/**
* @ORM\Column(type="string")
* @var string
*/
private $name;
private string $name;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Bug[] An ArrayCollection of Bug objects.
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
*/
private $reportedBugs;
private Collection $reportedBugs;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Bug[] An ArrayCollection of Bug objects.
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
*/
private $assignedBugs;
private Collection $assignedBugs;
// .. (other code)
}
.. code-block:: xml
<!-- config/xml/User.dcm.xml -->
@@ -1512,39 +1578,8 @@ The output of the engineers name is fetched from the database! What is happen
Since we only retrieved the bug by primary key both the engineer and reporter
are not immediately loaded from the database but are replaced by LazyLoading
proxies. These proxies will load behind the scenes, when the first method
is called on them.
Sample code of this proxy generated code can be found in the specified Proxy
Directory, it looks like:
.. code-block:: php
<?php
namespace MyProject\Proxies;
/**
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
**/
class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy
{
// .. lazy load code here
public function addReportedBug($bug)
{
$this->_load();
return parent::addReportedBug($bug);
}
public function assignedToBug($bug)
{
$this->_load();
return parent::assignedToBug($bug);
}
}
See how upon each method call the proxy is lazily loaded from the
database?
proxies. These proxies will load behind the scenes, when attempting to access
any of their un-initialized state.
The call prints:
@@ -1754,7 +1789,20 @@ we have to adjust the metadata slightly.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: BugRepository::class)]
#[ORM\Table(name: 'bugs')]
class Bug
{
// ...
}
.. code-block:: annotation
<?php
@@ -1763,7 +1811,7 @@ we have to adjust the metadata slightly.
/**
* @ORM\Entity(repositoryClass="BugRepository")
* @ORM\Table(name="bugs")
**/
*/
class Bug
{
// ...

View File

@@ -5,28 +5,42 @@ There are use-cases when you'll want to sort collections when they are
retrieved from the database. In userland you do this as long as you
haven't initially saved an entity with its associations into the
database. To retrieve a sorted collection from the database you can
use the ``@OrderBy`` annotation with a collection that specifies
use the ``#[OrderBy]`` attribute with a collection that specifies
a DQL snippet that is appended to all queries with this
collection.
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
can specify the ``@OrderBy`` in the following way:
Additional to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute you
can specify the ``#[OrderBy]`` in the following way:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
#[ManyToMany(targetEntity: Group::class)]
#[OrderBy(["name" => "ASC"])]
private Collection $groups;
}
.. code-block:: annotation
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
**/
private $groups;
* @var Collection<int, Group>
*/
private Collection $groups;
}
.. code-block:: xml
@@ -62,7 +76,7 @@ The DQL Snippet in OrderBy is only allowed to consist of
unqualified, unquoted field names and of an optional ASC/DESC
positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
class of the ``#[ManyToMany]`` or ``#[OneToMany]`` attribute.
The semantics of this feature can be described as follows:
@@ -106,5 +120,3 @@ You can reverse the order with an explicit DQL ORDER BY:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC

View File

@@ -14,47 +14,43 @@ Suppose we have a class ExampleEntityWithOverride. This class uses trait Example
.. code-block:: php
<?php
/**
* @Entity
*
* @AttributeOverrides({
* @AttributeOverride(name="foo",
* column=@Column(
* name = "foo_overridden",
* type = "integer",
* length = 140,
* nullable = false,
* unique = false
* )
* )
* })
*
* @AssociationOverrides({
* @AssociationOverride(name="bar",
* joinColumns=@JoinColumn(
* name="example_entity_overridden_bar_id", referencedColumnName="id"
* )
* )
* })
*/
#[Entity]
#[AttributeOverrides([
new AttributeOverride('foo', [
'column' => new Column([
'name' => 'foo_overridden',
'type' => 'integer',
'length' => 140,
'nullable' => false,
'unique' => false,
]),
]),
])]
#[AssociationOverrides([
new AssociationOverride('bar', [
'joinColumns' => new JoinColumn([
'name' => 'example_entity_overridden_bar_id',
'referencedColumnName' => 'id',
]),
]),
])]
class ExampleEntityWithOverride
{
use ExampleTrait;
}
/**
* @Entity
*/
#[Entity]
class Bar
{
/** @Id @Column(type="string") */
#[Id, Column(type: 'string')]
private $id;
}
The docblock is showing metadata override of the attribute and association type. It
basically changes the names of the columns mapped for a property ``foo`` and for
the association ``bar`` which relates to Bar class shown above. Here is the trait
which has mapping metadata that is overridden by the annotation above:
which has mapping metadata that is overridden by the attribute above:
.. code-block:: php
@@ -64,19 +60,15 @@ which has mapping metadata that is overridden by the annotation above:
*/
trait ExampleTrait
{
/** @Id @Column(type="string") */
private $id;
#[Id, Column(type: 'integer')]
private int|null $id = null;
/**
* @Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true)
*/
protected $foo;
#[Column(name: 'trait_foo', type: 'integer', length: 100, nullable: true, unique: true)]
protected int $foo;
/**
* @OneToOne(targetEntity="Bar", cascade={"persist", "merge"})
* @JoinColumn(name="example_trait_bar_id", referencedColumnName="id")
*/
protected $bar;
#[OneToOne(targetEntity: Bar::class, cascade: ['persist', 'merge'])]
#[JoinColumn(name: 'example_trait_bar_id', referencedColumnName: 'id')]
protected Bar|null $bar = null;
}
The case for just extending a class would be just the same but:

View File

@@ -23,6 +23,7 @@ Mapping Indexed Associations
You can map indexed associations by adding:
* ``indexBy`` argument to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute.
* ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation.
* ``index-by`` attribute to any ``<one-to-many />`` or ``<many-to-many />`` xml element.
* ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files.
@@ -30,7 +31,66 @@ You can map indexed associations by adding:
The code and mappings for the Market entity looks like this:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Doctrine\Tests\Models\StockExchange;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[Entity]
#[Table(name: 'exchange_markets')]
class Market
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
private string $name;
/** @var Collection<string, Stock> */
#[OneToMany(targetEntity: Stock::class, mappedBy: 'market', indexBy: 'symbol')]
private Collection $stocks;
public function __construct(string $name)
{
$this->name = $name;
$this->stocks = new ArrayCollection();
}
public function getId(): int|null
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock(string $symbol): Stock
{
if (!isset($this->stocks[$symbol])) {
throw new \InvalidArgumentException("Symbol is not traded on this market.");
}
return $this->stocks[$symbol];
}
/** @return array<string, Stock> */
public function getStocks(): array
{
return $this->stocks->toArray();
}
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\StockExchange;
@@ -47,19 +107,19 @@ The code and mappings for the Market entity looks like this:
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
private $id;
private int|null $id = null;
/**
* @Column(type="string")
* @var string
*/
private $name;
private string $name;
/**
* @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol")
* @var Stock[]
* @var Collection<int, Stock>
*/
private $stocks;
private Collection $stocks;
public function __construct($name)
{
@@ -67,22 +127,22 @@ The code and mappings for the Market entity looks like this:
$this->stocks = new ArrayCollection();
}
public function getId()
public function getId(): int|null
{
return $this->id;
}
public function getName()
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock)
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock($symbol)
public function getStock($symbol): Stock
{
if (!isset($this->stocks[$symbol])) {
throw new \InvalidArgumentException("Symbol is not traded on this market.");
@@ -91,7 +151,8 @@ The code and mappings for the Market entity looks like this:
return $this->stocks[$symbol];
}
public function getStocks()
/** @return array<string, Stock> */
public function getStocks(): array
{
return $this->stocks->toArray();
}
@@ -142,7 +203,38 @@ The ``Stock`` entity doesn't contain any special instructions that are new, but
here are the code and mappings for it:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Doctrine\Tests\Models\StockExchange;
#[Entity]
#[Table(name: 'exchange_stocks')]
class Stock
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string', unique: true)]
private string $symbol;
#[ManyToOne(targetEntity: Market::class, inversedBy: 'stocks')]
private Market|null $market;
public function __construct(string $symbol, Market $market)
{
$this->symbol = $symbol;
$this->market = $market;
$market->addStock($this);
}
public function getSymbol(): string
{
return $this->symbol;
}
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\StockExchange;
@@ -157,18 +249,18 @@ here are the code and mappings for it:
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
private $id;
private int|null $id = null;
/**
* @Column(type="string", unique=true)
*/
private $symbol;
private string $symbol;
/**
* @ManyToOne(targetEntity="Market", inversedBy="stocks")
* @var Market
*/
private $market;
private Market|null $market = null;
public function __construct($symbol, Market $market)
{
@@ -177,7 +269,7 @@ here are the code and mappings for it:
$market->addStock($this);
}
public function getSymbol()
public function getSymbol(): string
{
return $this->symbol;
}
@@ -249,7 +341,7 @@ now query for the market:
// $em is the EntityManager
$marketId = 1;
$symbol = "AAPL";
$market = $em->find("Doctrine\Tests\Models\StockExchange\Market", $marketId);
// Access the stocks by symbol now:

View File

@@ -337,6 +337,7 @@
<xs:attribute name="field-name" type="xs:NMTOKEN" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:attribute name="enum-type" type="xs:string" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>

View File

@@ -32,6 +32,8 @@ use function array_map;
use function array_shift;
use function assert;
use function count;
use function func_num_args;
use function in_array;
use function is_array;
use function is_numeric;
use function is_object;
@@ -196,9 +198,7 @@ abstract class AbstractQuery
return $this;
}
/**
* @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise.
*/
/** @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise. */
public function isCacheable()
{
return $this->cacheable;
@@ -226,17 +226,13 @@ abstract class AbstractQuery
return $this->cacheRegion;
}
/**
* @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise.
*/
/** @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise. */
protected function isCacheEnabled()
{
return $this->cacheable && $this->hasCache;
}
/**
* @return int
*/
/** @return int */
public function getLifetime()
{
return $this->lifetime;
@@ -325,7 +321,7 @@ abstract class AbstractQuery
/**
* Gets a query parameter.
*
* @param mixed $key The key (index or name) of the bound parameter.
* @param int|string $key The key (index or name) of the bound parameter.
*
* @return Parameter|null The value of the bound parameter, or NULL if not available.
*/
@@ -401,8 +397,7 @@ abstract class AbstractQuery
*
* @param mixed $value
*
* @return mixed[]|string|int|float|bool|object|null
* @psalm-return array|scalar|object|null
* @return mixed
*
* @throws ORMInvalidArgumentException
*/
@@ -547,6 +542,15 @@ abstract class AbstractQuery
public function setHydrationCacheProfile(?QueryCacheProfile $profile = null)
{
if ($profile === null) {
if (func_num_args() < 1) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9791',
'Calling %s without arguments is deprecated, pass null instead.',
__METHOD__
);
}
$this->_hydrationCacheProfile = null;
return $this;
@@ -572,9 +576,7 @@ abstract class AbstractQuery
return $this;
}
/**
* @return QueryCacheProfile|null
*/
/** @return QueryCacheProfile|null */
public function getHydrationCacheProfile()
{
return $this->_hydrationCacheProfile;
@@ -591,6 +593,15 @@ abstract class AbstractQuery
public function setResultCacheProfile(?QueryCacheProfile $profile = null)
{
if ($profile === null) {
if (func_num_args() < 1) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9791',
'Calling %s without arguments is deprecated, pass null instead.',
__METHOD__
);
}
$this->_queryCacheProfile = null;
return $this;
@@ -645,6 +656,15 @@ abstract class AbstractQuery
public function setResultCache(?CacheItemPoolInterface $resultCache = null)
{
if ($resultCache === null) {
if (func_num_args() < 1) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9791',
'Calling %s without arguments is deprecated, pass null instead.',
__METHOD__
);
}
if ($this->_queryCacheProfile) {
$this->_queryCacheProfile = new QueryCacheProfile($this->_queryCacheProfile->getLifetime(), $this->_queryCacheProfile->getCacheKey());
}
@@ -806,9 +826,7 @@ abstract class AbstractQuery
return $this->_expireResultCache;
}
/**
* @return QueryCacheProfile|null
*/
/** @return QueryCacheProfile|null */
public function getQueryCacheProfile()
{
return $this->_queryCacheProfile;
@@ -817,17 +835,22 @@ abstract class AbstractQuery
/**
* Change the default fetch mode of an association for this query.
*
* $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
*
* @param string $class
* @param string $assocName
* @param int $fetchMode
* @param class-string $class
* @param string $assocName
* @param int $fetchMode
* @psalm-param Mapping\ClassMetadata::FETCH_EAGER|Mapping\ClassMetadata::FETCH_LAZY $fetchMode
*
* @return $this
*/
public function setFetchMode($class, $assocName, $fetchMode)
{
if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
if (! in_array($fetchMode, [Mapping\ClassMetadata::FETCH_EAGER, Mapping\ClassMetadata::FETCH_LAZY], true)) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9777',
'Calling %s() with something else than ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY is deprecated.',
__METHOD__
);
$fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
}
@@ -1054,7 +1077,8 @@ abstract class AbstractQuery
*
* @param ArrayCollection|mixed[]|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
* @psalm-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
*
* @return IterableResult
*/
@@ -1297,9 +1321,11 @@ abstract class AbstractQuery
protected function getHydrationCacheId()
{
$parameters = [];
$types = [];
foreach ($this->getParameters() as $parameter) {
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
$types[$parameter->getName()] = $parameter->getType();
}
$sql = $this->getSQL();
@@ -1311,7 +1337,7 @@ abstract class AbstractQuery
ksort($hints);
assert($queryCacheProfile !== null);
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $types, $hints);
}
/**

View File

@@ -23,41 +23,31 @@ class CacheConfiguration
/** @var QueryCacheValidator|null */
private $queryValidator;
/**
* @return CacheFactory|null
*/
/** @return CacheFactory|null */
public function getCacheFactory()
{
return $this->cacheFactory;
}
/**
* @return void
*/
/** @return void */
public function setCacheFactory(CacheFactory $factory)
{
$this->cacheFactory = $factory;
}
/**
* @return CacheLogger|null
*/
/** @return CacheLogger|null */
public function getCacheLogger()
{
return $this->cacheLogger;
}
/**
* @return void
*/
/** @return void */
public function setCacheLogger(CacheLogger $logger)
{
$this->cacheLogger = $logger;
}
/**
* @return RegionsConfiguration
*/
/** @return RegionsConfiguration */
public function getRegionsConfiguration()
{
if ($this->regionsConfig === null) {
@@ -67,17 +57,13 @@ class CacheConfiguration
return $this->regionsConfig;
}
/**
* @return void
*/
/** @return void */
public function setRegionsConfiguration(RegionsConfiguration $regionsConfig)
{
$this->regionsConfig = $regionsConfig;
}
/**
* @return QueryCacheValidator
*/
/** @return QueryCacheValidator */
public function getQueryValidator()
{
if ($this->queryValidator === null) {
@@ -89,9 +75,7 @@ class CacheConfiguration
return $this->queryValidator;
}
/**
* @return void
*/
/** @return void */
public function setQueryValidator(QueryCacheValidator $validator)
{
$this->queryValidator = $validator;

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache;
use Doctrine\Deprecations\Deprecation;
/**
* Defines entity / collection / query key to be stored in the cache region.
* Allows multiple roles to be stored in the same cache region.
@@ -17,4 +19,18 @@ abstract class CacheKey
* @var string
*/
public $hash;
public function __construct(?string $hash = null)
{
if ($hash === null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10212',
'Calling %s() without providing a value for the $hash parameter is deprecated.',
__METHOD__
);
} else {
$this->hash = $hash;
}
}
}

View File

@@ -17,9 +17,7 @@ class CollectionCacheEntry implements CacheEntry
*/
public $identifiers;
/**
* @param CacheKey[] $identifiers List of entity identifiers hold by the collection
*/
/** @param CacheKey[] $identifiers List of entity identifiers hold by the collection */
public function __construct(array $identifiers)
{
$this->identifiers = $identifiers;

View File

@@ -52,6 +52,7 @@ class CollectionCacheKey extends CacheKey
$this->ownerIdentifier = $ownerIdentifier;
$this->entityClass = (string) $entityClass;
$this->association = (string) $association;
$this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association;
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association);
}
}

View File

@@ -20,8 +20,6 @@ interface CollectionHydrator
*/
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection);
/**
* @return mixed[]|null
*/
/** @return mixed[]|null */
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection);
}

View File

@@ -262,9 +262,7 @@ class DefaultCache implements Cache
return $this->queryCaches[$regionName];
}
/**
* @param mixed $identifier The entity identifier.
*/
/** @param mixed $identifier The entity identifier. */
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier): EntityCacheKey
{
if (! is_array($identifier)) {
@@ -274,9 +272,7 @@ class DefaultCache implements Cache
return new EntityCacheKey($metadata->rootEntityName, $identifier);
}
/**
* @param mixed $ownerIdentifier The identifier of the owning entity.
*/
/** @param mixed $ownerIdentifier The identifier of the owning entity. */
private function buildCollectionCacheKey(
ClassMetadata $metadata,
string $association,

View File

@@ -49,9 +49,7 @@ class DefaultCacheFactory implements CacheFactory
/** @var string|null */
private $fileLockRegionDirectory;
/**
* @param CacheItemPoolInterface $cacheItemPool
*/
/** @param CacheItemPoolInterface $cacheItemPool */
public function __construct(RegionsConfiguration $cacheConfig, $cacheItemPool)
{
if ($cacheItemPool instanceof LegacyCache) {
@@ -89,25 +87,19 @@ class DefaultCacheFactory implements CacheFactory
$this->fileLockRegionDirectory = (string) $fileLockRegionDirectory;
}
/**
* @return string
*/
/** @return string */
public function getFileLockRegionDirectory()
{
return $this->fileLockRegionDirectory;
}
/**
* @return void
*/
/** @return void */
public function setRegion(Region $region)
{
$this->regions[$region->getName()] = $region;
}
/**
* @return void
*/
/** @return void */
public function setTimestampRegion(TimestampRegion $region)
{
$this->timestampRegion = $region;

View File

@@ -27,9 +27,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
/** @var array<string,mixed> */
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param EntityManagerInterface $em The entity manager.
*/
/** @param EntityManagerInterface $em The entity manager. */
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;

View File

@@ -38,9 +38,7 @@ class DefaultEntityHydrator implements EntityHydrator
/** @var array<string,mixed> */
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param EntityManagerInterface $em The entity manager.
*/
/** @param EntityManagerInterface $em The entity manager. */
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Exception\FeatureNotImplemented;
use Doctrine\ORM\Cache\Exception\NonCacheableEntity;
@@ -17,6 +16,7 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\Proxy;
use function array_map;
use function array_shift;

View File

@@ -42,6 +42,7 @@ class EntityCacheKey extends CacheKey
$this->identifier = $identifier;
$this->entityClass = $entityClass;
$this->hash = str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier));
parent::__construct(str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier)));
}
}

View File

@@ -4,9 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
/**
* @deprecated
*/
/** @deprecated */
final class InvalidResultCacheDriver extends CacheException
{
public static function create(): self

View File

@@ -21,9 +21,7 @@ class Lock
$this->time = $time ?: time();
}
/**
* @return Lock
*/
/** @return Lock */
public static function createLockRead()
{
return new self(uniqid((string) time(), true));

View File

@@ -33,9 +33,7 @@ class CacheLoggerChain implements CacheLogger
return $this->loggers[$name] ?? null;
}
/**
* @return array<string, CacheLogger>
*/
/** @return array<string, CacheLogger> */
public function getLoggers()
{
return $this->loggers;

View File

@@ -141,25 +141,19 @@ class StatisticsCacheLogger implements CacheLogger
return $this->cachePutCountMap[$regionName] ?? 0;
}
/**
* @return array<string, int>
*/
/** @return array<string, int> */
public function getRegionsMiss()
{
return $this->cacheMissCountMap;
}
/**
* @return array<string, int>
*/
/** @return array<string, int> */
public function getRegionsHit()
{
return $this->cacheHitCountMap;
}
/**
* @return array<string, int>
*/
/** @return array<string, int> */
public function getRegionsPut()
{
return $this->cachePutCountMap;

View File

@@ -16,14 +16,10 @@ use Doctrine\ORM\Persisters\Collection\CollectionPersister;
*/
interface CachedCollectionPersister extends CachedPersister, CollectionPersister
{
/**
* @return ClassMetadata
*/
/** @return ClassMetadata */
public function getSourceEntityMetadata();
/**
* @return ClassMetadata
*/
/** @return ClassMetadata */
public function getTargetEntityMetadata();
/**

View File

@@ -14,9 +14,7 @@ use function spl_object_id;
class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/**
* @param mixed[] $association The association mapping.
*/
/** @param mixed[] $association The association mapping. */
public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association)
{
parent::__construct($persister, $region, $em, $association);

View File

@@ -177,9 +177,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->region;
}
/**
* @return EntityHydrator
*/
/** @return EntityHydrator */
public function getEntityHydrator()
{
return $this->hydrator;
@@ -207,9 +205,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $cached;
}
/**
* @param object $entity
*/
/** @param object $entity */
private function storeJoinedAssociations($entity): void
{
if ($this->joinedAssociations === null) {
@@ -250,7 +246,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*
* @param string $query
* @param string[]|Criteria $criteria
* @param string[] $orderBy
* @param string[]|null $orderBy
* @param int|null $limit
* @param int|null $offset
*

View File

@@ -14,9 +14,7 @@ use Doctrine\ORM\Persisters\Entity\EntityPersister;
*/
interface CachedEntityPersister extends CachedPersister, EntityPersister
{
/**
* @return EntityHydrator
*/
/** @return EntityHydrator */
public function getEntityHydrator();
/**

View File

@@ -82,9 +82,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache['update'][] = $entity;
}
/**
* @param object $entity
*/
/** @param object $entity */
private function updateCache($entity, bool $isChanged): bool
{
$class = $this->metadataFactory->getMetadataFor(get_class($entity));

View File

@@ -12,9 +12,7 @@ use Doctrine\ORM\Query\ResultSetMapping;
*/
interface QueryCache
{
/**
* @return bool
*/
/** @return bool */
public function clear();
/**
@@ -32,8 +30,6 @@ interface QueryCache
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []);
/**
* @return Region
*/
/** @return Region */
public function getRegion();
}

View File

@@ -34,18 +34,17 @@ class QueryCacheKey extends CacheKey
*/
public $timestampKey;
/**
* @psalm-param Cache::MODE_* $cacheMode
*/
/** @psalm-param Cache::MODE_* $cacheMode */
public function __construct(
string $cacheId,
int $lifetime = 0,
int $cacheMode = Cache::MODE_NORMAL,
?TimestampCacheKey $timestampKey = null
) {
$this->hash = $cacheId;
$this->lifetime = $lifetime;
$this->cacheMode = $cacheMode;
$this->timestampKey = $timestampKey;
parent::__construct($cacheId);
}
}

View File

@@ -31,9 +31,7 @@ use function strtr;
*/
class DefaultRegion implements Region
{
/**
* @internal since 2.11, this constant will be private in 3.0.
*/
/** @internal since 2.11, this constant will be private in 3.0. */
public const REGION_KEY_SEPARATOR = '_';
private const REGION_PREFIX = 'DC2_REGION_';
@@ -61,9 +59,7 @@ class DefaultRegion implements Region
/** @var CacheItemPoolInterface */
private $cacheItemPool;
/**
* @param CacheItemPoolInterface $cacheItemPool
*/
/** @param CacheItemPoolInterface $cacheItemPool */
public function __construct(string $name, $cacheItemPool, int $lifetime = 0)
{
if ($cacheItemPool instanceof LegacyCache) {

View File

@@ -103,17 +103,13 @@ class FileLockRegion implements ConcurrentRegion
return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
}
/**
* @return string|false
*/
/** @return string|false */
private function getLockContent(string $filename)
{
return @file_get_contents($filename);
}
/**
* @return int|false
*/
/** @return int|false */
private function getLockTime(string $filename)
{
return @fileatime($filename);

View File

@@ -31,9 +31,7 @@ class RegionsConfiguration
$this->defaultLockLifetime = (int) $defaultLockLifetime;
}
/**
* @return int
*/
/** @return int */
public function getDefaultLifetime()
{
return $this->defaultLifetime;
@@ -49,9 +47,7 @@ class RegionsConfiguration
$this->defaultLifetime = (int) $defaultLifetime;
}
/**
* @return int
*/
/** @return int */
public function getDefaultLockLifetime()
{
return $this->defaultLockLifetime;

View File

@@ -17,9 +17,7 @@ class TimestampCacheEntry implements CacheEntry
*/
public $time;
/**
* @param float|null $time
*/
/** @param float|null $time */
public function __construct($time = null)
{
$this->time = $time ? (float) $time : microtime(true);

View File

@@ -9,11 +9,9 @@ namespace Doctrine\ORM\Cache;
*/
class TimestampCacheKey extends CacheKey
{
/**
* @param string $space Result cache id
*/
/** @param string $space Result cache id */
public function __construct($space)
{
$this->hash = (string) $space;
parent::__construct((string) $space);
}
}

View File

@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use BadMethodCallException;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Cache\ArrayCache;
@@ -13,7 +13,6 @@ use Doctrine\Common\Cache\Cache as CacheDriver;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\Common\Persistence\PersistentObject;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Cache\Exception\CacheException;
@@ -36,21 +35,26 @@ use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\EntityListenerResolver;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Mapping\TypedFieldMapper;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\ObjectRepository;
use Doctrine\Persistence\Reflection\RuntimeReflectionProperty;
use LogicException;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\VarExporter\LazyGhostTrait;
use function class_exists;
use function is_a;
use function method_exists;
use function sprintf;
use function strtolower;
use function trait_exists;
use function trim;
/**
@@ -91,18 +95,19 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the strategy for automatically generating proxy classes.
*
* @return int Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
* @return int Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
* @psalm-return AutogenerateMode
*/
public function getAutoGenerateProxyClasses()
{
return $this->_attributes['autoGenerateProxyClasses'] ?? AbstractProxyFactory::AUTOGENERATE_ALWAYS;
return $this->_attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS;
}
/**
* Sets the strategy for automatically generating proxy classes.
*
* @param bool|int $autoGenerate Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
* @param bool|int $autoGenerate Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
* @psalm-param bool|AutogenerateMode $autoGenerate
* True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
*
* @return void
@@ -170,16 +175,21 @@ class Configuration extends \Doctrine\DBAL\Configuration
);
if (! class_exists(AnnotationReader::class)) {
throw new LogicException(sprintf(
throw new LogicException(
'The annotation metadata driver cannot be enabled because the "doctrine/annotations" library'
. ' is not installed. Please run "composer require doctrine/annotations" or choose a different'
. ' metadata driver.'
));
);
}
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
if ($useSimpleAnnotationReader) {
if (! class_exists(SimpleAnnotationReader::class)) {
throw new BadMethodCallException(
'SimpleAnnotationReader has been removed in doctrine/annotations 2.'
. ' Downgrade to version 1 or set $useSimpleAnnotationReader to false.'
);
}
// Register the ORM Annotations in the AnnotationRegistry
$reader = new SimpleAnnotationReader();
$reader->addNamespace('Doctrine\ORM\Mapping');
@@ -187,7 +197,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
$reader = new AnnotationReader();
}
if (class_exists(ArrayCache::class)) {
if (class_exists(ArrayCache::class) && class_exists(CachedReader::class)) {
$reader = new CachedReader($reader, new ArrayCache());
}
@@ -217,7 +227,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
$alias
);
} else {
NotSupported::createForPersistence3(sprintf(
throw NotSupported::createForPersistence3(sprintf(
'Using short namespace alias "%s" by calling %s',
$alias,
__METHOD__
@@ -570,7 +580,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
throw QueryCacheUsesNonPersistentCache::fromDriver($queryCacheImpl);
}
if ($this->getAutoGenerateProxyClasses() !== AbstractProxyFactory::AUTOGENERATE_NEVER) {
if ($this->getAutoGenerateProxyClasses() !== ProxyFactory::AUTOGENERATE_NEVER) {
throw ProxyClassesAlwaysRegenerating::create();
}
@@ -592,8 +602,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* DQL function names are case-insensitive.
*
* @param string $name Function name.
* @param string|callable $className Class name or a callable that returns the function.
* @param string $name Function name.
* @param class-string|callable $className Class name or a callable that returns the function.
* @psalm-param class-string<FunctionNode>|callable(string):FunctionNode $className
*
* @return void
*/
@@ -608,7 +619,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $name
*
* @return string|callable|null
* @psalm-return class-string|callable|null
* @psalm-return class-string<FunctionNode>|callable(string):FunctionNode|null
*/
public function getCustomStringFunction($name)
{
@@ -625,7 +636,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* Any previously added string functions are discarded.
*
* @psalm-param array<string, class-string|callable> $functions The map of custom DQL string functions.
* @psalm-param array<string, class-string<FunctionNode>|callable(string):FunctionNode> $functions The map of custom
* DQL string functions.
*
* @return void
*/
@@ -643,8 +655,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* DQL function names are case-insensitive.
*
* @param string $name Function name.
* @param string|callable $className Class name or a callable that returns the function.
* @param string $name Function name.
* @param class-string|callable $className Class name or a callable that returns the function.
* @psalm-param class-string<FunctionNode>|callable(string):FunctionNode $className
*
* @return void
*/
@@ -697,7 +710,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name Function name.
* @param string|callable $className Class name or a callable that returns the function.
* @psalm-param class-string|callable $className
* @psalm-param class-string<FunctionNode>|callable(string):FunctionNode $className
*
* @return void
*/
@@ -712,7 +725,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $name
*
* @return string|callable|null
* @psalm-return class-string|callable|null $name
* @psalm-return class-string|callable|null
*/
public function getCustomDatetimeFunction($name)
{
@@ -730,7 +743,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Any previously added date/time functions are discarded.
*
* @param array $functions The map of custom DQL date/time functions.
* @psalm-param array<string, string> $functions
* @psalm-param array<string, class-string<FunctionNode>|callable(string):FunctionNode> $functions
*
* @return void
*/
@@ -741,6 +754,22 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
}
/**
* Sets a TypedFieldMapper for php typed fields to DBAL types auto-completion.
*/
public function setTypedFieldMapper(?TypedFieldMapper $typedFieldMapper): void
{
$this->_attributes['typedFieldMapper'] = $typedFieldMapper;
}
/**
* Gets a TypedFieldMapper for php typed fields to DBAL types auto-completion.
*/
public function getTypedFieldMapper(): ?TypedFieldMapper
{
return $this->_attributes['typedFieldMapper'] ?? null;
}
/**
* Sets the custom hydrator modes in one pass.
*
@@ -970,9 +999,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
return $this->_attributes['repositoryFactory'] ?? new DefaultRepositoryFactory();
}
/**
* @return bool
*/
/** @return bool */
public function isSecondLevelCacheEnabled()
{
return $this->_attributes['isSecondLevelCacheEnabled'] ?? false;
@@ -988,17 +1015,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
$this->_attributes['isSecondLevelCacheEnabled'] = (bool) $flag;
}
/**
* @return void
*/
/** @return void */
public function setSecondLevelCacheConfiguration(CacheConfiguration $cacheConfig)
{
$this->_attributes['secondLevelCacheConfiguration'] = $cacheConfig;
}
/**
* @return CacheConfiguration|null
*/
/** @return CacheConfiguration|null */
public function getSecondLevelCacheConfiguration()
{
if (! isset($this->_attributes['secondLevelCacheConfiguration']) && $this->isSecondLevelCacheEnabled()) {
@@ -1074,4 +1097,28 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
$this->_attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses;
}
public function isLazyGhostObjectEnabled(): bool
{
return $this->_attributes['isLazyGhostObjectEnabled'] ?? false;
}
public function setLazyGhostObjectEnabled(bool $flag): void
{
if ($flag && ! trait_exists(LazyGhostTrait::class)) {
throw new LogicException(
'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library'
. ' version 6.2 or higher is not installed. Please run "composer require symfony/var-exporter:^6.2".'
);
}
if ($flag && ! class_exists(RuntimeReflectionProperty::class)) {
throw new LogicException(
'Lazy ghost objects cannot be enabled because the "doctrine/persistence" library'
. ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".'
);
}
$this->_attributes['isLazyGhostObjectEnabled'] = $flag;
}
}

View File

@@ -9,6 +9,8 @@ use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\ObjectManagerDecorator;
use function func_get_arg;
use function func_num_args;
use function get_debug_type;
use function method_exists;
use function sprintf;
@@ -211,6 +213,20 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
$this->wrapped->flush($entity);
}
/**
* {@inheritdoc}
*/
public function refresh($object)
{
$lockMode = null;
if (func_num_args() > 1) {
$lockMode = func_get_arg(1);
}
$this->wrapped->refresh($object, $lockMode);
}
/**
* {@inheritdoc}
*/

View File

@@ -35,7 +35,6 @@ use InvalidArgumentException;
use Throwable;
use function array_keys;
use function call_user_func;
use function class_exists;
use function get_debug_type;
use function gettype;
@@ -55,14 +54,14 @@ use function strpos;
* the static create() method. The quickest way to obtain a fully
* configured EntityManager is:
*
* use Doctrine\ORM\Tools\Setup;
* use Doctrine\ORM\Tools\ORMSetup;
* use Doctrine\ORM\EntityManager;
*
* $paths = array('/path/to/entity/mapping/files');
* $paths = ['/path/to/entity/mapping/files'];
*
* $config = Setup::createAnnotationMetadataConfiguration($paths);
* $dbParams = array('driver' => 'pdo_sqlite', 'memory' => true);
* $entityManager = EntityManager::create($dbParams, $config);
* $config = ORMSetup::createAttributeMetadataConfiguration($paths);
* $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
* $entityManager = new EntityManager($connection, $config);
*
* For more information see
* {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/configuration.html}
@@ -71,8 +70,10 @@ use function strpos;
* is not a valid extension point for the EntityManager. Instead you
* should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator}
* and wrap your entity manager in a decorator.
*
* @final
*/
/* final */class EntityManager implements EntityManagerInterface
class EntityManager implements EntityManagerInterface
{
/**
* The used Configuration.
@@ -155,11 +156,15 @@ use function strpos;
* Creates a new EntityManager that operates on the given database connection
* and uses the given Configuration and EventManager implementations.
*/
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
public function __construct(Connection $conn, Configuration $config, ?EventManager $eventManager = null)
{
if (! $config->getMetadataDriverImpl()) {
throw MissingMappingDriverImplementation::create();
}
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$this->eventManager = $eventManager ?? $conn->getEventManager();
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
@@ -242,7 +247,7 @@ use function strpos;
$this->conn->beginTransaction();
try {
$return = call_user_func($func, $this);
$return = $func($this);
$this->flush();
$this->conn->commit();
@@ -688,14 +693,16 @@ use function strpos;
* Refreshes the persistent state of an entity from the database,
* overriding any local changes that have not yet been persisted.
*
* @param object $entity The entity to refresh.
* @param object $entity The entity to refresh
* @psalm-param LockMode::*|null $lockMode
*
* @return void
*
* @throws ORMInvalidArgumentException
* @throws ORMException
* @throws TransactionRequiredException
*/
public function refresh($entity)
public function refresh($entity, ?int $lockMode = null)
{
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity);
@@ -703,7 +710,7 @@ use function strpos;
$this->errorIfClosed();
$this->unitOfWork->refresh($entity);
$this->unitOfWork->refresh($entity, $lockMode);
}
/**
@@ -762,6 +769,8 @@ use function strpos;
/**
* {@inheritDoc}
*
* @psalm-return never
*/
public function copy($entity, $deep = false)
{
@@ -805,7 +814,7 @@ use function strpos;
$entityName
);
} else {
NotSupported::createForPersistence3(sprintf(
throw NotSupported::createForPersistence3(sprintf(
'Using short namespace alias "%s" when calling %s',
$entityName,
__METHOD__
@@ -947,6 +956,8 @@ use function strpos;
/**
* Factory method to create EntityManager instances.
*
* @deprecated Use {@see DriverManager::getConnection()} to bootstrap the connection and call the constructor.
*
* @param mixed[]|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager|null $eventManager The EventManager instance to use.
@@ -959,18 +970,25 @@ use function strpos;
*/
public static function create($connection, Configuration $config, ?EventManager $eventManager = null)
{
if (! $config->getMetadataDriverImpl()) {
throw MissingMappingDriverImplementation::create();
}
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9961',
'%s() is deprecated. To boostrap a DBAL connection, call %s::getConnection() instead. Use the constructor to create an instance of %s.',
__METHOD__,
DriverManager::class,
self::class
);
$connection = static::createConnection($connection, $config, $eventManager);
return new EntityManager($connection, $config, $connection->getEventManager());
return new EntityManager($connection, $config);
}
/**
* Factory method to create Connection instances.
*
* @deprecated Use {@see DriverManager::getConnection()} to bootstrap the connection.
*
* @param mixed[]|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager|null $eventManager The EventManager instance to use.
@@ -983,6 +1001,14 @@ use function strpos;
*/
protected static function createConnection($connection, Configuration $config, ?EventManager $eventManager = null)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9961',
'%s() is deprecated, call %s::getConnection() instead.',
__METHOD__,
DriverManager::class
);
if (is_array($connection)) {
return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager());
}

View File

@@ -22,6 +22,7 @@ use Doctrine\Persistence\ObjectManager;
*
* @method Mapping\ClassMetadataFactory getMetadataFactory()
* @method mixed wrapInTransaction(callable $func)
* @method void refresh(object $object, ?int $lockMode = null)
*/
interface EntityManagerInterface extends ObjectManager
{

View File

@@ -65,9 +65,7 @@ class EntityRepository implements ObjectRepository, Selectable
/** @var Inflector|null */
private static $inflector;
/**
* @psalm-param ClassMetadata<T> $class
*/
/** @psalm-param ClassMetadata<T> $class */
public function __construct(EntityManagerInterface $em, ClassMetadata $class)
{
$this->_entityName = $class->name;
@@ -306,9 +304,7 @@ class EntityRepository implements ObjectRepository, Selectable
return $this->getEntityName();
}
/**
* @return EntityManagerInterface
*/
/** @return EntityManagerInterface */
protected function getEntityManager()
{
return $this->_em;

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