Black Hole: Deleting Entities
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!
Oh-oh, we just got word that this ship, the USS Leafy Cruiser, has fallen into a black hole. Luckily, no long-term, beloved characters were on board, but this ship is now spaghettified. Since it no longer exists in this reality, we need to remove it from our database.
app:ship:remove
Command
Let's create a command to handle this. At your terminal, run:
symfony console make:command
For the name, use app:ship:remove
. This created a new command class.
Command Constructor
Open it! src/Command/ShipRemoveCommand.php
. The maker added some
boilerplate code for us. Update the description to Delete a starship
:
// ... lines 1 - 13 | |
( | |
name: 'app:ship:remove', | |
description: 'Delete a starship', | |
) | |
class ShipRemoveCommand extends Command | |
// ... lines 19 - 56 |
In the constructor, we need to inject two things: private ShipRepository $shipRepo
and private EntityManagerInterface $em
:
// ... lines 1 - 17 | |
class ShipRemoveCommand extends Command | |
{ | |
public function __construct( | |
private StarshipRepository $shipRepo, | |
private EntityManagerInterface $em, | |
) { | |
parent::__construct(); | |
} | |
// ... lines 26 - 54 | |
} |
Whenever you need to find or fetch entities, use the repository. When you need to manage entities, like persisting, updating, or deleting, use the entity manager, or "EM" for short.
Command Configuration
In the configure()
method, remove addOption()
. For addArgument()
,
change the name to slug
, set InputArgument::REQUIRED
, and update the description
to The slug of the starship
:
// ... lines 1 - 17 | |
class ShipRemoveCommand extends Command | |
{ | |
// ... lines 20 - 26 | |
protected function configure(): void | |
{ | |
$this | |
->addArgument('slug', InputArgument::REQUIRED, 'The slug of the starship') | |
; | |
} | |
// ... lines 33 - 54 | |
} |
Command Logic
Down in execute()
, replace this $arg1 =
with $slug = $input->getArgument('slug')
:
// ... lines 1 - 17 | |
class ShipRemoveCommand extends Command | |
{ | |
// ... lines 20 - 33 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... line 36 | |
$slug = $input->getArgument('slug'); | |
// ... lines 38 - 53 | |
} | |
} |
Next, we need to find the ship by this slug. Each EntityRepository
already has the perfect
method for this. Write $ship = $this->shipRepo->findOneBy()
passing an array
where the key is the property to search on and the value is the value to search
for: ['slug' => $slug]
:
// ... lines 1 - 17 | |
class ShipRemoveCommand extends Command | |
{ | |
// ... lines 20 - 33 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 36 - 37 | |
$ship = $this->shipRepo->findOneBy(['slug' => $slug]); | |
// ... lines 39 - 53 | |
} | |
} |
When using these out-of-the-box find methods, Doctrine automatically escapes the values, so you don't need to worry about SQL injection attacks.
Adjust this if
statement to if (!$ship)
. findOneBy()
returns null
if an entity
wasn't found. Inside, write $io->error('Starship not found.')
and return Command::FAILURE
:
// ... lines 1 - 17 | |
class ShipRemoveCommand extends Command | |
{ | |
// ... lines 20 - 33 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 36 - 39 | |
if (!$ship) { | |
$io->error('Starship not found.'); | |
return Command::FAILURE; | |
} | |
// ... lines 45 - 53 | |
} | |
} |
Write a comment to let the user know we're about to remove the starship.
$io->comment(sprintf('Removing starship %s', $ship->getName()))
:
// ... lines 1 - 17 | |
class ShipRemoveCommand extends Command | |
{ | |
// ... lines 20 - 33 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 36 - 45 | |
$io->comment(sprintf('Removing starship: %s', $ship->getName())); | |
// ... lines 47 - 53 | |
} | |
} |
Remove this extra boilerplate and write $this->em->remove($ship)
. Like with persist()
,
remove()
doesn't actually delete the entity directly, it adds it to a queue of entities
that will be deleted when we call $this->em->flush()
:
// ... lines 1 - 17 | |
class ShipRemoveCommand extends Command | |
{ | |
// ... lines 20 - 33 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 36 - 47 | |
$this->em->remove($ship); | |
$this->em->flush(); | |
// ... lines 50 - 53 | |
} | |
} |
Add a success message with $io->success('Starship removed.')
:
// ... lines 1 - 17 | |
class ShipRemoveCommand extends Command | |
{ | |
// ... lines 20 - 33 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 36 - 50 | |
$io->success('Starship removed.'); | |
return Command::SUCCESS; | |
} | |
} |
Command done!
Jump back to our app and refresh the page to make doubly sure the ship is still there. Now, copy the slug from the URL.
Running the Command
Back in your terminal, run:
symfony console app:ship:remove
Paste the copied slug and execute. Success! Starship removed. Run the same command again.
symfony console app:ship:remove leafy-cruiser-ncc-0001
"Starship not found." Perfect! Back in the app, refresh the page. 404. The ship is gone from the database!
Okay! We've seen persisting and removing entities. Next, we'll see how to update the starship entity.