我似乎有两个Doctrine Entity Managers,但我只想要一个! o.O

时间:2017-02-14 18:06:30

标签: symfony doctrine-orm doctrine

我花了最后几天解决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

  1. 为什么我的服务使用一个,而存储库使用另一个?
  2. 如何让我的实体使用我在服务中注入的相同内容? (或者我如何注入我的实体使用的相同 - 是相同的)?
  3. 我如何注射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"]
    

    EntityManagersee 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注释中指定存储库。

    TESTS

    证明两个EntityManager是不同的

    TEST 1.再次坚持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

    TEST 2. 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}
    

    TEST 3.证明我有多个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个实体经理,报告的是我期望存在的那些。

    TEST 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始终相同:

    1. 为什么Job#retryOf对象尚未保留,我必须再次坚持它才能获得抛出的异常?
    2. 为什么VarDumper gives me two different internal object handles如果对象是相同的?

1 个答案:

答案 0 :(得分:1)

TEST 5.尝试删除对$this->entityManager->detach()

的所有调用

解决了!问题是detach()未检查状态:如果Job失败,我只是将其分离,使其不会在EntityManager中浮动,并且捆绑启动守护程序,我需要释放记忆。但是我只有在Job肯定失败的时候才需要释放它,而如果必须重试它,我就不必分离它......或者至少我必须重新加载它,如果处理{{1引用它......

该死的,建立一个守护进程需要大量关注细节!