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

# Casts personnalisés Eloquent

> Créez des casts personnalisés en implémentant l'interface CastsAttributes. Découvrez également des patterns avancés comme Value Object, casts entrants (inbound) et Castables.

## Qu'est-ce qu'un cast

Les casts Eloquent permettent de convertir la valeur brute récupérée en base en un type PHP et d'effectuer l'opération inverse lors de la sauvegarde. Ils sont définis via la méthode `casts`.

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

## Types de casts intégrés

Voici la liste des casts fournis en standard par Laravel.

| Cast                   | Description                                                      |
| ---------------------- | ---------------------------------------------------------------- |
| `integer` / `int`      | Convertit en entier                                              |
| `float` / `double`     | Convertit en nombre à virgule flottante                          |
| `string`               | Convertit en chaîne de caractères                                |
| `boolean` / `bool`     | Convertit en booléen (inclut `0`/`1`)                            |
| `array`                | Chaîne JSON ↔ tableau PHP                                        |
| `collection`           | Chaîne JSON ↔ instance Collection                                |
| `object`               | Chaîne JSON ↔ instance stdObject                                 |
| `datetime`             | Chaîne ↔ instance Carbon                                         |
| `immutable_datetime`   | Chaîne ↔ instance CarbonImmutable                                |
| `date`                 | Chaîne ↔ Carbon (sans heure)                                     |
| `timestamp`            | Chaîne ↔ timestamp UNIX                                          |
| `encrypted`            | Chiffré à la sauvegarde, déchiffré à la lecture                  |
| `hashed`               | Haché à la sauvegarde (à combiner avec un cast en lecture seule) |
| `AsStringable::class`  | Chaîne ↔ objet Stringable                                        |
| `AsArrayObject::class` | JSON ↔ instance ArrayObject                                      |
| `AsCollection::class`  | JSON ↔ instance Collection                                       |

<Info>
  `AsArrayObject` et `AsCollection` sont implémentés en interne dans Laravel comme des casts personnalisés, afin de permettre la modification directe d'offsets spécifiques du tableau.
</Info>

## Création d'une classe de cast personnalisée

Lorsque les casts intégrés ne suffisent pas, créez un cast personnalisé en implémentant l'interface `CastsAttributes`.

### Définition de l'interface

Le contrat du framework est défini ainsi :

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

interface CastsAttributes
{
    /**
     * Convertit la valeur brute de la BDD en valeur PHP (en lecture)
     *
     * @param  array<string, mixed>  $attributes  Toutes les valeurs d'attributs du modèle
     */
    public function get(Model $model, string $key, mixed $value, array $attributes);

    /**
     * Convertit la valeur PHP en une forme stockable en BDD (en écriture)
     *
     * @param  array<string, mixed>  $attributes  Toutes les valeurs d'attributs du modèle
     */
    public function set(Model $model, string $key, mixed $value, array $attributes);
}
```

L'argument `$attributes` contient tous les attributs du modèle, ce qui rend possible une conversion s'étendant sur plusieurs colonnes (voir le pattern Value Object ci-dessous).

### Implémentation d'un cast personnalisé de base

Générez un squelette avec la commande `make:cast`.

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

`app/Casts/AsMoney.php` est généré. Comme exemple, implémentons un cast qui convertit un montant (stocké sous forme d'entier) en 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
{
    /**
     * Convertit l'entier en BDD (en centimes) en objet Money
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Money {
        return new Money((int) $value);
    }

    /**
     * Convertit l'objet Money en entier stockable en BDD
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): int {
        if ($value instanceof Money) {
            return $value->amount;
        }

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

Appliquez le cast au modèle.

```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,
        ];
    }
}
```

Désormais, `$order->price` retourne une instance de `Money`.

## Cast Value Object

Pattern qui regroupe plusieurs colonnes BDD en un seul Value Object.

### Exemple d'implémentation : cast Adresse

Regroupez les deux colonnes `address_line_one` et `address_line_two` dans un 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
{
    /**
     * Construit un objet Address à partir de plusieurs colonnes
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Address {
        return new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        );
    }

    /**
     * Décompose l'objet Address en un tableau par colonne
     *
     * @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>
  Si `set` retourne un tableau, Eloquent utilise les clés comme noms de colonnes et sauvegarde chaque valeur dans la colonne correspondante. Pour un cast à une seule colonne, retournez une chaîne ou un entier.
</Info>

L'application au modèle et l'utilisation sont les suivantes :

```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);

