DDC-168: serialization/unserialization of ClassMetadata lose reflFields order causing insertSql statement to fail #208

Closed
opened 2026-01-22 12:30:39 +01:00 by admin · 11 comments
Owner

Originally created by @doctrinebot on GitHub (Nov 20, 2009).

Jira issue originally created by user rickdt:

This problem will be difficult to reproduce for you, but I isolated the exact cause of it.

To reproduce

  • Use any MetadataCache
  • Create a complex entity with some inheritance. (I may detail in later if your really need it)
  • Create an element of this entity with persist.
  • Create an other element of this entity but not on the same php run. (ex: reload the page)
  • The insert statement will fail because field names will not match parameters.

I narrowed it down to a serialization issue.

When you serialize an MetadataInfo, you don't serialize reflFields.
When you unserizlize you regenerate this value using ReflectionClass
That sound's like a good optimization.

The problem is that somehow, the reflection class can return the fields not in the same order that they were at the initial creation of the MetadataInfo.

Because the insertSql value is not re-generated, it can be out of sync with the new fields order.

The solution :
Call something like MetadataInfoFactory::_generateStaticSql (whish is private but ...) at wakeup time.

Originally created by @doctrinebot on GitHub (Nov 20, 2009). Jira issue originally created by user rickdt: This problem will be difficult to reproduce for you, but I isolated the exact cause of it. To reproduce - Use any MetadataCache - Create a complex entity with some inheritance. (I may detail in later if your really need it) - Create an element of this entity with persist. - Create an other element of this entity but not on the same php run. (ex: reload the page) - The insert statement will fail because field names will not match parameters. I narrowed it down to a serialization issue. When you serialize an MetadataInfo, you don't serialize reflFields. When you unserizlize you regenerate this value using ReflectionClass That sound's like a good optimization. The problem is that somehow, the reflection class can return the fields not in the same order that they were at the initial creation of the MetadataInfo. Because the insertSql value is not re-generated, it can be out of sync with the new fields order. The solution : Call something like MetadataInfoFactory::_generateStaticSql (whish is private but ...) at wakeup time.
admin added the Bug label 2026-01-22 12:30:39 +01:00
admin closed this issue 2026-01-22 12:30:40 +01:00
Author
Owner

@doctrinebot commented on GitHub (Nov 21, 2009):

Comment created by romanb:

Are you using composite keys anywhere?

The fact that reflFields is not serialized is not really an optimization but rather due to the fact that they simply can can not be serialized/unserialized properly.

We surely need some more concrete test case in order to reproduce this.

@doctrinebot commented on GitHub (Nov 21, 2009): Comment created by romanb: Are you using composite keys anywhere? The fact that reflFields is not serialized is not really an optimization but rather due to the fact that they simply can can not be serialized/unserialized properly. We surely need some more concrete test case in order to reproduce this.
Author
Owner

@doctrinebot commented on GitHub (Nov 23, 2009):

Comment created by rickdt:

I do not uses composite key anyware.

The field who change place is a foreign key.

EntityRevision extends Entity
EntityRevision.parent_entity : manyToOne entity

(the field parent_entity_id changes place during serialization)

What do you need to reproduce the case ? Just entitiy classes? a completely fuctionnal project? some kind of unit test ?

@doctrinebot commented on GitHub (Nov 23, 2009): Comment created by rickdt: I do not uses composite key anyware. The field who change place is a foreign key. EntityRevision extends Entity EntityRevision.parent_entity : manyToOne entity (the field parent_entity_id changes place during serialization) What do you need to reproduce the case ? Just entitiy classes? a completely fuctionnal project? some kind of unit test ?
Author
Owner

@doctrinebot commented on GitHub (Dec 5, 2009):

Comment created by romanb:

Hm. the order shouldnt even matter because insertSql only contains placeholders. Can you show the SQL INSERT that is failing? With parameters, if possible, and error message.

@doctrinebot commented on GitHub (Dec 5, 2009): Comment created by romanb: Hm. the order shouldnt even matter because insertSql only contains placeholders. Can you show the SQL INSERT that is failing? With parameters, if possible, and error message.
Author
Owner

@doctrinebot commented on GitHub (Dec 6, 2009):

Comment created by rickdt:

For what I can remember, parameters are not bound in the right order which is causing an error like "field x could not be null"

