Symfony 2.7.2。 Doctrine ORM 2.4.7。 MySQL 5.6.12。 PHP 5.5.0。
我有一个具有自定义ID生成器策略的实体。它完美无缺
在某些情况下,我必须用“手工制作”ID覆盖此策略。它在主要实体被刷新而没有关联时起作用。但它不适用于协会。抛出此示例错误:
使用params [“a004r0”,4]执行'INSERT INTO articles_tags(article_id,tag_id)VALUES(?,?)'时发生异常:
SQLSTATE [23000]:完整性约束违规:1452无法添加或更新子行:外键约束失败(
sf-test1
。articles_tags
,CONSTRAINTFK_354053617294869C
FOREIGN KEY({{ 1}})参考article_id
(article
)ON DELETE CASCADE)
以下是如何重现:
id
。使用示例app/config/parameters.yml
命名空间,在AppBundle
目录中创建Article
和Tag
个实体。
src/AppBundle/Entity
<?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;
}
}
为上述实体生成getter和setter:
<?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;
}
在php app/console doctrine:generate:entities AppBundle
中创建ArticleNumberGenerator
课程:
src/AppBundle/Doctrine
创建数据库:<?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';
}
}
。
php app/console doctrine:database:create
。修改位于php app/console doctrine:schema:create
的示例AppBundle DefaultController
。将内容替换为:
src\AppBundle\Controller
运行服务器:<?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() . '.');
}
}
。
导航至http://127.0.0.1:8000/create-default。刷新2次以查看此消息:
创建文章ID a003r0。
现在,导航至http://127.0.0.1:8000/create-handmade/test。预期结果是:
创建文章ID test1。
但你会得到错误:
使用params [“a004r0”,4]执行'INSERT INTO articles_tags(article_id,tag_id)VALUES(?,?)'时发生异常:
SQLSTATE [23000]:完整性约束违规:1452无法添加或更新子行:外键约束失败(
php app/console server:run
。sf-test1
,CONSTRAINTarticles_tags
FOREIGN KEY({{ 1}})参考FK_354053617294869C
(article_id
)ON DELETE CASCADE)
显然因为article
“a004r0”的文章不存在。
如果我在id
中对id
发表评论,那么就可以了 - 结果是:
创建文章内容测试。
并相应地更新数据库:
$article->getTags()->add($tag);
但不是在添加关系时。出于某种原因,Doctrine不使用手工制作的createHandmadeAction
作为关联,而是使用默认的Id生成器策略。
这里有什么问题?如何说服实体经理将我的手工制作的ID用于协会?
答案 0 :(得分:4)
在更改$em->persist($article);
之前,您的问题与调用ClassMetadata
有关。
持久保存新实体UnitOfWork
会生成id
ArticleNumberGenerator
并将其保存到entityIdentifiers
字段中。之后ManyToManyPersister
在PersistentCollection
的帮助下使用此值来填充关系表行。
在调用flush
UoW
时,会计算实体的更改集并保存实际的ID值 - 这就是为什么在纪念添加关联后获得正确数据的原因。但它不会更新entityIdentifiers
的数据。
要解决此问题,您只需移动persist
,即可更改ClassMetadata对象。但方式仍然看起来像黑客。 IMO更优化的方法是编写自定义生成器,如果提供了自定义生成器,则使用已分配的id或生成新的。
<强> PS 即可。另一件应该考虑的事情 - 你的生成方式不安全,它会在高负载时产生重复的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() . '.');
}
这就像预期的那样。