Return number of results from Doctrine query that uses createQuery - doctrine-orm

$q = $this->_em->createQuery("SELECT s FROM app\models\Quest s
LEFT JOIN s.que c
WHERE s.type = '$sub'
AND c.id = '$id'");
Given a query like the one above, how would I retrieve the number of results?

Alternatively one can look at what Doctrine Paginator class does to a Query object to get a count (this aproach is most probably an overkill though, but it answers your question):
public function count()
{
if ($this->count === null) {
/* #var $countQuery Query */
$countQuery = $this->cloneQuery($this->query);
if ( ! $countQuery->getHint(CountWalker::HINT_DISTINCT)) {
$countQuery->setHint(CountWalker::HINT_DISTINCT, true);
}
if ($this->useOutputWalker($countQuery)) {
$platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
$rsm = new ResultSetMapping();
$rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
$countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\CountOutputWalker');
$countQuery->setResultSetMapping($rsm);
} else {
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker'));
}
$countQuery->setFirstResult(null)->setMaxResults(null);
try {
$data = $countQuery->getScalarResult();
$data = array_map('current', $data);
$this->count = array_sum($data);
} catch(NoResultException $e) {
$this->count = 0;
}
}
return $this->count;
}

You can either perform a count query beforehand:
$count = $em->createQuery('SELECT count(s) FROM app\models\Quest s
LEFT JOIN s.que c
WHERE s.type=:type
AND c.id=:id)
->setParameter('type', $sub);
->setParameter('id', $id);
->getSingleScalarResult();
Or you can just execute your query and get the size of the results array:
$quests = $q->getResult();
$count = count($quests);
Use the first method if you need the count so that you can make a decision before actually retrieving the objects.

Related

How to achieve REPLACE INTO in doctrine2

I am refactoring a code base, because this is a legacy code with a lot of raw sql, and the whole thing is spaghetti code.
I am sadly facing that doctrine has no REPLACE INTO functionality, I know the reasons why.
I found some workaround, like merge but that is deprecated.
I spend a lot of hours to learn doctrine, because it is widely used ORM, and a lot of hours while I built the entities.
Is there any "legal" solution to achieve this REPLACE INTO?
You can handle duplicates by catching UniqueConstraintViolationException.
$entity = new PossibleDuplicatedEntity();
try {
$em->persist($entity);
$em->flush();
}
catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
// handle duplicated values
}
But beware – Doctrine uses implicit transaction. When exception is thrown, transaction is rolled-back and EntityManager is closed (entities are detached). See documentation.
Better would be handling cause of duplication occurring. I.e. if it's because of concurrency, try to use table locking etc.
If you just want to 'upsert' rows into the database and you don't care about change tracking you could use the piece of code below. It's probably more performant than relying on UniqueConstraintViolationException's.
I use it for batch-uploading rows into a table.
/**
* #throws Exception
*/
public static function replaceInto(EntityManagerInterface $em, iterable $entities): void
{
$i = 0;
$values = [];
$metadata = $fieldNames = $sql = null;
foreach ($entities as $entity) {
if (!isset($metadata)) {
$metadata = $em->getClassMetadata(get_class($entity));
$fieldNames = $metadata->getFieldNames();
$tableName = $metadata->getTableName();
$sql = "REPLACE INTO `$tableName` VALUES ";
}
$params = [];
foreach ($fieldNames as $fieldName) {
$paramName = ':' . $metadata->getColumnName($fieldName) . '_' . $i;
$params[] = $paramName;
$values[] = [$paramName, $metadata->getFieldValue($entity, $fieldName), $metadata->getTypeOfField($fieldName)];
}
if ($i > 0) {
$sql .= ",";
}
$sql .= "\n(" . join(', ', $params) . ")";
$i++;
}
$stmt = $em->getConnection()->prepare($sql);
foreach ($values as $value) {
$stmt->bindValue(...$value);
}
$stmt->executeQuery();
}

How to implement non-distributed locks which expire?

