Doctrine retrieve indexes of selected columns only - doctrine-orm

I am currently refactoring an application written in Symfony 3 and relies heavily on Doctrine ORM and I'm stuck trying to get an object/array with indexes of my selected columns.
Now I am fairly familiar with PHP PDO and as I recall a basic fetch of query results as shown below
$sth->fetchAll();
(Depending on my query) it would give me an array similar to the one below
[0] => Array
(
[name] => pear
[0] => pear
[colour] => green
[1] => green
)
[1] => Array
(
[name] => watermelon
[0] => watermelon
[colour] => pink
[1] => pink
)
With Doctrine i have tried to use several inbuilt functions with Hydration parameters
$query->getResult();
And with no luck I end up getting something like this
Array
(
[0] => Array
(
[name] => pear
[colour] => green
)
[1] => Array
(
[name] => Watermelon
[colour] => Pink
)
)
Can someone help me or point me in the right direction how to properly have this sorted out?
------Updated the question to include the full method I am currently using----
public function getDepartmentCount()
{
$qb = $this->createQueryBuilder('fruit')
->leftJoin('fruit.color','color')
$query=$qb->getQuery();
return $query->getArrayResult(); //Method that possibly needs to be changed
}

I have been able to solve this on my own after creating a custom Hydrator.
I will keep the solution here for anyone who might face a similar problem.
Below is a class for the custom Hydrator
namespace AppBundle\Doctrine;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
use PDO;
class CustomHydrator extends AbstractHydrator
{
/**
* Hydrates all rows from the current statement instance at once.
*
* #return array
*/
protected function hydrateAllData()
{
// TODO: Implement hydrateAllData() method.
return $this->_stmt->fetchAll(PDO::FETCH_NUM);
}
}
Added this in my config file under the orm section to tell Symfony where to find the Custom Hydrator and its name
hydrators:
GridDataHydrator: AppBundle\Doctrine\CustomHydrator
And Finally executed the query using this method
$query->getResult('GridDataHydrator');

Related

"IN" predicate with Criteria filtering isn't working

