> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom casts van Eloquent

> Hoe je custom casts maakt door de interface CastsAttributes te implementeren. Ook gevorderde patronen zoals het value-objectpatroon, inbound casts en Castables komen aan bod.

## Wat zijn casts?

Casts in Eloquent vormen het mechanisme dat ruwe waarden uit de database omzet naar PHP-datatypes en die omzetting bij het opslaan weer terugdraait. Je definieert ze met de `casts`-methode.

```php theme={null}
protected function casts(): array
{
    return [
        'is_admin'   => 'boolean',
        'settings'   => 'array',
        'created_at' => 'datetime',
    ];
}
```

## Soorten ingebouwde casts

Een overzicht van de casts die Laravel standaard biedt.

| Cast                   | Beschrijving                                                 |
| ---------------------- | ------------------------------------------------------------ |
| `integer` / `int`      | Omzetten naar een geheel getal                               |
| `float` / `double`     | Omzetten naar een kommagetal                                 |
| `string`               | Omzetten naar een string                                     |
| `boolean` / `bool`     | Omzetten naar een boolean (inclusief `0`/`1`)                |
| `array`                | JSON-string ↔ PHP-array                                      |
| `collection`           | JSON-string ↔ Collection-instantie                           |
| `object`               | JSON-string ↔ stdObject-instantie                            |
| `datetime`             | String ↔ Carbon-instantie                                    |
| `immutable_datetime`   | String ↔ CarbonImmutable-instantie                           |
| `date`                 | String ↔ Carbon (zonder tijd)                                |
| `timestamp`            | String ↔ UNIX-timestamp                                      |
| `encrypted`            | Versleutelen bij opslaan, ontsleutelen bij ophalen           |
| `hashed`               | Hashen bij opslaan (te combineren met een alleen-lezen cast) |
| `AsStringable::class`  | String ↔ Stringable-object                                   |
| `AsArrayObject::class` | JSON ↔ ArrayObject-instantie                                 |
| `AsCollection::class`  | JSON ↔ Collection-instantie                                  |

<Info>
  `AsArrayObject` en `AsCollection` zijn binnen Laravel geïmplementeerd als custom casts, zodat je een specifieke offset van de array direct kunt wijzigen.
</Info>

## Een custom castklasse maken

Heb je een omzetting nodig die de ingebouwde casts niet aankunnen, dan maak je een custom cast die de interface `CastsAttributes` implementeert.

### Definitie van de interface

Het contract in het framework zelf is als volgt gedefinieerd.

```php theme={null}
// src/Illuminate/Contracts/Database/Eloquent/CastsAttributes.php

interface CastsAttributes
{
    /**
     * Zet de ruwe DB-waarde om naar een PHP-waarde (bij lezen)
     *
     * @param  array<string, mixed>  $attributes  Alle attribuutwaarden van het model
     */
    public function get(Model $model, string $key, mixed $value, array $attributes);

    /**
     * Zet de PHP-waarde om naar een vorm die in de DB kan worden opgeslagen (bij schrijven)
     *
     * @param  array<string, mixed>  $attributes  Alle attribuutwaarden van het model
     */
    public function set(Model $model, string $key, mixed $value, array $attributes);
}
```

Het argument `$attributes` bevat alle attributen van het model, dus je kunt ook omzettingen over meerdere kolommen heen doen (zie het value-objectpatroon hieronder).

### Implementatie van een basale custom cast

Genereer een sjabloon met het commando `make:cast`.

```bash theme={null}
php artisan make:cast AsMoney
```

Er wordt een `app/Casts/AsMoney.php` gegenereerd. Als voorbeeld implementeren we een cast die een geldbedrag (opgeslagen als geheel getal) omzet naar het value object `Money`.

