Reputation: 1785
I'm having trouble in Doctrine-Fixtures. I'd like to add a user and a email in another entity, but in relation to the user. So here is my process:
// Create user
$user1 = new User();
// Create user email and add the foreign key to the user
$user1Mail = new UserEmail();
$user1Mail->setEmail('[email protected]');
$user1Mail->setUser($user1);
// Add attributes
$user1->setEmail($user1Mail);
// ...
$manager->persist($user1Mail);
$manager->persist($user1);
$manager->flush();
I add the user of the email in $user1Mail->setUser($user1);
before the persist, but the problem is, the user has no primary key --> the ID (auto increment). So to create the relation between the email and the user, the user needs to have a primary key to refer to.
I know the solution to create a unique token before and set this to the ID of the user, but I think this is a uncomfortable way because I need to check if the user ID is already in use.
Is there a good way to handle this?
// EDIT: Here is the necessary entity relation:
User:
class User implements UserInterface, \Serializable
{
// ...
/**
* @var Application\CoreBundle\Entity\UserEmail
*
* @ORM\OneToOne(
* targetEntity="UserEmail",
* cascade={"persist"}
* )
* @ORM\JoinColumn(
* name="primaryEmail",
* referencedColumnName="id",
* nullable=false,
* onDelete="restrict"
* )
*/
private $email;
// ...
}
UserEmail:
class UserEmail
{
// ...
/**
* @var Application\CoreBundle\Entity\User
* @ORM\ManyToOne(
* targetEntity="User",
* cascade={"persist", "remove"}
* )
* @ORM\JoinColumn(
* name="userID",
* referencedColumnName="id",
* nullable=false
* )
*/
private $user;
// ...
}
As you can see, if you add a user you have to add a UserEmail also. But the UserEmail requires that the userID is already set, but it is only set if you persist the user into the db. How can I realize a fix for it?
Upvotes: 0
Views: 10003
Reputation: 21817
I find it strange to see that your User
has a OneToOne association towards UserEmail
, and UserEmail
has a ManyToOne association towards User
, and those are 2 separate associations.
I think you'd rather have a single bidirectional OneToMany association:
class User implements UserInterface, \Serializable
{
// ...
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="UserEmail", mappedBy="user", cascade={"persist", "remove"})
*/
private $emails;
public function __construct()
{
$this->emails = new ArrayCollection();
}
/**
* @param UserEmail $email
*/
public function addEmail(UserEmail $email)
{
$this->emails->add($email);
$email->setUser($this);
}
/**
* @param UserEmail $email
*/
public function removeEmail(UserEmail $email)
{
$this->emails->removeElement($email);
$email->setUser(null);
}
/**
* @return UserEmail[]
*/
public function getEmails()
{
return $this->emails->toArray();
}
// ...
}
class UserEmail
{
// ...
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="User", inversedBy="emails")
* @ORM\JoinColumn(name="userID", referencedColumnName="id", nullable=FALSE)
*/
private $user;
/**
* @param User $user
*/
public setUser(User $user = null)
{
$this->user = $user;
}
/**
* @return User[]
*/
public function getUser()
{
return $this->user;
}
// ...
}
I've put a cascade
on User::$emails
, so any changes to User
get cascaded towards UserEmail
. This will make managing them easier.
Using this would look something like this:
$email = new UserEmail();
$user = new User();
$user->addEmail($email);
$em->persist($user);
$em->flush();
About foreign keys
Doctrine will manage the foreign keys of your entities for you. You don't need to manually set them on your entities when using associations.
Primary email
Personally I would add a property to UserEmail
to mark it as primary. You'll need a bit more logic in the entities, but managing them will become effortless.
Here's the additional code you need:
class User
{
// ...
/**
* @param UserEmail $email
*/
public function addEmail(UserEmail $email)
{
$this->emails->add($email);
$email->setUser($this);
$this->safeguardPrimaryEmail();
}
/**
* @param UserEmail $email
*/
public function removeEmail(UserEmail $email)
{
$this->emails->removeElement($email);
$email->setUser(null);
$this->safeguardPrimaryEmail();
}
/**
* @param UserEmail $email
*/
public function setPrimaryEmail(UserEmail $newPrimaryEmail)
{
if (!$this->emails->contains($newPrimaryEmail)) {
throw new \InvalidArgumentException('Unknown email given');
}
foreach ($this->emails as $email) {
if ($email === $newPrimaryEmail) {
$email->setPrimary(true);
} else {
$email->setPrimary(false);
}
}
}
/**
* @return UserEmail|null
*/
public function getPrimaryEmail()
{
foreach ($this->emails as $email) {
if ($email->isPrimary()) {
return $email;
}
}
return null;
}
/**
* Make sure there's 1 and only 1 primary email (if there are any emails)
*/
private function safeguardPrimaryEmail()
{
$primaryFound = false;
foreach ($this->emails as $email) {
if ($email->isPrimary()) {
if ($primaryFound) {
// make sure there's no more than 1 primary email
$email->setPrimary(false);
} else {
$primaryFound = true;
}
}
}
if (!$primaryFound and !$this->emails->empty()) {
// make sure there's at least 1 primary email
$this->emails->first()->setPrimary(true);
}
}
// ...
}
class UserEmail
{
// ...
/**
* @var boolean
*
* @ORM\Column(type="boolean")
*/
private $isPrimary = false;
/**
* @internal Use
* @param bool $isPrimary
*/
public function setPrimary($isPrimary)
{
$this->isPrimary = (bool)$isPrimary;
}
/**
* @return bool
*/
public function isPrimary()
{
return $this->isPrimary;
}
// ...
}
You'll probably notice safeguardPrimaryEmail()
. This will make sure the primary-mark will remain consistent when adding/removing emails.
Using this is very simple:
User::setPrimaryEmail()
.There are many variations to this concept possible, so just view this as an example and refine it to your needs.
Upvotes: 1
Reputation: 523
It's because Doctrine will generate the entity id when it's inserted into the database.
You can do it with an extra flush()
:
$user1 = new User();
$manager->persist($user1);
$manager->flush();
// Create user email and add the foreign key to the user
$user1Mail = new UserEmail();
$user1Mail->setEmail('[email protected]');
$user1Mail->setUser($user1);
// Add attributes
$user1->setEmail($user1Mail);
// ...
$manager->persist($user1Mail);
$manager->persist($user1);
$manager->flush();
Or you can set the email mapping in your User
class to cascade persist.
It means that if a new not persisted object is added to that object, then the new object will be saved as well.
I don't know the exact structure of the entites, but it would look like
/**
* @ORM\OneToOne(targetEntity="user", cascade={"persist"})
* @ORM\JoinColumn(name="user_email_id", referencedColumnName="id")
*/
private $userEmail
So if you set a new e-mail to the user it will be auto-persisted if you persist the User
entity.
I would prefer the second method if it works. I hope it will help.
Doctrine reference: Transitive persistence / Cascade Operations
Upvotes: 0