Symfony 信条:为什么可以';通过关联访问实体时是否释放内存?

Symfony 信条:为什么可以';通过关联访问实体时是否释放内存?,symfony,doctrine-orm,Symfony,Doctrine Orm,我有一个应用程序,它与应用程序文件有关系: /** * @ORM\OneToMany( * targetEntity="AppBundle\Entity\ApplicationFile", * mappedBy="application", * cascade={"remove"}, * orphanRemoval=true * ) */ private $files; 文件实体有一个存储二进制数据的字段,其大小可以达到2MB。当迭代大量应用程序及其文件时,PHP

我有一个
应用程序
,它与
应用程序文件
有关系:

/**
 * @ORM\OneToMany(
 *   targetEntity="AppBundle\Entity\ApplicationFile",
 *   mappedBy="application",
 *   cascade={"remove"},
 *   orphanRemoval=true
 * )
 */
private $files;
文件实体有一个存储二进制数据的字段,其大小可以达到2MB。当迭代大量应用程序及其文件时,PHP内存使用量会增加。我想保持低调

我试过这个:

$applications = $this->em->getRepository('AppBundle:Application')->findAll();
foreach ($applications as $app) {
  ...
  foreach ($app->getFiles() as $file) {
    ...
    $this->em->detach($file);
  }
  $this->em->detach($app);
}
分离该对象应该告诉实体管理器停止关心该对象并取消引用它,但令人惊讶的是,它对内存使用量没有影响—它一直在增加

相反,我必须手动加载应用程序文件(而不是通过关联方法检索它们),并且内存使用不会增加。这项工作:

$applications = $this->em->getRepository('AppBundle:Application')->findAll();
foreach ($applications as $app) {
  ...

  $appFiles = $this
      ->em
      ->getRepository('AppBundle:ApplicationFile')
      ->findBy(array('application' => $application));

  foreach ($appFiles as $file) {
    ...
    $this->em->detach($file);
  }
  $this->em->detach($app);
}
我使用
xdebug\u debug\u zval
跟踪对
$file
对象的引用。在第一个示例中,有一个额外的引用,这解释了内存膨胀的原因——PHP无法对其进行垃圾收集

有人知道这是为什么吗?这个额外的引用在哪里?如何删除它

EDIT:在循环结束时显式调用
unset($file)
无效。此时仍然有两个对对象的引用(通过
xdebug\u debug\u zval
验证)。一个包含在
$file
中(我可以取消设置),但另一个地方我无法取消设置。在主循环结束时调用
$this->em->clear()
,也没有效果

编辑2:SOLUTION:@origaminal的答案引导我找到了解决方案,因此我接受了他的答案,而不是提供我自己的答案

在第一种方法中,我通过
$application
上的关联访问文件,这有一个副作用,即初始化我在外部循环中迭代的
$application
对象上以前未初始化的
$files
集合

调用
$em->detach($application)
$em->detach($file)
只会告诉Doctrine的UOW停止跟踪对象,但这不会影响我正在迭代的
$applications
数组,它们现在已经填充了消耗内存的
$files
集合

我必须在处理完每个
$application
对象后将其取消设置,以删除对加载的
$files
的所有引用。为此,我对循环进行了如下修改:

    $applications = $em->getRepository('AppBundle:Application')->findAll();
    $count = count($applications);
    for ($i = 0; $i < $count; $i++) {
        foreach ($applications[$i]->getFiles() as $file) {
            $file->getData();
            $em->detach($file);
            unset($file);
        }
        $em->detach($applications[$i]);
        unset($applications[$i]);

        // Don't NEED to force GC, but doing so helps for testing.
        gc_collect_cycles();
    }
$applications=$em->getRepository('AppBundle:Application')->findAll();
$count=count($applications);
对于($i=0;$i<$count;$i++){
foreach($applications[$i]->getFiles()作为$file){
$file->getData();
$em->detach($file);
未设置($文件);
}
$em->detach($applications[$i]);
未设置($i)应用程序;
//不需要强制GC,但这样做有助于测试。
gc_collect_cycles();
}

诀窍在于PHP的垃圾收集器,它的工作原理有点奇怪。首先,每次脚本需要内存时,它都会从RAM分配内存,即使您使用
unset()
$object=null
或其他技巧来释放内存,分配的内存也不会返回到操作系统,直到脚本未完成且与其相关的进程被终止

如何修复该问题

  • 通常是在Linux系统上完成的 创建使用
    limit
    offset
    参数运行所需脚本的命令,并以小批量多次重新运行所需脚本。这样,脚本将使用更少的内存,并且每次脚本完成时都会释放内存
  • 摆脱条令它自己膨胀内存,
    PDO
    速度更快,成本更低

  • 对于内存中的对象可能导致泄漏的任务,您应该使用

    为了返回
    查询
    对象而不是
    数组集合
    ,应该进行重构,然后从该查询对象中,可以轻松调用
    迭代()
    方法,并在每次对象检查后清理内存

    编辑 您有“隐藏引用”,因为
    detach
    操作不会删除内存中的对象,它只告诉
    EntityManager
    不再处理它。这就是为什么您应该使用my solution或
    unset()
    带有php函数的对象。

    Cascade
    EntityManager::detach
    确实应该删除对enities的所有引用。但它不会自动对关联实体执行相同的操作

    您需要通过添加关联的
    cascade
    选项来级联此操作:

    /**
     * @ORM\OneToMany(
     *   targetEntity="AppBundle\Entity\ApplicationFile",
     *   mappedBy="application",
     *   cascade={"remove", "detach"},
     *   orphanRemoval=true
     * )
     */
    private $files;
    
    现在
    $em->detach($app)
    应该足以删除对
    应用程序
    实体及其关联的
    应用程序文件
    实体的引用

    查找vs集合 我非常怀疑通过关联加载
    ApplicationFile
    实体而不是使用存储库来
    findBy()
    它们是问题的根源

    确保通过关联加载时,集合将引用这些子实体。但是当父实体被取消引用时,整个树将被垃圾收集,除非有其他对这些子实体的引用

    我怀疑您显示的代码是伪代码/示例代码,而不是生产中的实际代码。请仔细检查该代码以查找其他引用

    清楚的 有时,清除整个EntityManager并合并几个实体是值得的。您可以尝试
    $em->clear()
    $em->clear('AppBundle\Entity\ApplicationFile')

    清除无效 您是说清除EntityManager没有效果。这意味着您正在搜索的引用不在EntityManager(UnitOfWork)中,因为您刚刚清除了t
    /**
     * @ORM\OneToMany(
     *   targetEntity="AppBundle\Entity\ApplicationFile",
     *   mappedBy="application",
     *   cascade={"remove", "detach"},
     *   orphanRemoval=true
     * )
     */
    private $files;