{"record":{"id":"99b5fc65a41b7348","repo":"doctrine/orm","slug":"dirty-entity-can-not-be-scheduled-for-insertion","errorCode":null,"errorMessage":"Dirty entity can not be scheduled for insertion.","messagePattern":"Dirty entity can not be scheduled for insertion\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/UnitOfWork.php","lineNumber":1396,"sourceCode":"            }\n        }\n\n        return $sort->sort();\n    }\n\n    /**\n     * Schedules an entity for insertion into the database.\n     * If the entity already has an identifier, it will be added to the identity map.\n     *\n     * @throws ORMInvalidArgumentException\n     * @throws InvalidArgumentException\n     */\n    public function scheduleForInsert(object $entity): void\n    {\n        $oid = spl_object_id($entity);\n\n        if (isset($this->entityUpdates[$oid])) {\n            throw new InvalidArgumentException('Dirty entity can not be scheduled for insertion.');\n        }\n\n        if (isset($this->entityDeletions[$oid])) {\n            throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);\n        }\n\n        if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {\n            throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);\n        }\n\n        if (isset($this->entityInsertions[$oid])) {\n            throw ORMInvalidArgumentException::scheduleInsertTwice($entity);\n        }\n\n        $this->entityInsertions[$oid] = $entity;\n\n        if (isset($this->entityIdentifiers[$oid])) {\n            $this->addToIdentityMap($entity);","sourceCodeStart":1378,"sourceCodeEnd":1414,"githubUrl":"https://github.com/doctrine/orm/blob/d9b9ff73016bf598ae07515f97289ce8074e97a5/src/UnitOfWork.php#L1378-L1414","documentation":"EntityManager::persist() delegates to UnitOfWork::scheduleForInsert(), which refuses entities already scheduled for update: entityUpdates is populated by computeChangeSets() while a flush is in progress, so an entity that is simultaneously 'has pending changes' and 'to be inserted' would produce conflicting SQL plans. Doctrine throws as soon as that combination is detected.","triggerScenarios":"Calling $em->persist($entity) from code that runs after change sets were computed during a flush - most commonly inside an onFlush listener or a nested flush attempt during preUpdate/postPersist - where the entity is already managed with computed changes (entityUpdates[spl_object_id($entity)] is set). Also reachable by calling $uow->scheduleForInsert() manually.","commonSituations":"onFlush listeners that persist the very entities they observe (audit loggers, event subscribers); re-entrant flush() inside lifecycle callbacks; generic 'ensure persisted' helper methods invoked during flush.","solutions":["Move the persist() call outside the flush cycle: do it before flush(), or collect the new entities and persist/flush them in a separate, later flush.","Inside onFlush, only persist genuinely new (STATE_NEW) entities; for already-managed entities just mutate them and call $uow->recomputeSingleEntityChangeSet() instead of persist().","Drop redundant persist() calls - an already-managed dirty entity is picked up by the change tracker automatically.","Never call flush() recursively from lifecycle listeners; queue work and flush after the current flush completes."],"exampleFix":"// before (inside an onFlush listener)\npublic function onFlush(OnFlushEventArgs $args): void\n{\n    foreach ($uow->getScheduledEntityUpdates() as $entity) {\n        $this->stamp($entity);\n        $args->getObjectManager()->persist($entity); // InvalidArgumentException: Dirty entity can not be scheduled for insertion.\n    }\n}\n\n// after\npublic function onFlush(OnFlushEventArgs $args): void\n{\n    $em  = $args->getObjectManager();\n    $uow = $em->getUnitOfWork();\n    foreach ($uow->getScheduledEntityUpdates() as $entity) {\n        $this->stamp($entity);\n        $uow->recomputeSingleEntityChangeSet($em->getClassMetadata($entity::class), $entity);\n    }\n}","handlingStrategy":"validation","validationCode":"// Inside flush-time listeners, only persist entities that are safe to insert\n$uow  = $em->getUnitOfWork();\n$state = $uow->getEntityState($entity);\n\nif ($state === UnitOfWork::STATE_NEW && ! $uow->isEntityScheduled($entity)) {\n    $em->persist($entity);\n    $uow->computeChangeSet($em->getClassMetadata($entity::class), $entity);\n}\n// already-managed entities: just mutate; the flush will pick changes up","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not call persist() from onFlush listeners for managed/dirty entities - mutate and recompute instead","Never trigger nested flush() from lifecycle callbacks; queue changes and flush after the current one","Restrict 'ensure persisted' helpers to STATE_NEW entities"],"tags":["doctrine-orm","unit-of-work","persist","onflush","lifecycle-listener","flush"],"backgroundTag":"orm-persist-during-flush","analyzedSha":"d9b9ff73016bf598ae07515f97289ce8074e97a5","analyzedAt":"2026-08-21T06:13:15.863Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}