草庐IT

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

coder 2024-01-02 原文

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

An exception occurred while executing 'INSERT INTO articles_tags (article_id, tag_id) VALUES (?, ?)' with params ["a004r0", 4]:

SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (sf-test1.articles_tags, CONSTRAINT FK_354053617294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE)

重现方法如下:

  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 次以查看此消息:

    Created article id a003r0.

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

    Created article id test1.

    但是你会得到错误:

    An exception occurred while executing 'INSERT INTO articles_tags (article_id, tag_id) VALUES (?, ?)' with params ["a004r0", 4]:

    SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (sf-test1.articles_tags, CONSTRAINT FK_354053617294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE)

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

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

Created article 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 时,使用 ArticleNumberGenerator 生成 id 并将其保存到 entityIdentifiers 字段中。稍后 ManyToManyPersisterPersistentCollection 的帮助下使用此值填充关系表行。

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

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

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

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

/**
 * @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() . '.');
}

这按预期工作。

关于php - 覆盖默认标识符生成策略对关联没有影响,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31594338/

有关php - 覆盖默认标识符生成策略对关联没有影响的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby - 难道Lua没有和Ruby的method_missing相媲美的东西吗? - 2

    我好像记得Lua有类似Ruby的method_missing的东西。还是我记错了? 最佳答案 表的metatable的__index和__newindex可以用于与Ruby的method_missing相同的效果。 关于ruby-难道Lua没有和Ruby的method_missing相媲美的东西吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7732154/

  3. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  4. ruby-on-rails - rails 目前在重启后没有安装 - 2

    我有一个奇怪的问题:我在rvm上安装了ruby​​onrails。一切正常,我可以创建项目。但是在我输入“railsnew”时重新启动后,我有“程序'rails'当前未安装。”。SystemUbuntu12.04ruby-v"1.9.3p194"gemlistactionmailer(3.2.5)actionpack(3.2.5)activemodel(3.2.5)activerecord(3.2.5)activeresource(3.2.5)activesupport(3.2.5)arel(3.0.2)builder(3.0.0)bundler(1.1.4)coffee-rails(

  5. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  6. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  7. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  8. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  9. ruby - 无法覆盖 irb 中的 to_s - 2

    我在pry中定义了一个函数:to_s,但我无法调用它。这个方法去哪里了,怎么调用?pry(main)>defto_spry(main)*'hello'pry(main)*endpry(main)>to_s=>"main"我的ruby版本是2.1.2看了一些答案和搜索后,我认为我得到了正确的答案:这个方法用在什么地方?在irb或pry中定义方法时,会转到Object.instance_methods[1]pry(main)>defto_s[1]pry(main)*'hello'[1]pry(main)*end=>:to_s[2]pry(main)>defhello[2]pry(main)

  10. ruby - 覆盖相似的方法,更短的语法 - 2

    在Ruby类中,我重写了三个方法,并且在每个方法中,我基本上做同样的事情:classExampleClassdefconfirmation_required?is_allowed&&superenddefpostpone_email_change?is_allowed&&superenddefreconfirmation_required?is_allowed&&superendend有更简洁的语法吗?如何缩短代码? 最佳答案 如何使用别名?classExampleClassdefconfirmation_required?is_a

随机推荐