Skip to content

Using Enum

Using Enum in Symfony

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.

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.