Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DDC-3745: OneToOne identity through foreign entity exception on flush #4584

Closed
doctrinebot opened this issue May 22, 2015 · 14 comments
Closed
Assignees
Labels
Milestone

Comments

@doctrinebot
Copy link

Jira issue originally created by user Daveet:

Also asked at SO: https://stackoverflow.com/questions/30402203
I'm adding it here as well as an issue here, because I believe it just might be Doctrine's bug.


I have User and UserProfile OneToOne–related Doctrine ORM entities. They should always exist as a pair, there should be no User without UserProfile.

User should get its id from autoincrement, while UserProfile should have User's id. So they both should have the same id and there is no other column to set up the relationship (Doctrine docs: Identity through foreign Entities).

User's id is both a primary key (PK) and foreign key (FK) at the same time.

I managed to set it up, but it requires that User is saved first and only later UserProfile is created and saved in a separate step.

What I want is that UserProfile is always created with User, in the constructor, but if I do that, I get this exception:

Doctrine\ORM\ORMInvalidArgumentException: The given entity of type 'AppBundle\Entity\UserProfile' (AppBundle\Entity\UserProfile@0000000052e1b1eb00000000409c6f2c) has no identity/no id values set. It cannot be added to the identity map.

Please see code below – it works, but not the way I want. The php comments show what I want to achieve.

    /****
     * It works, both saving and loading.
     * BUT, it requires that I create and save UserProfile 
     * in a separate step than saving User step.
     */

    // create and save User
    $user = new User();
    $objectManager->persist($user);
    $objectManager->flush();

    // create and save UserProfile (this should be unnecessary)
    $user->createProfile()
    $objectManager->flush();
    use Doctrine\ORM\Mapping as ORM;

    /****
     * @ORM\Entity(repositoryClass="AppBundle\Entity\UserRepository")
     * @ORM\Table(name="users")
     */
    class User
    {
        /****
         * @var int
         *
         * @ORM\Column(name="uid", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;

        /****
         * It's NULL at first, I create it later (after saving User).
         * 
         * @var UserProfile|null
         *
         * @ORM\OneToOne(targetEntity="UserProfile", mappedBy="user", cascade="persist")
         */
        private $profile = null;

        public function **construct()
        {
            // I want to create UserProfile inside User's constructor,
            // so that it is always present (never NULL):
            //$this->profile = new UserProfile($this);

            // but this would give me error:
            //
            // Doctrine\ORM\ORMInvalidArgumentException: 
            // The given entity of type 'AppBundle\Entity\UserProfile' 
            // (AppBundle\Entity\UserProfile@0000000058af220a0000000079dc875a)
            // has no identity/no id values set. It cannot be added to the identity map.
        }

        public function createProfile()
        {
            $this->profile = new UserProfile($this);
        }   
    }
    use Doctrine\ORM\Mapping as ORM;

    /****
     * @ORM\Entity
     * @ORM\Table(name="profiles")
     */
    class UserProfile
    {
        /****
         * – UserProfile's "uid" column points to User's "uid" column
         * – it is PK (primary key)
         * - it is FK (foreign key) as well
         * – "owning side"
         *
         * @var User
         *
         * @ORM\Id
         * @ORM\OneToOne(targetEntity="User", inversedBy="profile")
         * @ORM\JoinColumn(name="uid", referencedColumnName="uid", nullable=false)
        */
        private $user;

        public function **construct(User $user)
        {
            $this->user = $user;
        }    
    }
@petrjirasek
Copy link

Hello,

I would like to ask you if is there any progress? I have the same problem.

Thank you.

@dm3ch
Copy link

dm3ch commented Oct 5, 2016

Hello, is there any progress with this bug? Is it fixed

@aaleksandrov
Copy link

Same issue for me. It there any fix or workaround?

@scarbo87
Copy link

scarbo87 commented Nov 1, 2016

+1

@nico-incubiq
Copy link

Same here.

@jorge07
Copy link

jorge07 commented Jan 27, 2017

any fix or workaround?

@lcobucci
Copy link
Member

lcobucci commented Jan 27, 2017

This was already fixed on #1570 when $weight was introduced on CommitOrderCalculator.

I've ran the test bellow on master and it passes with no issues, so I'm closing this issue.

<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\Tests\OrmFunctionalTestCase;

class xxxxTest extends OrmFunctionalTestCase
{
    protected function setUp()
    {
        parent::setUp();

        $this->_schemaTool->createSchema(
            [
                $this->_em->getClassMetadata(xxxxUser::class),
                $this->_em->getClassMetadata(xxxxUserProfile::class),
            ]
        );
    }

    public function testThingsWorks()
    {
        $user = new xxxxUser();
        $this->_em->persist($user);
        $this->_em->flush();
    }
}

/**
 * @Entity
 */
class xxxxUser
{
    /**
     * @Id
     * @Column(name="uid", type="integer")
     * @GeneratedValue(strategy="AUTO")
     *
     * @var int
     */
    private $id;

    /**
     * @var xxxxUserProfile|null
     *
     * @OneToOne(targetEntity=xxxxUserProfile::class, mappedBy="user", cascade="persist")
     */
    private $profile = null;

    public function __construct()
    {
        $this->profile = new xxxxUserProfile($this);
    }
}
/**
 * @Entity
 */
class xxxxUserProfile
{
    /**
     * – UserProfile's "uid" column points to User's "uid" column
     * – it is PK (primary key)
     * - it is FK (foreign key) as well
     * – "owning side"
     *
     * @var xxxxUser
     *
     * @Id
     * @OneToOne(targetEntity=xxxxUser::class, inversedBy="profile")
     * @JoinColumn(name="uid", referencedColumnName="uid", nullable=false)
     */
    private $user;

