Traiter de larges jeux de données avec un itérateur Doctrine
Publié le 16/11/2019 • Actualisé le 27/11/2019
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);
namespace App\Controller\Snippet;
use App\Entity\Article;
use App\Repository\ArticleRepository;
use Doctrine\ORM\EntityManagerInterface;
/**
* 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
* @property EntityManagerInterface $entityManager
*/
trait Snippet56Trait
{
public function snippet56(): void
{
$qb = $this->articleRepo->createQueryBuilder('get_all_articles');
$processed = [];
$batchSize = 3;
$flushCount = 0;
echo 'Memory before: '.round(memory_get_usage() / 1024 / 1024, 2)." mb\n";
// using getQuery()->iterate() is deprecated. It's better now because we
// directly get the entity object as the loop item.
foreach ($qb->getQuery()->toIterable() as $article) {
// $article = $result[0] ?? null; // with iterate() we had to do this to get the Article object
if (!$article instanceof Article) {
throw new \TypeError('Invalid article object found.');
}
if ((\count($processed) % $batchSize) === 0) {
// persist here
$this->entityManager->flush();
$this->entityManager->clear(); // call clear, so memory can be freed.
++$flushCount;
}
$processed[] = $article->getId();
}
$this->entityManager->flush(); // for last rows
$this->entityManager->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 Snippet aléatoire