```php theme={null}
<?php

namespace App\Casts;

use App\ValueObjects\Money;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

class AsMoney implements CastsAttributes
{
    /**
     * Zet de gehele DB-waarde (in yen) om naar een Money-object
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Money {
        return new Money((int) $value);
    }

    /**
     * Zet het Money-object om naar een geheel getal dat in de DB kan worden opgeslagen
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): int {
        if ($value instanceof Money) {
            return $value->amount;
        }

        return (int) $value;
    }
}
```

Pas de cast toe op het model.

```php theme={null}
<?php

namespace App\Models;

use App\Casts\AsMoney;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected function casts(): array
    {
        return [
            'price' => AsMoney::class,
        ];
    }
}
```

Nu geeft `$order->price` een `Money`-instantie terug.

## Value-objectcasts

Een patroon waarbij je meerdere DB-kolommen samen als één value object behandelt.

### Implementatievoorbeeld: een adrescast

We bundelen de twee kolommen `address_line_one` en `address_line_two` in het value object `Address`.

```php theme={null}
<?php

namespace App\ValueObjects;

use Illuminate\Contracts\Support\Arrayable;

class Address implements Arrayable, \JsonSerializable
{
    public function __construct(
        public readonly string $lineOne,
        public readonly string $lineTwo,
    ) {}

    public function toArray(): array
    {
        return [
            'line_one' => $this->lineOne,
            'line_two' => $this->lineTwo,
        ];
    }

    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}
```

```php theme={null}
<?php

namespace App\Casts;

use App\ValueObjects\Address;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;

class AsAddress implements CastsAttributes
{
    /**
     * Stelt een Address-object samen uit meerdere kolommen
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Address {
        return new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        );
    }

    /**
     * Splitst het Address-object op in een array per kolom
     *
     * @return array<string, string>
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): array {
        if (! $value instanceof Address) {
            throw new InvalidArgumentException('The given value is not an Address instance.');
        }

        return [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ];
    }
}
```

<Info>
  Geef je in de `set`-methode een array terug, dan slaat Eloquent de sleutels op als kolomnamen en de waarden in de betreffende kolommen. Bij een cast voor een enkele kolom geef je een string of geheel getal terug.
</Info>

Toepassing op het model en gebruik:

```php theme={null}
<?php

namespace App\Models;

use App\Casts\AsAddress;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected function casts(): array
    {
        return [
            'address' => AsAddress::class,
        ];
    }
}
```

```php theme={null}
$user = User::find(1);

// Ophalen als Address-object
echo $user->address->lineOne;

// Wijzig je het value object, dan wordt dit bij het opslaan automatisch doorgevoerd in de DB
$user->address = new Address('123 Main St', 'Apt 4B');
$user->save();
```

### Caching van value objects

Attribuutwaarden die zijn omgezet naar een value object worden door Eloquent gecachet. Ook als je hetzelfde attribuut twee keer benadert, krijg je dezelfde objectinstantie terug.

Wil je de caching uitschakelen, voeg dan de property `$withoutObjectCaching` toe aan de castklasse.

```php theme={null}
class AsAddress implements CastsAttributes
{
    public bool $withoutObjectCaching = true;

    // ...
}
```

## Inbound casts (alleen-schrijven)

Een cast die alleen bij het schrijven naar de DB omzet en bij het lezen niets doet. Hiervoor implementeer je de interface `CastsInboundAttributes`.

Een typische toepassing is hashen: alleen omzetten bij het opslaan van wachtwoorden of geheime waarden, en bij het lezen de hashwaarde ongewijzigd teruggeven.

```bash theme={null}
php artisan make:cast AsHash --inbound
```

```php theme={null}
<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
use Illuminate\Database\Eloquent\Model;

class AsHash implements CastsInboundAttributes
{
    public function __construct(
        protected string|null $algorithm = null,
    ) {}

    /**
     * Hasht de waarde bij het opslaan
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return is_null($this->algorithm)
            ? bcrypt($value)
            : hash($this->algorithm, $value);
    }
}
```

## Castparameters

