5

When I try to cast a attribute from my model to one of my enums, it throws an erro:

Call to undefined method App\Enums\UserType::from()

I can not find anything about the required find() method.

I followed the instructions here.

My Enum UserType:

namespace App\Enums;

enum UserType
{
    case CUSTOMER;
    case PARTNER;
    case ADMIN;
}

And my User-model:

namespace App\Models;

use App\Enums\UserType;
use FaarenTech\LaravelCustomUuids\Interfaces\HasCustomUuidInterface;
use FaarenTech\LaravelCustomUuids\Models\UuidModel;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableInterface;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableInterface;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Auth\Authenticatable;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Auth\Passwords\CanResetPassword;

class User extends UuidModel implements
    AuthorizableInterface,
    AuthenticatableInterface,
    CanResetPasswordInterface,
    HasCustomUuidInterface
{
    use
        HasApiTokens,
        HasFactory,
        Notifiable,
        Authenticatable,
        Authorizable,
        CanResetPassword,
        MustVerifyEmail,
        SoftDeletes;

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'type' => UserType::class
    ];
}

2 Answers 2

5

You need to map each enum to a scalar value (int, float, string, bool), this way Laravel can convert from the (database stored) value -> enum.

e.g.

enum UserType: string
{
    case CUSTOMER = 'CUSTOMER';
    case PARTNER = 'PARTNER';
    case ADMIN = 'ADMIN';
}

See Backed Enums in PHP:

Backed enums implement an internal BackedEnum interface, which exposes two additional methods:

from(int|string): self will take a scalar and return the corresponding Enum Case. If one is not found, it will throw a ValueError. This is mainly useful in cases where the input scalar is trusted and a missing enum value should be considered an application-stopping error.

Sign up to request clarification or add additional context in comments.

1 Comment

In case you don't want to, as CUSTOMER=CUSTOMER makes no sense at all, you can use the From trait from the github.com/henzeb/enumhancer package.
0

Sometimes you don't want to write case SOMETHING = 'SOMETHING'; because it looks redundant, so I created small trait, that overwrites Illuminate's HasAttributes methods:

<?php

namespace App\Traits;

use BackedEnum;
use Exception;
use UnitEnum;

trait CastsEnums
{
    /**
     * Cast string from database to PHP Enum properly.
     *
     * @param $key
     * @param $value
     * @return mixed|void
     */
    protected function getEnumCastableAttributeValue($key, $value)
    {
        if (is_null($value)) {
            return;
        }

        $castType = $this->getCasts()[$key];

        if ($value instanceof $castType) {
            return $value;
        }

        if ($castType instanceof BackedEnum) {
            return $castType::from($value);
        }

        return $this->getEnumByName($value, $castType);
    }

    /**
     * Rewrite original method, so we can use enums names if values are not presented.
     *
     * @param $key
     * @param $value
     * @return void
     */
    protected function setEnumCastableAttribute($key, $value)
    {
        $enumClass = $this->getCasts()[$key];

        if (!isset($value)) {
            $this->attributes[$key] = null;
        } elseif ($value instanceof $enumClass) {
            $this->attributes[$key] = $value->value ?? $value->name;
        } elseif ($enumClass instanceof BackedEnum) {
            $this->attributes[$key] = $enumClass::from($value)->value;
        } else {
            $this->attributes[$key] = $this->getEnumByName($value, $enumClass);
        }
    }

    private function getEnumByName(string $name, string $enumClass): UnitEnum
    {
        foreach ($enumClass::cases() as $case) {
            if ($case->name === $name) {
                return $case;
            }
        }

        throw new Exception('Enum case not found');
    }
}

Once you've added this trait to your project, you should use it in your model (or in all models, adding it to parent model) that needs cast to non-backed enums.

If you use SQL-enums, make sure that you have same enum cases in PHP!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.