If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
In every query so far, Doctrine gives us objects. That's its default mode, but we can also easily use it to select specific fields.
On our category page, you can see how many of each fortune has been printed over time. At the top, let's total those numbers with a SUM() query and print it out.
In showCategoryAction()
, create a new variable - $fortunesPrinted
, that'll
be a number. And of course, we'll write a new query to get this. But instead
of shoving this into CategoryRepository
, this queries the FortuneCookie
entity, so we'll use its repository instead. So, AppBundle:FortuneCookie
,
and we'll call a new countNumberPrintedForCategory
method. Pass the $category
object as an argument:
... lines 1 - 9 | |
class FortuneController extends Controller | |
{ | |
... lines 12 - 35 | |
public function showCategoryAction($id) | |
{ | |
... lines 38 - 47 | |
$fortunesPrinted = $this->getDoctrine() | |
->getRepository('AppBundle:FortuneCookie') | |
->countNumberPrintedForCategory($category); | |
... lines 51 - 56 | |
} | |
} |
This will return the raw, summed number. To actually use this, pass this into the template:
... lines 1 - 35 | |
public function showCategoryAction($id) | |
{ | |
... lines 38 - 47 | |
$fortunesPrinted = $this->getDoctrine() | |
->getRepository('AppBundle:FortuneCookie') | |
->countNumberPrintedForCategory($category); | |
... lines 51 - 52 | |
return $this->render('fortune/showCategory.html.twig',[ | |
'category' => $category, | |
'fortunesPrinted' => $fortunesPrinted, | |
]); | |
} | |
... lines 58 - 59 |
And now print it out: {{ fortunesPrinted }}
, put that through Twig's
number_format
filter and add the word "total":
... lines 1 - 12 | |
<th> | |
Printed History ({{ fortunesPrinted|number_format }} total) | |
</th> | |
... lines 16 - 36 |
Amazing. You already know the next step: we need to create a new
countNumberPrintedForCategory
method inside of FortuneCookieRepository
and make it query not for an object, but just a single number: the sum of
how many times each fortune has been printed. That means we'll be totaling
the numberPrinted
property on FortuneCookie
:
... lines 1 - 12 | |
class FortuneCookie | |
{ | |
... lines 15 - 45 | |
/** | |
* @var integer | |
* | |
* @ORM\Column(name="numberPrinted", type="integer") | |
*/ | |
private $numberPrinted; | |
... lines 52 - 174 | |
} |
Open FortuneCookieRepository
and add the new public function. We're expecting
a Category
object, so I'll type-hint the argument like a nice, respectable
programmer. Every query starts the same: $this->createQueryBuilder()
and
we'll use fc
as the alias. Keep the alias consistent for an entity, it'll
save you heartache later.
Next, we need an andWhere()
because we need to only find FortuneCookie
results for this Category. So, fc.category
- because category
is the
name of the property on FortuneCookie
for the relationship. Now, equals
:category
. And next, we'll set that parameter:
... lines 1 - 14 | |
public function countNumberPrintedForCategory(Category $category) | |
{ | |
return $this->createQueryBuilder('fc') | |
->andWhere('fc.category = :category') | |
->setParameter('category', $category) | |
... lines 20 - 22 | |
} | |
... lines 24 - 26 |
This looks like every query we've made before, and if we finish now, it'll
return FortuneCookie
objects. That's lame - I want just the sum number.
To do this, call select()
. Nope, this is not addSelect
like we used
before. When we call createQueryBuilder
from inside FortuneCookieRepository
,
the query builder has a ->select('fc')
built into it. In other words, it's
selecting everything from FortuneCookie
. Calling select()
clears out
anything that's being selected and replaces it with our
SUM(fc.numberPrinted) as fortunesPrinted
:
... lines 1 - 14 | |
public function countNumberPrintedForCategory(Category $category) | |
{ | |
return $this->createQueryBuilder('fc') | |
->andWhere('fc.category = :category') | |
->setParameter('category', $category) | |
->select('SUM(fc.numberPrinted) as fortunesPrinted') | |
... lines 21 - 22 | |
} | |
... lines 24 - 26 |
We're giving the value an alias, just like you can do in SQL. Now, instead
of an object, we're getting back a single field. Let's finish it! Add getQuery()
.
Last, should we call execute()
or getOneOrNullResult()
? If you think
about the query in SQL, this will return a single row that has the fortunesPrinted
value. So we want to return just one result - use getOneOrNullResult()
:
... lines 1 - 14 | |
public function countNumberPrintedForCategory(Category $category) | |
{ | |
return $this->createQueryBuilder('fc') | |
->andWhere('fc.category = :category') | |
->setParameter('category', $category) | |
->select('SUM(fc.numberPrinted) as fortunesPrinted') | |
->getQuery() | |
->getOneOrNullResult(); | |
} | |
... lines 24 - 26 |
Love it! I'm curious to see what this query returns, so let's var_dump
$fortunesPrinted
inside our controller:
... lines 1 - 35 | |
public function showCategoryAction($id) | |
{ | |
... lines 38 - 47 | |
$fortunesPrinted = $this->getDoctrine() | |
->getRepository('AppBundle:FortuneCookie') | |
->countNumberPrintedForCategory($category); | |
var_dump($fortunesPrinted);die; | |
... lines 52 - 56 | |
} | |
... lines 58 - 59 |
Refresh! It's just what you'd expect: an array with a single key called fortunesPrinted
.
So the $fortunesPrinted
variable isn't quite a number - it's this array
with a key on it. But let me show you a trick. I told you about execute()
and getOneOrNullResult()
: the first returns many results, the second returns
a single result or null. But if you're returning a single row that has only
a single column, instead of getOneOrNullResult()
, you can say getSingleScalarResult()
:
... lines 1 - 14 | |
public function countNumberPrintedForCategory(Category $category) | |
{ | |
return $this->createQueryBuilder('fc') | |
->andWhere('fc.category = :category') | |
->setParameter('category', $category) | |
->select('SUM(fc.numberPrinted) as fortunesPrinted') | |
->getQuery() | |
->getSingleScalarResult(); | |
} | |
... lines 24 - 26 |
This says: ok, you're only returning one row with one column, let me just give you that value directly. This is really handy for SUMs and COUNTs.
Refresh! Hey, we have just the number! Time to celebrate - take out the
var_dump
, refresh and... great success! So not only can we select specific
fields instead of getting back objects, if you're selecting just one field
on one row, getSingleScalarResult()
is your new friend.
Hey brentheigold
Migrations are for updating your DB schema, so, whenever you have to add a new field, a new table or updating an existing field you should generate a migration for it
Cheers!
very interesting tutorial can i ask question how to addapt this tutorial into let say querying to count total_price? thank you
Hey Lavin Bassam
If you need to do a "COUNT" operation, then you only have to change the "SUM" statement by a "COUNT", select the right field, and that's it.
Cheers!
Hello,
About that getSingleScalarResult method :
What if there is no entity persisted at all in the first place ?
Will the method call raise an error / throw an exception ?
Hey Mykel Chang
That's correct!
If the query doesn't return a result it throws, or if it returns more than one result it throws too
Cheers!
Ok, EntityManager is only needed when you want to save data tego database. No need this guy when fetching data.
Hey Dominik,
Yes, getManager() return EntityManager instance which basically need when you want persist() an entity or flush() persisted ones.
Cheers!
Hey,
The use of alias into select('SUM(fc.numberPrinted) as fortunesPrinted') is not mandatory ? If you don't write the query works.
Hey Stéphane,
You're right, field alias is optional here. But if you missing this alias, the field name should be "SUM(fc.numberPrinted)" but "fortunesPrinted". So in this case you have to use $fortunesPrinted['SUM(fc.numberPrinted)'].
Cheers!
// composer.json
{
"require": {
"php": ">=5.3.3, <7.3.0",
"symfony/symfony": "2.6.*", // v2.6.13
"doctrine/orm": "~2.2,>=2.2.3", // v2.4.8
"doctrine/doctrine-bundle": "~1.2", // 1.6.4
"twig/extensions": "~1.0", // v1.5.4
"symfony/assetic-bundle": "~2.3", // v2.8.2
"symfony/swiftmailer-bundle": "~2.3", // v2.3.12
"symfony/monolog-bundle": "~2.4", // v2.12.1
"sensio/distribution-bundle": "~3.0.12", // v3.0.36
"sensio/framework-extra-bundle": "~3.0", // v3.0.29
"incenteev/composer-parameter-handler": "~2.0", // v2.1.3
"hautelook/alice-bundle": "0.2.*" // 0.2
},
"require-dev": {
"sensio/generator-bundle": "~2.3" // v2.5.3
}
}
Hi, I like the tutorial, very straight forward.
One question - do you have to actually create a new migration and add this new numberPrinted field to your physical database?