neos / flow-development-collection

The unified repository containing the Flow core packages, used for Flow development.
https://flow.neos.io/
MIT License
134 stars 187 forks source link

BUG: Changes to persistent objects are not stored in session #3346

Open cornerfarmer opened 2 months ago

cornerfarmer commented 2 months ago

Is there an existing issue for this?

Current Behavior

If you store an object, that is persisted, in the session, only the reference to the object is actually stored and all modifications are lost between requests.

Expected Behavior

In the docu it says Persistent objects which are modified are fully stored in the session. (https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/SessionHandling.html). This would also be the expected behavior, which is necessary for example if you want to edit an object across multiple requests in the session and only in the end write it into the database.

Steps To Reproduce

The entity class:

<?php
declare(strict_types=1);

namespace MyCompany\MyPackage\Domain\Model;

use Doctrine\ORM\Mapping as ORM;
use Neos\Flow\Annotations as Flow;
/**
 *
 * @Flow\Entity
 */
 class Number {

    /**
     * @ORM\Column(length=50, nullable=true)
     * @var int
     */
    public $value;

 }

The session class:

<?php
declare(strict_types=1);

namespace MyCompany\MyPackage\Domain\Session;

use Neos\Flow\Annotations as Flow;
use MyCompany\MyPackage\Domain\Model\Number;

/**
 * @Flow\Scope("session")
 */
class SessionTest
{
    /**
     * The user which corresponds to this settings container
     *
     * @var Number
     */
    protected $number;

    /**
     * @Flow\Session(autoStart = true)
     * @param Number $number
     */
    public function setSessionNumber(Number $number)
    {
        $this->number = $number;
    }

    /**
     * @return Number
     */
    public function getSessionNumber()
    {
        return $this->number;
    }

}

Now in the first request:

// Create new number object
$number = new Number();
$number->value = 5;

// Persist it
$this->numberRepository->add($number);
$this->persistenceManager->persistAll();

// Set it in the session
$this->sessionTest->setSessionNumber($number);

In the second request:

// Change the number only in the session
$number = $this->sessionTest->getSessionNumber();
$number->value = 6;

In the third request:

// Read the number 
$number = $this->sessionTest->getSessionNumber();
echo $number->value;

This outputs 5, not as expected 6!

If you dont persist the object in the first request, the output is 6, as expected.

Environment

- Flow: 8.3
- PHP: 8.2

Anything else?

In the serialization, the object only is fully serialized if it is not persited: https://github.com/neos/flow-development-collection/blob/00fa8ff0768afd91f54f507df1260fbbebf91fb0/Neos.Flow/Classes/ObjectManagement/Proxy/ObjectSerializationTrait.php#L84

Before #2700, there was a workaround, that if one creates a copy of the PHP object (same PID), then this copied object would be fully serialized.

Is there now any possibility to store a modified persted object in session, except creating a temporary copy with new PID?

kitsunet commented 2 months ago

Thanks for opening this report, you are correct that this is the current behavior. I wasn't aware of the discrepancy in the documentation and I am actually not aware when the described behavior was ever the one actually implemented (apart from new objects).

The problem is that the behavior you would like is inherently dangerous in that there could be write operations to this entity in the meantime which would be fully overwritten by the entity from the session. I think it would be much safer to only store the "changes" in session then load the current state of the entity at the end and apply the (session stored) changes to it.

Implementationwise that would mean a change object which is not an entity that has a relation to the respective entity. and storing this change object in the session.