Wil je parameters doorgeven aan een custom cast, dan zet je die achter de klassenaam, gescheiden door een dubbele punt. Meerdere parameters scheid je met komma's.

```php theme={null}
protected function casts(): array
{
    return [
        'secret' => AsHash::class.':sha256',
        'data'   => AsHash::class.':sha512',
    ];
}
```

De parameters worden doorgegeven aan de constructor van de castklasse.

```php theme={null}
class AsHash implements CastsInboundAttributes
{
    public function __construct(
        protected string|null $algorithm = null, // ':sha256' wordt doorgegeven
    ) {}
}
```

## Castables: de castlogica bij het value object leggen

Een value object dat de interface `Castable` implementeert, heeft een `castUsing`-methode die zijn eigen castklasse teruggeeft. Het model hoeft de castklasse dan niet te kennen, wat de domeinlogica overzichtelijker maakt.

```php theme={null}
<?php

namespace App\ValueObjects;

use App\Casts\AsAddress;
use Illuminate\Contracts\Database\Eloquent\Castable;

class Address implements Castable
{
    /**
     * Geeft de klasse terug die voor het casten van dit object wordt gebruikt
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): string
    {
        return AsAddress::class;
    }
}
```

Aan de modelkant geef je in plaats van de castklasse de value-objectklasse op.

```php theme={null}
protected function casts(): array
{
    return [
        'address' => Address::class,
    ];
}
```

<Tip>
  Combineer je `Castable` met een anonieme klasse, dan kun je het value object en de castlogica in één bestand bundelen.

  ```php theme={null}
  class Address implements Castable
  {
      public static function castUsing(array $arguments): CastsAttributes
      {
          return new class implements CastsAttributes
          {
              public function get(Model $model, string $key, mixed $value, array $attributes): Address
              {
                  return new Address(
                      $attributes['address_line_one'],
                      $attributes['address_line_two'],
                  );
              }

              public function set(Model $model, string $key, mixed $value, array $attributes): array
              {
                  return [
                      'address_line_one' => $value->lineOne,
                      'address_line_two' => $value->lineTwo,
                  ];
              }
          };
      }
  }
  ```
</Tip>

## Interactie met `$appends` en `$hidden`

Casts en `$appends` / `$hidden` zijn onafhankelijke mechanismen, maar bij het combineren moet je opletten.

```php theme={null}
class User extends Model
{
    protected function casts(): array
    {
        return [
            'address' => AsAddress::class,
        ];
    }

    // Sluit address uit bij toArray() / toJson()
    protected $hidden = ['address_line_one', 'address_line_two'];

    // Neem het gecaste address op in het serialisatieresultaat
    protected $appends = ['address'];
}
```

<Warning>
  In `$hidden` geef je de DB-kolomnamen op. Niet de attribuutnaam die via de cast ontstaat (`address`), maar de oorspronkelijke kolomnamen (`address_line_one`, `address_line_two`).
</Warning>

## Casts toevoegen tijdens runtime

Wil je alleen voor een specifieke query of request een cast toevoegen, gebruik dan de methode `mergeCasts`.

```php theme={null}
$user = User::find(1);

$user->mergeCasts([
    'extra_data' => 'array',
]);
```

## Volgende stap

<Card title="Eloquent-observers en modelevents" icon="bell" href="/nl/advanced/eloquent-observers">
  Leer hoe je inhaakt op lifecycle-events van modellen, zoals opslaan en verwijderen, om extra verwerking toe te voegen.
</Card>


## Related topics

- [Scopes in Eloquent](/nl/advanced/eloquent-scopes.md)
- [Upgraden van Laravel 10 naar 11](/nl/blog/upgrade-10-to-11.md)
- [Eloquent accessors, mutators en casts](/nl/eloquent-mutators.md)
- [Upgraden van Laravel 8 naar 9](/nl/blog/upgrade-8-to-9.md)
- [Eloquent-serialisatie](/nl/eloquent-serialization.md)
