patryk
patryk

Reputation: 651

Persist an entity and relate it to another entity without persiting the second again

I am building a front-end app with Durandal/Knockoutjs and a web service back-end with Symfony2 and I use Doctrine to access the database.

I have two entities that are in association of one-to-many, like this

use Doctrine\ORM\Mapping as ORM;

/**
 *  @ORM\Entity
 *  @ORM\Table(name = "drew_cat_door")
 */
class Door
{
    public function __construct()
    {
        //$this->setAddTime(new \DateTime(date('Y-m-d H:i:s')));

        if ($this->getAddTime() == null)
            $this->setAddTime(new \DateTime(date('Y-m-d H:i:s')));
        else
            $this->setUpdateTime(new \DateTime(date('Y-m-d H:i:s')));
    }

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

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

    /**
     *  @ORM\ManyToOne(targetEntity = "DoorType", inversedBy = "doors")
     *  @ORM\JoinColumn(name = "type_id", referencedColumnName = "id")
     */
    protected $type;

    /**
     *  @ORM\Column(type = "string", length = 30, nullable = true)
     */
    protected $filename;

    /**
     *  @ORM\Column(type = "string", length = 100, nullable = true)
     */
    protected $description;

    /**
     *  @ORM\Column(type = "integer", nullable = true)
     */
    protected $views;

    /**
     *  @ORM\Column(type = "datetime")
     */
    protected $add_time;

    /**
     *  @ORM\Column(type = "datetime", nullable = true)
     */
    protected $update_time;

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

    /**
     * Set name
     *
     * @param string $name
     * @return Door
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set filename
     *
     * @param string $filename
     * @return Door
     */
    public function setFilename($filename)
    {
        $this->filename = $filename;

        return $this;
    }

    /**
     * Get filename
     *
     * @return string 
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * Set description
     *
     * @param string $description
     * @return Door
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Get description
     *
     * @return string 
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Set views
     *
     * @param integer $views
     * @return Door
     */
    public function setViews($views)
    {
        $this->views = $views;

        return $this;
    }

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

    /**
     * Set add_time
     *
     * @param \DateTime $addTime
     * @return Door
     */
    public function setAddTime($addTime)
    {
        $this->add_time = $addTime;

        return $this;
    }

    /**
     * Get add_time
     *
     * @return \DateTime 
     */
    public function getAddTime()
    {
        return $this->add_time;
    }

    /**
     * Set update_time
     *
     * @param \DateTime $updateTime
     * @return Door
     */
    public function setUpdateTime($updateTime)
    {
        $this->update_time = $updateTime;

        return $this;
    }

    /**
     * Get update_time
     *
     * @return \DateTime 
     */
    public function getUpdateTime()
    {
        return $this->update_time;
    }

    /**
     * Set type
     *
     * @param \Drewkol\AdminBundle\Entity\DoorType $type
     * @return Door
     */
    public function setType(\Drewkol\AdminBundle\Entity\DoorType $type = null)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * Get type
     *
     * @return \Drewkol\AdminBundle\Entity\DoorType 
     */
    public function getType()
    {
        return $this->type;
    }
}

/**
 *  @ORM\Entity
 *  @ORM\Table(name = "drew_cat_doortype")
 */
class DoorType
{
    public function __construct()
    {
        $this->doors = new ArrayCollection();

        if ($this->getAddTime() == null)
            $this->setAddTime(new \DateTime(date('Y-m-d H:i:s')));
        else
            $this->setUpdateTime(new \DateTime(date('Y-m-d H:i:s')));
    }
    /**
     *  @ORM\Id
     *  @ORM\Column(type = "integer")
     *  @ORM\GeneratedValue
     */
    protected $id;

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

    /**
     *  @ORM\OneToMany(targetEntity = "Door", mappedBy = "type")
     */
    protected $doors;

    /**
     *  @ORM\Column(type = "datetime")
     */
    protected $add_time;

    /**
     *  @ORM\Column(type = "datetime", nullable = true)
     */
    protected $update_time;

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

    /**
     * Set name
     *
     * @param string $name
     * @return DoorType
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set add_time
     *
     * @param \DateTime $addTime
     * @return DoorType
     */
    public function setAddTime($addTime)
    {
        if ($addTime != null)
            $this->add_time = $addTime;

        return $this;
    }

    /**
     * Get add_time
     *
     * @return \DateTime 
     */
    public function getAddTime()
    {
        return $this->add_time;
    }

    /**
     * Set update_time
     *
     * @param \DateTime $updateTime
     * @return DoorType
     */
    public function setUpdateTime($updateTime)
    {
        $this->update_time = $updateTime;

        return $this;
    }

    /**
     * Get update_time
     *
     * @return \DateTime 
     */
    public function getUpdateTime()
    {
        return $this->update_time;
    }

    /**
     * Add doors
     *
     * @param \Drewkol\AdminBundle\Entity\Door $doors
     * @return DoorType
     */
    public function addDoor(\Drewkol\AdminBundle\Entity\Door $doors)
    {
        $this->doors[] = $doors;

        return $this;
    }

    /**
     * Remove doors
     *
     * @param \Drewkol\AdminBundle\Entity\Door $doors
     */
    public function removeDoor(\Drewkol\AdminBundle\Entity\Door $doors)
    {
        $this->doors->removeElement($doors);
    }

    /**
     * Get doors
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getDoors()
    {
        return $this->doors;
    }
}

Sorry for not ommiting any code. As you can see, a Door has a DoorType.

As it is easy, when adding a door, I post a JSON that goes

{"name":"nowe","type":{"id":5,"name":"loluk","add_time":"2013-09-25T01:05:05+0200"},"description":"hehe\n","filename":"hehe.jpg"}

and is a full door entity model with a type that has already been around. When I try to add this entity with doctrine with this code

$json_door = $this->get("request")->getContent();
if (empty($json_door))
    return $this->createBadRequestException();

$door = $this->container->get("serializer")
    ->deserialize($json_door, "Drewkol\\AdminBundle\\Entity\\Door", "json");
$door->setAddTime(new \DateTime(date('Y-m-d H:i:s')));

$manager = $this->getDoctrine()->getManager();
$manager->persist($door);
$manager->flush();

I get an error that says

A new entity was found through the relationship 'Drewkol\AdminBundle\Entity\Door#type' that was not configured to cascade persist operations for entity: Drewkol\AdminBundle\Entity\DoorType@000000002d1b74500000000063b1c8fb. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'Drewkol\AdminBundle\Entity\DoorType#__toString()' to get a clue. 

My question is: what is the most clean and efficient way to add door with a type that has already been added?

Is there any way to tell Doctrine to try and resolve if the type given in the entity being added is already in the database (as it must have been added before) or do I have to fetch and remove the type from the JSON deserialized entity to prevent it from being persisted (with the suggested cascade option) and then fetch the type with Doctrine and then set it as a type of my brand new deserialized door entity so Doctrine is aware of the type? I mean I like the data model generated by knockoutjs and transfered with JSON and it seems to me to be a general drawback not being able to do it the way I presented.

Upvotes: 0

Views: 85

Answers (1)

Ken Hannel
Ken Hannel

Reputation: 2748

I recommend using the Doctrine prePersist event to handle this. In the prePersist event you need to write some logic that checks the database to determine if the doorType exists.

If it exists, then fetch the doorType from the database and use that instead of the doorType object that was created when you unserialized your knockout JSON.

If it does not exist, persist the doorType that was created from your unserialized knockout JSON and then persist your Door.

http://docs.doctrine-project.org/en/latest/reference/events.html

Upvotes: 1

Related Questions