Meaning, they don't have to be distributed. I'm thinking about using memcached or redis for that. Probably the latter one. What I'm concerned about is "we've got to free some memory, so we'll delete this key/value before it expired" thing. But I'm open to other suggestions as well.
tl;dr Use ready-made solution, suggested by developers.
So, I decided not to use memcached for the purpose. Since it's a caching server. I don't see a way to ensure that it doesn't delete my keys because it's out of memory. With, redis that's not an issue as long as maxmemory-policy = noeviction.
There are 3 links I want to share with you. They are basically 3 ways, that I now know, to solve the issue. As long as you have redis >= 2.6.0 that is.
redis >= 2.6.12
If you've got redis >= 2.6.12, you're lucky and can simply use setnx command with its new options ex and nx:
$redis->set($name, <whatever>, array('nx', 'ex' => $ttl));
But we can't just delete the lock in the end, if we are to allow for critical section taking longer then we expected (>= ttl). Consider the following situation:
C1 acquires the lock
lock expires
C2 acquires the lock
C1 deletes C2's lock
For that not to happen we are going to store current timestamp as a value of the lock. Then, knowing that Lua scripts are atomic (see Atomicity of scripts):
$redis->eval('
if redis.call("get", KEYS[1]) == KEYS[2] then
redis.call("del", KEYS[1])
end
', array($name, $now));
However, is it possible for two clients to have equal now values? For that all the above actions should happen within one second and ttl must be equal to 0.
Resulting code:
function get_redis() {
static $redis;
if ( ! $redis) {
$redis = new Redis;
$redis->connect('127.0.0.1');
}
return $redis;
}
function acquire_lock($name, $ttl) {
if ( ! $ttl)
return FALSE;
$redis = get_redis();
$now = time();
$r = $redis->set($name, $now, array('nx', 'ex' => $ttl));
if ( ! $r)
return FALSE;
$lock = new RedisLock($redis, $name, $now);
register_shutdown_function(function() use ($lock) {
$r = $lock->release();
# if ( ! $r) {
# Here we can log the fact that lock has expired too early
# }
});
return $lock;
}
class RedisLock {
var $redis;
var $name;
var $now;
var $released;
function __construct($redis, $name, $now) {
$this->redis = get_redis();
$this->name = $name;
$this->now = $now;
}
function release() {
if ($this->released)
return TRUE;
$r = $this->redis->eval('
if redis.call("get", KEYS[1]) == KEYS[2] then
redis.call("del", KEYS[1])
return 1
else
return 0
end
', array($this->name, $this->now));
if ($r)
$this->released = TRUE;
return $r;
}
}
$l1 = acquire_lock('l1', 4);
var_dump($l1 ? date('H:i:s', $l1->expires_at) : FALSE);
sleep(2);
$l2 = acquire_lock('l1', 4);
var_dump($l2 ? date('H:i:s', $l2->expires_at) : FALSE); # FALSE
sleep(4);
$l3 = acquire_lock('l1', 4);
var_dump($l3 ? date('H:i:s', $l3->expires_at) : FALSE);
expire
The other solution I found here. You simply make the value expire with expire command:
$redis->eval('
local r = redis.call("setnx", ARGV[1], ARGV[2])
if r == 1 then
redis.call("expire", ARGV[1], ARGV[3])
end
', array($name, $now, $ttl));
So, only acquire_lock function changes:
function acquire_lock($name, $ttl) {
if ( ! $ttl)
return FALSE;
$redis = get_redis();
$now = time();
$r = $redis->eval('
local r = redis.call("setnx", ARGV[1], ARGV[2])
if r == 1 then
redis.call("expire", ARGV[1], ARGV[3])
end
return r
', array($name, $now, $ttl));
if ( ! $r)
return FALSE;
$lock = new RedisLock($redis, $name, $now);
register_shutdown_function(function() use ($lock) {
$r = $lock->release();
# if ( ! $r) {
# Here we can log that lock as expired too early
# }
});
return $lock;
}
getset
And the last one is described again in documentation. Marked with "left for historical reasons" note.
This time we store timestamp of the moment when the lock is to expire. We store it with setnx command. If it succeeds, we've acquired the lock. Otherwise, either someone else's holding the lock, or the lock has expired. Be it the latter, we use getset to set new value and if the old value hasn't changed, we've acquired the lock:
$r = $redis->setnx($name, $expires_at);
if ( ! $r) {
$cur_expires_at = $redis->get($name);
if ($cur_expires_at > time())
return FALSE;
$cur_expires_at_2 = $redis->getset($name, $expires_at);
if ($cur_expires_at_2 != $cur_expires_at)
return FALSE;
}
What makes me uncomfortable here is that we seem to have changed someone else's expires_at value, don't we?
On a side note, you can check which redis is it that you're using this way:
function get_redis_version() {
static $redis_version;
if ( ! $redis_version) {
$redis = get_redis();
$info = $redis->info();
$redis_version = $info['redis_version'];
}
return $redis_version;
}
if (version_compare(get_redis_version(), '2.6.12') >= 0) {
...
}
Some debugging functions:
function redis_var_dump($keys) {
foreach (get_redis()->keys($keys) as $key) {
$ttl = get_redis()->ttl($key);
printf("%s: %s%s%s", $key, get_redis()->get($key),
$ttl >= 0 ? sprintf(" (ttl: %s)", $ttl) : '',
nl());
}
}
function nl() {
return PHP_SAPI == 'cli' ? "\n" : '<br>';
}

Doctrine Querybuilder, binding Parameters

My Select function of my QueryManager:
/**
* Führt eine SELECT - Query durch
*
* #param $select = array( array(column, [...]), table, shortcut )
* $orderby = array(column, sorting-type)
* $where = array( array( column, value, type[or, and] ), [...] )
* $innerjoin = array( table, shortcut, condition )
* $pagination = array( page, limit )
*
* #return array $data
*/
public function select($select,$orderby, $where, $innerjoin, $pagination)
{
$qb = $this->conn->createQueryBuilder()
->select($select[0])
->from($select[1], $select[2])
;
if ($orderby) {
$qb->orderBy($orderby);
}
if ($where) {
foreach($where as $cond) {
$x = 0;
if ( key($cond) == 0 ) {
$qb
->where($cond[0] . ' = ?')
->setParameter($x,$cond[1]);
}
elseif ( $cond[2] == 'and' ) {
$qb
->andWhere($cond[0] . ' = ?')
->setParameter($x,$cond[1]);
}
elseif ( $cond[2] == 'and' ) {
$qb
->orWhere($cond[0] . ' = :' . $x)
->setParameter($x,$cond[1]);
}
$x++;
}
}
if ($innerjoin) {
$qb->join($select[2],$innerjoin);
}
$this->sql = $qb->getSQL();
$this->totalRowCount = count( $qb->execute() ) ;
if ($pagination) {
$max = $pagination[0] * $pagination[1];
$first = $max - $limit;
$qb
->setFirstResult($first)
->setMaxResults($max)
;
}
$stmt = $qb->execute();
return $stmt->fetchAll();
}
I don't know why, but in action, this function produces a select query without inserted values for the parameters:
/**
* Lädt einen User nach dessen Username
*
* #param $username
* #return User $user | null
*/
public function getUser($username)
{
if($data = $this->select(array('*','users','u'), null, array( array('username',$username) ), null,null)) {
return $user = $this->hydrate($data);
}
return null;
}
I didn't get a result, and the query is not setup correctly:
array(0) { }
SELECT * FROM users u WHERE username = ?
In my opinion the Builder doesn't supstitute my parameters with the provided values ...
I got the latest version of Doctrine DBAL (2.4) and this version should support this features!
Thanks for Help and Suggestions :)
I also had this Problem. I have readed here doctrine 2 querybuilder with set parameters not working that:
You cant bind parameters to QueryBuilder, only to Query
But im creating SQL conditions as collected AND & OR experssions in deep nested objects, and the toppest object creates the query object. So i cant create the query object before, i always return expression objects.
So i solved the problem with direct including the variable into the prepared variable's position.
$qb->where($cond[0] . '=' . $cond[1]);
And because i expect strings there i added hard coded quotes. This is not the desired way, but at the moment i dont know how to solve that in an other way with binding parameters to the QueryBuilder object.
$expr = $d_qb->expr()->between($t_c, "'" . $date_from . "'", "'" . $date_from . "'");
Other suggestions?
Following codes results:
$expr = $d_qb->expr()->between($t_c, ':from', ':to');
$d_qb->setParameter('from', 1);
$d_qb->setParameter('to', 1);
or
$expr = $d_qb->expr()->between($t_c, ':from', ':to');
$d_qb->setParameter(':from', 1);
$d_qb->setParameter(':to', 1);
Results:
e0_.created BETWEEN ? AND ?

Symfony2 Doctrine2 : querybuilder bad query

New to Symfony2 and Doctrine2, i have a function in my entity repository to search entities after form submission. Input is array $get that contain form fields like $get['name'] = 'aname'.
My problem is that when i request with an id, or an id and a name, it's ok by with only a name, all my entities are matched because the query that has been build have no where clause.
Here is my code :
public function search(array $get, $flag = False){
/* Indexed column (used for fast and accurate table cardinality) */
$alias = 'd';
/* DB table to use */
$tableObjectName = 'mysiteMyBundle:DB';
$qb = $this->getEntityManager()
->getRepository($tableObjectName)
->createQueryBuilder($alias)
->select($alias.'.id');
$arr = array();
//Simple array, will grow after problem solved
$numericFields = array(
'id');
$textFields = array(
'name');
while($el = current($get)) {
$field = key($get);
if ( $field == '' or $field == Null or $el == '' or $el == Null ) {
next($get);
}
if ( in_array($field,$numericFields) ){
if ( is_numeric($el) ){
$arr[] = $qb->expr()->eq($alias.".".$field, $el);
}
} else {
if ( in_array($field,$textFields) ) {
$arr[] = $qb->expr()->like($alias.".".$field, $qb->expr()->literal('%'.$el.'%') );
}
}
next($get);
}
if(count($arr) > 0) $qb->andWhere(new Expr\Orx($arr));
else unset($arr);
$query = $qb->getQuery();
if($flag)
return $query;
else
return $query->getResult();
}
The query generated with only a name (ex "myname") input is :
SELECT d0_.id AS id0 FROM DB d0_
It should be:
SELECT d0_.id AS id0 FROM DB d0_ WHERE d0_.name LIKE '%myname%'
What's wrong with my code ?
Thanks !
I don't know if it's related, but do not use "OR" or "AND" operators, because they have a different meaning that the classic "&&" or "||". cf http://php.net/manual/en/language.operators.logical.php
So, first, replace "AND" by "&&", and "OR" by "||".
you should use the setParameter method
$query->where('id = :id')->setParameter('id', $id);

How do I use LIMIT in a codeigniter/doctrine query?

I am using CodeIgniter2 + Doctrine2, and have the following query:
$query = $this->doctrine->em->createQuery("
SELECT u
FROM ORM\Dynasties2\Characters u
WHERE u.fathersId = $key
AND u.deathDate IS NULL
AND u.isRuler = '0'
AND u.isFemale = '0'
AND u.useAI = '1'
AND u.bornDate <= $of_age
");
$sons_of_age = $query -> getResult();
And I only want to get ONE result, assuming there are any hits.
I've looked at Doctrine documentation about using ->LIMIT(1) but I have tried putting this into my query in various places, and only get errors.
Codeigniter has some functions builtin to do $query->row() but this does not seem to work - I wager because of the Doctrine integration.
Thanks!
You're looking for method Query::setMaxResults($number); Then you can use Query:getSingleResult();, but method Query:getSingleResult(); throws error if there's no record.
$query = $this->doctrine->em->createQuery("
SELECT u
FROM ORM\Dynasties2\Characters u
WHERE u.fathersId = $key
AND u.deathDate IS NULL
AND u.isRuler = '0'
AND u.isFemale = '0'
AND u.useAI = '1'
AND u.bornDate <= $of_age
");
$query->setMaxResults(1);
try {
$sons_of_age = $query->getSingleResult();
} catch (\Doctrine\ORM\NoResultException $e) {
$sons_of_age = null;
}