Skip to content

Commit

Permalink
Remove remaining references to the merge() operation
Browse files Browse the repository at this point in the history
`EntityManager::merge()` has been deprecated in doctrine#8461 and removed in doctrine#9488.

This PR removes a few remaining references and artefacts that - to my understanding - refer to it.
  • Loading branch information
mpdude committed Jun 23, 2023
1 parent 21c3f4d commit 6b5ef2a
Show file tree
Hide file tree
Showing 25 changed files with 64 additions and 315 deletions.
2 changes: 1 addition & 1 deletion docs/en/reference/attributes-reference.rst
Expand Up @@ -926,7 +926,7 @@ Example:
#[OneToMany(
targetEntity: "Phonenumber",
mappedBy: "user",
cascade: ["persist", "remove", "merge"],
cascade: ["persist", "remove"],
orphanRemoval: true)
]
public $phonenumbers;
Expand Down
2 changes: 1 addition & 1 deletion docs/en/reference/best-practices.rst
Expand Up @@ -43,7 +43,7 @@ should use events judiciously.
Use cascades judiciously
------------------------

Automatic cascades of the persist/remove/merge/etc. operations are
Automatic cascades of the persist/remove/etc. operations are
very handy but should be used wisely. Do NOT simply add all
cascades to all associations. Think about which cascades actually
do make sense for you for a particular association, given the
Expand Down
2 changes: 0 additions & 2 deletions docs/en/reference/inheritance-mapping.rst
Expand Up @@ -387,7 +387,6 @@ Example:
<many-to-many field="groups" target-entity="Group" inversed-by="users">
<cascade>
<cascade-persist/>
<cascade-merge/>
<cascade-detach/>
</cascade>
<join-table name="users_groups">
Expand Down Expand Up @@ -499,7 +498,6 @@ Could be used by an entity that extends a mapped superclass to override a field
<many-to-one field="address" target-entity="Address">
<cascade>
<cascade-persist/>
<cascade-merge/>
</cascade>
<join-column name="address_id" referenced-column-name="id"/>
</many-to-one>
Expand Down
9 changes: 0 additions & 9 deletions docs/en/reference/limitations-and-known-issues.rst
Expand Up @@ -65,15 +65,6 @@ Where the ``attribute_name`` column contains the key and
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.

Cascade Merge with Bi-directional Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
Make sure to study the behavior of cascade merge if you are using it:

- `DDC-875 <https://github.com/doctrine/orm/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <https://github.com/doctrine/orm/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"

Custom Persisters
~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion docs/en/reference/working-with-associations.rst
Expand Up @@ -415,7 +415,7 @@ Transitive persistence / Cascade Operations
Doctrine ORM provides a mechanism for transitive persistence through cascading of certain operations.
Each association to another entity or a collection of
entities can be configured to automatically cascade the following operations to the associated entities:
``persist``, ``remove``, ``merge``, ``detach``, ``refresh`` or ``all``.
``persist``, ``remove``, ``detach``, ``refresh`` or ``all``.

The main use case for ``cascade: persist`` is to avoid "exposing" associated entities to your PHP application.
Continuing with the User-Comment example of this chapter, this is how the creation of a new user and a new
Expand Down
73 changes: 1 addition & 72 deletions docs/en/reference/working-with-objects.rst
Expand Up @@ -372,77 +372,6 @@ automatically without invoking the ``detach`` method:
The ``detach`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``.

Merging entities
----------------

Merging entities refers to the merging of (usually detached)
entities into the context of an EntityManager so that they become
managed again. To merge the state of an entity into an
EntityManager use the ``EntityManager#merge($entity)`` method. The
state of the passed entity will be merged into a managed copy of
this entity and this copy will subsequently be returned.

Example:

.. code-block:: php
<?php
$detachedEntity = unserialize($serializedEntity); // some detached entity
$entity = $em->merge($detachedEntity);
// $entity now refers to the fully managed copy returned by the merge operation.
// The EntityManager $em now manages the persistence of $entity as usual.
The semantics of the merge operation, applied to an entity X, are
as follows:


