Projectsphp-dataOptional And Lazy

PHP Data

Package

Lightweight, framework-agnostic Data Transfer Object (DTO) library with first-class Laravel integration.

Optional, Lazy & Computed

These attributes control whether a property is present in the input, deferred until serialization, derived instead of hydrated, or hidden from output.

Import them from BradieTilley\Data\Attributes.

Sometimes

Marks a property as optional. Type it as a union with Sometimes. When the value is absent from the input, it is omitted from toArray() rather than defaulted.

use BradieTilley\Data\Attributes\Sometimes;

class ProfileData extends Data
{
    public function __construct(
        public string $name,
        public Sometimes|array $preferences,
    ) {}
}

ProfileData::from(['name' => 'Ada'])->toArray();
// ['name' => 'Ada']  — "preferences" is omitted, not null

You can also build values conditionally with Sometimes::when():

Sometimes::when($user->isAdmin(), $sensitiveValue);
// returns $sensitiveValue when true, otherwise a "missing" marker that is
// filtered out of the serialized output

Laravel conditional hydration (WhenLoaded, …) also uses Sometimes|T so a failed condition becomes an omitted key — see Laravel Integration.

Lazy

Defers computing a value until serialization. Useful for expensive values that may never be read. A lazy property is omitted from toArray() unless it is explicitly requested via include().

use BradieTilley\Data\Attributes\Lazy;

$data = ReportData::from([
    'title' => 'Q1',
    'generatedAt' => Lazy::as(fn () => now()),
]);

$data->toArray();
// ['title' => 'Q1']  — "generatedAt" omitted, closure never runs

$data->include('generatedAt')->toArray();
// ['title' => 'Q1', 'generatedAt' => '...']  — closure runs now

Computed

Marks a public property as computed — derived (usually in the constructor body) rather than hydrated from input. Computed properties are included in toArray() but never populated from the payload; any input key of the same name is ignored. They are emitted after the regular properties.

use BradieTilley\Data\Attributes\Computed;

class UserData extends Data
{
    #[Computed]
    public string $fullName;

    public function __construct(
        public string $firstName,
        public string $lastName,
    ) {
        $this->fullName = "{$firstName} {$lastName}";
    }
}

UserData::from(['firstName' => 'Ada', 'lastName' => 'Lovelace'])->toArray();
// ['firstName' => 'Ada', 'lastName' => 'Lovelace', 'fullName' => 'Ada Lovelace']

ExcludeFromOutput

Omits a property from toArray() (and therefore from JSON output). The property is still hydrated and readable on the instance — it is simply skipped when serializing.

use BradieTilley\Data\Attributes\ExcludeFromOutput;

class UserData extends Data
{
    public function __construct(
        public string $name,
        #[ExcludeFromOutput]
        public string $passwordHash,
    ) {}
}

UserData::from(['name' => 'Ada', 'passwordHash' => '...'])->toArray();
// ['name' => 'Ada']  — "passwordHash" is omitted

For per-call shaping (only, except, include), see Serialization.