Skip to content

Instantly share code, notes, and snippets.

@bulton-fr
Last active May 2, 2023 06:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save bulton-fr/3abbb519f674049e221a5abcd94a8897 to your computer and use it in GitHub Desktop.
Save bulton-fr/3abbb519f674049e221a5abcd94a8897 to your computer and use it in GitHub Desktop.
Doctrine yield
<?php
namespace MyBundle\Repository;
use Doctrine\ORM\EntityRepository;
use MyBundle\Doctrine\RepositoryTrait;
class MyRepository extends EntityRepository
{
use RepositoryTrait;
/**
* Obtain all data and yield it
*
* @return \Generator
*/
public function yieldAll()
{
$qb = $this->createYieldQueryBuilder('m');
$fct = $qb->getQuery()->yieldIterate;
yield from $fct();
}
}
<?php
namespace MyBundle\Doctrine;
class QueryBuilder extends \Doctrine\ORM\QueryBuilder
{
/**
* {@inheritdoc}
*
* The parent method obtain a \Doctrine\ORM\Query object via EntityManager
* But this class is final (WTF !!) so to add method without copy/paste existing
* code (and kill version upgrade), need to use Closure::bindTo().
* The bindTo closure change the $this into the closure to the parameter object
*/
public function getQuery()
{
$query = parent::getQuery();
//Add a closure on yieldIterate property to $query object
$yieldIterate = $this->generateClosureYieldIterate();
$query->yieldIterate = $yieldIterate->bindTo($query, $query);
return $query;
}
/**
* This method is called like iterate() or getResult() froù
* the Query returned by getQuery()
* So we add a new method which can yield.
* We yield from the return of iterate parent method.
*
* the iterate parent method return an instance of \Doctrine\ORM\Internal\Hydration\IterableResult
* This class implement \Iterator (so we can foreach on it), and, mainly, this class
* not know all result ! Each this the foreach ask the current value (call next), the
* method hydrateRow() of the configurate Hydrator is called.
* So we only have one row :)
* With that, we just need to yield the row :)
*
* @return callable
*/
protected function generateClosureYieldIterate()
{
return function(...$args) {
$iterator = parent::iterate(...$args);
foreach ($iterator as $row) {
yield $row[0];
}
};
}
}
<?php
namespace MyBundle\Doctrine;
trait RepositoryTrait
{
/**
* Based on \Doctrine\ORM\EntityRepository::createQueryBuilder
* Override "$this->_em->createQueryBuilder()" to return my QueryBuilder
* The QueryBuilder is instancied by the entityManager. So if I don't want
* use Closure::bindTo, need to change the entityManager instance to override the method.
* It's the correct way for a new project or a clean project. But with a mess
* project, I prefer to keep the original EntityManager; Mainly for use that
* one to two time in the project.
*/
public function createYieldQueryBuilder($alias, $indexBy = null)
{
$newQueryBuilder = function() {
return new \MyBundle\Doctrine\QueryBuilder($this);
};
$newQueryBuilder = $newQueryBuilder->bindTo($this->_em, $this->_em);
return $newQueryBuilder()
->select($alias)
->from($this->_entityName, $alias, $indexBy)
;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment