我花了最后几天的时间解决捆绑包中一个非常微妙的错误。
实际上,我从数据库中获得了一个Job
实体。该实体与属性retryOf
上的另一个Job
实体具有one-to-one
、自引用关系。
当我尝试更新检索到的Job#status
属性时,我收到以下异常:
[Doctrine\ORM\ORMInvalidArgumentException]
通过关系发现了一个新实体 'SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job#retryOf' that 未配置为对实体进行级联持久操作: SerendipityHQ\Bundle\CommandQueuesBundle\Entity\Job@000000004ab2727c0000000065 d15d74.要解决此问题:显式调用 EntityManager#persist() 或配置级联 例如,将此关联保留在映射中 @ManyToOne(..,cascade={"persist"}).如果您找不到哪个 实体导致问题实现 'SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job#__toString()' to 获取线索。
最后,我发现我用于检索和持久/刷新实体的EntityManager
与我的实体用于管理自身和检索相关实体的不同。
所以,我的问题是:假设我在我的服务中注入了正确的EntityManager
(@doctrine.orm.default_entity_manager
):
- 为什么我的服务使用一个,而存储库使用另一个?
- 如何让我的实体使用我在服务中注入的相同内容?(或者我如何注入我的实体使用的相同 - 是相同的)?
我如何注入
EntityManager
services:
queues:
class: SerendipityHQBundleCommandsQueuesBundleServiceQueuesManager
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: SerendipityHQBundleCommandsQueuesBundleServiceQueuesDaemon
arguments: ["@commands_queues.do_not_use.entity_manager"]
EntityManager
在SHQCommandsQueuesExtension
中创建(请参阅 GitHub 上的代码)。
问题真的很困难,因为我收到错误,但我无法再次保留在服务使用的实体管理器中:这会导致数据库中的行重复!
如何指定JobRepository
类
/**
* Basic properties and methods o a Job.
*
* @ORMEntity(repositoryClass="SerendipityHQBundleCommandsQueuesBundleRepositoryJobRepository")
* @ORMTable(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();
}
}
存储库只是在Job
实体类的@Entity
注释中指定。
测试
证明两个EntityManager
是不同的
测试 1.再次坚持我在服务中使用的EntityManager
:有效,不再抛出异常
这迫使我做这样的事情:
...
// SerendipityHQBundleCommandsQueuesBundleServiceQueuesDaemon
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
测试 2.VarDump
:显示两个不同的唯一标识符
通过一个简单的VarDumper::dump($passedEntityManager)
和VarDumper::dump($entity)
我发现他们使用两个不同的实体管理器:
...
// SerendipityHQBundleCommandsQueuesBundleServiceQueuesDaemon
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__DoctrineORMEntityManager {#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
}
SerendipityHQBundleCommandsQueuesBundleEntityJob {#730
-id: 3
...
-childDependencies: DoctrineORMPersistentCollection {#41028
...
-em: DoctrineORMEntityManager {#788 …11}
...
#initialized: true
}
-parentDependencies: DoctrineORMPersistentCollection {#41019
...
-em: DoctrineORMEntityManager {#788 …11}
...
#initialized: true
}
...
-processedBy: SerendipityHQBundleCommandsQueuesBundleEntityDaemon {#806
...
-processedJobs: DoctrineORMPersistentCollection {#819
...
-em: DoctrineORMEntityManager {#788 …11}
...
#initialized: true
}
}
...
-childDependencies: DoctrineORMPersistentCollection {#40899
...
-em: DoctrineORMEntityManager {#788 …11}
...
#initialized: false
}
-parentDependencies: DoctrineORMPersistentCollection {#40901
...
-em: DoctrineORMEntityManager {#788 …11}
...
#initialized: true
}
...
}
在实体中,实体管理器#788
:
SerendipityHQBundleCommandsQueuesBundleEntityJob {#725
-id: 3
-command: "queues:test"
...
-childDependencies: DoctrineORMPersistentCollection {#41028
-snapshot: []
-owner: SerendipityHQBundleCommandsQueuesBundleEntityJob {#725}
-association: array:16 [ …16]
-em: DoctrineORMEntityManager {#788 …11}
当我注入的实体管理器768
时:
EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919__CG__DoctrineORMEntityManager {#768
-delegate: DoctrineORMEntityManager_000000007aac762a000000007cf8d85284b5df468960200ce73b9230d68d81c1. {#773 …2}
测试 3.证明我有不止一个EntityManager
:我有
Aerendir$ app/console debug:container | grep EntityManager
aws_ses_monitor.entity_manager DoctrineORMEntityManager
commands_queues.do_not_use.entity_manager DoctrineORMEntityManager
doctrine.orm.default_entity_manager EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919__CG__DoctrineORMEntityManager
stripe_bundle.entity_manager DoctrineORMEntityManager
这是正确的,因为我实际上有 4 个实体经理,而报告的那些是我期望存在的。
测试 4.确定我的服务使用哪个EntityManager
以及我的存储库中使用哪个
// SerendipityHQBundleCommandsQueuesBundleServiceQueuesDaemon
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
会给我两个不同的内部对象句柄?
测试 5。尝试删除对$this->entityManager->detach()
的所有调用
解决!问题是一个没有检查状态的detach()
:如果Job
失败,我只需将其分离,以免它在EntityManager
中漂浮,并且当捆绑包启动守护程序时,我需要释放内存。但是只有在Job
肯定失败的情况下,我才需要释放它,而如果必须重试,我不必分离它......或者至少如果处理Job
引用它,我必须重新加载它......
该死的,构建守护进程需要非常注意细节!