Quantum Refactor: Rich Entities
Keep on Learning!
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 SubscribeTake 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 | |
{ | |
$this->arrivedAt = $arrivedAt ?? new \DateTimeImmutable('now'); | |
$this->status = StarshipStatusEnum::WAITING; | |
// ... lines 164 - 165 | |
} | |
} |
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!
Hi again,
Just after
the code from previous paragraph is placed.
BR
Cezary