INSERT INTO table (a, b) VALUES (?, ?)
bind : $b, $a

I will post the real query on monday morning when I get back to work.

@doctrinebot commented on GitHub (Dec 6, 2009): Comment created by rickdt: For what I can remember, parameters are not bound in the right order which is causing an error like "field x could not be null" ``` INSERT INTO table (a, b) VALUES (?, ?) bind : $b, $a ``` I will post the real query on monday morning when I get back to work.
Author
Owner

@doctrinebot commented on GitHub (Dec 7, 2009):

Comment created by rickdt:

Here is the real world example. I put the statement, the parameters, the error, and all the related entities.

INSERT INTO fna*client_revision (fna_parent_record_id, record_id, display_name, last_name, maiden_name, first_name, civility, gender, birthdate, employer, employment, employment_from, civil_status, civil_status_from, insurability, fna_client_fna_id, fna_spouse_fna_id, rev_no, created_at, modified_at, created_by, modified*by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

Array
(
    [1] => 1
    [2] => George Simard2
    [3] => Simard2
    [4] =>
    [5] => George
    [6] =>
    [7] => MALE
    [8] => 1962-09-14
    [9] => Plomberie ABC Inc.
    [10] => Plombier
    [11] =>
    [12] => MARRIED
    [13] =>
    [14] => INSURABLE
    [15] => 5
    [16] => 2009-12-07 08:54:46
    [17] => 2009-12-07 08:54:46
    [18] =>  Eric Durand-Tremblay
    [19] =>  Eric Durand-Tremblay
    [20] => 1
    [21] =>
    [22] =>
)

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'employment' cannot be null
namespace ORM\BaseEntity;

/****
 * @MappedSuperclass
 */
class Entity
{
    private static $boolean_values = array("YES", 'NO');

     /****
     * @Column(name="id", type="integer")
     * @Id
     * @GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /****
     * @Column(name="created_at", type="datetime")
     */
    protected $created_at;

    /****
     * @Column(name="modified_at", type="datetime")
     */
    protected $modified_at;


     /****
     * @Column(name="created_by", type="string")
     */
    protected $created_by="";

    /****
     * @Column(name="modified_by", type="string")
     */
    protected $modified_by="";
}

/****
 * @MappedSuperclass
 * @HasLifecycleCallbacks
 */
class Revisionable extends ORM\BaseEntity\Entity
{

     /****
     * @Column(name="rev_no", type="integer")
     */
    protected $revision=1;
}


namespace ORM\BaseEntity;

/****
 * @MappedSuperclass
 * @HasLifecycleCallbacks
 */
class Client extends Revisionable
{

    /****
     * @Column(name="display_name", type="string")
     */
    protected $display_name;

    /****
     * @Column(name="last_name", type="string")
     */
    protected $last_name="";

    /****
     * @Column(name="maiden_name", type="string")
     */
    protected $maiden_name="";

    /****
     * @Column(name="first_name", type="string")
     */
    protected $first_name="";

    /****
     * @Column(name="civility", type="string")
     */
    protected $civility="";

    /****
     * @Column(name="gender", type="string", length="25")
     */
    protected $gender="";

    /****
     * @Column(name="birthdate", type="date", nullable="true")
     */
    protected $birthdate;

    /****
     * @Column(name="employer", type="string")
     */
    protected $employer="";

    /****
     * @Column(name="employment", type="string")
     */
    protected $employment="";

    /****
     * @Column(name="employment_from", type="date", nullable="true")
     */
    protected $employment_from;

    /****
     * @Column(name="civil_status", type="string", length="25")
     */
    protected $civil_status="";

    /****
     * @Column(name="civil*status*from", type="date", nullable="true")
     */
    protected $civil*status*from;

    /****
     * @Column(name="insurability", type="string", length="25")
     */
    protected $insurability="";


    /****
     * @OneToOne(targetEntity="ORM\Entity\FNA")
     * @JoinColumns({
     *   @JoinColumn(name="fna*client_fna*id", referencedColumnName="id")
     * })
     */
    protected $client_fna;

    /****
     * @OneToOne(targetEntity="ORM\Entity\FNA")
     * @JoinColumns({
     *   @JoinColumn(name="fna*spouse_fna*id", referencedColumnName="id")
     * })
     */
    protected $spouse_fna;
}


namespace ORM\RevisionEntity;

/****
 * @Entity
 * @Table(name="fna*client_revision", indexes={@index(name="idx_record_id", columns={"record*id"})})
 * @HasLifecycleCallbacks
 */
class Client extends ORM\BaseEntity\Client{

     /****
     * @ManyToOne(targetEntity="Kronos\FNA\ORM\Entity\Client")
     * @JoinColumns({
     *   @JoinColumn(name="fna*parent_record*id", referencedColumnName="id", onDelete="SET NULL")
     * })
     */
    protected $parent_record;  /// HERE IS THE  FIELD WHO CHANGE PLACE AFTER SERIALIZATION !!!!!!!!!!!!!!!!!!!!!!

    /****
     * 
     * @Column(name="record_id", type="int")
     */
    protected $record_id;
}

@doctrinebot commented on GitHub (Dec 7, 2009): Comment created by rickdt: Here is the real world example. I put the statement, the parameters, the error, and all the related entities. ``` INSERT INTO fna*client_revision (fna_parent_record_id, record_id, display_name, last_name, maiden_name, first_name, civility, gender, birthdate, employer, employment, employment_from, civil_status, civil_status_from, insurability, fna_client_fna_id, fna_spouse_fna_id, rev_no, created_at, modified_at, created_by, modified*by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) Array ( [1] => 1 [2] => George Simard2 [3] => Simard2 [4] => [5] => George [6] => [7] => MALE [8] => 1962-09-14 [9] => Plomberie ABC Inc. [10] => Plombier [11] => [12] => MARRIED [13] => [14] => INSURABLE [15] => 5 [16] => 2009-12-07 08:54:46 [17] => 2009-12-07 08:54:46 [18] => Eric Durand-Tremblay [19] => Eric Durand-Tremblay [20] => 1 [21] => [22] => ) SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'employment' cannot be null ``` ``` namespace ORM\BaseEntity; /**** * @MappedSuperclass */ class Entity { private static $boolean_values = array("YES", 'NO'); /**** * @Column(name="id", type="integer") * @Id * @GeneratedValue(strategy="AUTO") */ protected $id; /**** * @Column(name="created_at", type="datetime") */ protected $created_at; /**** * @Column(name="modified_at", type="datetime") */ protected $modified_at; /**** * @Column(name="created_by", type="string") */ protected $created_by=""; /**** * @Column(name="modified_by", type="string") */ protected $modified_by=""; } /**** * @MappedSuperclass * @HasLifecycleCallbacks */ class Revisionable extends ORM\BaseEntity\Entity { /**** * @Column(name="rev_no", type="integer") */ protected $revision=1; } namespace ORM\BaseEntity; /**** * @MappedSuperclass * @HasLifecycleCallbacks */ class Client extends Revisionable { /**** * @Column(name="display_name", type="string") */ protected $display_name; /**** * @Column(name="last_name", type="string") */ protected $last_name=""; /**** * @Column(name="maiden_name", type="string") */ protected $maiden_name=""; /**** * @Column(name="first_name", type="string") */ protected $first_name=""; /**** * @Column(name="civility", type="string") */ protected $civility=""; /**** * @Column(name="gender", type="string", length="25") */ protected $gender=""; /**** * @Column(name="birthdate", type="date", nullable="true") */ protected $birthdate; /**** * @Column(name="employer", type="string") */ protected $employer=""; /**** * @Column(name="employment", type="string") */ protected $employment=""; /**** * @Column(name="employment_from", type="date", nullable="true") */ protected $employment_from; /**** * @Column(name="civil_status", type="string", length="25") */ protected $civil_status=""; /**** * @Column(name="civil*status*from", type="date", nullable="true") */ protected $civil*status*from; /**** * @Column(name="insurability", type="string", length="25") */ protected $insurability=""; /**** * @OneToOne(targetEntity="ORM\Entity\FNA") * @JoinColumns({ * @JoinColumn(name="fna*client_fna*id", referencedColumnName="id") * }) */ protected $client_fna; /**** * @OneToOne(targetEntity="ORM\Entity\FNA") * @JoinColumns({ * @JoinColumn(name="fna*spouse_fna*id", referencedColumnName="id") * }) */ protected $spouse_fna; } namespace ORM\RevisionEntity; /**** * @Entity * @Table(name="fna*client_revision", indexes={@index(name="idx_record_id", columns={"record*id"})}) * @HasLifecycleCallbacks */ class Client extends ORM\BaseEntity\Client{ /**** * @ManyToOne(targetEntity="Kronos\FNA\ORM\Entity\Client") * @JoinColumns({ * @JoinColumn(name="fna*parent_record*id", referencedColumnName="id", onDelete="SET NULL") * }) */ protected $parent_record; /// HERE IS THE FIELD WHO CHANGE PLACE AFTER SERIALIZATION !!!!!!!!!!!!!!!!!!!!!! /**** * * @Column(name="record_id", type="int") */ protected $record_id; } ```
Author
Owner

@doctrinebot commented on GitHub (Dec 7, 2009):

Comment created by rickdt:

    [1] => 1  (fna*parent_record*id)
(expected record_id)
    [2] => George Simard2 (display_name)
    [3] => Simard2 (last_name)
    [4] => (maiden_name)
    [5] => George (first_name)
    [6] => (civility)
    [7] => MALE (gender)
    [8] => 1962-09-14 (birthdate)
    [9] => Plomberie ABC Inc. (employer)
    [10] => Plombier (employment)
    [11] => (employment_from)
    [12] => MARRIED (civil_status)
    [13] => (civil*status*from)
    [14] => INSURABLE (insurability)
    [15] => 5 (rev_no)
    [16] => 2009-12-07 08:54:46 (created_at)
    [17] => 2009-12-07 08:54:46 (modified_at)
    [18] =>  Eric Durand-Tremblay (created_by)
    [19] =>  Eric Durand-Tremblay (modified_by)
    [20] => 1 (record_id) !! Should be in position  2
    [21] => (fna*client_fna*id)
    [22] => (fna*spouse_fna*id)

@doctrinebot commented on GitHub (Dec 7, 2009): Comment created by rickdt: ``` [1] => 1 (fna*parent_record*id) (expected record_id) [2] => George Simard2 (display_name) [3] => Simard2 (last_name) [4] => (maiden_name) [5] => George (first_name) [6] => (civility) [7] => MALE (gender) [8] => 1962-09-14 (birthdate) [9] => Plomberie ABC Inc. (employer) [10] => Plombier (employment) [11] => (employment_from) [12] => MARRIED (civil_status) [13] => (civil*status*from) [14] => INSURABLE (insurability) [15] => 5 (rev_no) [16] => 2009-12-07 08:54:46 (created_at) [17] => 2009-12-07 08:54:46 (modified_at) [18] => Eric Durand-Tremblay (created_by) [19] => Eric Durand-Tremblay (modified_by) [20] => 1 (record_id) !! Should be in position 2 [21] => (fna*client_fna*id) [22] => (fna*spouse_fna*id) ```
Author
Owner

@doctrinebot commented on GitHub (Dec 7, 2009):

Comment created by @beberlei:

I could reproduce the issue and generated a failing test for this. We already discussed how this might be fixed.

@doctrinebot commented on GitHub (Dec 7, 2009): Comment created by @beberlei: I could reproduce the issue and generated a failing test for this. We already discussed how this might be fixed.
Author
Owner

@doctrinebot commented on GitHub (Dec 7, 2009):

Comment created by rickdt:

This is a very good news !

@doctrinebot commented on GitHub (Dec 7, 2009): Comment created by rickdt: This is a very good news !
Author
Owner

@doctrinebot commented on GitHub (Dec 17, 2009):

Comment created by romanb:

Should be fixed now.

@doctrinebot commented on GitHub (Dec 17, 2009): Comment created by romanb: Should be fixed now.
Author
Owner

@doctrinebot commented on GitHub (Dec 17, 2009):

Issue was closed with resolution "Fixed"

@doctrinebot commented on GitHub (Dec 17, 2009): Issue was closed with resolution "Fixed"
Author
Owner

@doctrinebot commented on GitHub (Dec 17, 2009):

Comment created by rickdt:

Resolution confirmed.

Thank your for your great work !

@doctrinebot commented on GitHub (Dec 17, 2009): Comment created by rickdt: Resolution confirmed. Thank your for your great work !
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#208