Symfony Doctrine2 manyToMany关系未删除 - 特定于SQLite

时间:2017-04-11 21:45:19

标签: php entity-framework symfony doctrine-orm

我有几个类使用Taggable特性来设置几个学说实体共有的标签系统(Project,Note,...)。

这些实体与这些标签之间的关系是一种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');
}

3 个答案:

答案 0 :(得分:2)

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

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

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 :(得分:2)

我认为我找到了一个更好的解决方案:您可以在Doctrine配置中设置PRAGMA。喜欢:

doctrine:
    dbal:
        # configure these for your database server
        driver: 'pdo_sqlite'
        #server_version: '5.7'
        #charset: utf8mb4
        #default_table_options:
            #charset: utf8mb4
            #collate: utf8mb4_unicode_ci

        url: '%env(resolve:DATABASE_URL)%'
        options:
            'PRAGMA foreign_keys': 'ON'

我刚刚在Symfony 4应用程序上进行了尝试,重新创建了数据库,并使用DB Browser for SQLite进行了测试,并且运行正常。

希望这会有所帮助

答案 2 :(得分:0)

我认为问题在于您将特征Taggable设置为ManyToMany关系的拥有方,但是您正在删除反面并期望某些事情发生。 Doctrine只会检查关系的拥有方,以便持续进行任何更改。有关此问题,请参阅here

您可以通过将Taggable与每个关系的反面相对应,或者通过手动告诉学说删除拥有方来解决。

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

第二种解决方案很简单。在您的deleteTag($ tag)函数的Project等实体中,请在拥有方调用删除函数(例如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;

// ...
}