DDC-871: @Version causes cascading to behave strangely #1079

Closed
opened 2026-01-22 13:01:11 +01:00 by admin · 6 comments
Owner

Originally created by @doctrinebot on GitHub (Nov 10, 2010).

Originally assigned to: @beberlei on GitHub.

Jira issue originally created by user felicitus:

As promised, I'm working on a tutorial for Doctrine2 in combination with versioning (if you are interested, feel free to look at http://piratenpad.de/0keOSIyxBy ). I noticed that the following example (loosely based on Benjamin's versioning blog post a while ago) does odd things. This example is written using the latest GIT version, which includes the fixes from Benjamin for Ticket DDC-870.

The code works without a problem, but if you have a look at the database after running the invocation code, you'll notice that "version" in the table SimpleTestVersion contains always 1. The strange thing is that the "content" column is updated correctly. I added some debug code to the constructor of SimpleTestVersion, and the passed object really contains the current version, so the SimpleTestVersion's properties are set correctly.

/****
 * @Entity
 * @HasLifeCycleCallbacks
 */
class SimpleTest {
     /****
     * @Id
     * @generatedValue(strategy="AUTO")
     * @Column(type="integer")
     */
    private $id;
    /****
     * @Column(type="integer")
     * @version
     */
    private $version = 1; // We need to set this, because we create our copy before persist, and version would be null then, causing an error. 
    /****
     * @Column
     */
    private $content;

     /****
     * @OneToMany(targetEntity="SimpleTestVersion", mappedBy="id", cascade={"all"})
     */
    private $auditLog = array();


    public function setContent ($content) {
        $this->content = $content;
    }

    public function getContent () {
        return $this->content;
    }

    public function getVersion () {
        return $this->version;
    }

    public function getId () {
        return $this->id;
    }

    /****
     * @PrePersist
     * @PreUpdate
     */
    public function logVersion()
    {
        $this->auditLog[] = new SimpleTestVersion($this);  
    }

}
/****
 * @Entity
 */
class SimpleTestVersion {
     /****
     * @Id
     * @generatedValue(strategy="AUTO")
     * @Column(type="integer")
     */
    private $id;
    /****
     * @Column(type="integer")
     */
    private $version;
    /****
     * @Column
     */
    private $content;

    /****
     * @ManyToOne(targetEntity="SimpleTest")
     */
    private $original;

    public function **construct (SimpleTest $original) {
        $this->version = $original->getVersion();
        $this->content = $original->getContent();
        $this->original = $original;
    }
}
$test = new SimpleTest();
$test->setContent("V1");
$em->persist($test);
$em->flush();


$test->setContent("V2");
$em->persist($test);
$em->flush();

$test->setContent("V3");
$em->persist($test);
$em->flush();
Originally created by @doctrinebot on GitHub (Nov 10, 2010). Originally assigned to: @beberlei on GitHub. Jira issue originally created by user felicitus: As promised, I'm working on a tutorial for Doctrine2 in combination with versioning (if you are interested, feel free to look at http://piratenpad.de/0keOSIyxBy ). I noticed that the following example (loosely based on Benjamin's versioning blog post a while ago) does odd things. This example is written using the latest GIT version, which includes the fixes from Benjamin for Ticket [DDC-870](http://www.doctrine-project.org/jira/browse/DDC-870). The code works without a problem, but if you have a look at the database after running the invocation code, you'll notice that "version" in the table SimpleTestVersion contains always 1. The strange thing is that the "content" column is updated correctly. I added some debug code to the constructor of SimpleTestVersion, and the passed object really contains the current version, so the SimpleTestVersion's properties are set correctly. ``` /**** * @Entity * @HasLifeCycleCallbacks */ class SimpleTest { /**** * @Id * @generatedValue(strategy="AUTO") * @Column(type="integer") */ private $id; /**** * @Column(type="integer") * @version */ private $version = 1; // We need to set this, because we create our copy before persist, and version would be null then, causing an error. /**** * @Column */ private $content; /**** * @OneToMany(targetEntity="SimpleTestVersion", mappedBy="id", cascade={"all"}) */ private $auditLog = array(); public function setContent ($content) { $this->content = $content; } public function getContent () { return $this->content; } public function getVersion () { return $this->version; } public function getId () { return $this->id; } /**** * @PrePersist * @PreUpdate */ public function logVersion() { $this->auditLog[] = new SimpleTestVersion($this); } } ``` ``` /**** * @Entity */ class SimpleTestVersion { /**** * @Id * @generatedValue(strategy="AUTO") * @Column(type="integer") */ private $id; /**** * @Column(type="integer") */ private $version; /**** * @Column */ private $content; /**** * @ManyToOne(targetEntity="SimpleTest") */ private $original; public function **construct (SimpleTest $original) { $this->version = $original->getVersion(); $this->content = $original->getContent(); $this->original = $original; } } ``` ``` $test = new SimpleTest(); $test->setContent("V1"); $em->persist($test); $em->flush(); $test->setContent("V2"); $em->persist($test); $em->flush(); $test->setContent("V3"); $em->persist($test); $em->flush(); ```
admin added the Bug label 2026-01-22 13:01:11 +01:00
admin closed this issue 2026-01-22 13:01:12 +01:00
Author
Owner

@doctrinebot commented on GitHub (Nov 10, 2010):

Comment created by @beberlei:

SimpleTestVersion has no @Version tag, and you don't need to call ->persist() for entities that are alraedy known to doctrine to update them. Does that fix your problem?

Your code should only increment the version of SimpleTest despite of the "cascade" though, since the SimpleVersionTest does not change (it doesnt get udpated).

@doctrinebot commented on GitHub (Nov 10, 2010): Comment created by @beberlei: SimpleTestVersion has no @Version tag, and you don't need to call ->persist() for entities that are alraedy known to doctrine to update them. Does that fix your problem? Your code should only increment the version of SimpleTest despite of the "cascade" though, since the SimpleVersionTest does not change (it doesnt get udpated).
Author
Owner

@doctrinebot commented on GitHub (Nov 10, 2010):

Comment created by felicitus:

Why I'm omitting @version in SimpleTestVersion:

SimpleTestVersion has no @Version tag, because versioning takes place in SimpleTest. SimpleTestVersion just stores copies of old versions. Since it seems that if you add @version to a property, it gets ignored by Doctrine's inserts (another bug report, or feature request, will follow). Or to be more precise: Set @version on fieldName, and doctrine doesn't do INSERT INTO table SET fieldName=version, but instead does an additional update statement and relies that fieldName's default value is set to 1. This is perfectly okay with me and this is not the issue. My example is similar as in your blog post; you did it actually the same way (and it didn't work there, either :)) The blog post I'm referring to is http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell

