Skip to main content
Sometimes you don’t want all properties included when transforming a data object. With lazy properties, you can include properties only when needed:
class AlbumData extends Data
{
    /**
    * @param Lazy|Collection<int, SongData> $songs
    */
    public function __construct(
        public string $title,
        public Lazy|Collection $songs,
    ) {
    }
    
    public static function fromModel(Album $album): self
    {
        return new self(
            $album->title,
            Lazy::create(fn() => SongData::collect($album->songs))
        );
    }
}
The songs key won’t be included when transforming:
AlbumData::from(Album::first())->toArray();
// ['title' => 'Together Forever']

Including Lazy Properties

Include the property explicitly:
AlbumData::from(Album::first())->include('songs');

Nested Includes

You can nest includes for properties within data objects:
AlbumData::from(Album::first())->include('songs.name', 'songs.artist');
Or combine includes:
AlbumData::from(Album::first())->include('songs.{name, artist}');
Include all properties:
AlbumData::from(Album::first())->include('songs.*');

Types of Lazy Properties

Basic Lazy

Must be explicitly included:
Lazy::create(fn() => SongData::collect($album->songs));

Conditional Lazy

Only included when a condition is true:
Lazy::when(fn() => $this->is_admin, fn() => SongData::collect($album->songs));

Relational Lazy

Only included when a relation is loaded:
Lazy::whenLoaded('songs', $album, fn() => SongData::collect($album->songs));

Default Included

Lazy property included by default:
Lazy::create(fn() => SongData::collect($album->songs))->defaultIncluded();
Exclude it explicitly:
AlbumData::create(Album::first())->exclude('songs');

Auto Lazy

Reduce boilerplate with auto lazy properties:
class UserData extends Data
{
    public function __construct(
        public string $title,
        public Lazy|SongData $favorite_song,
    ) {
    }

    public static function fromModel(User $user): self
    {
        return new self(
            $user->title,
            Lazy::create(fn() => SongData::from($user->favorite_song))
        );
    }
}

Property Level Auto Lazy

Apply auto lazy to specific properties:
class UserData extends Data
{
    public function __construct(
        public string $title,
        #[AutoLazy]
        public Lazy|SongData $favorite_song,
    ) {
    }
}

Auto Lazy with Model Relations

Automatically create lazy properties for model relations:
class UserData extends Data
{
    public function __construct(
        public string $title,
        #[AutoWhenLoadedLazy]
        public Lazy|SongData $favoriteSong,
    ) {
    }
}
Specify the relation name if it differs:
class UserData extends Data
{
    public function __construct(
        public string $title,
        #[AutoWhenLoadedLazy('favoriteSong')]
        public Lazy|SongData $favorite_song,
    ) {
    }
}

Only and Except

Completely remove properties from the response:
AlbumData::from(Album::first())->only('songs'); // only show `songs`
AlbumData::from(Album::first())->except('songs'); // show everything except `songs`
Multiple keys:
AlbumData::from(Album::first())->only('songs.name', 'songs.artist');
AlbumData::from(Album::first())->except('songs.name', 'songs.artist');
only and except always take precedence over include and exclude.

Conditional Methods

Add includes/excludes based on conditions:
AlbumData::from(Album::first())->includeWhen('songs', auth()->user()->isAdmin);
AlbumData::from(Album::first())->excludeWhen('songs', auth()->user()->isAdmin);
AlbumData::from(Album::first())->onlyWhen('songs', auth()->user()->isAdmin);
AlbumData::from(Album::first())->exceptWhen('songs', auth()->user()->isAdmin);
Using data object values:
AlbumData::from(Album::first())->includeWhen('songs', fn(AlbumData $data) => count($data->songs) > 0);

Class-Level Includes

Define includes on the class:
class AlbumData extends Data
{
    public function includeProperties(): array
    {
        return [
            'songs' => $this->title === 'Together Forever',
        ];
    }
}
Other methods:
  • excludeProperties() for excludes
  • exceptProperties() for except
  • onlyProperties() for only

Query String Includes

Allow includes via URL query strings:
class UserData extends Data
{
    public static function allowedRequestIncludes(): ?array
    {
        return ['favorite_song'];
    }
}
Request:
https://spatie.be/my-account?include=favorite_song
Multiple properties:
https://spatie.be/my-account?include=favorite_song,favorite_movie
Or using array input:
https://spatie.be/my-account?include[]=favorite_song&include[]=favorite_movie

Allow All Includes

Return null to allow all properties:
public static function allowedRequestIncludes(): ?array
{
    return null;
}

Other Operations

  • allowedRequestExcludes() with exclude query parameter
  • allowedRequestExcept() with except query parameter
  • allowedRequestOnly() with only query parameter

Mutability

Includes/excludes only affect the data object once:
AlbumData::from(Album::first())->include('songs')->toArray(); // includes songs
AlbumData::from(Album::first())->toArray(); // does not include songs
For permanent includes, use property methods or permanent methods:
AlbumData::from(Album::first())->includePermanently('songs');
AlbumData::from(Album::first())->excludePermanently('songs');
AlbumData::from(Album::first())->onlyPermanently('songs');
AlbumData::from(Album::first())->exceptPermanently('songs');
Or with conditionals:
AlbumData::from(Album::first())->includeWhen('songs', fn($data) => count($data->songs) > 0, permanent: true);

Build docs developers (and LLMs) love