I'm trying to filter a doctrine collection directly with Criteria in order to avoid all collection-loading when I'm searching only for certain elements.
public function bar()
{
$entity = ...; // not useful for the example
$property = ...; // not useful for the example but is a getter for a collection
$ids = [22, 13]; // just for the sake of this example
$criteria = Criteria::create()->where(Criteria::expr()->in("id", $ids));
return $this->foo($entity, $property, $criteria);
}
public function foo($entity, $property, Criteria $criteria)
{
return $this->propertyAccessor->getValue($entity, $property)->matching($criteria);
}
Above code will produce this DQL (I'll show only last and relevant part)
AND te.id = ?' with params [22,13]:
And so, this error
Notice: Array to string conversion
I suppose that wrong DQL is generated here, but I don't know why.
Does anyone got a clue?
This is the collection property I'm trying to match on
/**
* #ORM\ManyToMany(targetEntity="Vendor\Bundle\Entity\Foo")
* #ORM\JoinTable(name="foo_bar",
* joinColumns={#ORM\JoinColumn(name="foo_bar_id", referencedColumnName="id", onDelete="cascade")},
* inverseJoinColumns={#ORM\JoinColumn(name="foo_id", referencedColumnName="id")}
* )
*/
protected $foo;
Edit
If I try to dump $criteria object, I obtain this
Criteria {#10958 ▼
-expression: Comparison {#10956 ▼
-field: "id"
-op: "IN"
-value: Value {#10948 ▼
-value: array:2 [▼
0 => 22
1 => 13
]
}
}
-orderings: []
-firstResult: null
-maxResults: null
}
So this seems to be correct. I'm also aware I could use "or" expressions, but I would prefer to understand why this issue is taking place.
Edit2
I've changed $criteria as follows
$criteria = Criteria::create()
->where(Criteria::expr()->eq('id', 22))
->orWhere(Criteria::expr()->eq('id', 13));
As now, query is correct but collection isn't: it is empty but shouldn't be (if I run manually that query from mysql cli I obtain what I expect).
So ...
BONUS QUESTION
Can I use Criteria only inside entities getters or repository if I collection isn't already loaded?
Edit3 - Bonus question
It seems that if you don't have collection already loaded, matching criteria is pretty useless. Infact if I loop onto collection elements and call some getters and, then, I use criteria, resulting collection is what I was searching for (but, of course, ALL collection elements are now loaded)
I've found this
https://github.com/doctrine/doctrine2/issues/4910
It seems that is a "well-known" issue in ManyToMany collection situations

Doctrine ORM HYDRATE_SCALAR vs HYDRATE_ARRAY

I'm trying to get all values of a column from a table through Doctrine ORM using following query:
SELECT item.templateId FROM MyBundle:Item item WHERE item.id IN (:ids)
I know Doctrine has hydration mode Query::HYDRATE_SINGLE_SCALAR which returns single scalar value. When, however, having more than one record in result it throws NonUniqueResultException which is fine.
Naturally, I guessed that Query::HYDRATE_SCALAR would do the trick. However, it returns exactly the same result as when using Query::HYDRATE_ARRAY.
Code:
$ids = [1, 2, 3];
$query = $this->entityManager->createQuery('
SELECT item.templateId FROM MyBundle:Item item WHERE item.id IN (:ids)
')
->setParameter('ids', $ids)
;
// Regardles whether I use HYDRATE_SCALAR or HYDRATE_ARRAY
// $result is the same
$result = $query->getResult(Query::HYDRATE_SCALAR);
Here is how $result looks in both cases:
Array
(
[0] => Array
(
[templateId] => 52
)
[1] => Array
(
[templateId] => 90
)
[2] => Array
(
[templateId] => 89
)
)
How I wish it looked:
Array
(
[0] => 52
[1] => 90
[2] => 89
)
I know I can transform it into the form I want, but I wonder why HYDRATE_SCALAR isn't behaving as I expect it to.
I think it behaves like this because, as the documentation says :
[it] can contain duplicate data.
That's when array_column (php 5.5) comes into play.
Try this:
$result = $query->getResult(Query::HYDRATE_SCALAR);
return array_map('reset', $result);
There you go :)
10 years later, I'm adding another answer, that I will hope is better.
Once again, the documentation I mentioned 10 years ago is helpful:
Query#getArrayResult(): Retrieves an array graph (a nested array) that is largely interchangeable with the object graph generated by Query#getResult() for read-only purposes. .. note:: An array graph can differ from the corresponding object graph in certain scenarios due to the difference of the identity semantics between arrays and objects.
Query#getScalarResult(): Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The pure/mixed distinction does not apply.
ORM means Object-relational mapper: it maps an RDBMS with an object graph. One one hand you have tabular data, on the other hand you have objects that can be related to each other.
When using getArrayResult(), you will get an array graph, meaning something (deeply) nested, like a JSON document.
When using getScalarResult(), there will be little to no intervention besides converting column names to field names, and that's why it can contain duplicate data. So for instance, If you write a query that retrieves a user with 2 phonenumbers, the former will give you something like
[0 => [
'lastname' => 'Anderson',
'firstname' => 'Thomas',
'phoneNumbers' => [[
'alias' => 'home',
'number' => '0123456789',
], [
'alias' => 'work',
'number' => '987654321',
]],
]]
The latter will give you something flat and duplicated, like this:
[0 => [
'lastname' => 'Anderson',
'firstname' => 'Thomas',
'alias' => 'work',
'number' => '987654321',
],
],
1 => [
'lastname' => 'Anderson',
'firstname' => 'Thomas',
'alias' => 'work',
'number' => '987654321',
]]
Of course, if your query is simple enough, there is no difference.

filter a List according to multiple contains

