【问题标题】:Overriding default identifier generation strategy has no effect on associations覆盖默认标识符生成策略对关联没有影响
【发布时间】:2015-10-14 04:05:32
【问题描述】:

Symfony 2.7.2。教义 ORM 2.4.7。 MySQL 5.6.12。 PHP 5.5.0.
我有一个具有自定义 ID 生成器策略的实体。它完美无瑕。
在某些情况下,我必须用“手工” ID 覆盖此策略。它在没有关联的情况下刷新主要实体时起作用。但它不适用于关联。抛出此示例错误:

使用参数 ["a004r0", 4] 执行 'INSERT INTO article_tags (article_id, tag_id) VALUES (?, ?)' 时发生异常:

SQLSTATE[23000]:完整性约束违规:1452 无法添加或更新子行:外键约束失败(sf-test1.articles_tags,CONSTRAINT FK_354053617294869C FOREIGN KEY(article_id)参考article (id) 删除级联)

复制方法如下:

  1. Install and create a Symfony2 application
  2. 使用您的数据库参数编辑app/config/parameters.yml
  3. 使用示例AppBundle 命名空间,在src/AppBundle/Entity 目录中创建ArticleTag 实体。

    <?php
    // src/AppBundle/Entity/Article.php
    namespace AppBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity
     * @ORM\Table(name="article")
     */
    class Article
    {
        /**
         * @ORM\Column(type="string")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="CUSTOM")
         * @ORM\CustomIdGenerator(class="AppBundle\Doctrine\ArticleNumberGenerator")
         */
        protected $id;
    
        /**
         * @ORM\Column(type="string", length=255)
         */
        protected $title;
    
        /**
         * @ORM\ManyToMany(targetEntity="Tag", inversedBy="articles" ,cascade={"all"})
         * @ORM\JoinTable(name="articles_tags")
         **/
        private $tags;
    
        public function setId($id)
        {
            $this->id = $id;
        }
    }
    
    <?php
    // src/AppBundle/Entity/Tag.php
    namespace AppBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Doctrine\Common\Collections\ArrayCollection;
    
    /**
     * @ORM\Entity
     * @ORM\Table(name="tag")
     */
    class Tag
    {
        /**
         * @ORM\Column(type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue
         */
        protected $id;
    
        /**
         * @ORM\Column(type="string", length=255)
         */
        protected $name;
    
        /**
         * @ORM\ManyToMany(targetEntity="Article", mappedBy="tags")
         **/
        private $articles;
    }
    
  4. 为上述实体生成getter和setter:

    php app/console doctrine:generate:entities AppBundle
    
  5. src/AppBundle/Doctrine 中创建ArticleNumberGenerator 类:

    <?php
    // src/AppBundle/Doctrine/ArticleNumberGenerator.php
    namespace AppBundle\Doctrine;
    use Doctrine\ORM\Id\AbstractIdGenerator;
    use Doctrine\ORM\Query\ResultSetMapping;
    
    class ArticleNumberGenerator extends AbstractIdGenerator
    {
        public function generate(\Doctrine\ORM\EntityManager $em, $entity)
        {
            $rsm = new ResultSetMapping();
            $rsm->addScalarResult('id', 'article', 'string');
            $query = $em->createNativeQuery('select max(`id`) as id from `article` where `id` like :id_pattern', $rsm);
            $query->setParameter('id_pattern', 'a___r_');
            $idMax = (int) substr($query->getSingleScalarResult(), 1, 3);
            $idMax++;
            return 'a' . str_pad($idMax, 3, '0', STR_PAD_LEFT) . 'r0';
        }
    }
    
  6. 创建数据库:php app/console doctrine:database:create

  7. 创建表:php app/console doctrine:schema:create
  8. 编辑位于src\AppBundle\Controller 中的示例AppBundle DefaultController。将内容替换为:

    <?php
    // src/AppBundle/Controller/DefaultController.php
    namespace AppBundle\Controller;
    
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    
    use AppBundle\Entity\Article;
    use AppBundle\Entity\Tag;
    
    class DefaultController extends Controller
    {
        /**
         * @Route("/create-default")
         */
        public function createDefaultAction()
        {
            $tag = new Tag();
            $tag->setName('Tag ' . rand(1, 99));
    
            $article = new Article();
            $article->setTitle('Test article ' . rand(1, 999));
            $article->getTags()->add($tag);
    
            $em = $this->getDoctrine()->getManager();
    
            $em->getConnection()->beginTransaction();
            $em->persist($article);
    
            try {
                $em->flush();
                $em->getConnection()->commit();
            } catch (\RuntimeException $e) {
                $em->getConnection()->rollBack();
                throw $e;
            }
    
            return new Response('Created article id ' . $article->getId() . '.');
        }
    
        /**
         * @Route("/create-handmade/{handmade}")
         */
        public function createHandmadeAction($handmade)
        {
            $tag = new Tag();
            $tag->setName('Tag ' . rand(1, 99));
    
            $article = new Article();
            $article->setTitle('Test article ' . rand(1, 999));
            $article->getTags()->add($tag);
    
            $em = $this->getDoctrine()->getManager();
    
            $em->getConnection()->beginTransaction();
            $em->persist($article);
    
            $metadata = $em->getClassMetadata(get_class($article));
            $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
            $article->setId($handmade);
    
            try {
                $em->flush();
                $em->getConnection()->commit();
            } catch (\RuntimeException $e) {
                $em->getConnection()->rollBack();
                throw $e;
            }
    
            return new Response('Created article id ' . $article->getId() . '.');
        }
    }
    
  9. 运行服务器:php app/console server:run

  10. 导航到http://127.0.0.1:8000/create-default。刷新 2 次即可看到此消息:

    已创建文章 ID a003r0。

  11. 现在,导航到http://127.0.0.1:8000/create-handmade/test。预期结果是:

    已创建文章 ID test1。

    但是你会得到错误:

    使用参数 ["a004r0", 4] 执行 'INSERT INTO article_tags (article_id, tag_id) VALUES (?, ?)' 时发生异常:

    SQLSTATE[23000]:完整性约束违规:1452 无法添加或更新子行:外键约束失败(sf-test1.articles_tags,CONSTRAINT FK_354053617294869C FOREIGN KEY(article_id)参考article (id) 删除级联)

    显然是因为带有id“a004r0”的文章不存在。

