Ship Upgrades: Updating an Entity
Lucky you! You found an early release chapter - it will be fully polished and published shortly!
This Chapter isn't quite ready...
Rest assured, the gnomes are hard at work
completing this video!
Our starship repair scheme, ahem, business is doing well! We now have some repeat customers who want some after market upgrades. We need a way to check in an existing starship whose status is completed.
Finding a Completed Ship
This list on our homepage only lists incomplete starships, so we need to find a completed one. In your terminal, run:
symfony console doctrine:query:sql 'SELECT slug, status FROM starship'
lunar-marauder-1
is a completed ship. Copy the slug, and back in the app,
visit /starships/lunar-marauder-1
. Got it. To better see the update, let's
show the arrivedAt
date on the show page.
In templates/starship/show.html.twig
, copy these h4
and p
tags. Paste them below.
Update the h4
content to Arrived At
and the p
to {{ ship.arrivedAt|ago }}
:
// ... lines 1 - 4 | |
{% block body %} | |
// ... lines 6 - 11 | |
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8"> | |
// ... lines 13 - 15 | |
<div class="space-y-5"> | |
<div class="mt-8 max-w-xl mx-auto"> | |
<div class="px-8 pt-8"> | |
// ... lines 19 - 35 | |
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase">Arrived At</h4> | |
<p class="text-[22px] font-semibold">{{ ship.arrivedAt|ago }}</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
Back to the app, refresh, and there we go! This ship is completed and arrived 1 month ago.
To check in a ship when it arrives, let's create another command.
app:ship:check-in
Command
At your terminal, run:
symfony console make:command
For the name, use app:ship:check-in
.
Updating Command Boilerplate
Open the new command class: src/Command/ShipCheckInCommand.php
. Update the description - Check-in ship
:
// ... lines 1 - 4 | |
{% block body %} | |
// ... lines 6 - 11 | |
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8"> | |
// ... lines 13 - 15 | |
<div class="space-y-5"> | |
<div class="mt-8 max-w-xl mx-auto"> | |
<div class="px-8 pt-8"> | |
// ... lines 19 - 35 | |
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase">Arrived At</h4> | |
<p class="text-[22px] font-semibold">{{ ship.arrivedAt|ago }}</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
and for the constructor, we need the same things as the remove command. Open that,
copy the constructor, and paste it over ShipCheckInCommand::__construct()
:
// ... lines 1 - 4 | |
{% block body %} | |
// ... lines 6 - 11 | |
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8"> | |
// ... lines 13 - 15 | |
<div class="space-y-5"> | |
<div class="mt-8 max-w-xl mx-auto"> | |
<div class="px-8 pt-8"> | |
// ... lines 19 - 35 | |
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase">Arrived At</h4> | |
<p class="text-[22px] font-semibold">{{ ship.arrivedAt|ago }}</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
We'll also find the ship by slug, so copy the configure()
method from ShipRemoveCommand
and
paste it too:
// ... lines 1 - 18 | |
class ShipCheckInCommand extends Command | |
{ | |
// ... lines 21 - 27 | |
protected function configure(): void | |
{ | |
$this | |
->addArgument('slug', InputArgument::REQUIRED, 'The slug of the starship') | |
; | |
} | |
// ... lines 34 - 57 | |
} |
Command Logic
The first part of execute()
, finding the ship by slug, is also the same.
Copy that and paste. Update the IO comment to "Checking in starship...":
// ... lines 1 - 18 | |
class ShipCheckInCommand extends Command | |
{ | |
// ... lines 21 - 34 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$slug = $input->getArgument('slug'); | |
$ship = $this->shipRepo->findOneBy(['slug' => $slug]); | |
if (!$ship) { | |
$io->error('Starship not found.'); | |
return Command::FAILURE; | |
} | |
$io->comment(sprintf('Checking-in starship: %s', $ship->getName())); | |
// ... lines 48 - 56 | |
} | |
} |
Time for the actual "check-in" logic. First, update the arrived at date to the current
time with $ship->setArrivedAt(new \DateTimeImmutable('now'))
. Then set the status to
"waiting" with $ship->setStatus(StarshipStatusEnum::WAITING)
:
// ... lines 1 - 18 | |
class ShipCheckInCommand extends Command | |
{ | |
// ... lines 21 - 34 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 37 - 48 | |
$ship->setArrivedAt(new \DateTimeImmutable('now')); | |
$ship->setStatus(StarshipStatusEnum::WAITING); | |
// ... lines 51 - 56 | |
} | |
} |
These fields have been
updated on the object, but not yet in the database. To execute the UPDATE
query, below,
call, you guessed it, $this->em->flush()
:
// ... lines 1 - 18 | |
class ShipCheckInCommand extends Command | |
{ | |
// ... lines 21 - 34 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 37 - 51 | |
$this->em->flush(); | |
// ... lines 53 - 56 | |
} | |
} |
Just flush()
?
Wait, wait, wait! When we persist or remove an entity, we had to call a method - like persist
or remove
on
the entity manager to let Doctrine know our intention. Here, we don't? Nope! Doctrine
is super smart. Above, when we found the entity, Doctrine started tracking it. When
we call flush()
, it sees that it's been modified and determines the best SQL to
update the database. So awesome!
Finally, add a success message: "Starship checked-in":
// ... lines 1 - 18 | |
class ShipCheckInCommand extends Command | |
{ | |
// ... lines 21 - 34 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 37 - 53 | |
$io->success('Starship checked-in.'); | |
return Command::SUCCESS; | |
} | |
} |
Back in our app, this is the ship we want to check in. Copy the slug from the url.
Running the Command
In your terminal, run the new command with:
symfony console app:ship:check-in
paste the slug and execute! Success! Back in the app, refresh. The ship is now marked as "waiting" and arrived 9 seconds ago. It worked!
Jump back to the check-in logic inside ShipCheckInCommand
. We're calling setters
to update two fields. Next, let's encapsulate this logic into a method on the
Starship
entity.