Last active
August 26, 2021 03:26
-
-
Save huynguyen93/43d09919b635e8deb1e4864a6d2d7fe7 to your computer and use it in GitHub Desktop.
[Symfony - Doctrine] Iterate large amount of data without fetching all at once
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Service\Doctrine; | |
use Doctrine\ORM\EntityManagerInterface; | |
use Generator; | |
class CollectionStreamer | |
{ | |
public static function stream(callable $fetch, int $chunkSize, callable $clear): Generator | |
{ | |
$offset = 0; | |
while (true) { | |
$collection = $fetch($chunkSize, $offset); | |
$itemClass = null; | |
foreach ($collection as $item) { | |
if ($itemClass === null) { | |
$itemClass = $item::class; | |
} | |
yield $item; | |
} | |
$clear($collection); | |
if (count($collection) < $chunkSize) { | |
break; | |
} | |
$offset += $chunkSize; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// example usage in a service | |
namespace App\Service; | |
use App\Repository\ProductRepository; | |
class MyService | |
{ | |
public function __construct(private ProductRepository $productRepository) | |
{ | |
} | |
public function exampleMethod(Category $category) | |
{ | |
$products = $this->productRepository->streamByCategory($category); | |
foreach ($products as $product) { | |
dump($product); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// example repository | |
namespace App\Repository; | |
use App\Entity\Product; | |
use App\Entity\Category; | |
use App\Service\Doctrine\CollectionStreamer; | |
use Doctrine\Persistence\ManagerRegistry; | |
class ProductRepository extends ServiceEntityRepository | |
{ | |
public function __construct( | |
private ManagerRegistry $registry, | |
) { | |
parent::__construct($registry, Product::class); | |
} | |
public function streamByCategory(Category $category, int $chunkSize = 100): Generator | |
{ | |
$fetch = function (int $limit, int $offset) use ($cateogry) { | |
return $this->findByCategory($category, $limit, $offset); | |
}; | |
$clear = function () { | |
$this->getEntityManager()->clear(Product::class); | |
}; | |
return CollectionStreamer::stream($fetch, $chunkSize, $clear); | |
} | |
private function findByCategory(Category $category, int $limit, int $offset) | |
{ | |
return $this->createQueryBuilder('p') | |
->andWhere('p.category = :category') | |
->setParameter('category', $category) | |
->setMaxResults($limit) | |
->setFirstResult($offset) | |
->getQuery() | |
->getResult(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note: this "streamer" will iterate through all the rows in the table, we can add another argument to prevent that happen :)