Inside the loop, making things dynamic is nothing new... which is great! For in progress, say {{ ship.status }}
. When we refresh, it prints! Though, yikes! The statuses are running way out of their space. Our data doesn't match the design!
// ... lines 1 - 4 | |
{% block body %} | |
<main class="flex flex-col lg:flex-row"> | |
// ... lines 7 - 8 | |
<div class="px-12 pt-10 w-full"> | |
// ... lines 10 - 13 | |
<div class="space-y-5"> | |
{% for ship in ships %} | |
// ... lines 16 - 43 | |
{% endfor %} | |
</div> | |
// ... lines 46 - 50 | |
</div> | |
</main> | |
{% endblock %} |
Plot twist! Someone changed the project's requirements... right in the middle! That "never" happens! The new plan is this: each ship should have a status of in progress
, waiting
, or completed
. Over in src/Repository/StarshipRepository.php
, our ships do have a status
- it's this argument - but it's a string that can be set to anything.
Creating an Enum
So we need to do some refactoring to fit the new plan. Let's think: there are exactly three valid statuses. This a perfect use case for a PHP enum.
If you're not familiar with enums, they're lovely and a great way to organize a set of statuses - like published, unpublished & draft - or sizes - small, medium or large - or anything similar.
In the Model/
directory - though this could live anywhere... we're creating the enum for our own organization - create a new class and call it StarshipStatusEnum
. As soon as I typed the word enum, PhpStorm changed the template from class
to an enum
. So we're not creating a class, as you can see, we created an enum
// ... lines 1 - 2 | |
namespace App\Model; | |
enum StarshipStatusEnum: string | |
{ | |
// ... lines 7 - 9 | |
} |
Add a : string
to the enum to make what's called a "string-backed enum". We won't go too deep, but this allows us to define each status - like WAITING
and assign that to a string, which will be handy in a minute. Add a status for IN_PROGRESS
and finally one for COMPLETED
.
// ... lines 1 - 2 | |
namespace App\Model; | |
enum StarshipStatusEnum: string | |
{ | |
case WAITING = 'waiting'; | |
case IN_PROGRESS = 'in progress'; | |
case COMPLETED = 'completed'; | |
} |
That's it! That's all an enum is: a set of "states" that get centralized in one place.
Next: open up the Starship
class. The last argument is currently a string
status. Change it to be a StarshipStatusEnum
. And at the bottom, the getStatus
method will now return a StarshipStatusEnum
.
// ... lines 1 - 2 | |
namespace App\Model; | |
enum StarshipStatusEnum: string | |
{ | |
case WAITING = 'waiting'; | |
case IN_PROGRESS = 'in progress'; | |
case COMPLETED = 'completed'; | |
} |
Finally, in StarshipRepository
where we create each Starship
, my editor is angry. It says:
Hey! This argument accepts a
StarshipStatusEnum
, but you're passing a string!
Let's calm it down. Change this to StarshipStatusEnum::
... and it autocomplete the choices! Let's make the first one IN_PROGRESS
. And that did add the use
statement for the enum
to the top of the class. For the next one, make it COMPLETED
... and for the last, WAITING
.
// ... lines 1 - 5 | |
use App\Model\StarshipStatusEnum; | |
// ... lines 7 - 8 | |
class StarshipRepository | |
{ | |
// ... lines 11 - 14 | |
public function findAll(): array | |
{ | |
// ... lines 17 - 18 | |
return [ | |
new Starship( | |
// ... lines 21 - 24 | |
StarshipStatusEnum::IN_PROGRESS | |
), | |
new Starship( | |
// ... lines 28 - 31 | |
StarshipStatusEnum::COMPLETED | |
), | |
new Starship( | |
// ... lines 35 - 38 | |
StarshipStatusEnum::WAITING | |
), | |
]; | |
} | |
// ... lines 43 - 53 | |
} |
Refactoring done! Well... maybe. When we refresh, busted! It says:
object of class
StarshipStatusEnum
could not be converted to string
And it's coming from the ship.status
Twig call.
That makes sense: ship.status
is now an enum... which can't be directly printed as a string. The easiest fix, in homepage.html.twig
, is to add .value
.
// ... lines 1 - 4 | |
{% block body %} | |
<main class="flex flex-col lg:flex-row"> | |
// ... lines 7 - 8 | |
<div class="px-12 pt-10 w-full"> | |
// ... lines 10 - 13 | |
<div class="space-y-5"> | |
{% for ship in ships %} | |
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between"> | |
<div class="flex justify-center min-[1174px]:justify-start"> | |
// ... line 18 | |
<div class="ml-5"> | |
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10"> | |
// ... line 21 | |
<p class="uppercase text-xs text-nowrap">{{ ship.status.value }}</p> | |
</div> | |
// ... lines 24 - 29 | |
</div> | |
</div> | |
// ... lines 32 - 42 | |
</div> | |
{% endfor %} | |
</div> | |
// ... lines 46 - 50 | |
</div> | |
</main> | |
{% endblock %} |
Because we made our enum string-backed, it has a value
property, which will be the string that we assigned to the current status. Try it now. It looks great! In progress, completed, waiting.
Next: let's learn how we can make this last change a bit more elegant by creating smarter methods on our Starship
class. Then we'll put the finishing touches on our design.
Hi,
thank you for this very interesting video.
There are some places in my Symfony App that needs to be replaced by enums. However how do I have to make sure that my entities are loaded correctly from the database?
Suppose I have my Starships persisted in a database and the attribute status WAS of type string. How do I take care that the string is correctly transformed into an enum?
Thanks
Oliver