Home Backed Enums with Collection - Part 2: Transforming to Array and Appending Attributes

Enter a search term to find articles.
backed-enums-with-collection-part-2-transforming-to-array-and-appending-attributes
2022-04-24
461

Backed Enums with Collection - Part 2: Transforming to Array and Appending Attributes

In this post I will show how we can transform enums to array format and dynamically append attributes to it.

Disclaimer: This article is a continuation to the Supercharged Backend Enums with Collection post that I've previously published. If you haven't already, I would highly recommend reading through that article first as we will continue with the examples from there.

Now that that's out of the way, let's begin.

Enums, as objects, are plain and boring!

Enums as objects are plain and boring - because they can never hold other properties except name and value of each case. PHP will actually throw an error when you try to declare properties within enum classes.

However, there are times that you would want to append additional properties to an enum case. For instance, going back to our TicketStatus enum class, imagine that for each status, we also want them to have a color attribute. So that whenever our frontend consumes it, we can use it to design maybe the background color of the tickets based on the status. This is very useful since the enum class is becoming the single source of truth for the ticket status.

Create a method and match each case

First, let's see how we can assign a color for each enum case:

<?php

enum TicketStatus : string
{
        use AsCollection;

        case Draft = 'draft';
        case Open = 'open';
        case Ongoing = 'ongoing';
        case Closed = 'closed';

        public function color()
        {
                return match ($this) {
                        self::Draft => 'bg-gray-400',
                        self::Open => 'bg-orange-400',
                        self::Ongoing => 'bg-teal-400',
                        self::Closed => 'bg-blue-400',
                };
        }
}

By adding a color() method to our enum class and performing a match we can now get the color for each case.

For instance, if we do TicketStatus::Draft->color() this would return bg-gray-400. If you've noticed I am using Tailwind CSS color notation here, but feel free to use your own.

Convert case to array format

But now how can we append the color as an attribute? Our goal is basically to have something like this:

[
        'name' => 'Draft', 
        'value' => 'draft', 
        'color' => 'bg-gray-400',
]

So that when we send it to our frontend we have some sort of reference on which color to use for which status. Well, the next step is to convert each case to an array first. Maybe something like TicketStatus::Draft->toArray() would work.

<?php

enum TicketStatus : string
{
        use AsCollection;

        case Draft = 'draft';
        case Open = 'open';
        case Ongoing = 'ongoing';
        case Closed = 'closed';

        public function color()
        {
                return match ($this) {
                        self::Draft => 'bg-gray-400',
                        self::Open => 'bg-orange-400',
                        self::Ongoing => 'bg-teal-400',
                        self::Closed => 'bg-blue-400',
                };
        }

        public function toArray()
        {
                return [
                        'name' => $this->name,
                        'value' => $this->value,
                ];
        }
}

// Now if we do..
TicketStatus::Draft->toArray();

// We will get something like
[
        'name' => 'Draft', 
        'value' => 'draft', 
]

The append method

Great, we now have a way to convert each enum case to array format. All we need to do now is to append the color to it. So maybe we can have something like TicketStatus::Draft->append('color'). Let's see how we could accomplish that.

<?php

enum TicketStatus : string
{
        use AsCollection;

        case Draft = 'draft';
        case Open = 'open';
        case Ongoing = 'ongoing';
        case Closed = 'closed';

        public function color()
        {
                return match ($this) {
                        self::Draft => 'bg-gray-400',
                        self::Open => 'bg-orange-400',
                        self::Ongoing => 'bg-teal-400',
                        self::Closed => 'bg-blue-400',
                };
        }

        public function toArray()
        {
                return [
                        'name' => $this->name,
                        'value' => $this->value,
                ];
        }

        public function append($attributes)
        {
                $attributes = is_array($attributes) ? $attributes : func_get_args();

                $result = [...$this->toArray()];

                foreach ($attributes as $attribute) {
                        $result = [...$result, $attribute => $this->{$attribute}()];
                }

                return $result;
        }
}

// Now if we call...
TicketStatus::Draft->append('color');

// We get what we want...
[
        'name' => 'Draft', 
        'value' => 'draft', 
        'color' => 'bg-gray-400',
]

Now we have an append() method that can accept either string or array of attributes that we want to append to each enum case. This method will basically try to call each $attributes as a method. And since we have a color() method in our enum class, it will get the value from that method and merge it with the toArray() call.

Additional attributes to append

Like I mentioned earlier, our append() can accept string or array of attributes. This means, if we also had something like an animation() method that determines what kind of animation each enum case can have, we can just pass that as additional parameter to our append(...) method like so:

<?php

enum TicketStatus : string
{
        ...

        public function animation()
        {
                return match ($this) {
                        self::Draft => 'static',
                        self::Open => 'blink',
                        self::Ongoing => 'ping',
                        self::Closed => 'none',
                };
        }
}

// Now if we call...
TicketStatus::Draft->append('color', 'animation');

// We get..
[
        'name' => 'Draft', 
        'value' => 'draft', 
        'color' => 'bg-gray-400',
        'animation' => 'static',
]

Filter the attributes

But wait, we actually have a bug in our append() method. Think about it, if we pass it a string that does not exist as a method in our enum class, we will get an error. Something like TicketStatus::Draft->append('foo'). Let's refactor it to make sure that we filter the $attributes that we pass based on if the method exists in our class.

<?php

enum TicketStatus : string
{
        ...

        public function append($attributes)
        {
                $attributes = static::filterAttributes($attributes);

                $result = $this->toArray();

                foreach ($attributes as $attribute) {
                        $result = [...$result, $attribute => $this->{$attribute}()];
                }

                return $result;
        }

        protected static function filterAttributes($attributes)
        {
                $attributes = is_array($attributes) ? $attributes : func_get_args();

                return array_filter($attributes, fn ($attribute) => method_exists(static::class, $attribute));
        }
}

Now we've fixed that bug and we are filtering the attributes if they exist as a method in our class.

Append attributes when getting all the cases

Now that we can append additional attributes to each case, we will also want to do it whenever we are getting the entire collection. Remember that in the previous article we added some convenient collection related methods to wrap our enum class with? Maybe we can leverage that here. Maybe for the entire collection, we can have something like TicketStatus::with('color', 'animation');. We can do it by adding the method like so:

<?php

enum TicketStatus : string
{
        ...

        public static function with($attributes)
        {
                $attributes = static::filterAttributes($attributes);

                return static::collection()->map(function ($enum) use ($attributes) {
                        $result = $enum->toArray();

                        foreach ($attributes as $attribute) {
                                $result = [...$result, $attribute => $enum->{$attribute}()];
                        }

                        return $result;
                });
        }
}

// Now if we call..
TicketStatus::with('color', 'animation');

// We will get something like...
Illuminate\Support\Collection {
        all: [
                [
                        "name" => "Draft",
                        "value" => "draft",
                        "color" => "bg-gray-400",
                        "animation" => "static"
                ],
                [
                        "name" => "Open",
                        "value" => "open",
                        "color" => "bg-orange-400",
                        "animation" => "blink"
                ],
                [
                        "name" => "Ongoing",
                        "value" => "ongoing",
                        "color" => "bg-teal-400",
                        "animation" => "ping"
                ],
                [
                        "name" => "Closed",
                        "value" => "closed",
                        "color" => "bg-blue-400",
                        "animation" => "none"
                ],
        ],
}

Now you have 2 ways to append additional attributes to your enum depending on the context. For each enum case, you can use the append() method, and for the entire collection you have with() method.

Have fun!

Marvin Quezon

Full Stack Web Developer
Marvin Quezon · Copyright © 2024 · Privacy · Sitemap