De $casts-eigenschap van Laravel is een van die stille gemakken die bepalen hoe je met gegevens omgaat zonder veel aandacht te trekken. Voeg een ingebouwd type zoals boolean of datetime toe aan de array, en Eloquent converteert automatisch ruwe database-strings naar iets vriendelijkers voordat de waarde je controllers of views bereikt. Het houdt je code netjes. Maar zodra je je eigen helper direct in die array probeert aan te roepen, zet het framework zich ertegen af. Iets schrijven als 'username' => 'encrypt_data()' zal niet werken. Laravel verwacht ofwel een native cast-keyword of een klasse die het CastsAttributes-interface implementeert. Deze vereiste bestaat omdat de casting-laag diep in de hydratatie- en serialisatiecyclus van Eloquent draait; het heeft een voorspelbaar object met een duidelijk contract nodig in plaats van een willekeurige functiestring die mogelijk niet bestaat tijdens runtime.

Waarom helperfuncties falen in de $casts-array

Wanneer Eloquent een model laadt, scant het de $casts-array om te bepalen hoe elke kolom getransformeerd moet worden. Het framework herkent specifieke primitieven—string, integer, array, encrypted, enzovoort—en het herkent volledig gekwalificeerde klassenamen die CastsAttributes implementeren. Het evalueert de string niet als code. Dus 'encrypt_data()' wordt behandeld als een onbekend cast-type, wat een foutmelding veroorzaakt. Zelfs als Laravel het zou parsen, zou er geen betrouwbare manier zijn om de modelinstantie, de attribuut-sleutel, de huidige waarde en de omliggende attributen in de juiste volgorde aan je functie door te geven. Het interface bestaat precies om die handdruk tussen je eigen logica en de interne werking van Eloquent te standaardiseren.

Je hebt twee degelijke manieren om deze kloof te overbruggen. De ene is gericht op hergebruik op de lange termijn. De andere is gericht op snelheid wanneer je alleen een snelle patch in een enkel model nodig hebt.

Methode 1: Schrijf een custom cast-klasse

Als dezelfde transformatie van toepassing is op meerdere velden of verspreid is over verschillende modellen, dan is een specifieke cast-klasse de nettere keuze. Het staat in een eigen bestand, kan in isolatie worden getest met unit tests, en houdt je modellen vrij van repetitieve boilerplate.

Begin met het aanmaken van app/Casts/CustomEncrypt.php. De namespace moet overeenkomen met je autoloading-instellingen, meestal App\Casts. De klasse moet Illuminate\Contracts\Database\Eloquent\CastsAttributes implementeren, wat je dwingt om twee methoden te definiëren: get en set.

<?php

namespace App\Casts;

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

class CustomEncrypt implements CastsAttributes
{
    public function get(Model $model, string $key, mixed $value, array $attributes): mixed
    {
        return decrypt_data($value);
    }

    public function set(Model $model, string $key, mixed $value, array $attributes): mixed
    {
        return encrypt_data($value);
    }
}

De handtekeningen (signatures) zijn belangrijk. Eloquent geeft vier argumenten door aan elke methode. $model is de instantie die wordt ingevuld of opgeslagen, waardoor je andere velden kunt inspecteren als je logica daarvan afhangt. $key is de kolomnaam die momenteel wordt gecast. $value is de ruwe string of null die van de database komt bij een get, of de door de gebruiker opgegeven waarde bij een set. $attributes is de volledige ruwe array van kolommen voor die rij. Je hoeft ze niet altijd alle vier te gebruiken, maar het interface vereist ze wel.

In de get-methode hierboven wordt decrypt_data($value) uitgevoerd nadat Eloquent de rij uit de database heeft opgehaald en voordat de waarde in je model terechtkomt. In de set-methode wordt encrypt_data($value) uitgevoerd vóór de INSERT of UPDATE, zodat de database nooit platte tekst ziet.

Om het te koppelen, verwijs je naar de klasse in de $casts-array van je model:

protected $casts = [
    'username' => CustomEncrypt::class,
    'password' => CustomEncrypt::class,
];

Omdat je de klasseconstante gebruikt, regelt de autoloader de rest. Als je applicatie later het versleutelingsschema moet wijzigen, hoef je slechts één bestand aan te passen en verandert het gedrag van elk gekoppeld veld onmiddellijk. Die centralisatie is moeilijk te verslaan wanneer je gevoelige gegevens over veel tabellen beheert.

