DDC-436: Foreign keys on primary keys and vice versa #544

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

Originally created by @doctrinebot on GitHub (Mar 17, 2010).

Jira issue originally created by user romanb:

It was recently brought to our attention that foreign keys on primary keys and vice versa dont seem to work very well yet. This may be related to the problem of "mapping foreign keys to object properties" and also related to composite keys. We should make some tests with a schema similar to the following:

create table company ( id int not null auto_increment, primary key(id));
create table company*sponsorship ( company_id int not null, start_date datetime, primary key(company_id), foreign key company*id references company(id));
Originally created by @doctrinebot on GitHub (Mar 17, 2010). Jira issue originally created by user romanb: It was recently brought to our attention that foreign keys on primary keys and vice versa dont seem to work very well yet. This may be related to the problem of "mapping foreign keys to object properties" and also related to composite keys. We should make some tests with a schema similar to the following: ``` create table company ( id int not null auto_increment, primary key(id)); create table company*sponsorship ( company_id int not null, start_date datetime, primary key(company_id), foreign key company*id references company(id)); ```
admin added the Improvement label 2026-01-22 12:41:59 +01:00
admin closed this issue 2026-01-22 12:42:00 +01:00
Author
Owner

@doctrinebot commented on GitHub (Mar 17, 2010):

@doctrinebot commented on GitHub (Mar 17, 2010): - relates to [DDC-542: ManyToMany with Properties sharing Join columns with PRimary Key](http://www.doctrine-project.org/jira/browse/DDC-542)
Author
Owner

@doctrinebot commented on GitHub (Mar 18, 2010):

Comment created by @beberlei:

@doctrinebot commented on GitHub (Mar 18, 2010): Comment created by @beberlei:
Author
Owner

@doctrinebot commented on GitHub (May 1, 2010):

Comment created by mjh_ca:

This is also related to DDC-117 (I don't seem to be able to mark it as such directly on the ticket).

A one-to-many/many-to-one association between 3 participating classes as is described in the docs ( at http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#many-to-many,-unidirectional ) presently requires an additional and unnecessary @Id column that is not participating as a @JoinColumn. For example, in theory the following should generate a proper mapping with everything needed to maintain the association:

/*** @Entity **/
class Movie
{
    /****
     * @Id @Column(type="integer")
     * @GeneratedValue
     */
    protected $id;

    /****
     * @OneToMany(targetEntity="MovieCast", mappedBy="movie")
     */
    protected $cast;

    // ...
}

/*** @Entity **/
class MovieCast
{
    /****
     * @Id
     * @ManyToOne(targetEntity="Movie", inversedBy="cast")
     * @JoinColumn(name="movie_id", referencedColumnName="id")
     */
    protected $movie;

    /****
     * @Id
     * @ManyToOne(targetEntity="Person", inversedBy="movies")
     * @JoinColumn(name="person_id", referencedColumnName="id")
     */
    protected $person;
}

/*** @Entity **/
class Person
{
    /****
     * @Id @Column(type="integer")
     * @GeneratedValue
     */
    protected $id;

    /****
     * @OneToMany(targetEntity="MovieCast", mappedBy="person")
     */
    protected $movies;

    // ...
}

However, SchemaTool doesn't like this. It seems to ignore the @Id notations in MovieCast and throws the following exception:

  [Doctrine\ORM\Mapping\MappingException]

  No identifier/primary key specified for Entity 'MovieCast'. Every Entity must have an identifier/primary key.

Adjusting the MovieCast entity to have a separate PK satisfies SchemaTool but unnecessarily introduces an extra column into the association table which shouldn't need to be there:

/*** @Entity **/
class MovieCast
{
    /****
     * @Id @Column(type="integer")
     * @GeneratedValue
     */
    protected $extraPK; // this shouldn't be necessary

    /****
     * @ManyToOne(targetEntity="Movie", inversedBy="cast")
     * @JoinColumn(name="movie_id", referencedColumnName="id")
     */
    protected $movie;

    /****
     * @ManyToOne(targetEntity="Person", inversedBy="movies")
     * @JoinColumn(name="person_id", referencedColumnName="id")
     */
    protected $person;
}
@doctrinebot commented on GitHub (May 1, 2010): Comment created by mjh_ca: This is also related to [DDC-117](http://www.doctrine-project.org/jira/browse/DDC-117) (I don't seem to be able to mark it as such directly on the ticket). A one-to-many/many-to-one association between 3 participating classes as is described in the docs ( at http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#many-to-many,-unidirectional ) presently requires an additional and unnecessary @Id column that is not participating as a @JoinColumn. For example, in theory the following should generate a proper mapping with everything needed to maintain the association: ``` /*** @Entity **/ class Movie { /**** * @Id @Column(type="integer") * @GeneratedValue */ protected $id; /**** * @OneToMany(targetEntity="MovieCast", mappedBy="movie") */ protected $cast; // ... } /*** @Entity **/ class MovieCast { /**** * @Id * @ManyToOne(targetEntity="Movie", inversedBy="cast") * @JoinColumn(name="movie_id", referencedColumnName="id") */ protected $movie; /**** * @Id * @ManyToOne(targetEntity="Person", inversedBy="movies") * @JoinColumn(name="person_id", referencedColumnName="id") */ protected $person; } /*** @Entity **/ class Person { /**** * @Id @Column(type="integer") * @GeneratedValue */ protected $id; /**** * @OneToMany(targetEntity="MovieCast", mappedBy="person") */ protected $movies; // ... } ``` However, SchemaTool doesn't like this. It seems to ignore the @Id notations in MovieCast and throws the following exception: ``` [Doctrine\ORM\Mapping\MappingException] No identifier/primary key specified for Entity 'MovieCast'. Every Entity must have an identifier/primary key. ``` Adjusting the MovieCast entity to have a separate PK satisfies SchemaTool but unnecessarily introduces an extra column into the association table which shouldn't need to be there: ``` /*** @Entity **/ class MovieCast { /**** * @Id @Column(type="integer") * @GeneratedValue */ protected $extraPK; // this shouldn't be necessary /**** * @ManyToOne(targetEntity="Movie", inversedBy="cast") * @JoinColumn(name="movie_id", referencedColumnName="id") */ protected $movie; /**** * @ManyToOne(targetEntity="Person", inversedBy="movies") * @JoinColumn(name="person_id", referencedColumnName="id") */ protected $person; } ```
Author
Owner

@doctrinebot commented on GitHub (May 1, 2010):

Comment created by romanb:

@Marc: Yes, we are aware of that but composite keys are really such a pain to implement properly and also usually a pain to work with in the object model. We will work towards fixing this but we also think it is OK and only a small compromise to give the MovieCast its own (surrogate) ID, putting a unqiue constraint on (movie_id, person_id).

@doctrinebot commented on GitHub (May 1, 2010): Comment created by romanb: @Marc: Yes, we are aware of that but composite keys are really such a pain to implement properly and also usually a pain to work with in the object model. We will work towards fixing this but we also think it is OK and only a small compromise to give the MovieCast its own (surrogate) ID, putting a unqiue constraint on (movie_id, person_id).
Author
Owner

@doctrinebot commented on GitHub (May 20, 2010):

Comment created by jantichy:

Hi Roman, the original problem from the begining of this thread doesn't consider composite keys. So I try to reformulate the core problem to (as simple as possible) example now.

Let's have two simple entities - Foo and Bar - with 1:1 bidirectional relationship. Database definition:

CREATE TABLE foo (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY);
CREATE TABLE bar (foo_id INTEGER NOT NULL PRIMARY KEY REFERENCES foo (id));

And corresponding entities:

/*** @Entity **/
class Foo
{
    /****
     * @Id @Column(type="integer")
     * @GeneratedValue
     */
    private $id;

    /****
     * @OneToOne(targetEntity="Bar", mappedBy="foo")
     */
    private $bar;

    public function setBar(Bar $bar)
    {
        $this->bar = $bar;
    }
}

/*** @Entity **/
class Bar
{
    /****
     * @Id
     * @OneToOne(targetEntity="Foo", inversedBy="bar")
     * @JoinColumn(name="foo_id", referencedColumnName="id")
     */
    private $foo;

    public function setFoo(Foo $foo)
    {
        $this->foo = $foo;
    }
}

Any attempt to create and store any Bar instance fails on entity manager flush() with:

bq. Doctrine\ORM\Mapping\MappingException: No identifier/primary key specified for Entity 'Bar'. Every Entity must have an identifier/primary key.

Correct behavior of UnitOfWork should:

assign Foo the new generated ID,

save Foo to the database,

set Foo's new id to Bar as Bar's primary key,

save Bar to the database.

Corresponding test follows:

class FoobarTest extends TestCase
{
    public function testFoobar()
    {
        $foo = new Foo;
        $bar = new Bar;

        $foo->setBar($bar);
        $bar->setFoo($foo);

        $this->getEntityManager()->persist($foo);
        $this->getEntityManager()->persist($bar);

        $this->getEntityManager()->flush();
    }
}
@doctrinebot commented on GitHub (May 20, 2010): Comment created by jantichy: Hi Roman, the original problem from the begining of this thread doesn't consider composite keys. So I try to reformulate the core problem to (as simple as possible) example now. Let's have two simple entities - Foo and Bar - with 1:1 bidirectional relationship. Database definition: ``` CREATE TABLE foo (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY); CREATE TABLE bar (foo_id INTEGER NOT NULL PRIMARY KEY REFERENCES foo (id)); ``` And corresponding entities: ``` /*** @Entity **/ class Foo { /**** * @Id @Column(type="integer") * @GeneratedValue */ private $id; /**** * @OneToOne(targetEntity="Bar", mappedBy="foo") */ private $bar; public function setBar(Bar $bar) { $this->bar = $bar; } } /*** @Entity **/ class Bar { /**** * @Id * @OneToOne(targetEntity="Foo", inversedBy="bar") * @JoinColumn(name="foo_id", referencedColumnName="id") */ private $foo; public function setFoo(Foo $foo) { $this->foo = $foo; } } ``` Any attempt to create and store any Bar instance fails on entity manager flush() with: bq. Doctrine\ORM\Mapping\MappingException: No identifier/primary key specified for Entity 'Bar'. Every Entity must have an identifier/primary key. Correct behavior of UnitOfWork should: # assign Foo the new generated ID, # save Foo to the database, # set Foo's new id to Bar as Bar's primary key, # save Bar to the database. Corresponding test follows: ``` class FoobarTest extends TestCase { public function testFoobar() { $foo = new Foo; $bar = new Bar; $foo->setBar($bar); $bar->setFoo($foo); $this->getEntityManager()->persist($foo); $this->getEntityManager()->persist($bar); $this->getEntityManager()->flush(); } } ```
Author
Owner

@doctrinebot commented on GitHub (Jun 29, 2010):

Comment created by romanb:

Here is how something like that is mapped with JPA, just for the sake of completeness, I think it is intuitive.

@Entity
class MedicalHistory implements Serializable {
  @Id @OneToOne
  @JoinColumn(name = "person_id")
  Person patient;
}

@Entity
public class Person implements Serializable {
  @Id @GeneratedValue Integer id;
}
@doctrinebot commented on GitHub (Jun 29, 2010): Comment created by romanb: Here is how something like that is mapped with JPA, just for the sake of completeness, I think it is intuitive. ``` @Entity class MedicalHistory implements Serializable { @Id @OneToOne @JoinColumn(name = "person_id") Person patient; } @Entity public class Person implements Serializable { @Id @GeneratedValue Integer id; } ```
Author
Owner

@doctrinebot commented on GitHub (Aug 8, 2010):

Comment created by romanb:

For what its worth, after the recent patches it is now possible to map foreign keys to properties separately as a workaround for many such issues. Not pretty but useful.

@doctrinebot commented on GitHub (Aug 8, 2010): Comment created by romanb: For what its worth, after the recent patches it is now possible to map foreign keys to properties separately as a workaround for many such issues. Not pretty but useful.
Author
Owner

@doctrinebot commented on GitHub (Aug 8, 2010):

Comment created by romanb:

Also, just in case anyone watching this issue did not see this yet, we have experiments going on for improved support for such cases that might end up in a 2.x release: http://www.doctrine-project.org/blog/dc2-experimental-associations-id-fields

@doctrinebot commented on GitHub (Aug 8, 2010): Comment created by romanb: Also, just in case anyone watching this issue did not see this yet, we have experiments going on for improved support for such cases that might end up in a 2.x release: http://www.doctrine-project.org/blog/dc2-experimental-associations-id-fields
Author
Owner

@doctrinebot commented on GitHub (Dec 16, 2010):

Comment created by mueller-timo:

BasicEntityPersister.php Line 960. Statement $columns = array_unique($columns); does not always work correct. In some cases the order of the values and the order of the columns are unequal. Do you need an example?

@doctrinebot commented on GitHub (Dec 16, 2010): Comment created by mueller-timo: BasicEntityPersister.php Line 960. Statement $columns = array_unique($columns); does not always work correct. In some cases the order of the values and the order of the columns are unequal. Do you need an example?
Author
Owner

@doctrinebot commented on GitHub (Feb 5, 2011):

Comment created by @beberlei:

Ficed in DDC-117 already

@doctrinebot commented on GitHub (Feb 5, 2011): Comment created by @beberlei: Ficed in [DDC-117](http://www.doctrine-project.org/jira/browse/DDC-117) already
Author
Owner

@doctrinebot commented on GitHub (Feb 5, 2011):

Issue was closed with resolution "Duplicate"

@doctrinebot commented on GitHub (Feb 5, 2011): Issue was closed with resolution "Duplicate"
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#544