DDC-1680: Id is null in postRemove events #2110

Closed
opened 2026-01-22 13:41:07 +01:00 by admin · 28 comments
Owner

Originally created by @doctrinebot on GitHub (Mar 5, 2012).

Originally assigned to: @beberlei on GitHub.

Jira issue originally created by user redemption:

When using the postRemove event, $this->id (where id is the annotated @Id of the Entity) is null.

All other fields have the correct data (eg if you have an Entity with name, email, address, and id as fields, the id field is the only one that is null).

Originally created by @doctrinebot on GitHub (Mar 5, 2012). Originally assigned to: @beberlei on GitHub. Jira issue originally created by user redemption: When using the postRemove event, $this->id (where id is the annotated @Id of the Entity) is null. All other fields have the correct data (eg if you have an Entity with name, email, address, and id as fields, the id field is the only one that is null).
admin added the Bug label 2026-01-22 13:41:07 +01:00
admin closed this issue 2026-01-22 13:41:08 +01:00
Author
Owner

@doctrinebot commented on GitHub (Mar 5, 2012):

Comment created by @beberlei:

This is expected behavior, otherwise when you merge that entity back it will be detected as "known", although its actually deleted. You can save the id in preRemove and then reuse it in postRemove if you need it.

@doctrinebot commented on GitHub (Mar 5, 2012): Comment created by @beberlei: This is expected behavior, otherwise when you merge that entity back it will be detected as "known", although its actually deleted. You can save the id in preRemove and then reuse it in postRemove if you need it.
Author
Owner

@doctrinebot commented on GitHub (Mar 5, 2012):

Issue was closed with resolution "Invalid"

@doctrinebot commented on GitHub (Mar 5, 2012): Issue was closed with resolution "Invalid"
Author
Owner

@doctrinebot commented on GitHub (Mar 5, 2012):

Comment created by redemption:

Is there any chance of providing a native premade value (or method) to store the deleted id, for instance "deletedId"? It seems logical to me that postRemove events will need to know the Id that was removed, and hacking it in with preRemove events all the time is less elegant.

@doctrinebot commented on GitHub (Mar 5, 2012): Comment created by redemption: Is there any chance of providing a native premade value (or method) to store the deleted id, for instance "deletedId"? It seems logical to me that postRemove events will need to know the Id that was removed, and hacking it in with preRemove events all the time is less elegant.
Author
Owner

@doctrinebot commented on GitHub (Sep 17, 2014):

Comment created by umed:

yeah I also think that we need something native. This hack with preremove looks really ugly.

@doctrinebot commented on GitHub (Sep 17, 2014): Comment created by umed: yeah I also think that we need something native. This hack with preremove looks really ugly.
Author
Owner

@doctrinebot commented on GitHub (Sep 17, 2014):

Comment created by umed:

Here is https://groups.google.com/forum/#!topic/doctrine-user/bTukzq0QrSE a very good comment left by Pavel Horal

@doctrinebot commented on GitHub (Sep 17, 2014): Comment created by umed: Here is https://groups.google.com/forum/#!topic/doctrine-user/bTukzq0QrSE a very good comment left by Pavel Horal
Author
Owner

@doctrinebot commented on GitHub (Sep 17, 2014):

Comment created by @ocramius:

[~umed] I'm fine with reverting this, but it needs a new and well exposed issue.

@doctrinebot commented on GitHub (Sep 17, 2014): Comment created by @ocramius: [~umed] I'm fine with reverting this, but it needs a new and well exposed issue.
Author
Owner

@doctrinebot commented on GitHub (Jan 12, 2015):

Comment created by ahmadnazir:

There there another issue created for this problem? If not, then I would like to help.

What do you mean by a well exposed issue?

@doctrinebot commented on GitHub (Jan 12, 2015): Comment created by ahmadnazir: There there another issue created for this problem? If not, then I would like to help. What do you mean by a well exposed issue?
Author
Owner

@doctrinebot commented on GitHub (Jan 12, 2015):

Comment created by @ocramius:

[~ahmadnazir] I mean that we could need a functional test case

@doctrinebot commented on GitHub (Jan 12, 2015): Comment created by @ocramius: [~ahmadnazir] I mean that we could need a functional test case
Author
Owner

@doctrinebot commented on GitHub (Oct 20, 2015):

Comment created by pawelbaranski:

Hi,

I'd also see this issue solved. I have a case where I'm storing an entity both in MySQL and part of it in Elasticsearch. When I delete an entity from MySQL I'd like it to be deleted from ES as well. I would not want to remove it from ES too soon so postRemove event would be what I need.

I will use the hack Benjamin presented, but it is like an unwanted child