Methode 2: Gebruik een accessor en mutator

Soms wil je geen nieuw bestand voor een transformatie die alleen op één plek belangrijk is. Met de Attribute-klasse van Laravel kun je get- en set-logica direct op het model definiëren met behulp van de PHP 8+ closure-syntax.

use Illuminate\Database\Eloquent\Casts\Attribute;

protected function username(): Attribute
{
    return Attribute::make(
        get: fn ($value) => decrypt_data($value),
        set: fn ($value) => encrypt_data($value),
    );
}

Hier moet de methodenaam overeenkomen met de kolom of het attribuut dat je target. Wanneer Eloquent username uit de database leest, wordt de ruwe waarde door de get-closure gestuurd. Wanneer je een nieuwe waarde toekent aan de username-eigenschap van het model, versleutelt de set-closure deze voordat Eloquent de query opbouwt.

Deze aanpak blinkt uit in prototypes of legacy-modellen waar een volledige map met cast-klassen als overkill voelt. Het nadeel is herhaling. Als je later besluit dat email, phone en backup_code dezelfde behandeling nodig hebben, eindig je met het kopiëren van die closures naar meerdere methoden of modellen. Die ruis loopt op. Het voorkomt je ook om de logica te hergebruiken in een unit test zonder het volledige model te booten.

Kiezen tussen de twee

Gebruik een custom cast-klasse wanneer je waarde hecht aan herbruikbaarheid, testbaarheid en het slank houden van je modellen. Het geeft andere ontwikkelaars aan dat deze transformatie een belangrijk concept is in je applicatie, en geen eenmalige hack.

Gebruik een accessor wanneer de logica echt lokaal, experimenteel of onwaarschijnlijk is om de huidige sprint te overleven. Het stelt je in staat om snel te werken zonder kleine bestanden door de hele codebase te verspreiden. Zorg er alleen voor dat je klaar bent om te refactoren naar een cast-klasse zodra hetzelfde patroon voor de tweede of derde keer verschijnt.

Praktische details om in gedachten te houden

Custom casts zijn krachtig, maar ze introduceren gedrag dat je kan verrassen als je niet oplet. Onthoud eerst dat get ontvangt wat de database heeft geretourneerd, inclusief null. Als decrypt_data geen null-input toestaat, bouw dan een beveiliging in:

public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
    return is_null($value) ? null : decrypt_data($value);
}

Ten tweede worden casts uitgevoerd tijdens array- en JSON-serialisatie. Wanneer je een model teruggeeft als een API-resource of toArray() aanroept, blijft de get-logica van de cast van kracht. Dat is meestal wat je wilt, maar het is goed om te onthouden als je gedecrypteerde waarden blootstelt en daar extra toegangscontroles bovenop wilt leggen.

Ten derde, en het belangrijkste: casting vindt plaats na het ophalen van de gegevens. Je kunt niet query's uitvoeren op de getransformeerde waarde. Een query zoals User::where('username', 'john_doe')->first() stuurt de string john_doe rechtstreeks naar de database. Deze gaat nooit door je decrypt-logica heen. Als de kolom 'at rest' versleuteld is, zal die query niets vinden, tenzij je zoekt in de versleutelde ciphertext zelf. Plan je database-toegangspatronen dienovereenkomstig, want casts zijn geen vervanging voor native databasefuncties of geïndexeerde plaintext-kolommen.

Custom cast-klassen zijn ook uitstekende plekken voor kleine stukjes configuratie. Als je constructor-argumenten nodig hebt — bijvoorbeeld voor het doorgeven van een cipher-modus of een format string — dan ondersteunt Laravel dit via de $casts-array met een expressie zoals 'field' => CustomEncrypt::class . ':arg', hoewel dat een stap verder gaat dan de basisinstelling die hier wordt beschreven.

De belangrijkste conclusie

De beperking om geen helperfuncties direct in $casts te plaatsen, is geen willekeurige bureaucratie. Het dwingt je richting code die expliciet, testbaar en herbruikbaar is. Custom cast-klassen veranderen verspreide inline-logica in betrouwbare componenten die je zonder duplicatie tussen modellen kunt delen. Accessors houden de deur open voor snelle, lokale aanpassingen wanneer een apart bestand als te omslachtig aanvoelt. Beheers beide, kies op basis van de omvang van het probleem, en je Eloquent-laag blijft leesbaar, ook lang nadat de applicatie is gegroeid.