Symfony2 Doctrine2 trouble with optional one to one relation

daluq picture daluq · Apr 1, 2013 · Viewed 35.6k times · Source

I have a problem with Doctrine2 in Symfony2 and two relationed entities.

There is a user-entity that can (not must) have a usermeta-entity referenced which contains information like biography etc.

The usermeta is optional because user is imported by another system, while usermeta is managed in my application.

Of course I want to save both together, so that saving a user must create or update a usermeta-entity.

Both are joined by a column named aduserid (same name in both tables).

I've recognized that if usermeta is an optional reference the owning-side in this case should be usermeta, otherwise doctrine loads user and needs the usermeta entity - but it's not always there.

Please note the comments in User->setMeta..

/**
 * User
 *
 * @ORM\Table(name="user")
 * @ORM\Entity
 */
class User
{
/**
 * @var Usermeta
 * @ORM\OneToOne(targetEntity="Usermeta", mappedBy="user", cascade={"persist"})
 */
protected $meta;

public function getMeta()
{
    return $this->meta;
}

/**
 * 
 * @param Usermeta $metaValue 
 */
public function setMeta($metaValue)
{        
// I've tried setting the join-column-value here 
//  - but it's not getting persisted
// $metaValue->setAduserid($this->getAduserid());

// Then I've tried to set the user-object in Usermeta - but then 
//  it seems like Doctrine wants to update Usermeta and searches
//  for ValId names aduserid (in BasicEntityPersister->_prepareUpdateData) 
//  but only id is given -  so not luck here
// $metaValue->setUser($this);           

    $this->meta = $metaValue;
}

/**
 * @var integer
 *
 * @ORM\Column(name="rowid", type="integer", nullable=false)
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="IDENTITY")
 */
private $id;


/**
 * Get rowid
 *
 * @return integer 
 */
public function getId()
{
    return $this->id;
}

/**
 * @var integer
 *
 * @ORM\Column(name="ADuserid", type="integer", nullable=false)
 */
private $aduserid;

/**
 * Set aduserid
 *
 * @param integer $aduserid
 * @return User
 */
public function setAduserid($aduserid)
{
    $this->aduserid = $aduserid;

    return $this;
}

/**
 * Get aduserid
 *
 * @return integer 
 */
public function getAduserid()
{
    return $this->aduserid;
}

// some mor fields.... 
}

And the Usermeta class:

/**
 * Usermeta
 *
 * @ORM\Table(name="userMeta")
 * @ORM\Entity
 */
class Usermeta
{
/**
 * @ORM\OneToOne(targetEntity="User", inversedBy="meta")
 * @ORM\JoinColumn(name="ADuserid", referencedColumnName="ADuserid")
 */
protected $user;

public function getUser()
{
    return $this->$user;
}    

public function setUser($userObj)
{
    $this->user = $userObj;
}

/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer", nullable=false)
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="IDENTITY")
 */
private $id;

/**
 * @var integer
 *
 * @ORM\Column(name="ADuserid", type="integer", nullable=false)
 */
private $aduserid;

/**
 * Set aduserid
 *
 * @param integer $aduserid
 * @return User
 */
public function setAduserid($aduserid)
{
    $this->aduserid = $aduserid;

    return $this;
}

/**
 * Get aduserid
 *
 * @return integer 
 */
public function getAduserid()
{
    return $this->aduserid;
}
}

the controller code looks like this:

...

$userForm->bind($request);

    if($userForm->isValid()) {
        $em->persist($user);
        $em->flush();
    }
...

Answer

Francesco Casula picture Francesco Casula · Feb 19, 2014

The Zdenek Machek comment is almost correct. As you can see from the Doctrine2 documentation, the nullable option should be in the join annotation (@JoinColumn), not in the mapping one (@OneToOne).

@JoinColumn doc:

This annotation is used in the context of relations in @ManyToOne, @OneToOne fields and in the Context of @JoinTable nested inside a @ManyToMany. This annotation is not required. If its not specified the attributes name and referencedColumnName are inferred from the table and primary key names.

Required attributes:

name: Column name that holds the foreign key identifier for this relation. In the context of @JoinTable it specifies the column name in the join table.

referencedColumnName: Name of the primary key identifier that is used for joining of this relation.

Optional attributes:

unique: Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false.

nullable: Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true.

onDelete: Cascade Action (Database-level)

onUpdate: Cascade Action (Database-level)

columnDefinition: DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a “columnDefinition” attribute on @Column also sets the related @JoinColumn’s columnDefinition. This is necessary to make foreign keys work.

http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#annref-joincolumn

@OneToOne doc:

The @OneToOne annotation works almost exactly as the @ManyToOne with one additional option that can be specified. The configuration defaults for @JoinColumn using the target entity table and primary key column names apply here too.

Required attributes:

targetEntity: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. IMPORTANT: No leading backslash!

Optional attributes:

cascade: Cascade Option

fetch: One of LAZY or EAGER

orphanRemoval: Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any owning instance, should be removed by Doctrine. Defaults to false.

inversedBy: The inversedBy attribute designates the field in the entity that is the inverse side of the relationship.

http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#onetoone