如果我在 createHandmadeAction 中注释掉 $article-&gt;getTags()-&gt;add($tag);,它会起作用 - 结果是:

创建文章 ID 测试。

并相应更新数据库:

id     | title
-------+----------------
a001r0 | Test article 204
a002r0 | Test article 12
a003r0 | Test article 549
test   | Test article 723

但不是在添加关系时。出于某种原因,Doctrine 没有使用手工制作的id 进行关联,而是使用默认的 Id 生成器策略。

这里有什么问题?如何说服实体经理使用我的手工 Id 进行关联?

【问题讨论】:

    标签: php symfony oop doctrine-orm associations


    【解决方案1】:

    您的问题与在更改ClassMetadata 之前调用$em-&gt;persist($article); 有关。

    在持久化新实体UnitOfWork 时生成idArticleNumberGenerator 并将其保存到entityIdentifiers 字段中。稍后ManyToManyPersisterPersistentCollection 的帮助下使用此值填充关系表行。

    在调用 flush UoW 时计算实体的更改集并保存实际的 id 值 - 这就是您在添加关联后得到正确数据的原因。但不会更新entityIdentifiers的数据。

    要解决此问题,您只需将 persist 移到更改 ClassMetadata 对象的后面。但是这种方式仍然看起来像hack。 IMO 更理想的方法是编写自定义生成器,如果提供了分配的 id 或生成新的,则该生成器将使用该生成器。

    附言。应该考虑的另一件事-您的生成ID方式不安全,它会在高负载时产生重复的ID。

    UPD 错过了UoW不使用idGeneratorType(元数据工厂使用它来设置正确的idGenerator值)所以你应该设置正确的idGenerator

    /**
     * @Route("/create-handmade/{handmade}")
     */
    public function createHandmadeAction($handmade)
    {
        $tag = new Tag();
        $tag->setName('Tag ' . rand(1, 99));
    
        $article = new Article();
        $article->setTitle('Test article ' . rand(1, 999));
        $article->getTags()->add($tag);
    
        $em = $this->getDoctrine()->getManager();
    
        $em->getConnection()->beginTransaction();
    
        $metadata = $em->getClassMetadata(get_class($article));
        $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
        $metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
        $article->setId($handmade);
    
        $em->persist($article);
    
        try {
            $em->flush();
            $em->getConnection()->commit();
        } catch (\RuntimeException $e) {
            $em->getConnection()->rollBack();
            throw $e;
        }
    
        return new Response('Created article id ' . $article->getId() . '.');
    }
    

    这符合预期。

    【讨论】:

    • 就是这样。在具有所有类型关联的生产应用程序中进行了测试 - 它可以工作。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-28
    • 2022-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多