2017-04-11 114 views
0

我有几个类使用Taggable特质来设置一个标记系统为多个学说实体共同(项目,注意,...)。Symfony Doctrine2 manyToMany关系没有删除 - 具体到SQLite

这些实体与这些标签之间的关系是一种ManyToMany关系,我无法做出多方向的关系。

我的问题:当我删除项目的实体,它是从项目表中删除,但在这个项目和标签之间的project_tag表的关系不会被删除。然后,如果我创建一个新的Project实体,则会引发异常。

An exception exists while executing 'INSERT INTO project_tag (project_id, tag_id) VALUES (?,?)' With params [2, 4]: 

SQLSTATE [23000]: Integrity constraint violation: 19 UNIQUE constraint failed: project_tag.project_id, project_tag.tag_id 

实体:

标签

/** 
* Tag 
* 
* @ORM\Table(name="tag") 
* @ORM\Entity(repositoryClass="AppBundle\Repository\TagRepository") 
*/ 
class Tag 
{ 
    /** 
    * @var int 
    * 
    * @ORM\Column(name="id", type="integer") 
    * @ORM\Id 
    * @ORM\GeneratedValue(strategy="AUTO") 
    */ 
    private $id; 

    /** 
    * @var string 
    * 
    * @ORM\Column(name="name", type="string", length=255, unique=true) 
    */ 
    private $name; 

    /** 
    * @ORM\Column(name="last_use_at", type="datetime", nullable=false) 
    * @var \DateTime 
    */ 
    private $lastUseAt; 


    public function __construct() 
    { 
     $this->lastUseAt = new \DateTime(); 
    } 

    public function __toString() 
    { 
     return $this->name; 
    } 

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

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

     return $this; 
    } 

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

    /** 
    * @return \DateTime 
    */ 
    public function getLastUseAt(): \DateTime 
    { 
     return $this->lastUseAt; 
    } 

    /** 
    * @param \DateTime $lastUseAt 
    */ 
    public function setLastUseAt(\DateTime $lastUseAt) 
    { 
     $this->lastUseAt = $lastUseAt; 
    } 
} 

加标签

trait Taggable 
{ 

/** 
* @var ArrayCollection 
* 
* @ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", cascade={"persist"}) 
*/ 
protected $tags; 

/** 
* Add tag 
* 
* @param Tag $tag 
* 
* @return $this 
*/ 
public function addTag(Tag $tag) 
{ 
    $tag->setLastUseAt(new \DateTime()); 
    $this->tags[] = $tag; 

    return $this; 
} 

/** 
* Remove tag 
* 
* @param Tag $tag 
*/ 
public function removeTag(Tag $tag) 
{ 
    $this->tags->removeElement($tag); 
} 

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

项目

/** 
* Project 
* 
* @ORM\Table(name="project") 
* @ORM\Entity(repositoryClass="AppBundle\Repository\ProjectRepository") 
*/ 
class Project 
{ 
    use Taggable; 
} 

备注

class Note 
{ 
    use Taggable; 
} 

这是唯一的解决方案还是我的注释不完整/不正确? 我试着用JoinColumns,JoinTable和onDelete =“cascade”,但没有任何效果。

与此同时,我避开了这个指令放置在抑制之前的问题。

$project->getTags()->clear(); 

在控制器的动作全码:

/** 
* @Route("/project/{id}/delete", name="project_delete") 
*/ 
public function deleteAction($id) { 
    $em = $this->getDoctrine()->getManager(); 
    $project = $em->getRepository('AppBundle:Project')->find($id); 

    if(!$project) { 
     return $this->redirectToRoute('index'); 
    } 

    $project->getTags()->clear(); 
    $em->remove($project); 
    $em->flush(); 

    return $this->redirectToRoute('index'); 
} 

回答

1

我设法解决这个问题。这是我的解决方案适用于SQLite连接。

创建一个事件监听监听的kernel.request事件:

namespace AppBundle\EventListener; 


use Doctrine\Bundle\DoctrineBundle\Registry; 
use Doctrine\Common\Persistence\ObjectManager; 
use Symfony\Component\HttpKernel\Event\GetResponseEvent; 

class RequestListener 
{ 
    /** 
    * @var Registry 
    */ 
    private $doctrine; 

    public function __construct(Registry $doctrine) 
    { 
     $this->doctrine = $doctrine; 
    } 

    public function onKernelRequest(GetResponseEvent $event) 
    { 
     $this->doctrine->getConnection()->exec('PRAGMA foreign_keys = ON'); 
    } 
} 

服务宣言加标签和标签的

app.event_listener.request_listener: 
     class: AppBundle\EventListener\RequestListener 
     arguments: 
      - '@doctrine' 
     tags: 
      - { name: kernel.event_listener, event: kernel.request } 
+1

值得注意的是,这个解决方案特定于sqlite,如您在下面的评论中所建议的。 – ehymel

+1

也许更好的办法是使用Doctrine的[preFlush](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/events.html#preflush)事件([Symfony's事件订户](http://symfony.com/doc/current/doctrine/event_listeners_subscribers.html))。因为它会阻止在没有执行写入查询的页面上执行命令。 – naitsirch

0

我认为问题是,你有你的特质Taggable设定为多对多关系的拥有方,但你删除的反方并期待一些事情发生。学说只会检查关系的拥有方,以便坚持任何改变。有关此文档,请参阅here

您可以通过使Taggable成为每个关系的反面,或通过手动告诉原则删除拥有的一面来解决。

第一种解决方案可能不适合您,因为您不会(轻易)指定多个反面。 (你确定一个特质是正确的方法吗?)

第二个解决方案很简单。在你的实体,如Project您deleteTag($标记)功能,呼吁拥有方删除功能(例如,deleteProject($project)你将不得不建立,如果不存在

class Project 
{ 
    use Taggable; 

    public function deleteTag($tag) 
    { 
     $this->tags->removeElement($tag); 

     // persist on the owning side 
     $tag->deleteProject($this); 
    } 
} 

编辑:

在看完全部代码后,看起来你正在正确删除。现在你需要告诉教义来贯彻它。看到这个post的全部细节,但基本上您可以特质改成这样:

trait Taggable 
{ 

    /** 
     * @var ArrayCollection 
     * 
     * @ORM\ManyToMany(
     *  targetEntity="AppBundle\Entity\Tag", 
     *  cascade={"persist"}, 
     *  onDelete="CASCADE" 
     *) 
*/ 
protected $tags; 

// ... 
} 
+0

增加了完整的代码。 我不认为我们可以谈论Taggable的拥有方,它只不过是几个实体共有的一段代码的分解。这些是关系所有者的实体,不是吗? 然后,你是对的,我不能让这些实体属于反面:对于单一关系没有多个反面。 – Alex83690

+0

第二个解决方案似乎不符合我的问题。 我可以从项目中删除标签; Taggable中的setTags正常工作并正确删除了project_tag表中的关系。 这是完全删除有问题的项目:连接表project_tag与项目对应的外键不会被删除。 – Alex83690

+0

感谢您发布完整的代码,这很有帮助。看起来您正在从正确的实体中删除,所以问题在于其他地方。我会更新我的回答,并建议在你的特质中使用'onDelete =“CASCADE”''。 – ehymel