Doctrine batch processing query while iterate - symfony

In symfony4 i'm using the iteration solution as explained in doctrine documentation
The problem is that i need to perform a query inside the foreach cicle and if i do, the iteration ends.
//...
$repo = this->_em->getRepository('App\Entity\EmailAddressStatus');
// Gets 15k results
$q = $this->_em->createQuery('select u from App\Entity\User u');
$iterableResult = $q->iterate();
foreach ($iterableResult as $row) {
$emails = $row[0]->getEmails();
foreach($emails as $email){
// If i do this the iteration ends after first result
$check = $repo->isEmailBlackListed($email);
// Do something with $check and $email...
}
// detach from Doctrine, so that it can be Garbage-Collected immediately
$this->_em->detach($row[0]);
}

You should try to select the users with JOIN to EmailAddressStatus so that you get only valid users like this:
$this->_em->createQuery('select u From App\Entity\User u JOIN App\Entity\EmailAddressStatus s WITH s.email = u.email AND s.status <> ?1')
->setParameter(1, 'not-allowed-status');

Related

How to execute a pre-saved dql string

let's say for some reason I have a DQL string saved somewhere (in the DB) along with the necessary parameters, can I set it in a queryBuilder object and execute it?
I expected to be able to so something like
$builder = $entityManager->createQueryBuilder();
$query = $builder->getQuery()
->setDQL($stringDql)
->setParameters($arrayParams);
return $query->iterate();
The Entity manager has ::createQuery(string $dql); Where there string of DQL comes from it would not care.
$dql = 'SELECT u FROM MyProject\Model\User u WHERE u.age > 20';
// $dql = $this->getQueryFromDatabase();
$query = $em->createQuery($dql);

How to get previous and next entity through entity manager

This is how i'm able to fetch an user entity using it's id:
$userEntity = $em->getRepository('ModelBundle:User')->find($userId);
Is there a way to get previous and next entity based on this $userEntity?
For example: i fetched $userEntity and it's id is 100, now i want fetch previous entity irrespective to its id 99, 80, 70 whatever it is! same with case with next entity..
I can already fetch next and previous using some PHP hack, however i'm looking for better approach..
Edited:
Just to clarify i'm looking for object oriented approach, something like below:
$userEntity = $em->getRepository('ModelBundle:User')->find($userId);
$previousUserEntity = $userEntity->previous(); //example
$previousUserEntity = $userEntity->next(); //example
I know userEntity does't contain any method like previous() and next(), it's just an example, if anything like that exist which can help directly get next and previous entities in object oriented fashion!
Add this funstion to your repository class
public function previous(User $user)
{
return $this->getEntityManager()
->createQuery(
'SELECT u
FROM ModelBundle:User u
WHERE u.id = (SELECT MAX(us.id) FROM ModelBundle:User us WHERE us.id < :id )'
)
->setParameter(':id', $user->getId())
->getOneOrNullResult();
}
public function next(User $user)
{
return $this->getEntityManager()
->createQuery(
'SELECT u
FROM ModelBundle:User u
WHERE u.id = (SELECT MIN(us.id) FROM ModelBundle:User us WHERE us.id > :id )'
)
->setParameter(':id', $user->getId())
->getOneOrNullResult();
}
It's possible to get the previous and next records by searching for all the records with a value greater or less than a given value, sort them and then take only the first or last result.
Let's say we have these ids:
70
80
99
100
In order to get the user before 99, we want the last user which id is less than 99. Here is the code for adding this in a custom repository:
public function getPreviousUser($userId)
{
$qb = $this->createQueryBuilder('u')
->select('u')
// Filter users.
->where('u.id < :userId')
->setParameter(':userId', $userId)
// Order by id.
->orderBy('u.id', 'DESC')
// Get the first record.
->setFirstResult(0)
->setMaxResults(1)
;
$result = $qb->getQuery()->getOneOrNullResult();
}
And in the controller:
$em->getRepository('ModelBundle:User')->getPreviousUser($userId);
This will return the record with id 80.
In order to get the user after 99, we want the first user which id is greater than 99:
public function getNextUser($userId)
{
$qb = $this->createQueryBuilder('u')
->select('u')
->where('u.id > :userId')
->setParameter(':userId', $userId)
->orderBy('u.id', 'ASC')
->setFirstResult(0)
->setMaxResults(1)
;
$result = $qb->getQuery()->getOneOrNullResult();
}
And in the controller:
$em->getRepository('ModelBundle:User')->getNextUser($userId);
This will return the record with id 100.

Get total number of rows by using 'SQL_CALC_FOUND_ROWS' and Doctrine NativeQuery

