Traiter de larges jeux de données avec un itérateur Doctrine

Publié le 16/11/2019 • Mis à jour le 27/11/2019

English language detected! 🇬🇧

  We noticed that your browser is using English. Do you want to read this post in this language?

Read the english version 🇬🇧

Quand on a un grand nombre de lignes à traiter avec Doctrine, ce n'est pas une bonne idée d'utiliser les fonctions classiques comme getResult() parce que vous allez rapidement rencontrer des problèmes de mémoire. C'est là qu'intervient la fonction iterate. Elle retourne un objet IterableResult sur lequel on peut boucler sans avoir à être effrayé par ceux-ci. Voici un exemple. Évidemment, sur ce blog, je n'ai pas des millions de de lignes... bientôt peut-être ! 😉
J'ai testé recemment ce code pour la migration d'une table vers une autre avec des règles fonctionnelles. Il y avait un demi million de lignes à traiter. Le traitement a consommé environ 250mo de mémoire et a pris cinq minutes pour finir.
PS : N'oubliez pas de désactiver le mode debug !


<?php declare(strict_types=1);

// src/Controller/Snippet/Snippet56Trait.php

namespace App\Controller\Snippet;

use App\Entity\Article;
use App\Entity\ArticleRepository;
use Doctrine\Bundle\DoctrineBundle\Registry;

/**
 * J'utilise un trait PHP afin d'isoler chaque snippet dans un fichier.
 * Ce code doit être apellé d'un contrôleur Symfony étendant AbstractController (depuis Symfony 4.2)
 * ou Symfony\Bundle\FrameworkBundle\Controller\Controller (Symfony <= 4.1).
 * Les services sont injectés dans le constructeur du contrôleur principal.
 *
 * @property ArticleRepository $articleRepo
 */
trait Snippet56Trait
{
    public function snippet56(): void
    {
        $doctrine = $this->getDoctrine();
        if (!$doctrine instanceof Registry) {
            throw new \RuntimeException("Houston, We've Got a Problem. 💥");
        }

        $manager = $doctrine->getManager();
        $qb = $this->articleRepo->createQueryBuilder('get_all_articles');
        $processed = [];
        $batchSize = 3;
        $flushCount = 0;

        echo 'Memory before: '.round((memory_get_usage()/1024/1024), 2)." mb\n";
        foreach ($qb->getQuery()->iterate() as $row) {
            $article = $row[0] ?? null;
            if ($article instanceof Article) {
                if ((count($processed) % $batchSize) === 0) {
                    // persist here
                    $manager->flush();
                    $manager->clear(); // call clear, so memory can be freed.
                    ++$flushCount;
                }
                $processed[] = $article->getId();
            }
        }
        $manager->flush(); // for last rows
        $manager->clear();

        echo 'Memory after: '.round((memory_get_usage()/1024/1024), 2).' mb'.PHP_EOL;
        echo 'Number of flush/clear: '.($flushCount+1).PHP_EOL;
        echo 'Processed articles with IDs: '.implode(',', $processed);

        // That's it! 😁
    }
}

 Exécuter le snippet  Plus sur Stackoverflow   Lire la doc