覆盖默认标识符生成策略对关联没有影响



Symfony 2.7.2。原则ORM 2.4.7。MySQL 5.6.12。PHP发送的。
我有一个自定义ID生成器策略的实体。它可以完美地工作。
在某些情况下,我必须使用"手工"Id来覆盖此策略。它在没有关联的情况下刷新主实体时起作用。但它对联想不起作用。抛出的错误示例如下:

执行INSERT INTO articles_tags (article_id, tag_id) VALUES (?, ?)' with params ["a004r0", 4]:

SQLSTATE[23000]: Integrity constraint violation: 1452不能添加或更新子行:a foreign key constraint fails (sf-test1 . c .)articles_tags,约束FK_354053617294869C外键(article_id)引用article (id) ON DELETE CASCADE

下面是如何复制的方法:
  1. 安装并创建Symfony2应用程序。
  2. 用DB参数编辑app/config/parameters.yml
  3. AppBundle命名空间为例,在src/AppBundle/Entity目录下创建ArticleTag实体。

    <?php
    // src/AppBundle/Entity/Article.php
    namespace AppBundleEntity;
    use DoctrineORMMapping as ORM;
    /**
     * @ORMEntity
     * @ORMTable(name="article")
     */
    class Article
    {
        /**
         * @ORMColumn(type="string")
         * @ORMId
         * @ORMGeneratedValue(strategy="CUSTOM")
         * @ORMCustomIdGenerator(class="AppBundleDoctrineArticleNumberGenerator")
         */
        protected $id;
        /**
         * @ORMColumn(type="string", length=255)
         */
        protected $title;
        /**
         * @ORMManyToMany(targetEntity="Tag", inversedBy="articles" ,cascade={"all"})
         * @ORMJoinTable(name="articles_tags")
         **/
        private $tags;
        public function setId($id)
        {
            $this->id = $id;
        }
    }
    
    <?php
    // src/AppBundle/Entity/Tag.php
    namespace AppBundleEntity;
    use DoctrineORMMapping as ORM;
    use DoctrineCommonCollectionsArrayCollection;
    /**
     * @ORMEntity
     * @ORMTable(name="tag")
     */
    class Tag
    {
        /**
         * @ORMColumn(type="integer")
         * @ORMId
         * @ORMGeneratedValue
         */
        protected $id;
        /**
         * @ORMColumn(type="string", length=255)
         */
        protected $name;
        /**
         * @ORMManyToMany(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 AppBundleDoctrine;
    use DoctrineORMIdAbstractIdGenerator;
    use DoctrineORMQueryResultSetMapping;
    class ArticleNumberGenerator extends AbstractIdGenerator
    {
        public function generate(DoctrineORMEntityManager $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. 编辑示例AppBundle DefaultController位于srcAppBundleController。将内容替换为:

    <?php
    // src/AppBundle/Controller/DefaultController.php
    namespace AppBundleController;
    use SensioBundleFrameworkExtraBundleConfigurationRoute;
    use SymfonyBundleFrameworkBundleControllerController;
    use SymfonyComponentHttpFoundationRequest;
    use SymfonyComponentHttpFoundationResponse;
    use AppBundleEntityArticle;
    use AppBundleEntityTag;
    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(DoctrineORMMappingClassMetadata::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. Run server: 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.

    但是你会得到错误:

    执行INSERT INTO articles_tags (article_id, tag_id) VALUES (?, ?)' with params ["a004r0", 4]:

    SQLSTATE[23000]: Integrity constraint violation: 1452不能添加或更新子行:a foreign key constraint fails (sf-test1 . 0)。articles_tags,约束FK_354053617294869C外键(article_id)引用article (id) ON DELETE CASCADE

    显然是因为id "a004r0"的文章不存在

如果我在createHandmadeAction中注释掉$article->getTags()->add($tag);,它可以工作-结果是:

已创建文章id test.

并相应地更新数据库:

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

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

怎么了?如何说服实体管理器使用我的手工id进行关联?

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

在持久化新实体UnitOfWork时,生成idArticleNumberGenerator并保存到entityIdentifiers字段。后来ManyToManyPersisterPersistentCollection的帮助下使用这个值来填充关系表行。

调用flush时,UoW计算实体的更改集并保存实际的id值-这就是为什么在添加关联后注释得到正确的数据。但是没有更新entityIdentifiers的数据

要解决这个问题,您可以将persist移动到更改ClassMetadata对象之后。但这条路看起来还是很糟糕。在我看来,更理想的方法是编写自定义生成器,如果提供了指定的id,它将使用指定的id或生成新的id。

p 。另一件应该考虑的事情是——您生成id的方式是不安全的,它会在高负载时产生重复的id。

乌利希期刊指南错过了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(DoctrineORMMappingClassMetadata::GENERATOR_TYPE_NONE);
    $metadata->setIdGenerator(new DoctrineORMIdAssignedGenerator());
    $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() . '.');
}

最新更新