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
- 安装并创建Symfony2应用程序。
- 用DB参数编辑
app/config/parameters.yml
。 以
AppBundle
命名空间为例,在src/AppBundle/Entity
目录下创建Article
和Tag
实体。<?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; }
为上述实体生成getter和setter:
php app/console doctrine:generate:entities AppBundle
在
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'; } }
创建数据库:
php app/console doctrine:database:create
.- 创建表:
php app/console doctrine:schema:create
. 编辑示例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() . '.'); } }
Run server:
php app/console server:run
.导航到http://127.0.0.1:8000/create-default。刷新2次以查看此消息:
已创建的文章id a003r0.
现在,导航到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
时,生成id
和ArticleNumberGenerator
并保存到entityIdentifiers
字段。后来ManyToManyPersister
在PersistentCollection
的帮助下使用这个值来填充关系表行。
调用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() . '.');
}