doctrine/orm · error · UnexpectedValueException

Unexpected entity state: %s. %s

Error message

Unexpected entity state: %s. %s

What it means

UnitOfWork::doPersist switches over the entity's computed state (STATE_MANAGED, STATE_NEW, STATE_REMOVED each have explicit handling, STATE_DETACHED throws a dedicated exception). This default arm fires only when the state value falls outside all known constants - an internal invariant break rather than a user input problem. The message includes the numeric state and a string representation of the entity for diagnosis.

Source

Thrown at src/UnitOfWork.php:1849

            case self::STATE_REMOVED:
                // Entity becomes managed again
                unset($this->entityDeletions[$oid]);
                $this->addToIdentityMap($entity);

                $this->entityStates[$oid] = self::STATE_MANAGED;

                if ($class->isChangeTrackingDeferredExplicit()) {
                    $this->scheduleForDirtyCheck($entity);
                }

                break;

            case self::STATE_DETACHED:
                // Can actually not happen right now since we assume STATE_NEW.
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, 'persisted');

            default:
                throw new UnexpectedValueException(sprintf(
                    'Unexpected entity state: %s. %s',
                    $entityState,
                    self::objToStr($entity),
                ));
        }

        $this->cascadePersist($entity, $visited);
    }

    /**
     * Deletes an entity as part of the current unit of work.
     */
    public function remove(object $entity): void
    {
        $visited = [];

        $this->doRemove($entity, $visited);
    }

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Check $uow->getEntityState($entity) before persisting and handle non-standard/detached states explicitly (e.g. merge or reload instead of persist).
  2. Verify only one version of doctrine/orm is loaded: composer show doctrine/orm and check for duplicate vendor copies or phar bundling.
  3. Remove code that subclasses or reflects into UnitOfWork internals, or that serializes/clones the EntityManager.
Defensive patterns

Strategy: validation

Validate before calling

// Validate the state is one Doctrine handles before persisting
$state = $em->getUnitOfWork()->getEntityState($entity);
if (! in_array($state, [UnitOfWork::STATE_NEW, UnitOfWork::STATE_MANAGED, UnitOfWork::STATE_REMOVED], true)) {
    throw new InvalidArgumentException('Cannot persist entity in state ' . $state); // includes DETACHED: merge/find first
}
$em->persist($entity);

Prevention

When it happens

Trigger: $em->persist($entity) when UnitOfWork::getEntityState() yields an unexpected value: custom UnitOfWork subclasses or reflection tampering with internal state maps, an EntityManager corrupted by serialization/cloning of internal objects, or doctrine/orm classes from two different versions loaded in the same process.

Common situations: Essentially never in normal usage; seen with duplicated composer dependencies/phar classmaps loading mixed ORM versions, extensions subclassing UnitOfWork internals, or diagnostics tooling serializing the EntityManager.

Related errors


AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21). Data as JSON: /api/errors/fc2735511e405dba. Report an issue: GitHub.