skip to Main Content

I don’t understand how detach() works..
This is a use case:

/**
 * @ORMEntity(repositoryClass="UsersRepository")
 * @ORMTable(name="users")
 */
class Users
{
    // ....
    
    /**
     * @ORMManyToOne(targetEntity="AppEntityParentType")
     * @ORMJoinColumn(name="ID_PARENT_TYPE", referencedColumnName="ID_PARENT_TYPE")
     *
    private ParentType $parentType;
}

/**
 * @ORMEntity(repositoryClass="CustomInfoRepository")
 * @ORMTable(name="custom_info")
 */
class CustomInfo
{
    // ....

    /**
     * @ORMManyToOne(targetEntity="AppEntityUsers")
     * @ORMJoinColumn(name="ID_USER", referencedColumnName="ID_USER")
     *
     */
    private Users $user;
    
    /**
     * @ORMManyToOne(targetEntity="AppEntityParentType")
     * @ORMJoinColumn(name="ID_PARENT_TYPE", referencedColumnName="ID_PARENT_TYPE")
     *
    private ParentType $parentType;
}

// UsersRepository
public function detach(object $entity) {
    $this->_em->detach($entity);
}


// CustomInfoRepository
public function save(CustomInfo $customInfo, bool $isUpdate = false)
{
    if($isUpdate === false) {
        $this->_em->persist($monthlyBalance);
    }
    $this->_em->flush();
}

// $users = $this->createQueryBuilder('q')->getQuery()

foreach ($users->toIterable() as $user) {
  // return CustomInfo object
  $customInfo = $customInfoRepository->getCustomInfo($user->getId());
  // update some fields of $customInfo...
  $customInfoRepository->save($customInfo)

  // detach $user to keep memory...
  $this->usersRepository->detach($user);

}

On the first iteration: save is ok

On the second iteration: ORMInvalidArgumentException.php line 102:
A new entity was found through the relationship ‘AppEntityCustomInfo#user’ that was not configured to cascade persist operations for entity: AppEntityUsers …

Why detach() detach other entities of $users collections ?

2

Answers


  1. Chosen as BEST ANSWER

    In batch loop, if you get an entity but don't flush it, doctrine keep them (thanks xdebug), even after a detach

    $users = $userRepo->getUsersQuery();
    foreach ($users->toIterable() as $user) {
        $skills = $userSkillRepo->getSkills($user->getId());
        
        foreach ($skills as $skill) {
            // update from conditions.. so not on each iteration
            if($conditions) {
               $skill->setCustomField($newValue);
               $userSkillRepo->flush(); // Entity manager flush
            } else {
              // doctrine keep entity with reference of current $user
              // So on next $users iteration, and if you want to flush,
              // doctrine try to flush cascaded entity but $user of previous iteration don't was detached
            }
            $userSkillRepo->detach($skill)
        }
    
        $userRepo->detach($user); // Entity manager detach
        // Solution: You have to clear here: after all managed entity was finish flushing
        $userRepo->clear();
    }
    

  2. Though it’s more a guess as it’s not quite clear what changes you make with customInfo (if you do). You seems to only retrieve and immediately save again.

    I would suggest that you only detached child object but parent is still managed. Try detaching customInfo as well.

    EntityManager keeps track of all object saved/retrieved from db (until cleared or detached at least). This helps it to generate least possible set of SQL queries to persist the state of entities kept inside EntityManager onto your DB.

    After detaching $user the state of customInfo changed (as if you destroyed the user object or set user property to null). Doctrine flush call is applied to all entities inside EntityManager hence doctrine saw change of state – it tried to save customInfo again, but the state was invalid and error was thrown


    .

    Doc about entity states

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search