Creating casts like Carbon in Laravel
Tags: laravel, php, programming, tutorial
Published: 2022-05-21 15:06:00

Most of us using Laravel have used **Carbon** for date casting since it comes with the package and also simply because it is just awesome. 

What if you wanted to **create a similar experience for your own custom casts**?

So, here is a short guide how to achieve something similar:
_(I will use Currency as an example however you can apply this to anything)_

Firstly, we will need our trusty type class with a simple constructor and a a couple static initiators (just like `Carbon::parse('05/05/22')`). Also it would be great to have some getters like `toUsd()` and `toCents()` for **ease of use**.

```php
class Currency
{
    public function __construct(int $amountInCents)
    {
        $this->amountInCents = $amountInCents;
    }

    public static function fromCents(int $amountInCents):Currency
    {
        return new self($amountInCents);
    }

    public static function fromUsd(float|int $amount): Currency
    {
        return new self($amount * 100);
    }

    public function toUsd(): float|int
    {
        return $this->amountInCents / 100;
    }

    public function toCents(): int
    {
        return $this->amountInCents;
    }
}
```

Now we are able to initiate our class using `Currency::fromCents(100)`, I prefer to store all my currencies in cents since floats are weird in database level but let's not get into the nitty gritty here.

Secondly, we will utilize Laravel's **custom casts** feature for this

```php
class Currency implements CastsAttributes
{
    /**
     * Cast the given value.
     *
     * @param  Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return CurrencyType
     */
    public function get($model, string $key, mixed $value, array $attributes): CurrencyType
    {
        return CurrencyType::fromCents($value);
    }

    /**
     * @param  Model  $model
     * @param  string  $key
     * @param  CurrencyType|string|float|int  $value
     * @param  array  $attributes
     * @return int
     * @throws InvalidCurrencyFormatException
     */
    public function set($model, string $key, mixed $value, array $attributes): int
    {
        if (!($value instanceof CurrencyType)) {
            $currency = CurrencyType::class;
            $type = getType($value);
            throw new InvalidCurrencyFormatException("The given value must be $currency. $type was given.");
        }

        return $value->toCents();
    }
```
_Please have a look at the [docs](https://laravel.com/docs/9.x/eloquent-mutators#custom-casts) for more info about this step_

We now have our shiny `Currency` cast that we can use on any model like:

```
'money_column' => Currency::class,
```

I am sure we are all aware of how magical🪄 casts are but let me outline we achieved here.

- Whenever you want to store this column you can just use a `Currency` object and just send it over to the database and **it will automatically to store it in cents** without questions.

- Whenever you fetch this record from the database, **it will automatically be in the format of `Currency`** and you can use the getters as you wish.



_Now you can stop at this point and call it a day, I wouldn't blame you at all but we are here to have the full `Carbon` style cast experience. So lets go a little bit further, you won't regret it I am sure (I hope you won't)._


Our next step is a beautiful detail that most of us take for granted about Carbon._(at least I did until recently)_

**When a Carbon object is passed to front-end (blade or json) it magically turns into a formatted string instead of an Object.**

Let's add this to our `Currency` class.

```php
use JsonSerializable;

class Currency implements JsonSerializable
{
    protected int $amountInCents;

    public function __construct(int $amountInCents)
    {
        $this->amountInCents = $amountInCents;
    }

    public static function fromCents(int $amountInCents): Currency
    {
        return new self($amountInCents);
    }

    public static function fromUsd(float|int $amount): Currency
    {
        return new self($amount * 100);
    }

    public function toUsd(): float|int
    {
        return $this->amountInCents / 100;
    }

    public function toCents(): int
    {
        return $this->amountInCents;
    }

    public function toReadable(): string
    {
        return number_format($this->toUsd(), 2);
    }

    public function jsonSerialize(): string
    {
        return $this->toReadable();
    }

    public function __toString(): string
    {
        return $this->toReadable();
    }
}
```
_JsonSerializable is a native php interface for more info have a look at [php docs](https://www.php.net/manual/en/class.jsonserializable.php)_

Using `_JsonSerializable` interface makes sure whenever you class is going through a `json_encode()` the `jsonSerialize(` function is used and Laravel almost always runs any response through `json_encode()` within the `ResponseFactory` class.
_I have to admit it took me way too long to figure this out_

I also added the `__toString()` method so if at any point you would like to use this class as a string you wouldn't need any other helpers.

```php
$currency = Currency::fromUsd(1);
json_encode($currency); //"1.00"
echo $currency; //"1.00"

...
return response()->json(['amount' => $currency]) //would be "1.00" on the front-end
```

I think that is just elegant, all my worries about storing currency in cents or usd are gone. 

If you have any other use-cases for something like this I would love to see it in the comments, I am sure there must be many.



Thanks for reading, drop a ❤️ if you made it all the way down here.

Also let me know in the comments if you have any further improvements to this or found this useful or just plain hate the idea 😬