使用 Symfony2 表单更新共享主键的两个实体

Update two entities sharing the primary key with a Symfony2 form

我有两个实体:UserCompanyInfo

两者的关系是oneToOneUser可以有0个也可以有1个CompanyInfo,1个CompanyInfo属于1个User

因此我将它们设置为具有相同的主键(用户 ID):

class User extends BaseUser
{
/**
 * @ORM\Id
 * @ORM\Column(type="integer")
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;
/**
 * @ORM\OneToOne(targetEntity="CompanyInfo", mappedBy="user", cascade={"persist"})
 * @var CompanyInfo
 */
protected $companyInfo;
...
}

class CompanyInfo
{

/**
 * @ORM\OneToOne(targetEntity="User", inversedBy="companyInfo", cascade={"persist"})
 * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
 * @ORM\Id
 * @var User
 */
protected $user;
....
 }

我在尝试同时公开它们时遇到问题,以便可以通过仅提交一个表单来更新它们:

在 UserFormType 中,我有以下行:

    $builder->add('companyInfo', new CompanyInfoFormType(), ['required' => false, 'by_reference' => false])

CompanyInfoFormType有以下内容:

    /**
 * @param OptionsResolverInterface $resolver
 */
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => '....\Entity\CompanyInfo',
        'intention'  => 'registration'
    ));
}

一切正常,表单显示了用户和公司信息字段。当创建一个新的用户 & companyInfo 它工作但只是因为我在 UserFormHandleronSuccess 中做了以下操作(基本上首先坚持 User,看起来有点 hackie 但不能'找不到更好的方法):

    if ($user->getCompanyInfo() instanceof CompanyInfo) {
        $companyInfo = $user->getCompanyInfo()->setUser($user);
        $user->setCompanyInfo(null);
        $this->entityManager->beginTransaction();
        $this->entityManager->persist($user);
        $this->entityManager->flush();
        $this->entityManager->persist($companyInfo);
        $this->entityManager->flush();
        $this->entityManager->commit();
        $this->entityManager->refresh($user);
    }

现在,问题是当我尝试更新已有 companyInfo 的用户时。出于某种奇怪的原因,学说认为 CompanyInfo 实体不存在,并且它正在尝试执行插入而不是更新。这就像如果实体 CompanyInfo 它不受 Doctrine 管理,因此在进行级联时,会尝试创建一个新实体。

我终于设法找到了解决方案,尽管它并不理想,但它确实有效。基本上,我的 UpdateFormHandlerprocess 方法如下所示:

/**
 * @param UserInterface $user
 * @return bool
 */
public function process(UserInterface $user)
{
    $this->form->setData($user);

    $method = $this->request->getMethod();
    if (in_array($method, ['PUT', 'PATCH'])) {
        $this->form->submit($this->request, 'PATCH' !== $method);

        if ($this->form->isValid()) {
            /** @var User $user */
            $this->saveCompanyInfo($user);
            $this->onSuccess($user);
            $this->userManager->reloadUser($user);
            return true;
        }
    }

    return false;
}

/**
 * Create link companyInfo -> User when persisting CompanyInfo for first time
 *
 * @param User $user
 */
protected function saveCompanyInfo(User $user)
{
    if ($user->getCompanyInfo() instanceof CompanyInfo && !$user->getCompanyInfo()->getUser()) {

        $user->getCompanyInfo()->setUser($user);

    } elseif ($user->getCompanyInfo() instanceof CompanyInfo) {
        /**
         * If companyInfo exists and has been modified, doctrine things that it's a new entity
         * detached from the manager, and will try to insert a new row on the company_info table (which crashes, as the PK (user_id) already exists).
         * By calling ->merge, we obtain an attached/managed instance,
         * so that it will be properly cascade persisted when the user is saved.
         */
        /** @var CompanyInfo $companyInfo */
        $companyInfo = $this->getEntityManager()->merge($user->getCompanyInfo());
        $user->setCompanyInfo($companyInfo);
    }
}

我确信(我希望)有更好的方法,但我期待听到它们,因为这是唯一对我有用的方法。

希望对您有所帮助。

此致, 哈维尔