I have two entities: User
and CompanyInfo
.
The relationship between both is oneToOne
, the User
can have zero or one CompanyInfo
, and one CompanyInfo
belongs to one User
.
Therefore I setup them to have the same primary key (User's 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;
....
}
I'm having an issue trying to expose the them at the same time so that they can be updated by submitting only one form:
In the UserFormType I have the following line:
$builder->add('companyInfo', new CompanyInfoFormType(), ['required' => false, 'by_reference' => false])
The CompanyInfoFormType
has the following:
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '....\Entity\CompanyInfo',
'intention' => 'registration'
));
}
It all works fine, the form is rendered with both the user and the company info fields. When creating a new user & companyInfo it works but only because I did the following in the onSuccess
of the UserFormHandler
(Basically persist the User
in first place, looks a bit hackie but couldn't find a nicer way):
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);
}
Now, the issue is when I'm trying to update a user that already has a companyInfo. For some weird reason, doctrine is thinking that the CompanyInfo
entity doesn't exist and it's trying to do an INSERT rather than an UPDATE. It's like if the entity CompanyInfo
it's not managed by Doctrine and therefore when doing the cascade persist, tries to create a new one.
I finally managed to find a solution for this, it's not ideal though, but it works. Basically, the process
method of my UpdateFormHandler
looks like follows:
/**
* @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);
}
}
I'm sure (I hope) there are better ways, but I'm looking forward to hear them, as this is the only thing that worked for me.
I hope it helps.
Regards, Javier