Wie Sie eigene Casts durch Implementierung des Interfaces CastsAttributes erstellen. Fortgeschrittene Muster wie Value Objects, Inbound-Casts und Castables werden ebenfalls vorgestellt.
Eloquent-Casts wandeln aus der Datenbank gelesene Rohwerte in PHP-Typen um und beim Speichern in die umgekehrte Richtung. Sie werden über die Methode casts definiert.
// src/Illuminate/Contracts/Database/Eloquent/CastsAttributes.phpinterface CastsAttributes{ /** * Wandelt den DB-Rohwert in einen PHP-Wert (beim Lesen). * * @param array<string, mixed> $attributes Alle Attributwerte des Modells */ public function get(Model $model, string $key, mixed $value, array $attributes); /** * Wandelt einen PHP-Wert in ein DB-taugliches Format (beim Schreiben). * * @param array<string, mixed> $attributes Alle Attributwerte des Modells */ public function set(Model $model, string $key, mixed $value, array $attributes);}
Weil das Argument $attributes alle Modellattribute enthält, sind auch spaltenübergreifende Umwandlungen möglich (siehe Value-Object-Muster unten).
Die Datei app/Casts/AsMoney.php wird angelegt. Als Beispiel implementieren wir einen Cast, der einen als Integer gespeicherten Geldbetrag in ein Money-Value-Object umwandelt.
<?phpnamespace App\Casts;use App\ValueObjects\Money;use Illuminate\Contracts\Database\Eloquent\CastsAttributes;use Illuminate\Database\Eloquent\Model;class AsMoney implements CastsAttributes{ /** * Wandelt den DB-Integerwert (in Yen) in ein Money-Objekt um. */ public function get( Model $model, string $key, mixed $value, array $attributes, ): Money { return new Money((int) $value); } /** * Wandelt ein Money-Objekt in einen DB-tauglichen Integer um. */ public function set( Model $model, string $key, mixed $value, array $attributes, ): int { if ($value instanceof Money) { return $value->amount; } return (int) $value; }}
Aktivieren Sie den Cast am Modell:
<?phpnamespace App\Models;use App\Casts\AsMoney;use Illuminate\Database\Eloquent\Model;class Order extends Model{ protected function casts(): array { return [ 'price' => AsMoney::class, ]; }}
Die zwei Spalten address_line_one und address_line_two werden in ein Address-Value-Object zusammengefasst.
<?phpnamespace 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(); }}
<?phpnamespace App\Casts;use App\ValueObjects\Address;use Illuminate\Contracts\Database\Eloquent\CastsAttributes;use Illuminate\Database\Eloquent\Model;use InvalidArgumentException;class AsAddress implements CastsAttributes{ /** * Aus mehreren Spalten ein Address-Objekt zusammenbauen. */ public function get( Model $model, string $key, mixed $value, array $attributes, ): Address { return new Address( $attributes['address_line_one'], $attributes['address_line_two'], ); } /** * Ein Address-Objekt in ein Array pro Spalte zerlegen. * * @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, ]; }}
Gibt set ein Array zurück, verwendet Eloquent die Schlüssel als Spaltennamen und speichert die Werte in die jeweiligen Spalten. Bei Ein-Spalten-Casts geben Sie einen String oder Integer zurück.
Die Anwendung am Modell und die Nutzung:
<?phpnamespace App\Models;use App\Casts\AsAddress;use Illuminate\Database\Eloquent\Model;class User extends Model{ protected function casts(): array { return [ 'address' => AsAddress::class, ]; }}
$user = User::find(1);// Als Address-Objekt lesenecho $user->address->lineOne;// Änderungen am Value Object werden beim Speichern automatisch in die DB übertragen$user->address = new Address('123 Main St', 'Apt 4B');$user->save();
In Value Objects umgewandelte Attributwerte werden von Eloquent gecacht. Beim zweifachen Zugriff auf dasselbe Attribut erhalten Sie dieselbe Instanz.Wenn Sie den Cache deaktivieren möchten, fügen Sie in der Cast-Klasse die Property $withoutObjectCaching hinzu.
class AsAddress implements CastsAttributes{ public bool $withoutObjectCaching = true; // ...}
Ein Cast, der nur beim Schreiben in die DB umwandelt, beim Lesen aber nicht. Implementiert das Interface CastsInboundAttributes.Typischer Anwendungsfall ist Hashing: Beim Speichern von Passwörtern oder geheimen Werten wird gehasht, beim Lesen wird der Hash unverändert zurückgegeben.
php artisan make:cast AsHash --inbound
<?phpnamespace 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, ) {} /** * Beim Speichern hashen. */ public function set( Model $model, string $key, mixed $value, array $attributes, ): string { return is_null($this->algorithm) ? bcrypt($value) : hash($this->algorithm, $value); }}
Die Parameter werden dem Konstruktor der Cast-Klasse übergeben.
class AsHash implements CastsInboundAttributes{ public function __construct( protected string|null $algorithm = null, // ':sha256' wird übergeben ) {}}
Ein Value Object, das das Interface Castable implementiert, besitzt eine Methode castUsing, die die zu verwendende Cast-Klasse zurückgibt. So muss das Modell die Cast-Klasse nicht kennen und die Domänenlogik bleibt aufgeräumt.
<?phpnamespace App\ValueObjects;use App\Casts\AsAddress;use Illuminate\Contracts\Database\Eloquent\Castable;class Address implements Castable{ /** * Gibt die zu verwendende Cast-Klasse zurück. * * @param array<string, mixed> $arguments */ public static function castUsing(array $arguments): string { return AsAddress::class; }}
Am Modell geben Sie statt der Cast-Klasse die Value-Object-Klasse an.
protected function casts(): array{ return [ 'address' => Address::class, ];}
In Kombination mit Castable und einer anonymen Klasse können Sie Value Object und Cast-Logik in einer Datei bündeln.
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, ]; } }; }}
Casts, $appends und $hidden sind eigenständige Mechanismen. Bei der Kombination ist Sorgfalt geboten.
class User extends Model{ protected function casts(): array { return [ 'address' => AsAddress::class, ]; } // address bei toArray()/toJson() ausschließen protected $hidden = ['address_line_one', 'address_line_two']; // Gecastetes address im Serialisierungsergebnis mitliefern protected $appends = ['address'];}
In $hidden geben Sie DB-Spaltennamen an. Nicht den durch den Cast entstandenen Attributnamen (address), sondern die Ursprungsnamen (address_line_one, address_line_two).