How to perist only the first level of an entities hierarchy with Doctrine?

Viewed 250

I'm migrating the DBAL of a ZF3 application to Doctrine and want to go ahead step by step. Currently I'm using a hierarchy of Mapper objects. Each entity in the like FooEntity hierarchy has an according FooMapper. Saving of nested entities is performed by nested Mappers. Every Mappers saves its entity with Zend\Db\Sql\Insert or Zend\Db\Sql\Update and calls the proper Mappers for the sub-entities like BarMapper for BarEntity.

Now, before I start with Doctrine's convenience features like cascade={"persist"}, I want to keep the Mapper's hierarchy and just to perform the saving of the top level of the nested entity with persist(...) & flush().

But when I try it

public function save(AbstractDataObject $dataObject)
{
    $newLogicalConnection = $this->logicalConnectionMapper->save($dataObject->getLogicalConnection());
    $newUser = $this->userMapper->save($dataObject->getUser());

    $dataObject->setLogicalConnection($this->entityManager->find(LogicalConnection::class, $newLogicalConnection->getId()));
    $dataObject->setUser($this->entityManager->find(User::class, $newUser->getId()));

    $this->entityManager->persist($dataObject);
    $this->entityManager->flush();

    return $dataObject;
}

I get an error

A new entity was found through the relationship 'MyNamespace\DataObject\AbstractEndpoint#externalServer' that was not configured to cascade persist operations for entity: MyNamespace\DataObject\ExternalServer@000000006098ccff0000000068c23676. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist  this association in the mapping for example @ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'MyNamespace\DataObject\ExternalServer#__toString()' to get a clue.

So, Doctrine seems to try saving the whole entity with its sub-entities, and this attempt fails on one of the lower levels. But why? I have not activated any cascade options and expect Doctrine to save only the top level.

Why does Doctrine try to save the whole entity and not only the top level? And how to get it saving only the top level of the given entity?

1 Answers

You get this error because you have a new entity (not persisted yet) in AbstractEndpoint->externalServer and this field is not annotated as cascade={"persist"}

In other words you have just created a new entity ExternalServer and did not persisted it and added it as a relation to AbstractEndpoint->externalServer entity which is not annotated as cascade={"persist"}

So Doctrine ends up having this new entity and does not know what to do with it. In order not to lost any data this exception is raised.

To fix this you can do two things:

  1. Add $this->entityManager->persist($externalServer); right after you create ExternalServer entity
  2. Annotate AbstractEndpoint->externalServer with cascade={"persist"}. Which you don't want to do because you want only top level entity to be saved to DB so you need to persist it manually or DO NOT add it is a relation.

And now answering your question:

But why? I have not activated any cascade options and expect Doctrine to save only the top level.

Somehow through relations in your object model Doctrine goes down to ExternalServer entity and finds it in unpersisted state. You can not save only top level of object hierarchy with link to unexisting record in relational database. If you do not want Doctrine to do it for you - you must handle this situations by yourself or remove not persisted entities from relations

Related