Skip to content

Conversation

SherinBloemendaal
Copy link

@SherinBloemendaal SherinBloemendaal commented Sep 19, 2025

UnitOfWork was incorrectly detecting changes for database-generated columns with insertable: false and updatable: false, causing entities to be scheduled for update when only generated values changed.

This adds checks to skip notInsertable fields for NEW entities and notUpdatable fields for MANAGED entities during change detection, aligning UnitOfWork behavior with BasicEntityPersister.

Fixes #12017

(Retarget on 3.6.x #12018)

UnitOfWork was incorrectly detecting changes for database-generated
columns with insertable: false and updatable: false, causing entities
to be scheduled for update when only generated values changed.

This adds checks to skip notInsertable fields for NEW entities and
notUpdatable fields for MANAGED entities during change detection,
aligning UnitOfWork behavior with BasicEntityPersister.

Fixes doctrine#12017
@beberlei
Copy link
Member

Not happy that this needs to add addtional checks into the hot path of changeset computation.

Would it be better to move this into the $actualData computation the block before?

Because that already checks that $name !== $class->versionField, which is something similar conceptually.

if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) {

We could change that code to be using guard clauses for better readability.

if ($class->isIdentifier($name) && $class->isIdGeneratorIdentity()) {
    continue;
}
if ($name === $class->versionField) {
    continue;
}
// new checks here, requires ! isset($this->originalEntityData[$oid]) for insert vs update

This will not be better in terms of performance in the hot path I suppose though.

@SherinBloemendaal
Copy link
Author

Thanks for the suggestion to move the checks out of the hot path! I implemented it and works great for fully generated fields (notInsertable AND notUpdatable) - they get filtered out early and never hit the hot path loops.

However, I ran into an issue with mixed flags like insertable: false, updatable: true. When these fields get filtered out during INSERT, they're missing from $this->originalEntityData, so during UPDATE there's nothing to compare against and they get skipped as "partially omitted":

orm/src/UnitOfWork.php

Lines 660 to 663 in 2ca63df

// skip field, its a partially omitted one!
if (! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
continue;
}

Maybe we could keep a combination of both? Fully generated fields are never added to the $actualData since its useless.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants