16.

PHP Enums

|

Share this awesome video!

|

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!

54 lines | templates/main/homepage.html.twig
// ... 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

11 lines | src/Model/StarshipStatusEnum.php
// ... 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.

11 lines | src/Model/StarshipStatusEnum.php
// ... 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.

11 lines | src/Model/StarshipStatusEnum.php
// ... 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.

55 lines | src/Repository/StarshipRepository.php
// ... 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.

54 lines | templates/main/homepage.html.twig
// ... 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.