Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doctrine OneToOne identity through foreign entity exception on flush

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

UserProfile'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.

Test.php:

/**
 * 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();

User.php:

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

UserProfile.php:

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

Test app: https://github.com/MacDada/DoctrineOneToOneTest

like image 572
MacDada Avatar asked Nov 09 '22 12:11

MacDada


1 Answers

Please keep in mind that the actual object needs to be saved by the EntityManager. Just giving the class as reference to the other class does not make the entityManager aware of the fact both classes exists.

You should persist the actual userProfile to the EntityManager to be able to save the relation.

UPDATE because of negative comment:

Please read the Doctrine docs... You should persist!

The following example is an extension to the User-Comment example of this chapter. Suppose in our application a user is created whenever he writes his first comment. In this case we would use the following code:

<?php
$user = new User();
$myFirstComment = new Comment();
$user->addComment($myFirstComment);

$em->persist($user);
$em->persist($myFirstComment);
$em->flush();

Even if you persist a new User that contains our new Comment this code would fail if you removed the call to EntityManager#persist($myFirstComment). Doctrine 2 does not cascade the persist operation to all nested entities that are new as well.

Update2: I understand what it is you wish to accomplish, but by design you should not move this logic within your entities. Entities should represent as less logic as possible, since they represent your modal.

Have that said, I believe you could accomplish what you are trying to do like this:

$user = new User();
$profile = $user->getProfile();
$objectManager->persist($user);
$objectManager->persist($profile);
$objectManager->flush();

You should however consider creating a userService containing the entitymanager and make that responsible for creating, linking and persisting the user + userProfile entity.

like image 79
Armand Avatar answered Nov 15 '22 06:11

Armand