ManyToOne two-columns (combined primary key) self-referencing association. Can't insert entry #5315

Closed
opened 2026-01-22 15:04:16 +01:00 by admin · 8 comments
Owner

Originally created by @pozdeiev on GitHub (Nov 4, 2016).

Originally assigned to: @lcobucci on GitHub.

PHP 5.6, Doctrine ORM 2.5.5.

Hello! I got trouble with the inserting/updating Entity which has OneToMany two-columns self-referencing association.

I have table Shop with shops and table Group with groups. Every shop has its own set of groups. Every group can has one parent group within the same shop.

Model:

<?php
/**
 * @Entity
 */
class Shop
{
/**
 * @Id @Column(type="integer") @GeneratedValue
 */
private $id;

/**
 * @Column(type="string")
 */
private $name;
}
/**
 * @Entity
 */
class Group
{
/**
 * @Id @ManyToOne(targetEntity="Shop")
 * @JoinColumn(name="shop",referencedColumnName="id")
 */
private $shop;

/**
 * @Id @Column(type="string")
 */
private $id;

/**
 * @ManyToOne(targetEntity="Group")
 * @JoinColumns
 * (
 *       @JoinColumn(name="shop",referencedColumnName="shop"),
 *       @JoinColumn(name="parent",referencedColumnName="id"),
 * )
 */
private $parent;

public function __construct(Shop $shop, $id, Group $parent = null)
{
        $this->shop = $shop;
        $this->id = $id;
        $this->parent = $parent;
}
}

Works fine when I insert with the $parent specified, but get error, if $parent=null (case of a root group):

<?php
$shop = $em->find("Shop", 1); // surely existing Shop
$group = new Group($shop, 'some_id', null);
$em->persist($group);
$em->flush();

An exception occurred while executing 'INSERT INTO Group (id, shop, parent) VALUES (?, ?, ?)' with params ["some_id", null, null]: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'shop' cannot be null

I can't understand what is wrong. Why shop=null? I'm sure that $shop is passing correct to the __construct() (checked it).

Same thing if I try to update an existing row by setting $parent=null - Doctrine 2 will try to set shop=null too.

Please help. Thanks in advance.

Originally created by @pozdeiev on GitHub (Nov 4, 2016). Originally assigned to: @lcobucci on GitHub. PHP 5.6, Doctrine ORM 2.5.5. Hello! I got trouble with the inserting/updating Entity which has OneToMany two-columns self-referencing association. I have table `Shop` with shops and table `Group` with groups. Every shop has its own set of groups. Every group can has one parent group within the same shop. Model: ```php <?php /** * @Entity */ class Shop { /** * @Id @Column(type="integer") @GeneratedValue */ private $id; /** * @Column(type="string") */ private $name; } ``` ```php /** * @Entity */ class Group { /** * @Id @ManyToOne(targetEntity="Shop") * @JoinColumn(name="shop",referencedColumnName="id") */ private $shop; /** * @Id @Column(type="string") */ private $id; /** * @ManyToOne(targetEntity="Group") * @JoinColumns * ( * @JoinColumn(name="shop",referencedColumnName="shop"), * @JoinColumn(name="parent",referencedColumnName="id"), * ) */ private $parent; public function __construct(Shop $shop, $id, Group $parent = null) { $this->shop = $shop; $this->id = $id; $this->parent = $parent; } } ``` Works fine when I insert with the `$parent` specified, but get error, if `$parent=null` (case of a root group): ```php <?php $shop = $em->find("Shop", 1); // surely existing Shop $group = new Group($shop, 'some_id', null); $em->persist($group); $em->flush(); ``` > An exception occurred while executing 'INSERT INTO `Group` (id, shop, parent) VALUES (?, ?, ?)' with params ["some_id", null, null]: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'shop' cannot be null I can't understand what is wrong. Why `shop=null`? I'm sure that `$shop` is passing correct to the `__construct()` (checked it). Same thing if I try to update an existing row by setting `$parent=null` - Doctrine 2 will try to set `shop=null` too. Please help. Thanks in advance.
admin added the BugInvalid labels 2026-01-22 15:04:16 +01:00
admin closed this issue 2026-01-22 15:04:18 +01:00
Author
Owner

@Ocramius commented on GitHub (Nov 5, 2016):

@mittahru weird edge case, but can you eventually abstract it into a test case? See d3f6c5ec70/tests/Doctrine/Tests/ORM/Functional/Ticket for examples.

@Ocramius commented on GitHub (Nov 5, 2016): @mittahru weird edge case, but can you eventually abstract it into a test case? See https://github.com/doctrine/doctrine2/tree/d3f6c5ec70aac4b029a4b61ecf1e2ba61a1a4a6d/tests/Doctrine/Tests/ORM/Functional/Ticket for examples.
Author
Owner

@pozdeiev commented on GitHub (Nov 5, 2016):

@Ocramius, you mean this?
DDC6117Test.php

<?php

namespace Doctrine\Tests\ORM\Functional\Ticket;

class DDC6117Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
    public function setUp()
    {
        parent::setUp();
        parent::setUpEntitySchema(array
        (
            __NAMESPACE__ . '\DDC6117Shop',
            __NAMESPACE__ . '\DDC6117Group',
        ));
    }
    /**
     * @group DDC-6117
     */
    public function addRootGroup()
    {
        $shop = new DDC6117Shop();
        $this->_em->persist($shop);
        $this->_em->flush();
        var_dump($shop->getId());
        $group = new DDC6117Group($shop, 'test-group-id', null);
        $this->_em->persist($group);
        $this->_em->flush();
    }
}

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

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

/**
 * @Entity
 * @Table(name="DDC6117Group")
 */
class DDC6117Group
{
    /**
     * @Id @ManyToOne(targetEntity="DDC6117Shop")
     * @JoinColumn(name="shop",referencedColumnName="id")
     */
    private $shop;

    /**
     * @Id @Column(type="string")
     */
    private $id;

    /**
     * @ManyToOne(targetEntity="DDC6117Group")
     * @JoinColumns
     * (
     *  @JoinColumn(name="shop",referencedColumnName="shop"),
     *  @JoinColumn(name="parent",referencedColumnName="id"),
     * )
     */
    private $parent;

    public function __construct(DDC6117Shop $shop, $id, DDC6117Group $parent = null)
    {
        $this->shop = $shop;
        $this->id = $id;
        $this->parent = $parent;
    }
}
@pozdeiev commented on GitHub (Nov 5, 2016): @Ocramius, you mean this? `DDC6117Test.php` ``` <?php namespace Doctrine\Tests\ORM\Functional\Ticket; class DDC6117Test extends \Doctrine\Tests\OrmFunctionalTestCase { public function setUp() { parent::setUp(); parent::setUpEntitySchema(array ( __NAMESPACE__ . '\DDC6117Shop', __NAMESPACE__ . '\DDC6117Group', )); } /** * @group DDC-6117 */ public function addRootGroup() { $shop = new DDC6117Shop(); $this->_em->persist($shop); $this->_em->flush(); var_dump($shop->getId()); $group = new DDC6117Group($shop, 'test-group-id', null); $this->_em->persist($group); $this->_em->flush(); } } /** * @Entity * @Table(name="DDC6117Shop") */ class DDC6117Shop { /** * @Id @Column(type="integer") @GeneratedValue */ private $id; public function getId() { return $this->id; } } /** * @Entity * @Table(name="DDC6117Group") */ class DDC6117Group { /** * @Id @ManyToOne(targetEntity="DDC6117Shop") * @JoinColumn(name="shop",referencedColumnName="id") */ private $shop; /** * @Id @Column(type="string") */ private $id; /** * @ManyToOne(targetEntity="DDC6117Group") * @JoinColumns * ( * @JoinColumn(name="shop",referencedColumnName="shop"), * @JoinColumn(name="parent",referencedColumnName="id"), * ) */ private $parent; public function __construct(DDC6117Shop $shop, $id, DDC6117Group $parent = null) { $this->shop = $shop; $this->id = $id; $this->parent = $parent; } } ```
Author
Owner

@Ocramius commented on GitHub (Nov 6, 2016):

Perfect, thanks!

On 5 Nov 2016 17:39, "Dmitry Pozdeiev" notifications@github.com wrote:

@Ocramius https://github.com/Ocramius, you mean this?
DDC6117Test.php

<?php namespace Doctrine\Tests\ORM\Functional\Ticket; class DDC6117Test extends \Doctrine\Tests\OrmFunctionalTestCase { public function setUp() { parent::setUp(); parent::setUpEntitySchema(array ( **NAMESPACE** . '\DDC6117Shop', **NAMESPACE** . '\DDC6117Group', )); } /** \* @group DDC-6117 */ public function addRootGroup() { $shop = new DDC6117Shop(); $this->_em->persist($shop); $this->_em->flush(); var_dump($shop->getId()); $group = new DDC6117Group($shop, 'test-group-id', null); $this->_em->persist($group); $this->_em->flush(); } } /** - @Entity - @Table(name="DDC6117Shop") _/ class DDC6117Shop { /_* - @Id @Column(type="integer") @GeneratedValue */ private $id; public function getId() { return $this->id; } } /** - @Entity - @Table(name="DDC6117Group") _/ class DDC6117Group { /_* - @Id @ManyToOne(targetEntity="DDC6117Shop") - @JoinColumn(name="shop",referencedColumnName="id") */ private $shop; /** - @Id @Column(type="string") */ private $id; /** - @ManyToOne(targetEntity="DDC6117Group") - @JoinColumns - ( - @JoinColumn(name="shop",referencedColumnName="shop"), - @JoinColumn(name="parent",referencedColumnName="id"), - ) */ private $parent; public function __construct(DDC6117Shop $shop, $id, DDC6117Group $parent = null) { $this->shop = $shop; $this->id = $id; $this->parent = $parent; } } — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/doctrine/doctrine2/issues/6117#issuecomment-258623237, or mute the thread https://github.com/notifications/unsubscribe-auth/AAJakACHrQdMZfDz--5cel0kF_U3RO_aks5q7LFYgaJpZM4KqE3I .
@Ocramius commented on GitHub (Nov 6, 2016): Perfect, thanks! On 5 Nov 2016 17:39, "Dmitry Pozdeiev" notifications@github.com wrote: > @Ocramius https://github.com/Ocramius, you mean this? > DDC6117Test.php > > <?php > > namespace Doctrine\Tests\ORM\Functional\Ticket; > > class DDC6117Test extends \Doctrine\Tests\OrmFunctionalTestCase > { > public function setUp() > { > parent::setUp(); > parent::setUpEntitySchema(array > ( > **NAMESPACE** . '\DDC6117Shop', > **NAMESPACE** . '\DDC6117Group', > )); > } > /** > \* @group DDC-6117 > */ > public function addRootGroup() > { > $shop = new DDC6117Shop(); > $this->_em->persist($shop); > $this->_em->flush(); > var_dump($shop->getId()); > $group = new DDC6117Group($shop, 'test-group-id', null); > $this->_em->persist($group); > $this->_em->flush(); > } > } > > /** > - @Entity > - @Table(name="DDC6117Shop") > _/ > class DDC6117Shop > { > /_* > - @Id @Column(type="integer") @GeneratedValue > */ > private $id; > > public function getId() > { > return $this->id; > } > } > > /** > - @Entity > - @Table(name="DDC6117Group") > _/ > class DDC6117Group > { > /_* > - @Id @ManyToOne(targetEntity="DDC6117Shop") > - @JoinColumn(name="shop",referencedColumnName="id") > */ > private $shop; > > /** > - @Id @Column(type="string") > */ > private $id; > > /** > - @ManyToOne(targetEntity="DDC6117Group") > - @JoinColumns > - ( > - @JoinColumn(name="shop",referencedColumnName="shop"), > - @JoinColumn(name="parent",referencedColumnName="id"), > - ) > */ > private $parent; > > public function __construct(DDC6117Shop $shop, $id, DDC6117Group $parent = null) > { > $this->shop = $shop; > $this->id = $id; > $this->parent = $parent; > } > } > > — > You are receiving this because you were mentioned. > Reply to this email directly, view it on GitHub > https://github.com/doctrine/doctrine2/issues/6117#issuecomment-258623237, > or mute the thread > https://github.com/notifications/unsubscribe-auth/AAJakACHrQdMZfDz--5cel0kF_U3RO_aks5q7LFYgaJpZM4KqE3I > .
Author
Owner