- If X is a detached entity, the state of X is copied onto a
pre-existing managed entity instance X' of the same identity.
- If X is a new entity instance, a new managed copy X' will be
created and the state of X is copied onto this managed instance.
- If X is a removed entity instance, an InvalidArgumentException
will be thrown.
- If X is a managed entity, it is ignored by the merge operation,
however, the merge operation is cascaded to entities referenced by
relationships from X if these relationships have been mapped with
the cascade element value MERGE or ALL (see ":ref:`transitive-persistence`").
- For all entities Y referenced by relationships from X having the
cascade element value MERGE or ALL, Y is merged recursively as Y'.
For all such Y referenced by X, X' is set to reference Y'. (Note
that if X is managed then X is the same object as X'.)
- If X is an entity merged to X', with a reference to another
entity Y, where cascade=MERGE or cascade=ALL is not specified, then
navigation of the same association from X' yields a reference to a
managed object Y' with the same persistent identity as Y.

The ``merge`` operation will throw an ``OptimisticLockException``
if the entity being merged uses optimistic locking through a
version field and the versions of the entity being merged and the
managed copy don't match. This usually means that the entity has
been modified while being detached.

The ``merge`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``. The most common scenario for
the ``merge`` operation is to reattach entities to an EntityManager
that come from some cache (and are therefore detached) and you want
to modify and persist such an entity.

.. warning::

If you need to perform multiple merges of entities that share certain subparts
of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the
successive calls to ``EntityManager#merge()``. Otherwise you might end up with
multiple copies of the "same" object in the database, however with different ids.

.. note::

If you load some detached entities from a cache and you do
not need to persist or delete them or otherwise make use of them
without the need for persistence services there is no need to use
``merge``. I.e. you can simply pass detached objects from a cache
directly to the view.


Synchronization with the Database
---------------------------------
Expand Down Expand Up @@ -553,7 +482,7 @@ during development.
.. note::

Do not invoke ``flush`` after every change to an entity
or every single invocation of persist/remove/merge/... This is an
or every single invocation of persist/remove/... This is an
anti-pattern and unnecessarily reduces the performance of your
application. Instead, form units of work that operate on your
objects and call ``flush`` when you are done. While serving a
Expand Down
1 change: 0 additions & 1 deletion docs/en/reference/xml-mapping.rst
Expand Up @@ -691,7 +691,6 @@ specified by their respective tags:


- ``<cascade-persist />``
- ``<cascade-merge />``
- ``<cascade-remove />``
- ``<cascade-refresh />``
- ``<cascade-detach />``
Expand Down
Expand Up @@ -66,7 +66,7 @@ which has mapping metadata that is overridden by the attribute above:
#[Column(name: 'trait_foo', type: 'integer', length: 100, nullable: true, unique: true)]
protected int $foo;
#[OneToOne(targetEntity: Bar::class, cascade: ['persist', 'merge'])]
#[OneToOne(targetEntity: Bar::class, cascade: ['persist'])]
#[JoinColumn(name: 'example_trait_bar_id', referencedColumnName: 'id')]
protected Bar|null $bar = null;
}
Expand Down
1 change: 0 additions & 1 deletion doctrine-mapping.xsd
Expand Up @@ -35,7 +35,6 @@
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-detach" type="orm:emptyType" minOccurs="0"/>
Expand Down
8 changes: 0 additions & 8 deletions lib/Doctrine/ORM/Mapping/Builder/AssociationBuilder.php
Expand Up @@ -60,14 +60,6 @@ public function cascadeRemove(): static
return $this;
}

/** @return $this */
public function cascadeMerge(): static
{
$this->mapping['cascade'][] = 'merge';

return $this;
}

/** @return $this */
public function cascadeDetach(): static
{
Expand Down
9 changes: 2 additions & 7 deletions lib/Doctrine/ORM/Mapping/ClassMetadata.php
Expand Up @@ -115,7 +115,6 @@
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* isOnDeleteCascade?: bool,
* isOwningSide: bool,
Expand Down Expand Up @@ -618,7 +617,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* - <b>cascade</b> (array, optional)
* The names of persistence operations to cascade on the association. The set of possible
* values are: "persist", "remove", "detach", "merge", "refresh", "all" (implies all others).
* values are: "persist", "remove", "detach", "refresh", "all" (implies all others).
*
* - <b>orderBy</b> (array, one-to-many/many-to-many only)
* A map of field names (of the target entity) to sorting directions (ASC/DESC).
Expand Down Expand Up @@ -1585,7 +1584,7 @@ protected function _validateAndCompleteAssociationMapping(array $mapping): array
// Cascades
$cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];

$allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
$allCascades = ['remove', 'persist', 'refresh', 'detach'];
if (in_array('all', $cascades, true)) {
$cascades = $allCascades;
} elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
Expand All @@ -1600,7 +1599,6 @@ protected function _validateAndCompleteAssociationMapping(array $mapping): array
$mapping['isCascadeRemove'] = in_array('remove', $cascades, true);
$mapping['isCascadePersist'] = in_array('persist', $cascades, true);
$mapping['isCascadeRefresh'] = in_array('refresh', $cascades, true);
$mapping['isCascadeMerge'] = in_array('merge', $cascades, true);
$mapping['isCascadeDetach'] = in_array('detach', $cascades, true);

return $mapping;
Expand All @@ -1624,7 +1622,6 @@ protected function _validateAndCompleteAssociationMapping(array $mapping): array
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
Expand Down Expand Up @@ -1737,7 +1734,6 @@ protected function _validateAndCompleteOneToOneMapping(array $mapping): array
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* orphanRemoval: bool
* }
Expand Down Expand Up @@ -1780,7 +1776,6 @@ protected function _validateAndCompleteOneToManyMapping(array $mapping): array
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
Expand Down
2 changes: 1 addition & 1 deletion lib/Doctrine/ORM/Mapping/MappingException.php
Expand Up @@ -521,7 +521,7 @@ public static function invalidCascadeOption(array $cascades, string $className,
$cascades = implode(', ', array_map(static fn (string $e): string => "'" . $e . "'", $cascades));

return new self(sprintf(
"You have specified invalid cascade options for %s::$%s: %s; available options: 'remove', 'persist', 'refresh', 'merge', and 'detach'",
"You have specified invalid cascade options for %s::$%s: %s; available options: 'remove', 'persist', 'refresh', and 'detach'",
$className,
$propertyName,
$cascades,
Expand Down
2 changes: 1 addition & 1 deletion tests/Doctrine/Tests/Models/CMS/CmsPhonenumber.php
Expand Up @@ -21,7 +21,7 @@ class CmsPhonenumber
public $phonenumber;

/** @var CmsUser */
#[ManyToOne(targetEntity: 'CmsUser', inversedBy: 'phonenumbers', cascade: ['merge'])]
#[ManyToOne(targetEntity: 'CmsUser', inversedBy: 'phonenumbers', cascade: [])]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
public $user;

Expand Down
4 changes: 2 additions & 2 deletions tests/Doctrine/Tests/Models/CMS/CmsUser.php
Expand Up @@ -42,7 +42,7 @@ class CmsUser
public $name;

/** @psalm-var Collection<int, CmsPhonenumber> */
#[OneToMany(targetEntity: 'CmsPhonenumber', mappedBy: 'user', cascade: ['persist', 'merge'], orphanRemoval: true)]
#[OneToMany(targetEntity: 'CmsPhonenumber', mappedBy: 'user', cascade: ['persist',], orphanRemoval: true)]
public $phonenumbers;

/** @psalm-var Collection<int, CmsArticle> */
Expand All @@ -62,7 +62,7 @@ class CmsUser
#[JoinTable(name: 'cms_users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'CmsGroup', inversedBy: 'users', cascade: ['persist', 'merge', 'detach'])]
#[ManyToMany(targetEntity: 'CmsGroup', inversedBy: 'users', cascade: ['persist', 'detach'])]
public $groups;

/** @var Collection<int, CmsTag> */
Expand Down
Expand Up @@ -21,7 +21,7 @@ trait DDC1872ExampleTrait
protected $foo;

/** @var DDC1872Bar */
#[OneToOne(targetEntity: 'DDC1872Bar', cascade: ['persist', 'merge'])]
#[OneToOne(targetEntity: 'DDC1872Bar', cascade: ['persist'])]
#[JoinColumn(name: 'example_trait_bar_id', referencedColumnName: 'id')]
protected $bar;
}
8 changes: 4 additions & 4 deletions tests/Doctrine/Tests/Models/DDC964/DDC964User.php
Expand Up @@ -27,14 +27,14 @@ class DDC964User
protected $id;

/** @psalm-var Collection<int, DDC964Group> */
#[ManyToMany(targetEntity: DDC964Group::class, inversedBy: 'users', cascade: ['persist', 'merge', 'detach'])]
#[ManyToMany(targetEntity: DDC964Group::class, inversedBy: 'users', cascade: ['persist', 'detach'])]
#[JoinTable(name: 'ddc964_users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
protected $groups;

/** @var DDC964Address */
#[ManyToOne(targetEntity: DDC964Address::class, cascade: ['persist', 'merge'])]
#[ManyToOne(targetEntity: DDC964Address::class, cascade: ['persist'])]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
protected $address;

Expand Down Expand Up @@ -110,7 +110,7 @@ public static function loadMetadata(ClassMetadata $metadata): void
[
'fieldName' => 'address',
'targetEntity' => 'DDC964Address',
'cascade' => ['persist','merge'],
'cascade' => ['persist'],
'joinColumns' => [['name' => 'address_id', 'referencedColumnMame' => 'id']],
],
);
Expand All @@ -120,7 +120,7 @@ public static function loadMetadata(ClassMetadata $metadata): void
'fieldName' => 'groups',
'targetEntity' => 'DDC964Group',
'inversedBy' => 'users',
'cascade' => ['persist','merge','detach'],
'cascade' => ['persist','detach'],
'joinTable' => [
'name' => 'ddc964_users_groups',
'joinColumns' => [
Expand Down
2 changes: 1 addition & 1 deletion tests/Doctrine/Tests/Models/Legacy/LegacyUser.php
Expand Up @@ -47,7 +47,7 @@ class LegacyUser
#[JoinTable(name: 'legacy_users_cars')]
#[JoinColumn(name: 'iUserId', referencedColumnName: 'iUserId')]
#[InverseJoinColumn(name: 'iCarId', referencedColumnName: 'iCarId')]
#[ManyToMany(targetEntity: 'LegacyCar', inversedBy: 'users', cascade: ['persist', 'merge'])]
#[ManyToMany(targetEntity: 'LegacyCar', inversedBy: 'users', cascade: ['persist'])]
public $cars;

public function __construct()
Expand Down
Expand Up @@ -22,7 +22,7 @@ class CompositeToOneKeyState

/** @var Country */
#[Id]
#[ManyToOne(targetEntity: 'Country', cascade: ['MERGE'])]
#[ManyToOne(targetEntity: 'Country', cascade: [])]
#[JoinColumn(referencedColumnName: 'country')]
public $country;
}
2 changes: 1 addition & 1 deletion tests/Doctrine/Tests/Models/VersionedManyToOne/Article.php
Expand Up @@ -27,7 +27,7 @@ class Article
public $name;

/** @var Category */
#[ManyToOne(targetEntity: 'Category', cascade: ['merge', 'persist'])]
#[ManyToOne(targetEntity: 'Category', cascade: ['persist'])]
public $category;

/**
Expand Down
6 changes: 3 additions & 3 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2602Test.php
Expand Up @@ -172,7 +172,7 @@ class DDC2602User
public $name;

/** @var DDC2602Biography */
#[OneToOne(targetEntity: 'DDC2602Biography', inversedBy: 'user', cascade: ['persist', 'merge', 'refresh', 'remove'])]
#[OneToOne(targetEntity: 'DDC2602Biography', inversedBy: 'user', cascade: ['persist', 'refresh', 'remove'])]
#[JoinColumn(nullable: false)]
public $biography;
}
Expand All @@ -187,7 +187,7 @@ class DDC2602Biography
public $id;

/** @var DDC2602User */
#[OneToOne(targetEntity: 'DDC2602User', mappedBy: 'biography', cascade: ['persist', 'merge', 'refresh'])]
#[OneToOne(targetEntity: 'DDC2602User', mappedBy: 'biography', cascade: ['persist', 'refresh'])]
public $user;

/** @var string */
Expand Down Expand Up @@ -216,7 +216,7 @@ class DDC2602BiographyField
public $label;

/** @var ArrayCollection */
#[OneToMany(targetEntity: 'DDC2602BiographyFieldChoice', mappedBy: 'field', cascade: ['persist', 'merge', 'refresh'])]
#[OneToMany(targetEntity: 'DDC2602BiographyFieldChoice', mappedBy: 'field', cascade: ['persist', 'refresh'])]
public $choiceList;

public function __construct()
Expand Down

0 comments on commit 6b5ef2a

Please sign in to comment.