I want to filter a List, and I only want to keep a string if the string contains .jpg,.jpeg or .png:
scala> var list = List[String]("a1.png","a2.amr","a3.png","a4.jpg","a5.jpeg","a6.mp4","a7.amr","a9.mov","a10.wmv")
list: List[String] = List(a1.png, a2.amr, a3.png, a4.jpg, a5.jpeg, a6.mp4, a7.amr, a9.mov, a10.wmv)
I am not finding that .contains will help me!
Required output:
List("a1.png","a3.png","a4.jpg","a5.jpeg")
Use filter method.
list.filter( name => name.contains(pattern1) || name.contains(pattern2) )
If you have undefined amount of extentions:
val extensions = List("jpg", "png")
list.filter( p => extensions.exists(e => p.matches(s".*\\.$e$$")))
To select anything that contains one of an arbitrary number of extensions:
list.filter(p => extensions.exists(e => p.contains(e)))
Which is what #SergeyLagutin said above, but I thought I'd point out it doesn't need matches.
Why not use filter() with an appropriate function performing your selection/predicate?
e.g.
list.filter(x => x.endsWith(".jpg") || x.endsWith(".jpeg")
etc.

Ignore returned expression in DQL

I have a DQL query containing a SELECT expression that's only used for ordering:
SELECT u, (u.orderTotal / u.orderCount) AS orderAverage
FROM User u
ORDER BY orderAverage
I have to use this trick because so far, Doctrine does not support ordering by an expression.
That works fine, expect that for a reason outside the scope of this question, I'd like to have only u returned by getResult():
[0] => object(User)
[1] => object(User)
and not a nested array containing u and orderAverage:
[0] =>
[0] => object(User)
['orderAverage'] => float
[1] =>
[0] => object(User)
['orderAverage'] => float
I thought I'd read somewhere that there was a DQL keyword for not returning an expression in the result, but I've failed to find it in the documentation. It'd be something like:
SELECT u, IGNORE (u.orderTotal / u.orderCount) AS orderAverage
FROM ...
Does that exist, or did I dream?
Ok, I finally managed to find it in the EBNF documentation.
The correct keyword is HIDDEN, and goes after AS:
SELECT u, (u.orderTotal / u.orderCount) AS HIDDEN orderAverage

How to get the *total* number of results from a paginated `findBy` in Doctrine2?

I'm trying to see if there is a way to do pagination with Doctrine2 without writing custom DQL. I've found that the findBy() function returns the result set that I want, however I'm missing one piece of information to properly paginate on the UI, namely the total number of records that the result could have returned.
I'm really hoping that this is possible, since it's a "simple" one liner.
$transactions = \Icarus\Entity\ServicePlan\Transaction::getRepository()->findBy(array('user' => $userId, 'device' => $device), null, $transactionsPerPage, $currentPage);
Does anyone know how/if I can get this information from the findBy() function?
Short anwser, no. You're essentially running this query:
SELECT * FROM transaction WHERE user = $userId AND device = "$device" LIMIT $currentPage, $transactionPerPage;
By specifying a limit, the query is only going to return the amount of rows from your offset inside that limit. So if $transactionPerPage = 10, the total rows returned by that query will be 10.
Assuming the total count is somewhat static, I would suggest first running a count on the total matching documents on the first page request and caching that result ( or storing in sessions ), so you only need to grab the total count once.
edit: Example of count query, using just normal php sessions:
if ( !isset( $_SESSION['transactionCount'] ) )
{
$transactionCount = $em->createQuery('SELECT COUNT(*) FROM \Icarus\Entity\ServicePlan\Transaction WHERE user = ?1 AND device = ?2')
->setParameters( array( 1 => $userId, 2 => $device ) )
->getSingleScalarResult();
$_SESSION['transactionCount'] = $transactionCount;
}
edit2: If you really dont want to use DQL, you can run your .findBy() with out the offset and limit, and do a sizeof on the results:
$transactions = \Icarus\Entity\ServicePlan\Transaction::getRepository()->findBy(array('user' => $userId, 'device' => $device) );
$totalTransactions = sizeof( $transactions );
But the performance on this wont be as good, as you are actually fetching all the objects.
Did you try this ?
$queryBuilder->select('p.id, p.name')
->from('\Api\Project\Entity\Project', 'p')
->where('p.status = :status')
->setParameter('status', 1)
->orderBy('p.createdDate', 'asc')
->setFirstResult($page)
->setMaxResults($limit);