@doctrinebot commented on GitHub (Oct 20, 2015): Comment created by pawelbaranski: Hi, I'd also see this issue solved. I have a case where I'm storing an entity both in MySQL and part of it in Elasticsearch. When I delete an entity from MySQL I'd like it to be deleted from ES as well. I would not want to remove it from ES too soon so postRemove event would be what I need. I will use the hack Benjamin presented, but it is like an unwanted child
Author
Owner

@ebuildy commented on GitHub (Nov 4, 2017):

Why this is closed?

@ebuildy commented on GitHub (Nov 4, 2017): Why this is closed?
Author
Owner

@lcobucci commented on GitHub (Nov 21, 2017):

@ebuildy apparently nobody bothered to create a "new and well exposed issue" (which means it has a clear explanation and a functional test) as @Ocramius asked. Would you be wiling to do that?

You can find examples of functional tests on 388afb46d0/tests/Doctrine/Tests/ORM/Functional/Ticket

@lcobucci commented on GitHub (Nov 21, 2017): @ebuildy apparently nobody bothered to create a "new and well exposed issue" (which means it has a clear explanation and a functional test) as @Ocramius asked. Would you be wiling to do that? You can find examples of functional tests on https://github.com/doctrine/doctrine2/tree/388afb46d0cb3ed0c51332e8df0de9e942c2690b/tests/Doctrine/Tests/ORM/Functional/Ticket
Author
Owner

@dkarlovi commented on GitHub (Jan 18, 2018):

@lcobucci if I'm creating a test case, it's a PR against master or where?

@dkarlovi commented on GitHub (Jan 18, 2018): @lcobucci if I'm creating a test case, it's a PR against master or where?
Author
Owner

@lcobucci commented on GitHub (Jan 18, 2018):

@dkarlovi you can use 2.6 as base.

@lcobucci commented on GitHub (Jan 18, 2018): @dkarlovi you can use `2.6` as base.
Author
Owner

@dkarlovi commented on GitHub (Jan 26, 2018):

Until I create the test, it get's reviewed, accepted/rejected/fixed/released, I need the described workaround.

Where exactly am I supposed to store the identifier in the preRemove handler? On the event? But it's not the same object in preRemove and postRemove. I imagine we can abuse PHP's synchronous nature and store as a property inside my handler and count on things not getting out of order, but that's just too messy for comfort, TBH.

IMO if this state is the solution for

when you merge that entity back it will be detected as "known", although its actually deleted

then this exact problem needed a better solution. For example, mark the entity as "deleted" or "unmergeable" or something.

@dkarlovi commented on GitHub (Jan 26, 2018): Until I create the test, it get's reviewed, accepted/rejected/fixed/released, I need the described workaround. Where exactly am I supposed to store the identifier in the preRemove handler? On the event? But it's not the same object in preRemove and postRemove. I imagine we can abuse PHP's synchronous nature and store as a property inside my handler and count on things not getting out of order, but that's just too messy for comfort, TBH. IMO if this state is the solution for > when you merge that entity back it will be detected as "known", although its actually deleted then this exact problem needed a better solution. For example, mark the entity as "deleted" or "unmergeable" or something.
Author
Owner

@Ocramius commented on GitHub (Jan 26, 2018):

Where exactly am I supposed to store the identifier in the preRemove handler?

I usually record everything I need to "keep" in onFlush, which right after the changes in the UnitOfWork were computed.

The id being null is currently by design, so we'd break BC for a scenario like this if we reverted it:

$entity = new Foo(); // has generated id
$em->persist($entity);
$em->flush();
$em->remove($entity);
$em->flush();
$em->persist($entity); // if we revert the change, this one would fail due to duplicate identifier
$em->flush();
@Ocramius commented on GitHub (Jan 26, 2018): > Where exactly am I supposed to store the identifier in the preRemove handler? I usually record everything I need to "keep" in `onFlush`, which right after the changes in the `UnitOfWork` were computed. The id being `null` is currently *by design*, so we'd break BC for a scenario like this if we reverted it: ```php $entity = new Foo(); // has generated id $em->persist($entity); $em->flush(); $em->remove($entity); $em->flush(); $em->persist($entity); // if we revert the change, this one would fail due to duplicate identifier $em->flush(); ```
Author
Owner

@dkarlovi commented on GitHub (Jan 26, 2018):

@Ocramius thanks, makes sense.

The id being null is currently by design, so we'd break BC for a scenario like this if we reverted it:
if we revert the change, this one would fail due to duplicate identifier

I know, that was my point with

mark the entity as "deleted" or "unmergeable" or something.

so, when you try to merge it back, it just throws. This allows for correct behavior in both cases (entity which is deleted is a orphaned entity you still have a reference to, you still don't get to reuse it just like that).

@dkarlovi commented on GitHub (Jan 26, 2018): @Ocramius thanks, makes sense. > The id being null is currently by design, so we'd break BC for a scenario like this if we reverted it: > if we revert the change, this one would fail due to duplicate identifier I know, that was my point with > mark the entity as "deleted" or "unmergeable" or something. so, when you try to merge it back, it just throws. This allows for correct behavior in both cases (entity which is deleted is a orphaned entity you still have a reference to, you still don't get to reuse it just like that).
Author
Owner

