我在Doctrine-Fixtures遇到了麻烦。我想在另一个实体中添加用户和电子邮件,但与用户有关。所以这是我的过程:
// Create user
$user1 = new User();
// Create user email and add the foreign key to the user
$user1Mail = new UserEmail();
$user1Mail->setEmail('test@example.com');
$user1Mail->setUser($user1);
// Add attributes
$user1->setEmail($user1Mail);
// ...
$manager->persist($user1Mail);
$manager->persist($user1);
$manager->flush();
我在保留前添加了$user1Mail->setUser($user1);
中的电子邮件用户,但问题是,用户没有主键 - > ID(自动增量)。因此,要创建电子邮件和用户之间的关系,用户需要具有要引用的主键。
我知道之前创建唯一令牌的解决方案并将其设置为用户的ID,但我认为这是一种令人不舒服的方式,因为我需要检查用户ID是否已被使用。
有没有一种好方法可以解决这个问题?
//编辑: 这是必要的实体关系:
用户:
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;
// ...
}
如您所见,如果添加用户,您还必须添加UserEmail。但UserEmail要求已设置userID,但仅在将用户持久保存到数据库时才设置。我怎样才能实现它的修复?
答案 0 :(得分:1)
我发现您的User
与UserEmail
之间存在OneToOne关联,而UserEmail
与User
之间存在ManyToOne关联,我觉得很奇怪,这些是2个单独的关联
我认为您宁愿拥有单一的双向OneToMany关联:
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;
}
// ...
}
我在cascade
上加了User::$emails
,因此对User
的任何更改都会向UserEmail
级联。这将使他们更容易管理。
使用它看起来像这样:
$email = new UserEmail();
$user = new User();
$user->addEmail($email);
$em->persist($user);
$em->flush();
关于外键
Doctrine将为您管理实体的外键。使用关联时,无需在实体上手动设置它们。
主电子邮件
我个人会向UserEmail
添加一个属性以将其标记为主要属性。您需要在实体中使用更多逻辑,但管理它们将变得毫不费力。
以下是您需要的其他代码:
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;
}
// ...
}
您可能会注意到safeguardPrimaryEmail()
。这将确保在添加/删除电子邮件时主标记保持一致。
使用它非常简单:
User::setPrimaryEmail()
。这个概念可能有很多变化,所以只需将其视为一个示例,并根据您的需要进行优化。
答案 1 :(得分:0)
这是因为Doctrine会在插入数据库时生成实体ID。
您可以使用额外的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('test@example.com');
$user1Mail->setUser($user1);
// Add attributes
$user1->setEmail($user1Mail);
// ...
$manager->persist($user1Mail);
$manager->persist($user1);
$manager->flush();
或者您可以将User
课程中的电子邮件映射设置为级联持久。
这意味着如果将一个新的非持久化对象添加到该对象,那么新对象也将被保存。
我不知道entites的确切结构,但它看起来像
/**
* @ORM\OneToOne(targetEntity="user", cascade={"persist"})
* @ORM\JoinColumn(name="user_email_id", referencedColumnName="id")
*/
private $userEmail
因此,如果您向用户设置新电子邮件,则如果您保留User
实体,则会自动保留该电子邮件。
如果有效,我更喜欢第二种方法。我希望它会有所帮助。
Doctrine reference: Transitive persistence / Cascade Operations