Matching in Enums

php laravel

Let’s suppose we’re working on a project in Laravel. We have a Post model that uses an Enum for the status property, let’s suppose that it’s indicating whether the Post is draft, published, moderated, or some other Post status. The actual cases don’t matter, but the status property is used to determine whether the post can be seen, edited, or other actions.

namespace App\Models;
use App\Models\Enums\PostStatus;

class Post {
    protected function casts() {
        return ['status' => PostStatus::class];
    }
}

Usually, we would do something like:

public function publishPost(Post $post) {
    if ($post->status === PostStatus::Published) {
        abort(403, 'Already Published');
    }
    $post->status = PostStatus::Published;
    $post->save();
}

Let’s make this a little better. We will use a PostState class to take control of all the conditionals based on the status property. We will write code like $post->state->publish(). Let’s start by adding a state attribute on the Post model.

use Illuminate\Database\Eloquent\Casts\Attribute;

    /** @return Attribute<PostState, $this> */
    protected function state(): Attribute
    {
        return Attribute::make(
            get: fn(): PostState => $this->status->createPostStatus($this),
        );
    }

We already have a cast on the Post model’s, status property to use the PostStatus Enum. Remember that an Enum is just a class, so we can add a mapping method on the PostStatus Enum.

enum PostStatus: string {
    case Published = 'published';
    case Draft = 'draft';
    case Moderated = 'moderated';

    public function createPostStatus(Post $post): PostState {
        return match ($this) {
            self::Published => new PublishedPostState($post),
            self::Draft => new DraftPostState($post),
            self::Moderated => new ModeratedPostState($post),
        };
    }
}

Now we can create our PostState classes:

abstract class PostState {
    public function __construct(protected Post $post) {}
    public function publish() {
        $post->status = PostStatus::Published;
        $post->save();
    }
}

class DraftPostState extends PostStatus {}

class ModeratedPostState extends PostStatus {}

class PublishedPostState extends PostStatus {
    public function publish() {
        abort(403, 'Already Published');
    }
}

Any Post action depending on the value of the Post’s status should be added to the PostState hierarchy. All the logic is tucked away in organized classes.

Hope this helps.

Weather in Charlotte, NC