8
votes

I'm attempting to use Doctrine ORM Associations. I've read several tutorials and the online docs, but it's not working, and I'm honestly not sure what I'm doing wrong here. Seems like my experiments with Doctrine are pretty hit or miss. Any assistance would be appreciated.

I'd like to have a User entity and UserHistory entity, which has multiple rows for a single User. That to me sounds like One-To-Many. I don't necessarily need it to be bidirectional.

The problem I'm having is adding a history item to a user results in an error when the user is saved, because the user_id column is not set in the user_history table.

Entities:

# Entity\User.php
<?php
namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * @ORM\Entity
 */
class User
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    protected $id;

    /**
     * @ORM\Column(type="string",length=255)
     */
    protected $username;

    /**
     * @ORM\OneToMany(targetEntity="UserHistory", mappedBy="user", cascade={"persist","remove"})
     * @ORM\JoinColumn(name="id", referencedColumnName="user_id")
     */
    protected $history;

    public function __construct()
    {
        $this->setHistory(new ArrayCollection());
    }

    public function getHistory()
    {
        return $this->history;
    }
}

# Entity\UserHistory.php
<?php
namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="user_history")
 */
class UserHistory
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    protected $id;

    /**
     * @ORM\ManyToOne(targetEntity="User")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
     */
    protected $user;

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

In my controller, I'm trying this:

    $em = $this->getUserService()->getEntityManager();
    $user = $em->find('Picpara\Entity\User', 1);
    $history = new UserHistory();
    $user->getHistory()->add($history);
    $em->persist($user);
    $em->flush();

All of which results in an integrity constraint violation.

Doctrine\DBAL\DBALException

File: /myproject/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php:91

Message:

An exception occurred while executing 'INSERT INTO user_history (user_id) VALUES (?)' with params [null]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null

Like I said, I've dug around here on SO, googled, tried moving things around in the annotations. This is as far as I get. Not sure what I'm doing wrong. Can anyone help?

1
Its not clear what user history is actually keeping a history of; if you intend it to save the state of a user at any given time then you approach will not work as it will always reference the latest user record.AlexP
I'm sorry - I stripped all the fields out of both entities in the example to keep it clean. The UserHistory will store audit-trail stuff like: "user X did Y on DateTime". 'Added some content' 'Favorited something' 'Modified something.'Phillip Harrington

1 Answers

10
votes

The problem is that you are not setting the user entity in your history class. The association stuff does not do this automatically.

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

class User
{
    public function addHistory($history)
    {
        $this->history->add($history);
        $history->addUser($this);  // *** This is what you are missing
    }

// In your controller class
$user->addHistory($history);