You're right, the multiple $em->persist calls are a mistake, but the (IMHO wrong) behavior doesn't change. I'll try to be a bit more precise:

Updated test code with a more precise comment:

$test = new SimpleTest();
$test->setContent("V1");
$em->persist($test);
$em->flush();

$test->setContent("V2");
$em->flush();

$test->setContent("V3");
/* $test's version is now 2. What happens now is that logVersion() gets called, creates a new instance of SimpleTestVersion.
   In the SimpleTestVersion constructor, I'm copying all properties, **including** the version (which is definitely 2 at this point)
   to the newly created instance. What I'm expecting is that once the new SimpleTestVersion is persisted, the database contains
   version 2 as set in the property - but it is set to 1 instead. */
$em->flush();
@doctrinebot commented on GitHub (Nov 10, 2010): Comment created by felicitus: **Why I'm omitting @version in SimpleTestVersion:** SimpleTestVersion has no @Version tag, because versioning takes place in SimpleTest. SimpleTestVersion just stores copies of old versions. Since it seems that if you add @version to a property, it gets ignored by Doctrine's inserts (another bug report, or feature request, will follow). Or to be more precise: Set @version on fieldName, and doctrine doesn't do INSERT INTO table SET fieldName=version, but instead does an additional update statement and relies that fieldName's default value is set to 1. This is perfectly okay with me and this is not the issue. My example is similar as in your blog post; you did it actually the same way (and it didn't work there, either :)) The blog post I'm referring to is http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell You're right, the multiple $em->persist calls are a mistake, but the (IMHO wrong) behavior doesn't change. I'll try to be a bit more precise: **Updated test code with a more precise comment:** ``` $test = new SimpleTest(); $test->setContent("V1"); $em->persist($test); $em->flush(); $test->setContent("V2"); $em->flush(); $test->setContent("V3"); /* $test's version is now 2. What happens now is that logVersion() gets called, creates a new instance of SimpleTestVersion. In the SimpleTestVersion constructor, I'm copying all properties, **including** the version (which is definitely 2 at this point) to the newly created instance. What I'm expecting is that once the new SimpleTestVersion is persisted, the database contains version 2 as set in the property - but it is set to 1 instead. */ $em->flush(); ```
Author
Owner

@doctrinebot commented on GitHub (Nov 10, 2010):

Comment created by felicitus:

It seems that either I have a major misunderstanding of how Doctrine2 works, or there is a big bug.

I've rewritten the classes above to avoid relations. What I wish to achieve is that every time SimpleTest gets persisted or updated, a new SimpleTestVersion is created.

Doctrine2 creates a new SimpleTestVersion for the first time I call persist() then flush(), but not for subsequent calls to flush(). The logVersion() method is actually called.

/****
 * @Entity
 * @HasLifeCycleCallbacks
 */
class SimpleTest {
     /****
     * @Id
     * @generatedValue(strategy="AUTO")
     * @Column(type="integer")
     */
    private $id;
    /****
     * @Column(type="integer")
     * @version
     */
    private $version = 1; // We need to set this, because we create our copy before persist, and version would be null then. 
    /****
     * @Column
     */
    private $content;

    public function setContent ($content) {
        $this->content = $content;
    }

    public function getContent () {
        return $this->content;
    }

    public function getVersion () {
        return $this->version;
    }

    public function getId () {
        return $this->id;
    }

    /****
     * @PrePersist
     * @PreUpdate
     */
    public function logVersion()
    {
        echo sprintf("Creating a new version for ID %s, version %s\n", $this->getId(), $this->getVersion());
        $entity = new SimpleTestVersion($this);
       // don't be confused by Netraver::getEM(), this is the $em.
        \de\netraver\Netraver::getEM()->persist($entity);
    }

}
/****
 * @Entity
 */
class SimpleTestVersion {
     /****
     * @Id
     * @generatedValue(strategy="AUTO")
     * @Column(type="integer")
     */
    private $id;
    /****
     * @Column(type="integer")
     */
    private $version;
    /****
     * @Column
     */
    private $content;

    //private $original;

    public function **construct (SimpleTest $original) {
        $this->version = $original->getVersion();
        $this->content = $original->getContent();
    }
}

Same invocation code as in my last comment. Maybe Doctrine2 becomes confused about another persist call during a flush() operation? I'll file another bug about that issue, since I believe Doctrine2 should either throw an Exception or do the persist with an additional flush.

@doctrinebot commented on GitHub (Nov 10, 2010): Comment created by felicitus: It seems that either I have a major misunderstanding of how Doctrine2 works, or there is a big bug. I've rewritten the classes above to avoid relations. What I wish to achieve is that every time SimpleTest gets persisted or updated, a new SimpleTestVersion is created. Doctrine2 creates a new SimpleTestVersion for the first time I call persist() then flush(), but not for subsequent calls to flush(). The logVersion() method is actually called. ``` /**** * @Entity * @HasLifeCycleCallbacks */ class SimpleTest { /**** * @Id * @generatedValue(strategy="AUTO") * @Column(type="integer") */ private $id; /**** * @Column(type="integer") * @version */ private $version = 1; // We need to set this, because we create our copy before persist, and version would be null then. /**** * @Column */ private $content; public function setContent ($content) { $this->content = $content; } public function getContent () { return $this->content; } public function getVersion () { return $this->version; } public function getId () { return $this->id; } /**** * @PrePersist * @PreUpdate */ public function logVersion() { echo sprintf("Creating a new version for ID %s, version %s\n", $this->getId(), $this->getVersion()); $entity = new SimpleTestVersion($this); // don't be confused by Netraver::getEM(), this is the $em. \de\netraver\Netraver::getEM()->persist($entity); } } ``` ``` /**** * @Entity */ class SimpleTestVersion { /**** * @Id * @generatedValue(strategy="AUTO") * @Column(type="integer") */ private $id; /**** * @Column(type="integer") */ private $version; /**** * @Column */ private $content; //private $original; public function **construct (SimpleTest $original) { $this->version = $original->getVersion(); $this->content = $original->getContent(); } } ``` Same invocation code as in my last comment. Maybe Doctrine2 becomes confused about another persist call during a flush() operation? I'll file another bug about that issue, since I believe Doctrine2 should either throw an Exception or do the persist with an additional flush.
Author
Owner

@doctrinebot commented on GitHub (Nov 10, 2010):

Comment created by @beberlei:

adding new associations in lifecycle events prepersist is not allowed, please see the onFlush event.

@doctrinebot commented on GitHub (Nov 10, 2010): Comment created by @beberlei: adding new associations in lifecycle events prepersist is not allowed, please see the onFlush event.
Author
Owner

@doctrinebot commented on GitHub (Nov 10, 2010):

Issue was closed with resolution "Invalid"

@doctrinebot commented on GitHub (Nov 10, 2010): Issue was closed with resolution "Invalid"
Author
Owner

@doctrinebot commented on GitHub (Nov 10, 2010):

Comment created by felicitus:

That enlightens the problem a bit. Maybe you should add a notice to your post on http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell stating that this is outdated. On the other hand, shouldn't Doctrine2 complain if you attempt to persist something during events?

@doctrinebot commented on GitHub (Nov 10, 2010): Comment created by felicitus: That enlightens the problem a bit. Maybe you should add a notice to your post on http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell stating that this is outdated. On the other hand, shouldn't Doctrine2 complain if you attempt to persist something during events?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#1079