0
votes

I have two entities configured called User and ApiKey. They have a one-to-one relationship mapping below.

User:

/**
 * @var integer
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;

/**
 * @ORM\Column(name="username", type="string", length=225, unique=true)
 * @Assert\NotBlank(message="Please enter a username.")
 * @Assert\Length(
 *     min = 2,
 *     max = 180,
 *     minMessage = "Your username must be at least {{ limit }} characters",
 *     maxMessage = "Your username cannot be longer than {{ limit }} characters"
 * )
 */
protected $username;

/**
 * @ORM\Column(name="email", type="string", length=225)
 * @Assert\NotBlank(message="Please enter a email.")
 * @Assert\Email(message = "The email '{{ value }}' is not a valid email.")
 */
protected $email;

/**
 * @ORM\Column(name="salt", type="text")
 */
protected $salt;

/**
 * @ORM\Column(name="password", type="string", length=225)
 * @Assert\NotBlank(message="Please enter a password.")
 * @Assert\Length(
 *     min = 7,
 *     minMessage = "Your password must be at least {{ limit }} characters long."
 * )
 */
protected $password;

/**
 * @Assert\NotBlank(message="Please enter a confirmation password.")
 */
protected $confirmationPassword;

/**
 * @ORM\Column(name="roles", type="json_array")
 */
protected $roles;

/**
 * @ORM\OneToOne(targetEntity="ApiKey", mappedBy="user")
 * @ORM\JoinColumn(name="id", referencedColumnName="user_id")
 */
protected $apiKey;

ApiKey:

/**
 * @var integer
 * @ORM\Column(name="user_id", type="integer")
 * @ORM\Id
 */
protected $user_id;

/**
 * @var string
 * @ORM\Column(name="api_key", unique=true, type="string", length=225)
 * @Assert\NotBlank(message="Please enter a username.")
 */
protected $api_key;

/**
 * @var string
 * @ORM\Column(name="expires", type="datetime")
 * @Assert\NotBlank(message="Please enter a username.")
 */
protected $expires;

/**
 * @ORM\OneToOne(targetEntity="User", inversedBy="apiKey")
 * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
 */
protected $user;

However, when I run the following code:

$user = new User();
    $user->setUsername($request->get('username'));
    $user->setSalt(uniqid(mt_rand(), true));
    $user->setPassword(hash('sha256', $user->getSalt() . $request->get('password')));
    $user->setRoles(['ROLE_API']);

    $apiKey = new ApiKey();
    $apiKey->setUser($user);

    $this->getDoctrine()->getManager()->persist($user);
    $this->getDoctrine()->getManager()->flush();

I get this error, what am I doing wrong?

An exception occurred while executing 'INSERT INTO users (username, email, salt, password, roles, id) VALUES (?, ?, ?, ?, ?, ?)' with params ["bobsmith", "[email protected]", "147423531058bd98d9ac9af4.48409486", "6196012972b1294e7f3fbf9c140bc26f0bf3b354ea37d00753bc1ecba0a72866", "[\"ROLE_API\"]", null]:SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (app.users, CONSTRAINT FK_1483A5E9BF396750 FOREIGN KEY (id) REFERENCES api_key (user_id))

Change:

    $user = new User();
    $user->setUsername($request->get('username'));
    $user->setSalt(uniqid(mt_rand(), true));
    $user->setPassword(hash('sha256', $user->getSalt() . $request->get('password')));
    $user->setRoles(['ROLE_API']);

    $apiKey = new ApiKey();
    $this->getDoctrine()->getManager()->persist($apiKey); 
    $apiKey->setUser($user);

    $this->getDoctrine()->getManager()->persist($user);
    $this->getDoctrine()->getManager()->flush();

Entity of type AppBundle\Entity\ApiKey is missing an assigned ID for field 'user_id'. The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called. If you want automatically generated identifiers instead you need to adjust the metadata mapping accordingly.

2
try adding " $user->setApiKey($apiKey); " after $apiKey = new ApiKey();Dan Ionescu
I added your change ^. But now get this error: A new entity was found through the relationship 'AppBundle\Entity\User#apiKey' that was not configured to cascade persist operations for entity: AppBundle\Entity\ApiKey@000000004adc072f00000000540f2869. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'AppBundle\Entity\ApiKey#__toString()' to get a clue.Freid001
Add this line too before flush :$this->getDoctrine()->getManager()->persist($apiKey);Dan Ionescu
Now getting this error: Entity of type AppBundle\Entity\ApiKey is missing an assigned ID for field 'user_id'. The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called. If you want automatically generated identifiers instead you need to adjust the metadata mapping accordingly.Freid001

2 Answers

3
votes

You seem to have a cascading issue here. You are saving a User in the database, but that user is linked to an ApiKey that is NOT yet saved in the database. To tell the player that it needs to persist it's ApiKey along with it you need to add a cascade on persist between User and ApiKey

/**
 * @ORM\OneToOne(targetEntity="ApiKey", mappedBy="user", cascade="persist")
 */
protected $api_key;

If you also want to delete the ApiKey along with the User you can use cascade={"persist", "remove"} You can find all the Doctrine documentation about associations here

EDIT: Removed the line // @ORM\JoinColumn(name="id", referencedColumnName="user_id") that shouldn't exist on the owner side, as commented by Chausser

2
votes

This is 2 fold, what Cyril said, but its also because you have extra mapping in your User class:

/**
 * @ORM\OneToOne(targetEntity="ApiKey", mappedBy="user")
 * @ORM\JoinColumn(name="id", referencedColumnName="user_id") // Remove this line
 */
protected $apiKey;

Should just be:

/**
 * @ORM\OneToOne(targetEntity="ApiKey", mappedBy="user")
 */
protected $apiKey;

Only the mapping that actually doing the mapping needs the join column. By Using mappedBy="user" you are telling doctrine to look at the ApiKey class user property for how to map this association. Having a join column in this is class is causing problems.