@pozdeiev commented on GitHub (Sep 27, 2017):

This issue still missing tests?

@pozdeiev commented on GitHub (Sep 27, 2017): This issue still missing tests?
Author
Owner

@Ocramius commented on GitHub (Sep 27, 2017):

No, lemme remove the label. It wasn't picked up by anyone though.

@Ocramius commented on GitHub (Sep 27, 2017): No, lemme remove the label. It wasn't picked up by anyone though.
Author
Owner

@lcobucci commented on GitHub (Nov 26, 2017):

@mittahru the problem is happening because your mapping is incorrect... since $parent is a nullable field the join columns should also be nullable (and they should not use the same columns of the entity itself), like:


/**
 * @Entity
 */
class DDC6117Group
{
    /**
     * @Id @ManyToOne(targetEntity=DDC6117Shop::class)
     * @JoinColumn(name="shop",referencedColumnName="id")
     */
    public $shop;

    /**
     * @Id @Column(type="string")
     */
    public $id;

    /**
     * @ManyToOne(targetEntity=DDC6117Group::class)
     * @JoinColumns(
     *     {
     *          @JoinColumn(name="parent_shop", referencedColumnName="shop", nullable=true),
     *          @JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
     *      }
     * )
     */
    public $parent;

    public function __construct(DDC6117Shop $shop, string $id, DDC6117Group $parent = null)
    {
        $this->shop   = $shop;
        $this->id     = $id;
        $this->parent = $parent;
    }
}

With mapping above the test bellow passes with no issues:

/**
 * @group 6117
 */
public function testAddRootGroup() : void
{
    $shop   = new DDC6117Shop();
    $group  = new DDC6117Group($shop, 'test-group-id', null);
    $group2 = new DDC6117Group($shop, 'test-group2-id', $group);

    $this->_em->persist($shop);
    $this->_em->flush();

    $this->_em->persist($group);
    $this->_em->persist($group2);
    $this->_em->flush();

    self::assertSame($group2, $this->_em->getRepository(DDC6117Group::class)->findOneBy(['parent' => $group]));
}
@lcobucci commented on GitHub (Nov 26, 2017): @mittahru the problem is happening because your mapping is incorrect... since `$parent` is a nullable field the join columns should also be nullable (and they should not use the same columns of the entity itself), like: ```php /** * @Entity */ class DDC6117Group { /** * @Id @ManyToOne(targetEntity=DDC6117Shop::class) * @JoinColumn(name="shop",referencedColumnName="id") */ public $shop; /** * @Id @Column(type="string") */ public $id; /** * @ManyToOne(targetEntity=DDC6117Group::class) * @JoinColumns( * { * @JoinColumn(name="parent_shop", referencedColumnName="shop", nullable=true), * @JoinColumn(name="parent_id", referencedColumnName="id", nullable=true) * } * ) */ public $parent; public function __construct(DDC6117Shop $shop, string $id, DDC6117Group $parent = null) { $this->shop = $shop; $this->id = $id; $this->parent = $parent; } } ``` With mapping above the test bellow passes with no issues: ```php /** * @group 6117 */ public function testAddRootGroup() : void { $shop = new DDC6117Shop(); $group = new DDC6117Group($shop, 'test-group-id', null); $group2 = new DDC6117Group($shop, 'test-group2-id', $group); $this->_em->persist($shop); $this->_em->flush(); $this->_em->persist($group); $this->_em->persist($group2); $this->_em->flush(); self::assertSame($group2, $this->_em->getRepository(DDC6117Group::class)->findOneBy(['parent' => $group])); } ```
Author
Owner

@lcobucci commented on GitHub (Nov 26, 2017):

I'll close this ticket as Invalid due to the given explanation.

@lcobucci commented on GitHub (Nov 26, 2017): I'll close this ticket as `Invalid` due to the given explanation.
Author
Owner

@pozdeiev commented on GitHub (Nov 27, 2017):

@lcobucci thanks a lot!

@pozdeiev commented on GitHub (Nov 27, 2017): @lcobucci thanks a lot!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5315