41
votes

I'm having a hard time making sense of the Doctrine manual's explanation of cascade operations and need someone to help me understand the options in terms of a simple ManyToOne relationship.

In my application, I have a table/entity named Article that has a foreign key field referencing the 'id' field in a table/entity named Topic.

When I create a new Article, I select the Topic from a dropdown menu. This inserts an integer into the 'topic_id' foreign key field in the Article table.

I have the $topic association set up in the Article entity like this:

/**
 * @ManyToOne(targetEntity="Topic")
 * @JoinColumn(name="topic_id", referencedColumnName="id", nullable=false)
 */
private $topic;

The Topic entity doesn't have any reciprocating annotation regarding the Article entity. Topics don't care what Articles reference them and nothing needs to happen to a Topic when an Article that references the Topic is deleted.

Because I'm not specifying the cascade operation in the Article entity, Doctrine throws an error when I try to create a new Article: "A new entity was found through a relationship that was not configured to cascade persist operations. Explicitly persist the new entity or configure cascading persist operations on the relationship."

So I know I need to choose a cascade operation to include in the Article entity, but how do I know which operation to choose in this situation?

From reading the Doctrine manual, "detach" sounds like the right option. But researching others' similar questions here and here makes me think I want to use "persist" instead.

Can anyone help me understand what "persist," "remove," "merge," and "detach" mean in terms of a simple ManyToOne relationship like the one I've described?

2
+1 for the good question. Hope that someone will answer it soon, i'd like to learn more on cascade option. There is a lack of books or documentation about Doctrine2 at the moment.gremo

2 Answers

32
votes

In the Doctrine2 documentation "9.6. Transitive persistence / Cascade Operations" there are few examples of how you should configure your entities so that when you persist $article, the $topic would be also persisted. In your case I'd suggest this annotation for Topic entity:

/**
 * @OneToMany(targetEntity="Article", mappedBy="topic", cascade={"persist", "remove"})
 */
 private $articles;  

The drawback of this solution is that you have to include $articles collection to Topic entity, but you can leave it private without getter/setter.

And as @kurt-krueckeberg mentioned, you must pass the real Topic entity when creating new Article, i.e.:

$topic = $em->getRepository('Entity\Topic')->find($id);
$article = new Article($topic);
$em->persist($article);
$em->flush();

// perhaps, in this case you don't even need to configure cascade operations

Good luck!

1
votes

If you have a @OneToMany unidirectional association, like that described in section 6.10 of the Doctrine Reference, then most likely you forgot to persist the Topic before calling flush. Don't set the topic_id primary key in Article. Instead set the Topic instance.

For example, given Article and Topic entities like these:

<?php
namespace Entities;

/**
@Entity
@Table(name="articles")
*/
class Article {

/**
*  @Id
*  @Column(type="integer", name="article_id") 
*  @GeneratedValue
*/
    protected $id;  

/**
*  @Column(type="text") 
*/
 protected $text;

/** 
* @ManyToOne(targetEntity="Topic", inversedBy="articles")
* @JoinColumn(name="topic_id", referencedColumnName="topic_id")
*/ 
 protected $topic; 

 public function __construct($text=null)
 {
    if (!is_null($text)) {
         $this->text = $text;
    }
 }
 public function setArticle($text)
 {
     $this->text = $text;
 }

 public function setTopic(Topic $t)
{
     $this->topic = $t;
}
} 

<?php
namespace Entities;
/**
  @Entity
  @Table(name="topics")
*/
class Topic {

/**
*  @Id
*  @Column(type="integer", name="topic_id") 
*  @GeneratedValue
*/
    protected $id;  

    public function __construct() {}

    public function getId() {return $this->id;}
}

After you generate the schema:

# doctrine orm:schema-tool:create

your code to persist these entities would look like something this

//configuration omitted..
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);

$topic = new Entities\Topic();
$article1 = new Entities\Article("article 1");
$article2 = new Entities\Article("article 2");
$article1->setTopic($topic);
$article2->setTopic($topic);
$em->persist($article1);
$em->persist($article2);
$em->persist($topic);

try {
    $em->flush();
} catch(Exception $e) {
    $msg= $e->getMessage();
    echo $msg . "<br />\n";
}
return;

I hope this helps.