I have a simple query which selects entities and uses limit statement. I am using Doctrine NativeQuery because I have FIELD() function in sql query, and I need a collection of objects as a result.
That query works.
However I need also a total number of records, so I use SQL_CALC_FOUND_ROWS in the first query. After the first gets the result I create another ResultSetMapping, another $nativeQuery, execute SELECT FOUND_ROWS() AS found_rows and I keep getting total number of '1'.
$rsm = new ResultSetMapping();
$rsm->addEntityResult('\\MyCompany\\Administration\\Domain\\Model\\Applicant\\Applicant', 'a');
$rsm->addFieldResult('a', 'first_name', 'firstName');
$rsm->addFieldResult('a', 'last_name', 'lastName');
$query = $this->em->createNativeQuery('SELECT SQL_CALC_FOUND_ROWS * FROM recruitment_applicant ORDER BY FIELD(id,5,15,8,17,2,1,16,9,7,11,6,10,12,13,14,18)', $rsm);
$result = $query->getResult(); // this result is ok
$sqlCountRows = "SELECT FOUND_ROWS() AS found_rows";
$countRowsRsm = new ResultSetMapping();
$countRowsRsm->addScalarResult('found_rows', 'foundRows');
$countRowsQuery = $this->em->createNativeQuery($sqlCountRows,$countRowsRsm);
$rowsCount = $countRowsQuery->getResult();
$total = $rowsCount[0]['foundRows']; // result is '1' when it should be '16'
I used this example.
You don't have to use native query. FIELD() is really very easy to implement as a custom DQL function:
Read DQL User Defined Functions and How to Register Custom DQL Functions on Doctrine/Symfony documentation.
FIELD() implementation:
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
class Field extends FunctionNode
{
private $field = null;
private $values = array();
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->field = $parser->arithmeticPrimary();
while (count($this->values) < 1 || $parser->getLexer()->lookahead['type'] !== Lexer::T_CLOSE_PARENTHESIS) {
$parser->match(Lexer::T_COMMA);
$this->values[] = $parser->arithmeticPrimary();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(SqlWalker $sqlWalker)
{
$values = array();
foreach ($this->values as $value) {
$values[] = $value->dispatch($sqlWalker);
}
return sprintf('FIELD(%s, %s)', $this->field->dispatch($sqlWalker), implode(', ', $values));
}
}
You won't event need a count query. However, if you'd need COUNT(*) query you can easily clone your original query and use CountWalker to create count query from select query.
I found out what might be a cause of the problem: Symfony2 profiler, queries section, shows total of 22 queries executed. My first query gets run third in a row and my second query, the one to return the number of rows gets executed 13th.
SQL_CALC_FOUND_ROWS works if SELECT FOUND_ROWS() is run immediately after the first query.

How to write DQL query innner join to avoid lazy loading?

My code:
$entityManager = $this->getDoctrine()->getEntityManager();
$result = $entityManager->createQueryBuilder()
->select('c')
->from('BlogHomepageBundle:Comment', 'c')
->innerJoin('BlogHomepageBundle:Post', 'po', 'WITH', 'c.postFk = po.postId')
->getQuery()->getResult();
foreach ($result as $c) {
//additional sql query
echo $c->getPostFk()->getName();
}
It works just fine, but when Im trying to get postFk ( post relate to comment one-to-many relation) I'm getting additional query to db ( lazy loading). Can I avoid this situtation to get all data in one DQL query ? Simple to performe sql query
select c.*, p.* from comment as c inner join post as p on c.post_fk = p.post_id
I want to use DQL ( not raw sql ).
EDIT
I would like to have Objects and access to them like in foreach loop / not scalar data.
You need to specifically select a property or a join to avoid lazy loading :
->select('c', 'po')
http://www.doctrine-project.org/blog/doctrine-lazy-loading.html
I think I found solution #Rpg600 thanks for help.
This is code:
$entityManager = $this->getDoctrine()->getEntityManager();
$result = $entityManager->createQueryBuilder()
->select('c', 'po')
->from('BlogHomepageBundle:Comment', 'c')
->innerJoin('c.postFk', 'po', 'WITH', 'c.postFk = po.postId')
->getQuery()
->getResult();
foreach ($result as $c) {
echo $c->getContent();
echo $c->getPostFk()->getName();
}

Doctrine Paginator Iteration is an array of 1 object

I'm trying to use doctrines Paginate class to fetch some tasks.
$qb = $this->getEntityManager()
->createQueryBuilder()
->select('DISTINCT task, priority, company, invoice')
->addSelect('priority.id as priority_id')
->from('TekniqSD4Bundle:task', 'task')
->leftJoin('task.slips', 'slips')
->leftJoin('task.comments', 'comments')
->leftJoin('task.files', 'files')
->leftJoin('task.steps', 'steps')
->leftJoin('task.status', 'status')
->join('task.invoice', 'invoice')
->join('task.priority', 'priority')
->join('invoice.company', 'company');
$query = $qb->getQuery()
->setMaxResults(2)
->setFirstResult($offset);
$paginator = new Paginator($query, true);
foreach($paginator as $task){
var_dump($task); //this spits out an array containing 1 task
}
My question is, why is $task an array?
The selected priority.id in your DQL is causing this problem, since the ORM has to retrieve it as scalar result. Accessing $task[0] should give you fetch-joined task object, while $task['priority_id'] gives you the scalar representing priority.id.

Resources