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

Created on 5 Nov 2016  Â·  8Comments  Â·  Source: doctrine/orm

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;
}

```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.

Bug Invalid

Most helpful comment

@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]));
}

All 8 comments

@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.

@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;
    }
}

Perfect, thanks!

On 5 Nov 2016 17:39, "Dmitry Pozdeiev" [email protected] wrote:

@Ocramius https://github.com/Ocramius, you mean this?
DDC6117Test.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
.

This issue still missing tests?

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

@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]));
}

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

@lcobucci thanks a lot!

Was this page helpful?
0 / 5 - 0 ratings