If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe are successfully changing the value of the votes
property. Now we need to make an update query to save that to the database.
To insert a VinylMix
, we used the EntityManagerInterface
service, and then called persist()
and flush()
. To update, we'll use that exact same service.
Add a new argument to the vote()
method type-hinted with EntityManagerInterface
. I'll call it $entityManager
. Then, very simply, after we've set the votes
property to the new value, call $entityManager->flush()
.
... lines 1 - 12 | |
class MixController extends AbstractController | |
{ | |
... lines 15 - 44 | |
public function vote(VinylMix $mix, Request $request, EntityManagerInterface $entityManager): Response | |
{ | |
... lines 47 - 53 | |
$entityManager->flush(); | |
... line 55 | |
} | |
} |
That's it people! Before I explain this, let's make sure it works. Refresh. We have 49 votes right now. I'll hit up. It says 50. But the real proof is that when we refresh... it still shows 50! It did save!
Ok, so when we created a new VinylMix
earlier, we had to call persist()
- passing the VinylMix
object - and then flush()
. But now, all we need is flush()
. Why?
Here's the full story. When you call flush()
, Doctrine loops over all of the entity objects that it "knows about" and "saves" them. And that "save" is smart. If Doctrine determines that an entity has not been saved yet, it will execute an INSERT query. But if it's an object that does already exist in the database, Doctrine will figure out what has changed on the object - if anything - and execute an UPDATE
query. Yep! We just call flush()
and Doctrine figures out what to do. It's... the best thing since Starburst Jellybeans.
But... why don't we need to call persist()
when we're updating? Well, you can say $entityManager->persist($mix)
if you want to. It's just... totally redundant!
When you call persist()
, it tells Doctrine:
Hey! I want you to be aware of this object so that, next time I call
flush()
, you'll know to save it.
When you create a new entity object, Doctrine doesn't really know about that object until you call persist()
. But when you're updating an entity, it means that you've already asked Doctrine to query for that object. So Doctrine is already aware of it... and when we call flush()
, Doctrine will - automatically - check that object to see if any changes have been made to it.
So... we are successfully saving the new vote count to the database! Now what? Because... I don't think this die
statement is going to look good on production.
Well, anytime you submit a form successfully, you always do the same thing: redirect to another page. How do we redirect in Symfony? With return $this->redirect()
passing whatever URL you want to redirect to. Though, usually we're redirecting to another page on our site... so we use a similar shortcut called redirectToRoute()
and then pass a route name.
Let's redirect back to the show page. Copy the app_mix_show
route name, paste... and just like with the Twig path()
function, this accepts a second argument: an array of the route wildcards that we need to fill in. In this case, we have an {id}
wildcard... so pass id
set to $mix->getId()
.
... lines 1 - 44 | |
public function vote(VinylMix $mix, Request $request, EntityManagerInterface $entityManager): Response | |
{ | |
... lines 47 - 55 | |
return $this->redirectToRoute('app_mix_show', [ | |
'id' => $mix->getId(), | |
]); | |
} | |
... lines 60 - 61 |
Now, remember: controllers always return a Response
object. And, whelp it turns out that a redirect is a response. It's a response that, instead of containing HTML, basically says:
Please send the user to this other URL
The redirectToRoute()
method is a shortcut that returns this special response object, called a RedirectResponse
.
Anyways, let's test the whole flow! Refresh, and... got it! After voting, we end up right back on this page. And, thanks to Turbo, this is all happening via Ajax calls... which is a nice bonus.
The only problem is that... it's so smooth that it's not super obvious that my vote was actually saved - other than seeing the vote number change. It might be better if we showed a success message. Let's do that next by learning about flash messages. We're also going to make our VinylMix
entity trendier by exploring the concept of smart versus anemic models.
Hey Thephilosoft,
Yes, you're totally correct, the price should be changed to make the 2nd query, otherwise the Doctrine is smart enough to ignore the last flush()
call. Thank you for reporting it, I changed the price in the last setPrice()
call to avoid editing the explanation message.
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.7", // v3.7.0
"doctrine/doctrine-bundle": "^2.7", // 2.7.0
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
"doctrine/orm": "^2.12", // 2.12.3
"knplabs/knp-time-bundle": "^1.18", // v1.19.0
"pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
"pagerfanta/twig": "^3.6", // v3.6.1
"sensio/framework-extra-bundle": "^6.2", // v6.2.6
"stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
"symfony/asset": "6.1.*", // v6.1.0
"symfony/console": "6.1.*", // v6.1.2
"symfony/dotenv": "6.1.*", // v6.1.0
"symfony/flex": "^2", // v2.2.2
"symfony/framework-bundle": "6.1.*", // v6.1.2
"symfony/http-client": "6.1.*", // v6.1.2
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/proxy-manager-bridge": "6.1.*", // v6.1.0
"symfony/runtime": "6.1.*", // v6.1.1
"symfony/twig-bundle": "6.1.*", // v6.1.1
"symfony/ux-turbo": "^2.0", // v2.3.0
"symfony/webpack-encore-bundle": "^1.13", // v1.15.1
"symfony/yaml": "6.1.*", // v6.1.2
"twig/extra-bundle": "^2.12|^3.0", // v3.4.0
"twig/twig": "^2.12|^3.0" // v3.4.1
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
"symfony/debug-bundle": "6.1.*", // v6.1.0
"symfony/maker-bundle": "^1.41", // v1.44.0
"symfony/stopwatch": "6.1.*", // v6.1.0
"symfony/web-profiler-bundle": "6.1.*", // v6.1.2
"zenstruck/foundry": "^1.21" // v1.21.0
}
}
In a challenge after this video. Shouldn't
1
be the answer? Or at least there should be a call forsetPrice
with a different value...