PHP 8.1 introduces the official Enum support. Doctrine brought Enum type support in its ORM framework, and Symfony added serialization and deserialization support of a Enum type.

Photo by te chan on Unsplash

It is time to migrate your projects to use PHP Enum if you are using 3rd-party enumeration solutions.

To use PHP Enum, you have to upgrade to PHP 8.1, and set the PHP version to 8.1 in the project composer file.

{
//...
"require": {
"php": ">=8.1",
//...
}
}

Creating Enum Class

For example, we will add a Status to the Post entity, and defined several fixed values of the post status.

<?php
namespace App\Entity;
enum Status: string
{
case Draft = "DRAFT";
case PendingModerated = "PENDING_MODERATED";
case Published = "PUBLISHED";
}

Here we use a string backed enum, add a field in the Post class.

#[Column(type: "string", enumType: Status::class)]
private Status $status;

Note, set the enumType as the Status class. It will store the status value as a string in the database tables.

In the Post constructor, assign a default value to the status.

public function __construct()
{
$this->status = Status::Draft;
//...
}

Now everything is ok.

Creating HttpMethod

When we setup the Route attribute on the Controller class, we use a literal value to set up the HTTP method.

#[Route(path: "/{id}", name: "byId", methods: ["GET"])]

For the methods value, there are only several options available to choose. Obviously, if introducing Enum, it will provide a type-safe way to setup the values and decrease the typo errors.

Create an Enum named HttpMethod.

<?php
namespace App\Annotation;
enum HttpMethod
{
case GET;
case POST;
case HEAD;
case OPTIONS;
case PATCH;
case PUT;
case DELETE;
}

Then refactor the Route attribute and create a series of attributes(Get, Post, Put, Delete, etc.) that are mapped to different HTTP methods.

//file : src/Annotation/Get.php
#[Attribute]
class Get extends Route
{
public function getMethods()
{
return [HttpMethod::GET->name];
}
}
//file : src/Annotation/Head.php
#[Attribute]
class Head extends Route
{
public function getMethods()
{
return [HttpMethod::HEAD->name];
}
}
//file : src/Annotation/Options.php
#[Attribute]
class Options extends Route
{
public function getMethods()
{
return [HttpMethod::OPTIONS->name];
}
}
//file : src/Annotation/Patch.php
#[Attribute]
class Patch extends Route
{
public function getMethods()
{
return [HttpMethod::PATCH->name];
}
}
//file : src/Annotation/Post.php
#[Attribute]
class Post extends Route
{
public function getMethods()
{
return [HttpMethod::POST->name];
}
}
//file : src/Annotation/Put.php
#[Attribute]
class Put extends Route
{
public function getMethods()
{
return [HttpMethod::PUT->name];
}
}
//file : src/Annotation/Delete.php
#[Attribute]
class Delete extends Route
{
public function getMethods()
{
return [HttpMethod::DELETE->name];
}
}

Now you can polish the PostController, use these attributes instead. As you see, the naming of the new attributes literally look more clear.

#[Route(path: "/posts", name: "posts_")]
class PostController extends AbstractController
{
    // constructor...
    // #[Route(path: "", name: "all", methods: ["GET"])]
#[Get(path: "", name: "all")]
public function all(#[QueryParam] string $keyword,
#[QueryParam] int $offset = 0,
#[QueryParam] int $limit = 20): Response
{
//...
}
    // #[Route(path: "/{id}", name: "byId", methods: ["GET"])]
#[Get(path: "/{id}", name: "byId")]
public function getById(Uuid $id): Response
{
//...
}
    //#[Route(path: "", name: "create", methods: ["POST"])]
#[Post(path: "", name: "create")]
public function create(#[Body] CreatePostDto $data): Response
{
//...
}
    //#[Route(path: "/{id}", name: "update", methods: ["PUT"])]
#[Put(path: "/{id}", name: "update")]
public function update(Uuid $id, #[Body] UpdatePostDto $data): Response
{
//...
}
    // #[Route(path: "/{id}/status", name: "update_status", methods: ["PUT"])]
#[Put(path: "/{id}/status", name: "update_status")]
public function updateStatus(Uuid $id, #[Body] UpdatePostStatusDto $data): Response
{
//...
}
    //#[Route(path: "/{id}", name: "delete", methods: ["DELETE"])]
#[Delete(path: "/{id}", name: "delete")]
public function deleteById(Uuid $id): Response
{
//...
}
    // comments sub resources.
//#[Route(path: "/{id}/comments", name: "commentByPostId", methods: ["GET"])]
#[GET(path: "/{id}/comments", name: "commentByPostId")]
public function getComments(Uuid $id): Response
{
//...
}
    //#[Route(path: "/{id}/comments", name: "addComments", methods: ["POST"])]
#[Post(path: "/{id}/comments", name: "addComments")]
public function addComment(Uuid $id, Request $request): Response
{
//...
}
}

Run the application again to make sure it works.

Get the source codes from my Github.

BTW, I have updated my original Symfony posts into a step-by-step guide, read it online: https://hantsy.github.io/symfony-rest-sample/


Using Enum in Symfony was originally published in ITNEXT on Medium, where people are continuing the conversation by highlighting and responding to this story.