Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Smiffy's avatar

Custom cast against a custom attribute?

I'm not sure if this is possible. I have a custom attribute which returns the amount due on oustanding invoices. I want this to be converted to a currency for display and wanted hopefully to use a cast. Relevant code:

//Client class
public function outstandingInvoicesAmount(): Attribute
{
    return Attribute::make(
        //returns an integer representing cents/pence
        get: fn($value) => $this->invoices()->notSettled()->get()->sum('amount')
    );
}

//Currency cast
public function get(Model $model, string $key, mixed $value, array $attributes)
{
    return \NumberFormatter::create('en_GB', \NumberFormatter::CURRENCY)
        ->formatCurrency($value/100, 'GBP');
}

//Also in Client class and I think this is where my expectation falls down
protected $casts = [
    'outstanding_invoices_amount' => Currency::class
];

protected $appends = [
    'outstanding_invoices_amount'
];

Can custom casts be used against custom attributes or is it just fields that can be pulled from the DB?

I could amend outstandingInvoicesAmount to format the number there and then but I plan on bulding more attributes like this and a cast would seem logical and prevent me from DRY!

0 likes
2 replies
LaryAI's avatar
Level 58

Yes, it is possible to use a custom cast against a custom attribute. In the provided code, the outstandingInvoicesAmount method returns an instance of the Attribute class, which is not a field in the database. Therefore, the outstanding_invoices_amount attribute needs to be appended to the model's attributes using the $appends property.

The get method in the Currency cast should be updated to handle the Attribute instance instead of a regular field value. Here's an updated version of the Currency cast:

class Currency implements CastsAttributes
{
    public function get($model, $key, $value, $attributes)
    {
        if ($value instanceof Attribute) {
            $value = $value->get($model);
        }

        return \NumberFormatter::create('en_GB', \NumberFormatter::CURRENCY)
            ->formatCurrency($value/100, 'GBP');
    }

    public function set($model, $key, $value, $attributes)
    {
        return $value;
    }
}

Note that the get method checks if the value is an instance of Attribute and calls its get method to retrieve the actual value. The set method is a no-op and simply returns the value as is.

With these changes, the outstanding_invoices_amount attribute should be correctly cast to a currency value.

Smiffy's avatar

I haven't removed @LarryAIs answer. This didn't help me totally. I did amend his check in the casts get method to read:

if($model->hasAttributeGetMutator($key))
{
    $value = $model->getAttributeValue($key);
}

Now when I output my model to an array with $model->toArray() the attribute is filled and formatted correctly. However, accessing the attribute directly just results in an integer:

dump($model->toArray();

array:6 [
  "id" => 1
  "name" => "Client"
  "invoice_prefix" => "CLIENT"
  "created_at" => "2023-03-20T13:59:20.000000Z"
  "updated_at" => "2023-03-20T13:59:20.000000Z"
  "outstanding_invoices_amount" => "£1,072.50"
]

dump($model->outstanding_invoices_amount);

107250

Am i misunderstanding casts completely?

Please or to participate in this conversation.