skip to Main Content

I’m using an Enum on my model’s AppLoanPurpose field which stores an integer in the database. The integer then corrosponds to something, for example, 0 means "Other" and 1 means "Groceries".

When I cast my field, I’m still seeing the integer value returned rather than my string.

What am I missing?

<?php

namespace AppEnumsApplicationsGBPayday;

enum LoanPurpose: Int
{
    case OTHER = 0;
    case GROCERIES = 1;

    public function label()
    {
        return match($this) {
            self::OTHER => 'Other',
            self::GROCERIES => 'Groceries'
        };
    }
}

Model

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentSoftDeletes;
use AppCastsJson;
use AppEnumsApplicationsGBPaydayLoanPurpose;

class ApplicationGBPayday extends Model
{
    use HasFactory, SoftDeletes;

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'application_gb_paydays';

    /**
     * The attributes that aren't mass assignable.
     *
     * @var array
     */
    protected $guarded = [];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'AppLoanPurpose' => LoanPurpose::class,
    ];

    /**
     * Get the model's application
     */
    public function application()
    {
        return $this->morphOne(Application::class, 'modelable');
    }
}

2

Answers


  1. Casting is converting an attribute value into a supported data type,

    You can’t just cast an attribute to your own data type, check the supported data type here

    You have to create your own custom cast before you can use it, otherwise, just use an Accessor and Mutator

    EDIT
    enum casting is supported in PHP 8.1 or above

    Login or Signup to reply.
  2. If you really want to use enum, you have to treat enum as a regular object.

    enum LoanPurpose: Int {
        case OTHER = 0;
        case GROCERIES = 1;
    
        public function label() {
            return match($this) {
                self::OTHER => 'Other',
                self::GROCERIES => 'Groceries'
            };
        }
    }
    
    $enum = LoanPurpose::GROCERIES;
    

    The above will create an instance of LoanPurpose containing two attributes, name and value.

    = LoanPurpose {#xxxx
        +name: "GROCERIES",
        +value: 1,
      }
    

    Calling those attributes

    // will output: GROCERIES
    $enum->name;
    // will output 1
    $enum->value;
    

    If you add a method to LoanPurpose, you still have to call that method in order to do something useful. In your setup, you just have to call the label method which performs a match with whatever you’re matching.

    // will output: Groceries 
    $enum->label();
    

    If you do not want to call the name or value of the enum instance, you could use an attribute accessor like getAppLoanPurposeAttribute and do just that.

    use AppEnumsApplicationsGBPaydayLoanPurpose;
    
    class ApplicationGBPayday extends Model {
    // ...
      public function getAppLoanPurposeAttribute($value) {
        return return LoanPurpose::from($value)->name;
      }
    // ...
    

    Just remember that you will have lost any usefulness an enum has to offer because ApplicationGBPayday::AppLoanPurpose will never return a LoanPurpose instance.

    So the following would not be possible

    $ApplicationGBPayday::AppLoanPurpose == LoanPurpose::OTHER
    

    As stated in @silver’s answer, you can also use the CastAttribute interface, or entirely create an instance of Attribute and return that instead, which I think would be much easier.

    update

    So reading all the comments you made, the attribute AppLoanPurpose for model ApplicationGBPayday can be used as follow as specified in the example given in your question.

    $model = new ApplicationGBPayday([
      'AppLoanPurpose' => 1 // or LoanPurpose::GROCERIES
    ]);
    
    // will output: 1
    $model->AppLoanPurpose->value;
    // will output: GROCERIES
    $model->AppLoanPurpose->name;
    // will output: Groceries
    $model->AppLoanPurpose->label();
    

    If you’re not already using it, I can really advice using tinker with laravel-web-tinker from spatie. It gives you a lot of space to test around.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search