@Ocramius commented on GitHub (Jan 26, 2018):

->merge() will no longer be improved, and is deprecated (and removed in 3.x

@Ocramius commented on GitHub (Jan 26, 2018): `->merge()` will no longer be improved, and is deprecated (and removed in `3.x`
Author
Owner

@dkarlovi commented on GitHub (Jan 26, 2018):

Sorry, "merge" is a lapsus, I meant "when you try to treat it as a regular entity in any way" (persist, update, etc).

@dkarlovi commented on GitHub (Jan 26, 2018): Sorry, "merge" is a lapsus, I meant "when you try to treat it as a regular entity in any way" (persist, update, etc).
Author
Owner

@Ocramius commented on GitHub (Jan 26, 2018):

Ah, sorry. No, there is no way to do that. After flush(), memory usage must stay constant in the ORM, and adding a registry that marks removed entities would be a big issue.

@Ocramius commented on GitHub (Jan 26, 2018): Ah, sorry. No, there is no way to do that. After `flush()`, memory usage must stay constant in the ORM, and adding a registry that marks removed entities would be a big issue.
Author
Owner

@dkarlovi commented on GitHub (Jan 26, 2018):

Fair point.

Would a better workaround be to introduce a new event type, PostRemoveEventArgs which would have getIdentifier()? Then Doctrine can do this for you and I wouldn't be too bad.

@dkarlovi commented on GitHub (Jan 26, 2018): Fair point. Would a better workaround be to introduce a new event type, `PostRemoveEventArgs` which would have `getIdentifier()`? Then Doctrine can do this for you and I wouldn't be too bad.
Author
Owner

@Ocramius commented on GitHub (Jan 26, 2018):

@dkarlovi we can improve this in 3.x by adding the identifier to PostRemoveEventArgs or by breaking BC as I described in https://github.com/doctrine/doctrine2/issues/2326#issuecomment-360719911

@Ocramius commented on GitHub (Jan 26, 2018): @dkarlovi we can improve this in 3.x by adding the identifier to `PostRemoveEventArgs` or by breaking BC as I described in https://github.com/doctrine/doctrine2/issues/2326#issuecomment-360719911
Author
Owner

@ismail1432 commented on GitHub (Jan 23, 2019):

PostRemoveEventArgs which would have getIdentifier()

One year later, I think it's a good idea 👍

@ismail1432 commented on GitHub (Jan 23, 2019): > PostRemoveEventArgs which would have getIdentifier() One year later, I think it's a good idea :+1:
Author
Owner

@Nathanael-Shermett commented on GitHub (Jan 5, 2021):

PostRemoveEventArgs which would have getIdentifier()

One year later, I think it's a good idea 👍

Another two years later, and I also think it's a good idea.

It doesn't make sense to modify an entity class (or perhaps every entity class), complete with getters and setters, just to store a historical ID. It's hacky, and it subverts the entity's integrity. With that said, I have a hacky solution that is at least self-contained:

public function preRemove(LifecycleEventArgs $args): void {
    $entity = $args->getObject();
    $entity->historicalId = $entity->getId(); // $historicalId is not defined or referenced anywhere but here
}

public function postRemove(LifecycleEventArgs $args): void {
    $entity = $args->getObject(); // the old ID is accessible via $entity->historicalId
}

I'm sure some purists on here will say this is gross. I agree. However, this limitation shouldn't exist in the first place, and a man's gotta eat somehow.

@Nathanael-Shermett commented on GitHub (Jan 5, 2021): > > PostRemoveEventArgs which would have getIdentifier() > > One year later, I think it's a good idea 👍 Another two years later, and I also think it's a good idea. It doesn't make sense to modify an entity class (or perhaps every entity class), complete with getters and setters, just to store a historical ID. It's hacky, and it subverts the entity's integrity. With that said, I have a hacky solution that is at least self-contained: public function preRemove(LifecycleEventArgs $args): void { $entity = $args->getObject(); $entity->historicalId = $entity->getId(); // $historicalId is not defined or referenced anywhere but here } public function postRemove(LifecycleEventArgs $args): void { $entity = $args->getObject(); // the old ID is accessible via $entity->historicalId } I'm sure some purists on here will say this is gross. I agree. However, this limitation shouldn't exist in the first place, and a man's gotta eat somehow.
Author
Owner

@Warxcell commented on GitHub (May 10, 2021):

I suggest just to move
https://github.com/doctrine/orm/blob/2.8.x/lib/Doctrine/ORM/UnitOfWork.php#L1244-L1246
after
https://github.com/doctrine/orm/blob/2.8.x/lib/Doctrine/ORM/UnitOfWork.php#L1248-L1250.

That will bring the ID in postRemove events. I think it's not convenient to have getIdentifier in event, for examples on places like that: https://github.com/Warxcell/files/blob/master/src/NamingStrategy/IdToPathStrategy.php

+1 for BC-break, It would be nice to have the ID even after postRemove. The use case is, when/if https://github.com/doctrine/dbal/pull/4622 is accepted, I will be able to refactor my listener on postRemove it will collect entities for removal, and on onTransactionCommit I can loop over pending entities for removal and call $fileManager->remove($entity) which could use ID to determine the path to the file. That will be impossible if IDs are gone after postRemove.

@Warxcell commented on GitHub (May 10, 2021): I suggest just to move https://github.com/doctrine/orm/blob/2.8.x/lib/Doctrine/ORM/UnitOfWork.php#L1244-L1246 after https://github.com/doctrine/orm/blob/2.8.x/lib/Doctrine/ORM/UnitOfWork.php#L1248-L1250. That will bring the ID in `postRemove` events. I think it's not convenient to have `getIdentifier` in event, for examples on places like that: https://github.com/Warxcell/files/blob/master/src/NamingStrategy/IdToPathStrategy.php +1 for BC-break, It would be nice to have the ID even after `postRemove`. The use case is, when/if https://github.com/doctrine/dbal/pull/4622 is accepted, I will be able to refactor my listener on `postRemove` it will collect entities for removal, and on `onTransactionCommit` I can loop over pending entities for removal and call `$fileManager->remove($entity)` which could use `ID` to determine the path to the file. That will be impossible if IDs are gone after postRemove.
Author
Owner

@Nathanael-Shermett commented on GitHub (Nov 15, 2021):

FYI, my above fix (using a dynamic property to store the entity ID before it is lost during the delete process) will be deprecated in PHP 8.2 and will throw an error in PHP 9.0.

You can read more here.

@Nathanael-Shermett commented on GitHub (Nov 15, 2021): FYI, my above fix (using a dynamic property to store the entity ID before it is lost during the delete process) will be deprecated in PHP 8.2 and will throw an error in PHP 9.0. You can read more [here](https://wiki.php.net/rfc/deprecate_dynamic_properties).
Author
Owner

@justin-oh commented on GitHub (May 16, 2024):

For anyone searching in the future, I had to do something like the following to maintain an ID-based value (which was a directory path).

Before:

public function getDirectory(): string
{
    return __DIR__.'/../../entity-uploads/'.$this->id;
}

After:

private string $directory;

public function getDirectory(): string
{
    return $this->directory ?? __DIR__.'/../../entity-uploads/'.$this->id;
}

/**
 * For preRemove lifecycle callback because $this->id is NULL in postRemove.
 */
public function setDirectory(): static
{
    $this->directory = $this->getDirectory();

    return $this;
}
@justin-oh commented on GitHub (May 16, 2024): For anyone searching in the future, I had to do something like the following to maintain an ID-based value (which was a directory path). Before: ```php public function getDirectory(): string { return __DIR__.'/../../entity-uploads/'.$this->id; } ``` After: ```php private string $directory; public function getDirectory(): string { return $this->directory ?? __DIR__.'/../../entity-uploads/'.$this->id; } /** * For preRemove lifecycle callback because $this->id is NULL in postRemove. */ public function setDirectory(): static { $this->directory = $this->getDirectory(); return $this; } ```
Author
Owner

@orangevinz commented on GitHub (May 28, 2024):

Using a trait can be convenient as well

trait StoreIdAfterDeletionEntity
{
    private ?int $storedId;

    public function getStoredId(): ?int
    {
        return $this->storedId;
    }

    #[ORM\PreRemove]
    public function setStoredId(): static
    {
        $this->storedId = $this->getId();

        return $this;
    }
}
#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
class MyEntity
{
    use StoreIdAfterDeletionEntity;
}
@orangevinz commented on GitHub (May 28, 2024): Using a trait can be convenient as well ```php trait StoreIdAfterDeletionEntity { private ?int $storedId; public function getStoredId(): ?int { return $this->storedId; } #[ORM\PreRemove] public function setStoredId(): static { $this->storedId = $this->getId(); return $this; } } ``` ```php #[ORM\Entity] #[ORM\HasLifecycleCallbacks] class MyEntity { use StoreIdAfterDeletionEntity; } ```
Author
Owner

@Richardds commented on GitHub (Jun 2, 2024):

@orangevinz Unfortunately, the trait methods won't get registered as Lifecycle callbacks.

@Richardds commented on GitHub (Jun 2, 2024): @orangevinz Unfortunately, the trait methods won't get registered as Lifecycle callbacks.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#2110