我花了最后几天解决a very subtle bug in my bundle。
实际上我从数据库中获得了一个Job
实体。此实体与属性one-to-one
上的另一个Job
实体具有retryOf
自引用关系。
当我尝试更新检索到的Job#status
属性时,我会抛出以下异常:
[学说\ ORM \ ORMInvalidArgumentException]
通过这种关系找到了一个新实体 ' SerendipityHQ \包\ CommandsQueuesBundle \实体\作业#retryOf'那 未配置为级联实体的持久操作: SerendipityHQ \包\ CommandsQueuesBundle \实体\作业@ 000000004ab2727c0000000065 d15d74。解决此问题:显式调用 EntityManager#persist()在此未知实体上或配置级联 例如,在映射中保持此关联 @ManyToOne(..,级联= {"坚持"})。如果你找不到哪个 实体导致问题实施 ' SerendipityHQ \捆绑\ CommandsQueuesBundle \实体\作业#__的toString()'至 得到一个线索。
最后,我发现我用来检索和保存/刷新实体的EntityManager
与我的实体用来管理自己和检索相关实体的实例不同。
所以,我的问题是:假设我在我的服务(EntityManager
)中注入了正确的@doctrine.orm.default_entity_manager
:
EntityManager
services:
queues:
class: SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesManager
arguments: ["@commands_queues.do_not_use.entity_manager"]
#This service is meant to be privately used by QueuesRunCommand
commands_queues.do_not_use.daemon:
class: SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesDaemon
arguments: ["@commands_queues.do_not_use.entity_manager"]
在EntityManager
(see its code on GitHub)中创建了SHQCommandsQueuesExtension
。
问题非常严重,因为我收到错误但我无法在服务使用的实体管理器中再次存在:这会导致数据库中的行重复!
JobRepository
CLASS /**
* Basic properties and methods o a Job.
*
* @ORM\Entity(repositoryClass="SerendipityHQ\Bundle\CommandsQueuesBundle\Repository\JobRepository")
* @ORM\Table(name="queues_scheduled_jobs")
*/
class Job
{
...
}
/**
* {@inheritdoc}
*/
class JobRepository extends EntityRepository
{
/**
* @param int $id
*
* @return null|object|Job
*/
public function findOneById(int $id)
{
return parent::findOneBy(['id' => $id]);
}
/**
* Returns a Job that can be run.
*
* A Job can be run if it hasn't a startDate in the future and if its parent Jobs are already terminated with
* success.
*
* @return null|Job
*/
public function findNextRunnableJob()
{
// Collects the Jobs that have to be excluded from the next findNextJob() call
$excludedJobs = [];
while (null !== $job = $this->findNextJob($excludedJobs)) {
// If it can be run...
if (false === $job->hasNotFinishedParentJobs()) {
// ... Return it
return $job;
}
// The Job cannot be run or its lock cannot be acquired
$excludedJobs[] = $job->getId();
// Remove it from the Entity Manager to free some memory
$this->_em->detach($job);
}
}
/**
* Finds the next Job to process.
*
* @param array $excludedJobs The Jobs that have to be excluded from the SELECT
*
* @return Job|null
*/
private function findNextJob(array $excludedJobs = [])
{
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$queryBuilder->select('j')->from('SHQCommandsQueuesBundle:Job', 'j')
->orderBy('j.priority', 'ASC')
->addOrderBy('j.createdAt', 'ASC')
// The status MUST be NEW
->where($queryBuilder->expr()->eq('j.status', ':status'))->setParameter('status', Job::STATUS_NEW)
// It hasn't an executeAfterTime set or the set time is in the past
->andWhere(
$queryBuilder->expr()->orX(
$queryBuilder->expr()->isNull('j.executeAfterTime'),
$queryBuilder->expr()->lt('j.executeAfterTime', ':now')
)
)->setParameter('now', new \DateTime(), 'datetime');
// If there are excluded Jobs...
if (false === empty($excludedJobs)) {
// The ID hasn't to be one of them
$queryBuilder->andWhere(
$queryBuilder->expr()->notIn('j.id', ':excludedJobs')
)->setParameter('excludedJobs', $excludedJobs, Connection::PARAM_INT_ARRAY);
}
return $queryBuilder->getQuery()->setMaxResults(1)->getOneOrNullResult();
}
}
只需在@Entity
实体类的Job
注释中指定存储库。
EntityManager
是不同的EntityManager
我在服务中使用:工作,不再抛出异常这迫使我做这样的事情:
...
// SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesDaemon
if ($job->isRetry()) {
// Here I have to persist AGAIN in my injected entity manager the Entity referenced by Job#retryOf
$this->entityManager->persist($job->getRetryOf());
...
}
...
如果我不这样做,则会再次抛出异常。
所以似乎annotations
使用EntityManager
和我的服务加载实体,而是使用我指定的实体管理器... 这是非常奇怪,从未发生在我身上! O.O
VarDump
:显示两个不同的唯一标识符使用简单的VarDumper::dump($passedEntityManager)
和VarDumper::dump($entity)
,我发现他们使用了两个不同的实体经理:
...
// SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesDaemon
if ($job->isRetry()) {
//$job->setRetryOf($this->entityManager->getRepository('SHQCommandsQueuesBundle:Job')->findOneById($job->getRetryOf()->getId()));
VarDumper::dump($this->entityManager);
VarDumper::dump($job);
die;
...
}
...
结果是:
EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager {#768
-delegate: DoctrineORMEntityManager_000000007aac762a000000007cf8d85284b5df468960200ce73b9230d68d81c1 {#773 …2}
-container: appDevDebugProjectContainer {#495 …12}
-config: null
-conn: null
-metadataFactory: null
-unitOfWork: null
-eventManager: null
-proxyFactory: null
-repositoryFactory: null
-expressionBuilder: null
-closed: false
-filterCollection: null
-cache: null
}
SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job {#730
-id: 3
...
-childDependencies: Doctrine\ORM\PersistentCollection {#41028
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: true
}
-parentDependencies: Doctrine\ORM\PersistentCollection {#41019
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: true
}
...
-processedBy: SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Daemon {#806
...
-processedJobs: Doctrine\ORM\PersistentCollection {#819
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: true
}
}
...
-childDependencies: Doctrine\ORM\PersistentCollection {#40899
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: false
}
-parentDependencies: Doctrine\ORM\PersistentCollection {#40901
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: true
}
...
}
在实体中,实体经理是#788
:
SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job {#725
-id: 3
-command: "queues:test"
...
-childDependencies: Doctrine\ORM\PersistentCollection {#41028
-snapshot: []
-owner: SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job {#725}
-association: array:16 [ …16]
-em: Doctrine\ORM\EntityManager {#788 …11}
虽然我注入的实体经理是768
:
EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager {#768
-delegate: DoctrineORMEntityManager_000000007aac762a000000007cf8d85284b5df468960200ce73b9230d68d81c1. {#773 …2}
EntityManager
:我有Aerendir$ app/console debug:container | grep EntityManager
aws_ses_monitor.entity_manager Doctrine\ORM\EntityManager
commands_queues.do_not_use.entity_manager Doctrine\ORM\EntityManager
doctrine.orm.default_entity_manager EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager
stripe_bundle.entity_manager Doctrine\ORM\EntityManager
这是正确的,因为我实际上有4个实体经理,报告的是我期望存在的那些。
EntityManager
以及我的存储库中使用了哪个 // SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesDaemon
if ($job->isRetry()) {
VarDumper::dump('entityManager');
VarDumper::dump(spl_object_hash($this->entityManager));
VarDumper::dump('aws_ses_monitor.entity_manager');
VarDumper::dump(spl_object_hash($this->container->get('aws_ses_monitor.entity_manager')));
VarDumper::dump('commands_queues.do_not_use.entity_manager');
VarDumper::dump(spl_object_hash($this->container->get('commands_queues.do_not_use.entity_manager')));
VarDumper::dump('doctrine.orm.default_entity_manager');
VarDumper::dump(spl_object_hash($this->container->get('doctrine.orm.default_entity_manager')));
VarDumper::dump('stripe_bundle.entity_manager');
VarDumper::dump(spl_object_hash($this->container->get('stripe_bundle.entity_manager')));
模具; ... }
在JobRepository
课程中:
private function findNextJob(array $excludedJobs = [])
{
VarDumper::dump('Inside repository');
VarDumper::dump(spl_object_hash($this->getEntityManager()));
...
结果:
"Inside repository"
"00000000326f44a000000000730f8ec4"
"entityManager"
"00000000326f44b400000000730f8ec4"
"aws_ses_monitor.entity_manager"
"00000000326f44b400000000730f8ec4"
"commands_queues.do_not_use.entity_manager"
"00000000326f44b400000000730f8ec4"
"doctrine.orm.default_entity_manager"
"00000000326f44b400000000730f8ec4"
"stripe_bundle.entity_manager"
"00000000326f44b400000000730f8ec4"
它永远都是一样的!这真是出乎意料!如果EntityManager
始终相同:
Job#retryOf
对象尚未保留,我必须再次坚持它才能获得抛出的异常?VarDumper
gives me two different internal object handles如果对象是相同的?答案 0 :(得分:1)
$this->entityManager->detach()
解决了!问题是detach()
未检查状态:如果Job
失败,我只是将其分离,使其不会在EntityManager
中浮动,并且捆绑启动守护程序,我需要释放记忆。但是我只有在Job
肯定失败的时候才需要释放它,而如果必须重试它,我就不必分离它......或者至少我必须重新加载它,如果处理{{1引用它......
该死的,建立一个守护进程需要大量关注细节!