// Récupéré comme objet Address
echo $user->address->lineOne;

// Modifier le Value Object le reflète automatiquement en BDD à la sauvegarde
$user->address = new Address('123 Main St', 'Apt 4B');
$user->save();
```

### Mise en cache des Value Objects

Les attributs convertis en Value Object sont mis en cache par Eloquent. Accéder deux fois au même attribut retourne la même instance d'objet.

Pour désactiver la mise en cache, ajoutez la propriété `$withoutObjectCaching` à la classe de cast.

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

    // ...
}
```

## Cast entrant (inbound, écriture seule)

Cast qui effectue la conversion uniquement lors de l'écriture en BDD, sans conversion lors de la lecture. Implémentez l'interface `CastsInboundAttributes`.

Le cas d'usage typique est le hachage. Convertir uniquement à la sauvegarde d'un mot de passe ou d'une valeur secrète, la lecture retourne le hash tel quel.

```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,
    ) {}

    /**
     * Hache lors de la sauvegarde
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return is_null($this->algorithm)
            ? bcrypt($value)
            : hash($this->algorithm, $value);
    }
}
```

## Paramètres de cast

Pour passer des paramètres à un cast personnalisé, spécifiez-les après le nom de la classe séparés par un `:`. Plusieurs paramètres sont séparés par des virgules.

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

Les paramètres sont passés au constructeur de la classe de cast.

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

## Castables : porter la logique de cast dans le Value Object

Un Value Object implémentant l'interface `Castable` a une méthode `castUsing` qui retourne sa propre classe de cast. Comme le modèle n'a plus besoin de connaître la classe de cast, la logique métier gagne en clarté.

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

namespace App\ValueObjects;

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

class Address implements Castable
{
    /**
     * Retourne la classe utilisée pour le cast de cet objet
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): string
    {
        return AsAddress::class;
    }
}
```

Côté modèle, spécifiez la classe Value Object au lieu de la classe de cast.

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

<Tip>
  En combinant `Castable` avec une classe anonyme, vous pouvez regrouper le Value Object et la logique de cast dans un même fichier.

  ```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>

## Interaction avec `$appends` et `$hidden`

Les casts, `$appends` et `$hidden` sont des mécanismes indépendants, mais leur combinaison demande de l'attention.

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

    // Exclure address_line_one/two du toArray() / toJson()
    protected $hidden = ['address_line_one', 'address_line_two'];

    // Inclure l'address casté dans la sérialisation
    protected $appends = ['address'];
}
```

<Warning>
  Ce qui est spécifié dans `$hidden` correspond aux noms de colonnes BDD. Précisez les noms de colonnes originales (`address_line_one`, `address_line_two`) plutôt que le nom d'attribut produit par le cast (`address`).
</Warning>

## Ajout de casts au runtime

Pour ajouter des casts uniquement pour une requête ou un traitement particulier, utilisez la méthode `mergeCasts`.

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

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

## Étapes suivantes

<Card title="Observateurs Eloquent et événements de modèle" icon="bell" href="/fr/advanced/eloquent-observers">
  Apprenez à ajouter du traitement en s'accrochant aux événements du cycle de vie du modèle (sauvegarde, suppression, etc.).
</Card>


## Related topics

- [Accesseurs, mutateurs et casts Eloquent](/fr/eloquent-mutators.md)
- [Attributs PHP](/fr/advanced/php-attributes.md)
- [Mise à niveau de Laravel 10 à 11](/fr/blog/upgrade-10-to-11.md)
- [Scopes Eloquent](/fr/advanced/eloquent-scopes.md)
- [Collections Eloquent](/fr/eloquent-collections.md)
