【问题标题】:Symfony Doctrine2 manyToMany relationship not removed - Specific to SQLiteSymfony Doctrine2 manyToMany 关系未删除 - 特定于 SQLite
【发布时间】:2017-04-11 21:45:18
【问题描述】:

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

这些实体和这些标签之间的关系是多对多的关系,我无法做到多向。

我的问题:当我删除一个 Project 实体时,它从 project 表中被删除了,但是 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');
}

【问题讨论】:

    标签: php entity-framework symfony doctrine-orm


    【解决方案1】:

    我想我找到了一个更好的解决方案:您可以在 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 进行了测试,它按我的预期工作。

    希望对你有帮助

    【讨论】:

    • @Alex83690 我不这么认为...如果您使用的是 Symfony 的旧版本,您进行更改的文件可能是另一个文件,但内容应该相同
    【解决方案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 }
    

    【讨论】:

    • 值得注意的是,正如您在下面的 cmets 中建议的那样,此解决方案特定于 sqlite。
    • 也许更好的是使用 Doctrine 的preFlush 事件(使用Symfony's event subscriber)。因为它会阻止在没有执行写入查询的页面上执行命令。
    【解决方案3】:

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

    您可以通过将Taggable 设置为每个关系的反面来解决,或者通过手动告诉教义删除拥有方。

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

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

    【讨论】:

    • 添加了Taggable和Tag的完整代码。我认为我们不能谈论 Taggable 的拥有方,这只不过是对多个实体共有的一段代码的分解。这些是关系所有者的实体,不是吗?那么,你是对的,我不能让这些实体属于反面:单个关系没有多个反面。
    • 第二种解决方案似乎不适合我的问题。我可以从项目中删除标签; Taggable 中的 setTags 工作正常,并正确删除了 project_tag 表中的关系。有问题的是项目的完全删除:没有删除项目对应的外键的连接表project_tag。
    • 感谢您发布完整代码,这很有帮助。看起来您正在从正确的实体中删除,所以问题出在其他地方。我会更新我的答案,建议在你的 trait 中使用 onDelete="CASCADE"
    • [Creation Error] The annotation @ORM\ManyToMany declared on property AppBundle\Entity\Project::$tags does not have a property named "onDelete". Available properties: targetEntity, mappedBy, inversedBy, cascade, fetch, orphanRemoval, indexBy 我也在 ORM \JoinColumn 中尝试过 onDelete="cascade"。
    • 我想我找到了问题所在。显然,这来自于 Doctrine 错误配置的 sqlite。如何将此配置添加到我的连接中? foreign_keys:开
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多