    public function __construct(xxxxUser $user)
    {
        $this->user = $user;
    }
}

On the PR we have the explanation why this wasn't backported on v2.5.x.

@igaponov
Copy link

igaponov commented May 22, 2017

I have the same issue
@lcobucci Your test is wrong, I've checked it on the last version and UserProfile is not actually saved after flush, it's not passed to UOW::scheduleForInsert() as well as to UOW::addToIdentityMap(), that's why there is no exception. But if you manually persist profile before flush you'll get this:

Doctrine\ORM\ORMInvalidArgumentException: The given entity of type 'Doctrine\Tests\ORM\Functional\Ticket\xxxxUserProfile' (Doctrine\Tests\ORM\Functional\Ticket\xxxxUserProfile@0000000036941110000000004e41ba5a) has no identity/no id values set. It cannot be added to the identity map.

/home/user/projects/lib/doctrine2/lib/Doctrine/ORM/ORMInvalidArgumentException.php:68
/home/user/projects/lib/doctrine2/lib/Doctrine/ORM/UnitOfWork.php:1402
/home/user/projects/lib/doctrine2/lib/Doctrine/ORM/UnitOfWork.php:1214
/home/user/projects/lib/doctrine2/lib/Doctrine/ORM/UnitOfWork.php:900
/home/user/projects/lib/doctrine2/lib/Doctrine/ORM/UnitOfWork.php:1664
/home/user/projects/lib/doctrine2/lib/Doctrine/ORM/UnitOfWork.php:1620
/home/user/projects/lib/doctrine2/lib/Doctrine/ORM/EntityManager.php:595
/home/user/projects/lib/doctrine2/tests/Doctrine/Tests/ORM/Functional/Ticket/xxxxTest.php:24
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\Tests\OrmFunctionalTestCase;

class xxxxTest extends OrmFunctionalTestCase
{
    protected function setUp()
    {
        parent::setUp();

        $this->_schemaTool->createSchema(
            [
                $this->_em->getClassMetadata(xxxxUser::class),
                $this->_em->getClassMetadata(xxxxUserProfile::class),
            ]
        );
    }

    public function testThingsWorks()
    {
        $user = new xxxxUser();
        $this->_em->persist($user);
        $this->_em->persist($user->getProfile());
        $this->_em->flush();
    }
}

/**
 * @Entity
 */
class xxxxUser
{
    /**
     * @Id
     * @Column(name="uid", type="integer")
     * @GeneratedValue(strategy="AUTO")
     *
     * @var int
     */
    private $id;

    /****
     * @var xxxxUserProfile|null
     *
     * @OneToOne(targetEntity=xxxxUserProfile::class, mappedBy="user", cascade={"persist"})
     */
    private $profile = null;

    public function __construct()
    {
        $this->profile = new xxxxUserProfile($this);
    }
    
    public function getProfile() 
    {
        return $this->profile;
    }
}
/**
 * @Entity
 */
class xxxxUserProfile
{
    /**
     * – UserProfile's "uid" column points to User's "uid" column
     * – it is PK (primary key)
     * - it is FK (foreign key) as well
     * – "owning side"
     *
     * @var xxxxUser
     *
     * @Id
     * @OneToOne(targetEntity=xxxxUser::class, inversedBy="profile")
     * @JoinColumn(name="uid", referencedColumnName="uid", nullable=false)
     */
    private $user;

    public function __construct(xxxxUser $user)
    {
        $this->user = $user;
    }
}

Upd. the problem is in four asterisks in phpdoc for $profile property, doctrine doesn't see that annotation and User has no relation:

    /****    <------ wrong phpdoc
     * @var xxxxUserProfile|null
     *
     * @OneToOne(targetEntity=xxxxUserProfile::class, mappedBy="user", cascade={"persist"})
     */
    private $profile = null;

@lcobucci
Copy link
Member

@igaponov you're right about the docblock, now my test fails as well... and it actually fails on persist() and not flush().

IMO this is an edge-case since it only happens if xxxxUser#id is a post insert generated id (auto increment or sequences). If strategy is NONE or UUID it works just fine.

@guilhermeblanco @Ocramius I'd say this is a won't fix and we should actually document this, what do you think?

@ste93cry
Copy link

ste93cry commented Jan 9, 2018

After updating to Doctrine 2.6 I get the same error while before it was working fine. I now have to use two persist and flush calls even though the main entity has the cascade option set to persist. Plus, I was relying on the fact that all INSERT queries were automatically executed in a transaction and that if any of the statements failed everything failed as well but now I have to wrap all entity operations inside a transactional function call

@ro0NL
Copy link

ro0NL commented Jan 9, 2018

Im also hitting the same issue. I've identified it for now with int(1) as a value is generated anyway. WFM :) but totally unexpected yes.

@timostamm
Copy link

Just followed the doctrine docs on Identity through foreign Entities and stumbled upon this bug.

When I create a new Article using the entities declared in "Use-Case 1: Dynamic Attributes", I get the mentioned exception:

$article = new Article();
$article->addAttribute("foo", "bar");
$this->em->persist($article); // throws ORMInvalidArgumentException

This was really unexpected.

@ro0NL thanks for the tip. Initializing the auto-generated id works:

class Article
{
    /** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
    private $id = -1;

Maybe this bug should be reopened? Or mentioned in the docs?

@robertonetresults
Copy link

I have the same error too after updating to Doctrine 2.6. I can't apply the @ro0NL tip because I can't change the DB structure.

@NicolaF
Copy link

NicolaF commented Jan 23, 2018

For people having issues with 2.6, see #7003.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests