Quantum Refactor: Rich 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!
Take a look at our Starship
entity. It's a bunch of properties and getters and setters.
Kind of boring, right? It doesn't have to be! Since entities are standard PHP classes,
we can add meaningful, explicit methods that describe our business logic, like
goToWarp(7)
or enterOrbitAround($millersPlanet)
. These are called rich entity methods.
Let's try this out and explore the benefits.
Reduce Duplication
Our Starship
check-in logic currently lives in the ShipCheckInCommand::execute()
method.
After we fetch the ship, we update its arrivedAt
and status
.
What if, in the future, we add a check-in controller. We'd have to duplicate this logic
there. And if the logic for "checking in" changes - like we need to update another field, we'd have
to remember to change it in multiple places. That is super not sci-fi.
Adding a Starship::checkIn()
Method
The better way is to move, or encapsulate, this check-in logic into a method on the
entity. Open src/Entity/Starship.php
and scroll to the bottom. Create a
new: public function checkIn()
. Have it accept an optional
?\DateTimeImmutable $arrivedAt = null
and return static
, which is a fancy way of saying
"return the current object":
// ... lines 1 - 10 | |
class Starship | |
{ | |
// ... lines 13 - 159 | |
public function checkIn(?\DateTimeImmutable $arrivedAt = null): static | |
{ | |
// ... lines 162 - 165 | |
} | |
} |
return $this
:
// ... lines 1 - 10 | |
class Starship | |
{ | |
// ... lines 13 - 159 | |
public function checkIn(?\DateTimeImmutable $arrivedAt = null): static | |
{ | |
// ... lines 162 - 164 | |
return $this; | |
} | |
} |
Above, add the check-in logic: $this->arrivedAt = $arrivedAt
, and
if it wasn't passed, ?? new \DateTimeImmutable('now')
.
Next, $this->status = StarshipStatusEnum::WAITING
:
// ... lines 1 - 10 | |
class Starship | |
{ | |
// ... lines 13 - 159 | |
public function checkIn(?\DateTimeImmutable $arrivedAt = null): static | |
{ | |
// ... lines 162 - 164 | |
return $this; | |
} | |
} |
Using the Starship::checkIn()
Method
Jump back to the ShipCheckInCommand
and replace the logic with $ship->checkIn()
:
// ... lines 1 - 17 | |
class ShipCheckInCommand extends Command | |
{ | |
// ... lines 20 - 33 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 36 - 47 | |
$ship->checkIn(); | |
// ... lines 49 - 54 | |
} | |
} |
Wow, that's clear! The command now reads like a story: "Find the ship, then check it in".
To make sure it still works, head back to the homepage and refresh. Find a ship that's not "waiting"... Here we go: "Stellar Pirate". Click that and copy the slug from the URL. Back at your terminal, run:
symfony console app:ship:check-in
paste the slug, and execute! Success! Back in the app, refresh. Perfect! The ship is now marked as "waiting" and arrived 6 seconds ago.
If you find yourself repeating common operations on your entities, consider adding, then using, a method that describes the work being done. It's an easy win for readability and maintainability.
Ok crew, that's it for this Doctrine Fundamentals course! If you're looking to upgrade your Doctrine skills, search for "Doctrine" on SymfonyCasts to find more advanced courses. The Doctrine documentation is a great resource too. And as always, if you have any questions, we're here for you down in the comments.
'Til next time, happy coding!