> ## 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 personalizados de Eloquent

> Aprende cómo crear casts personalizados implementando la interfaz CastsAttributes. Incluye patrones avanzados como Value Object, casts entrantes y Castables.

## Qué son los casts

Los casts de Eloquent convierten los valores en bruto obtenidos de la base de datos a tipos de PHP y, al guardar, aplican la conversión inversa. Se declaran en el método `casts`.

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

## Tipos de casts integrados

Lista de los casts que Laravel ofrece de forma predeterminada.

| Cast                   | Descripción                                                    |
| ---------------------- | -------------------------------------------------------------- |
| `integer` / `int`      | Convierte a entero.                                            |
| `float` / `double`     | Convierte a punto flotante.                                    |
| `string`               | Convierte a cadena.                                            |
| `boolean` / `bool`     | Convierte a booleano (incluidos `0`/`1`).                      |
| `array`                | Cadena JSON ↔ array de PHP.                                    |
| `collection`           | Cadena JSON ↔ instancia de Collection.                         |
| `object`               | Cadena JSON ↔ instancia stdClass.                              |
| `datetime`             | Cadena ↔ instancia de Carbon.                                  |
| `immutable_datetime`   | Cadena ↔ instancia de CarbonImmutable.                         |
| `date`                 | Cadena ↔ Carbon (sin hora).                                    |
| `timestamp`            | Cadena ↔ timestamp UNIX.                                       |
| `encrypted`            | Cifra al guardar, descifra al leer.                            |
| `hashed`               | Aplica hash al guardar (se combina con casts de solo lectura). |
| `AsStringable::class`  | Cadena ↔ objeto Stringable.                                    |
| `AsArrayObject::class` | JSON ↔ instancia de ArrayObject.                               |
| `AsCollection::class`  | JSON ↔ instancia de Collection.                                |

<Info>
  `AsArrayObject` y `AsCollection` se implementan internamente en Laravel como casts personalizados para permitir modificar directamente offsets concretos del array.
</Info>

## Crear una clase de cast personalizado

Cuando los casts integrados no cubren la conversión que necesitas, crea un cast personalizado implementando la interfaz `CastsAttributes`.

### Definición de la interfaz

El contrato del framework se define así:

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

interface CastsAttributes
{
    /**
     * Convierte el valor en bruto de la BD a un valor PHP (lectura)
     *
     * @param  array<string, mixed>  $attributes  todos los atributos del modelo
     */
    public function get(Model $model, string $key, mixed $value, array $attributes);

    /**
     * Convierte un valor PHP a una forma persistible en la BD (escritura)
     *
     * @param  array<string, mixed>  $attributes  todos los atributos del modelo
     */
    public function set(Model $model, string $key, mixed $value, array $attributes);
}
```

Como el argumento `$attributes` contiene todos los atributos del modelo, es posible hacer conversiones que abarquen varias columnas (ver el patrón Value Object más abajo).

### Implementación básica de un cast personalizado

Genera el esqueleto con el comando `make:cast`.

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

Se crea `app/Casts/AsMoney.php`. Como ejemplo, vamos a implementar un cast que convierte un importe (almacenado como entero) en un 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
{
    /**
     * Convierte el entero de la BD (en yenes) a un objeto Money
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Money {
        return new Money((int) $value);
    }

    /**
     * Convierte el objeto Money en el entero que se guarda en la BD
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): int {
        if ($value instanceof Money) {
            return $value->amount;
        }

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

Aplica el cast en el modelo.

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

Con esto, `$order->price` devuelve una instancia de `Money`.

## Casts de Value Object

Patrón para tratar varias columnas de la BD como un único Value Object.

### Ejemplo: cast de dirección

Combina las dos columnas `address_line_one` y `address_line_two` en 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
{
    /**
     * Construye el objeto Address a partir de varias columnas
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Address {
        return new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        );
    }

    /**
     * Descompone el objeto Address en un array por columnas
     *
     * @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` devuelve un array, Eloquent interpreta las claves como nombres de columna y guarda cada valor en su columna correspondiente. En casts de una sola columna se devuelve una cadena o un entero.
</Info>

Aplicación al modelo y uso:

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

// Puedes obtenerlo como objeto Address
echo $user->address->lineOne;

// Al modificar el Value Object, los cambios se reflejan automáticamente en la BD al guardar
$user->address = new Address('123 Main St', 'Apt 4B');
$user->save();
```

### Caché del Value Object

Eloquent cachea los atributos convertidos en Value Objects. Si accedes dos veces al mismo atributo, se devuelve la misma instancia.

Si quieres desactivar la caché, añade la propiedad `$withoutObjectCaching` a la clase del cast.

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

    // ...
}
```

## Casts entrantes (solo escritura)

Casts que solo aplican la conversión al escribir en la BD, sin transformar nada al leer. Se implementan mediante la interfaz `CastsInboundAttributes`.

Un uso típico es el hashing. Solo se transforma al guardar contraseñas o valores secretos; al leer se devuelve el hash tal cual.

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

    /**
     * Aplica hash al guardar
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return is_null($this->algorithm)
            ? bcrypt($value)
            : hash($this->algorithm, $value);
    }
}
```

## Parámetros de cast

Para pasar parámetros a un cast personalizado, especifícalos tras el nombre de la clase separados por dos puntos. Varios parámetros se separan por comas.

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

Los parámetros se pasan al constructor de la clase del cast.

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

## Castables: colocar la lógica de cast en el propio Value Object

Un Value Object que implementa la interfaz `Castable` proporciona un método `castUsing` que devuelve su clase de cast. Con esto, el modelo no necesita conocer la clase de cast y la lógica de dominio queda mejor organizada.

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

namespace App\ValueObjects;

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

class Address implements Castable
{
    /**
     * Devuelve la clase que se usará para castear este objeto
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): string
    {
        return AsAddress::class;
    }
}
```

En el modelo, indica la clase del Value Object en lugar de la del cast.

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

<Tip>
  Combinando `Castable` con una clase anónima puedes reunir el Value Object y la lógica de cast en un solo archivo.

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

## Interacción con `$appends` y `$hidden`

Los casts y `$appends` / `$hidden` son mecanismos independientes, pero al combinarlos hay que tener cuidado.

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

    // Excluye address en toArray() / toJson()
    protected $hidden = ['address_line_one', 'address_line_two'];

    // Incluye el address casteado en la serialización
    protected $appends = ['address'];
}
```

<Warning>
  En `$hidden` se especifican nombres de columna de la BD. No debes indicar el atributo generado por el cast (`address`), sino las columnas originales (`address_line_one`, `address_line_two`).
</Warning>

## Añadir casts en tiempo de ejecución

Cuando quieras añadir un cast solo para una consulta o una petición concreta, usa el método `mergeCasts`.

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

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

## Próximos pasos

<Card title="Observers de Eloquent y eventos del modelo" icon="bell" href="/es/advanced/eloquent-observers">
  Aprende a engancharte a los eventos del ciclo de vida (guardar, borrar, etc.) del modelo para añadir procesamiento.
</Card>


## Related topics

- [Accesores, mutadores y casts de Eloquent](/es/eloquent-mutators.md)
- [Actualización de Laravel 10 a 11](/es/blog/upgrade-10-to-11.md)
- [Scopes de Eloquent](/es/advanced/eloquent-scopes.md)
- [Actualización de Laravel 8 a 9](/es/blog/upgrade-8-to-9.md)
- [Implementación de un guard de autenticación personalizado](/es/advanced/custom-auth-guard.md)
