2

Intro

So, i have an entity Presence that is watched by Doctrine.
I also have another entity Decision that is not watched by Doctrine.

The Presence entity has an attribute $decisions, typed ArrayCollection and contains 0 to n Decision entities.

Since i don't store each Decision object that is contained within the ArrayCollection, i typed the attribute $decision as Doctrine Array and everything is working so far.

I can get the whole Presence object back from database & it contains every Decision objects that i added to it.

The problem

When i edit one Decision object that is present in the ArrayCollection of my Presence object like :

    foreach($Presence->getDecisions() as $decision) {
        $decision->setMorning($value);
    }

The modifications are applied locally, and i can see them when doind a dump($Presence);

Presence {#2354 ▼
  #id: 8
  #decisions: ArrayCollection {#1409 ▼
    -elements: array:6 [▼
      0 => Decision {#1408 ▼
        #token_id: "005867b2915438c03383bb831d87c26346c586d6ecd46b735c7a23b4fabb682a8ac34a90ea3d53cc55c2209deaa83337abeb2b98ded43967fcfb0f4d04d5ada6"
        #date: DateTime @1531692000 {#1407 ▶}
        #morning: -1
        #afternoon: 0
      }
      1 => Decision {#1406 ▶}
      2 => Decision {#1404 ▶}
      3 => Decision {#1402 ▶}
      4 => Decision {#1400 ▶}
      5 => Decision {#1398 ▶}
    ]
  }
  #last_edit_date: DateTime @1532109183 {#2454 ▼
    date: 2018-07-20 19:53:03.062940 Europe/Berlin (+02:00)
  }
  #user: User {#1393 ▶}
}

On this dump() you can see that the attribute #morning: -1 is the result of the setter call.

But when i try to flush the changes with $this->entityManager->flush(); the ArrayCollection has not been modified.

At first i thought it was a problem of changes detection, so i added a #last_edit_date DateTime field onto my Presence entity.
The fun part is that this attribute is updating just fine when flushing.

It seems that the ArrayCollection won't update for some reason.

UPDATE

To complete my post here is additional informations :

The full method in the controller

/**
* @Route(name="edit_decision", path="/edit-decision/{tokenDate}/{dayPart}/{newValue}")
*/
public function EditDecisionAction(Request $request)
{
    $token = $request->get('tokenDate');
    $dayPart = $request->get('dayPart');
    $newValue = intval($request->get('newValue'));

    $myPresence = $this->em->getRepository('AppBundle:Presence')->findOneBy([
        'user' => $this->connectedUser
    ]);

    /** @var ArrayCollection $decisionCollection */
    $decisionCollection = $myPresence->getDecisions();

    $found = FALSE;
    
    /** @var Decision $decision */
    foreach ($decisionCollection as $decision) {
        if ($decision->getTokenId() == $token) {
            
            $found = TRUE;

            if ($dayPart === 'morning') {
                $decision->setMorning($newValue);
            }elseif ($dayPart === 'afternoon') {
                $decision->setAfternoon($newValue);
            }
            break;
        }
    }
    
    if (!$found) {
        throw $this->createNotFoundException();
    }
    
    // I set a new Date to trigger a modification on the Presence Entity : It works
    $myPresence->setLastEditDate(new \DateTime());
    
    // Here is the dump that you can see above
    dump($myPresence);
    
    $this->em->flush();
    
    try{
        $this->em->flush();
        return $this->json([
            'status' => TRUE
        ]);
    }catch (\Exception $exception) {
        return $this->json([
            'status' => FALSE
        ]);
    }
}

The attribute that contains the ArrayCollection :

/**
 * Doctrine Type Array
 *
 * @ORM\Column(type="array", nullable=true)
 */
 protected $decisions;

Any help or even hints are welcome ! Thanks :)

UPDATE 2

I may have found the solution thanks to this post :
How to force Doctrine to update array type fields?

The problem comes from the doctrine Array type under which i store my objects :

Doctrine uses identical operator (===) to compare changes between old and new values. The operator used on the same object (or array of objects) with different data always return true. There is the other way to solve this issue, you can clone an object that needs to be changed.

Update 3

Nope still doesn't persists the changes made on the ArrayCollection's items in the database. Gonna try Cloning : Adding : Removing the old one and we will se what happens.

Community
  • 1
  • 1
Matt
  • 21
  • 4
  • Rather than the dump, the full function in the controller would help better. Can you complete your answer please? Also add the part of your twig view where you render the form, and the `FormType` you're using in your controller – Preciel Jul 20 '18 at 20:09
  • You maybe need to use on-flush: $unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity) Or you need to use pre-update: https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html – nicandr Jul 21 '18 at 12:11
  • Here is another link https://stackoverflow.com/questions/18762695/how-to-access-old-values-on-prepersist-lifecyclecallback-in-doctrine2 – nicandr Jul 21 '18 at 12:15
  • Add your controller and twig – Shaithya Kannan Jul 23 '18 at 06:58

1 Answers1

0

FINAL UPDATE

Ok so i finally got it to work using a trick.

The main problem was the behaviour of Doctrine changes detection towards the Array type.

As quoted above, doctrine does : Array === Array to check if there is changes to be made. Kinda weird because when you ADD or REMOVE an element from your ArrayCollection, for Doctrine the collection is still equivalent to the precedent state. There should be a way to test equivalences between 2 objects like in Java .equals to remove those ambiguities.

Anyway what i did to solve it :
I store my collection to an initial state :

$decisionCollection = $myPresence->getDecisions();

I place my setters & modify my collection accordingly :

foreach ($myPresence->getDecisions() as $key => $decision) {
    if ($decision->getTokenId() == $token) {
                
        if ($dayPart === 'morning') {
            $decision->setMorning($newValue);
        } elseif ($dayPart === 'afternoon') {
            $decision->setAfternoon($newValue);
        }
                
        break;
    }
}

And then the Trick :

$myPresence->unsetDecisions();
$this->em->flush();

I unset completly my collection ($this->collection = NULL) & flush a first time.
It triggers the Update since Array !== NULL
And then i set my collection with the updated one that contains the setted objects :

$myPresence->setDecisions($decisionCollection);
$myPresence->setLastEditDate(new \DateTime());

And flush a last time to keep the new changes.

Thanks everyone for your time & your comments !

Community
  • 1
  • 1
Matt
  • 21
  • 4