Casts & encryption
castAs() decides how the stored value is read and written. It accepts any Laravel cast string, a custom CastsAttributes (class-string or instance), or a cast class-string with parameters:
public function castAs(): string|CastsAttributes
{
return 'boolean'; // integer, float, array, collection, immutable_datetime, …
}Dates round-trip as Carbon instances:
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use RoundlyConsulting\Options\BaseOption;
final class TrialEndsAtOption extends BaseOption
{
public function key(): string
{
return 'trial-ends-at';
}
public function castAs(): string|CastsAttributes
{
return 'immutable_datetime';
}
}
TrialEndsAtOption::for($team)->set(now()->toImmutable()->addDays(14));
TrialEndsAtOption::for($team)->value(); // CarbonImmutableBacked enums
EnumCast casts to and from a backed enum — string- or int-backed. Pass the enum class as the cast parameter:
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use RoundlyConsulting\Options\BaseOption;
use RoundlyConsulting\Options\Casts\EnumCast;
enum Status: string
{
case Active = 'active';
case Paused = 'paused';
}
final class StatusOption extends BaseOption
{
public function key(): string
{
return 'status';
}
public function castAs(): string|CastsAttributes
{
return EnumCast::class.':'.Status::class; // backed-enum cast (string- or int-backed)
}
}
Options::set(StatusOption::class, Status::Paused); // stores 'paused'
Options::get(StatusOption::class); // Status::PausedIt stores the enum’s backing value and returns the case. For an int-backed enum the numeric string the text column returns is coerced to an int first. set() accepts a case or a raw scalar; an empty stored value reads as null, and a stored value that no longer matches a case reads as null instead of throwing. You can also return new EnumCast(Status::class) — but a cast instance can’t be encrypted and shows up as type custom in group definitions.
Encryption
Return true from encrypted() to encrypt the value at rest. It’s serialized through its cast, encrypted with Laravel’s encrypter, and decrypted and cast back on read — so an encrypted integer still reads as an int:
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use RoundlyConsulting\Options\BaseOption;
final class ApiTokenOption extends BaseOption
{
public function key(): string
{
return 'api-token';
}
public function castAs(): string|CastsAttributes
{
return 'string'; // must be a string when encrypted
}
public function encrypted(): bool
{
return true;
}
}
Options::set(ApiTokenOption::class, 'tok_example_123', $tenant); // ciphertext at rest
Options::get(ApiTokenOption::class, $tenant); // 'tok_example_123'- castAs() must return a string when encrypted() is true — 'integer', 'boolean', 'collection', a cast class-string such as EnumCast::class.':'.Status::class, … A cast instance throws EncryptionNotSupported.
- Every cast round-trips: the inner cast serializes the value before encryption and rehydrates it after decryption, so integers, booleans, dates, collections and enums come back in their type.
- Encryption uses your APP_KEY. Tampered ciphertext throws Laravel’s DecryptException on read.
- The in-request memo and the persistent cache only ever hold the ciphertext — a cache store never sees the plain value.
- Events, observers and group definitions see the plain value — mind what you log.
Reads always return the cast type
set() stores the value the way the database would — the raw string its cast produces — and caches that, not your input. Every read casts it, including the one right after set(): setting 'active' on an enum option returns Status::Active, an integer option set to '5' returns 5, and a collection option set to an array returns a Collection. options:set, which passes a string (or decoded JSON with --json), therefore reads back in the option’s type straight away.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.