Including and excluding properties | laravel-data | Spatie

 SPATIE

  Laravel Data
===============

spatie.be/open-source

  [Docs](https://spatie.be/docs)  [Laravel-data](https://spatie.be/docs/laravel-data/v4)  As-a-resource  Including and excluding properties

 Version   v4   v3   v2   v1

 Other versions for crawler [v4](https://spatie.be/docs/laravel-data/v4) [v3](https://spatie.be/docs/laravel-data/v3) [v2](https://spatie.be/docs/laravel-data/v2) [v1](https://spatie.be/docs/laravel-data/v1)

- [ Introduction ](https://spatie.be/docs/laravel-data/v4/introduction)
- [ Support us ](https://spatie.be/docs/laravel-data/v4/support-us)
- [ Requirements ](https://spatie.be/docs/laravel-data/v4/requirements)
- [ Installation &amp; setup ](https://spatie.be/docs/laravel-data/v4/installation-setup)
- [ Third party packages ](https://spatie.be/docs/laravel-data/v4/third-party-packages)
- [ Questions and issues ](https://spatie.be/docs/laravel-data/v4/questions-issues)
- [ Changelog ](https://spatie.be/docs/laravel-data/v4/changelog)
- [ About us ](https://spatie.be/docs/laravel-data/v4/about-us)

Getting started
---------------

- [ Quickstart ](https://spatie.be/docs/laravel-data/v4/getting-started/quickstart)

As a DTO
--------

- [ Creating a data object ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/creating-a-data-object)
- [ Nesting ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/nesting)
- [ Collections ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/collections)
- [ Abstract Data ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/abstract-data)
- [ Casts ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/casts)
- [ Optional properties ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/optional-properties)
- [ Mapping property names ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/mapping-property-names)
- [ Default values ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/defaults)
- [ Computed values ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/computed)
- [ From a request ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/request-to-data-object)
- [ From a model ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/model-to-data-object)
- [ Injecting property values ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/injecting-property-values)
- [ Factories ](https://spatie.be/docs/laravel-data/v4/as-a-data-transfer-object/factories)

Validation
----------

- [ Introduction ](https://spatie.be/docs/laravel-data/v4/validation/introduction)
- [ Auto rule inferring ](https://spatie.be/docs/laravel-data/v4/validation/auto-rule-inferring)
- [ Using validation attributes ](https://spatie.be/docs/laravel-data/v4/validation/using-validation-attributes)
- [ Manual rules ](https://spatie.be/docs/laravel-data/v4/validation/manual-rules)
- [ Working with the validator ](https://spatie.be/docs/laravel-data/v4/validation/working-with-the-validator)
- [ Nesting Data ](https://spatie.be/docs/laravel-data/v4/validation/nesting-data)
- [ Skipping validation ](https://spatie.be/docs/laravel-data/v4/validation/skipping-validation)

As a resource
-------------

- [ From data to array ](https://spatie.be/docs/laravel-data/v4/as-a-resource/from-data-to-array)
- [ From data to resource ](https://spatie.be/docs/laravel-data/v4/as-a-resource/from-data-to-resource)
- [ Mapping property names ](https://spatie.be/docs/laravel-data/v4/as-a-resource/mapping-property-names)
- [ Appending properties ](https://spatie.be/docs/laravel-data/v4/as-a-resource/appending-properties)
- [ Wrapping ](https://spatie.be/docs/laravel-data/v4/as-a-resource/wrapping)
- [ Including and excluding properties ](https://spatie.be/docs/laravel-data/v4/as-a-resource/lazy-properties)
- [ Transforming data ](https://spatie.be/docs/laravel-data/v4/as-a-resource/transformers)

Advanced usage
--------------

- [ Eloquent casting ](https://spatie.be/docs/laravel-data/v4/advanced-usage/eloquent-casting)
- [ Transforming to TypeScript ](https://spatie.be/docs/laravel-data/v4/advanced-usage/typescript)
- [ Working with dates ](https://spatie.be/docs/laravel-data/v4/advanced-usage/working-with-dates)
- [ Normalizers ](https://spatie.be/docs/laravel-data/v4/advanced-usage/normalizers)
- [ Pipeline ](https://spatie.be/docs/laravel-data/v4/advanced-usage/pipeline)
- [ Creating a cast ](https://spatie.be/docs/laravel-data/v4/advanced-usage/creating-a-cast)
- [ Creating a transformer ](https://spatie.be/docs/laravel-data/v4/advanced-usage/creating-a-transformer)
- [ Creating a rule inferrer ](https://spatie.be/docs/laravel-data/v4/advanced-usage/creating-a-rule-inferrer)
- [ Use with Inertia ](https://spatie.be/docs/laravel-data/v4/advanced-usage/use-with-inertia)
- [ Use with Livewire ](https://spatie.be/docs/laravel-data/v4/advanced-usage/use-with-livewire)
- [ Internal structures ](https://spatie.be/docs/laravel-data/v4/advanced-usage/internal-structures)
- [ Mapping rules ](https://spatie.be/docs/laravel-data/v4/advanced-usage/mapping-rules)
- [ Validation attributes ](https://spatie.be/docs/laravel-data/v4/advanced-usage/validation-attributes)
- [ Get data from a class quickly ](https://spatie.be/docs/laravel-data/v4/advanced-usage/get-data-from-a-class-quickly)
- [ Performance ](https://spatie.be/docs/laravel-data/v4/advanced-usage/performance)
- [ Commands ](https://spatie.be/docs/laravel-data/v4/advanced-usage/commands)
- [ Traits and interfaces ](https://spatie.be/docs/laravel-data/v4/advanced-usage/traits-and-interfaces)
- [ In Packages ](https://spatie.be/docs/laravel-data/v4/advanced-usage/in-packages)
- [ Available property mappers ](https://spatie.be/docs/laravel-data/v4/advanced-usage/available-property-mappers)

 Including and excluding properties
==================================

###  On this page

1. [ Including lazy properties ](#content-including-lazy-properties)
2. [ Types of Lazy properties ](#content-types-of-lazy-properties)
3. [ Default included lazy properties ](#content-default-included-lazy-properties)
4. [ Auto Lazy ](#content-auto-lazy)
5. [ Only and Except ](#content-only-and-except)
6. [ Using query strings ](#content-using-query-strings)
7. [ Mutability ](#content-mutability)

Sometimes you don't want all the properties included when transforming a data object to an array, for example:

```
class AlbumData extends Data
{
    /**
    * @param Collection $songs
    */
    public function __construct(
        public string $title,
        public Collection $songs,
    ) {
    }
}
```

This will always output a collection of songs, which can become quite large. With lazy properties, we can include properties when we want to:

```
class AlbumData extends Data
{
    /**
    * @param Lazy|Collection $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 in the resource when transforming it from a model. Because the closure that provides the data won't be called when transforming the data object unless we explicitly demand it.

Now when we transform the data object as such:

```
AlbumData::from(Album::first())->toArray();
```

We get the following array:

```
[
    'title' => 'Together Forever',
]
```

As you can see, the `songs` property is missing in the array output. Here's how you can include it.

```
AlbumData::from(Album::first())->include('songs');
```

Including lazy properties
-----------------------------------------------------------------------------------------------------------------------------------

Lazy properties will only be included when the `include` method is called on the data object with the property's name.

It is also possible to nest these includes. For example, let's update the `SongData` class and make all of its properties lazy:

```
class SongData extends Data
{
    public function __construct(
        public Lazy|string $title,
        public Lazy|string $artist,
    ) {
    }

    public static function fromModel(Song $song): self
    {
        return new self(
            Lazy::create(fn() => $song->title),
            Lazy::create(fn() => $song->artist)
        );
    }
}
```

Now `name` or `artist` should be explicitly included. This can be done as such on the `AlbumData`:

```
AlbumData::from(Album::first())->include('songs.name', 'songs.artist');
```

Or you could combine these includes:

```
AlbumData::from(Album::first())->include('songs.{name, artist}');
```

If you want to include all the properties of a data object, you can do the following:

```
AlbumData::from(Album::first())->include('songs.*');
```

Explicitly including properties of data objects also works on a single data object. For example, our `UserData` looks like this:

```
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))
        );
    }
}
```

We can include properties of the data object just like we would with collections of data objects:

```
return UserData::from(Auth::user())->include('favorite_song.name');
```

Types of Lazy properties
--------------------------------------------------------------------------------------------------------------------------------

### Conditional Lazy properties

You can include lazy properties in different ways:

```
Lazy::create(fn() => SongData::collect($album->songs));
```

With a basic `Lazy` property, you must explicitly include it when the data object is transformed.

Sometimes you only want to include a property when a specific condition is true. This can be done with conditional lazy properties:

```
Lazy::when(fn() => $this->is_admin, fn() => SongData::collect($album->songs));
```

The property will only be included when the `is_admin` property of the data object is true. It is not possible to include the property later on with the `include` method when a condition is not accepted.

### Relational Lazy properties

You can also only include a lazy property when a particular relation is loaded on the model as such:

```
Lazy::whenLoaded('songs', $album, fn() => SongData::collect($album->songs));
```

Now the property will only be included when the song's relation is loaded on the model.

Default included lazy properties
--------------------------------------------------------------------------------------------------------------------------------------------------------

It is possible to mark a lazy property as included by default:

```
Lazy::create(fn() => SongData::collect($album->songs))->defaultIncluded();
```

The property will now always be included when the data object is transformed. You can explicitly exclude properties that were default included as such:

```
AlbumData::create(Album::first())->exclude('songs');
```

Auto Lazy
-----------------------------------------------------------------------------------

Writing Lazy properties can be a bit cumbersome. It is often a repetitive task to write the same code over and over again while the package can infer almost everything.

Let's take a look at our previous example:

```
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))
        );
    }
}
```

The package knows how to get the property from the model and wrap it into a data object, but since we're using a lazy property, we need to write our own magic creation method with a lot of repetitive code.

In such a situation auto lazy might be a good fit, instead of casting the property directly into the data object, the casting process is wrapped in a lazy Closure.

This makes it possible to rewrite the example as such:

```
#[AutoLazy]
class UserData extends Data
{
    public function __construct(
        public string $title,
        public Lazy|SongData $favorite_song,
    ) {
    }
}
```

While achieving the same result!

Auto Lazy wraps the casting process of a value for every property typed as `Lazy` into a Lazy Closure when the `AutoLazy` attribute is present on the class.

It is also possible to use the `AutoLazy` attribute on a property level:

```
class UserData extends Data
{
    public function __construct(
        public string $title,
        #[AutoLazy]
        public Lazy|SongData $favorite_song,
    ) {
    }
}
```

The auto lazy process won't be applied in the following situations:

- When a null value is passed to the property
- When the property value isn't present in the input payload and the property typed as `Optional`
- When a Lazy Closure is passed to the property

### Auto lazy with model relations

When you're constructing a data object from an Eloquent model, it is also possible to automatically create lazy properties for model relations which are only resolved when the relation is loaded:

```
class UserData extends Data
{
    public function __construct(
        public string $title,
        #[AutoWhenLoadedLazy]
        public Lazy|SongData $favoriteSong,
    ) {
    }
}
```

When the `favoriteSong` relation is loaded on the model, the property will be included in the data object.

If the name of the relation doesn't match the property name, you can specify the relation name:

```
class UserData extends Data
{
    public function __construct(
        public string $title,
        #[AutoWhenLoadedLazy('favoriteSong')]
        public Lazy|SongData $favorite_song,
    ) {
    }
}
```

The package will use the regular casting process when the relation is loaded, so it is also perfectly possible to create a collection of data objects:

```
class UserData extends Data
{
    /**
    * @param Lazy|array $favoriteSongs
     */
    public function __construct(
        public string $title,
        #[AutoWhenLoadedLazy]
        public Lazy|array $favoriteSongs,
    ) {
    }
}
```

Only and Except
-----------------------------------------------------------------------------------------------------

Lazy properties are great for reducing payloads sent over the wire. However, when you completely want to remove a property Laravel's `only` and `except` methods can be used:

```
AlbumData::from(Album::first())->only('songs'); // will only show `songs`
AlbumData::from(Album::first())->except('songs'); // will show everything except `songs`
```

It is also possible to use multiple keys:

```
AlbumData::from(Album::first())->only('songs.name', 'songs.artist');
AlbumData::from(Album::first())->except('songs.name', 'songs.artist');
```

And special keys like described above:

```
AlbumData::from(Album::first())->only('songs.{name, artist}');
AlbumData::from(Album::first())->except('songs.{name, artist}');
```

Only and except always take precedence over include and exclude, which means that when a property is hidden by `only` or `except` it is impossible to show it again using `include`.

### Conditionally

It is possible to add an `include`, `exclude`, `only` or `except` if a certain condition is met:

```
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())->except('songs', auth()->user()->isAdmin);
```

You can also use the values of the data object in such condition:

```
AlbumData::from(Album::first())->includeWhen('songs', fn(AlbumData $data) => count($data->songs) > 0);
AlbumData::from(Album::first())->excludeWhen('songs', fn(AlbumData $data) => count($data->songs) > 0);
AlbumData::from(Album::first())->onlyWhen('songs', fn(AlbumData $data) => count($data->songs) > 0);
AlbumData::from(Album::first())->exceptWhen('songs', fn(AlbumData $data) => count($data->songs) > 0);
```

In some cases, you may want to define an include on a class level by implementing a method:

```
class AlbumData extends Data
{
    /**
    * @param Lazy|Collection $songs
    */
    public function __construct(
        public string $title,
        public Lazy|Collection $songs,
    ) {
    }

    public function includeProperties(): array
    {
        return [
            'songs' => $this->title === 'Together Forever',
        ];
    }
}
```

It is even possible to include nested properties:

```
class AlbumData extends Data
{
    /**
    * @param Lazy|Collection $songs
    */
    public function __construct(
        public string $title,
        public Lazy|Collection $songs,
    ) {
    }

    public function includeProperties(): array
    {
        return [
            'songs.title' => $this->title === 'Together Forever',
        ];
    }
}
```

You can define exclude, except and only partials on a data class:

- You can define **excludes** in a `excludeProperties` method
- You can define **except** in a `exceptProperties` method
- You can define **only** in a `onlyProperties` method

Using query strings
-----------------------------------------------------------------------------------------------------------------

It is possible to include or exclude lazy properties by the URL query string:

For example, when we create a route `my-account`:

```
// in web.php

Route::get('my-account', fn() => UserData::from(User::first()));
```

We now specify that a key of the data object is allowed to be included by query string on the data object:

```
class UserData extends Data
{
    public static function allowedRequestIncludes(): ?array
    {
        return ['favorite_song'];
    }

    // ...
}
```

Our JSON would look like this when we request `https://spatie.be/my-account`:

```
{
    "name" : "Ruben Van Assche"
}
```

We can include `favorite_song` by adding it to the query in the URL as such:

```
https://spatie.be/my-account?include=favorite_song
```

```
{
    "name" : "Ruben Van Assche",
    "favorite_song" : {
        "name" : "Never Gonna Give You Up",
        "artist" : "Rick Astley"
    }
}
```

We can also include multiple properties by separating them with a comma:

```
https://spatie.be/my-account?include=favorite_song,favorite_movie
```

Or by using a group input:

```
https://spatie.be/my-account?include[]=favorite_song&include[]=favorite_movie
```

Including properties works for data objects and data collections.

### Allowing includes by query string

By default, it is disallowed to include properties by query string:

```
class UserData extends Data
{
    public static function allowedRequestIncludes(): ?array
    {
        return [];
    }
}
```

You can pass several names of properties which are allowed to be included by query string:

```
class UserData extends Data
{
    public static function allowedRequestIncludes(): ?array
    {
        return ['favorite_song', 'name'];
    }
}
```

Or you can allow all properties to be included by query string:

```
class UserData extends Data
{
    public static function allowedRequestIncludes(): ?array
    {
        return null;
    }
}
```

### Other operations

It is also possible to run exclude, except and only operations on a data object:

- You can define **excludes** in `allowedRequestExcludes` and use the `exclude` key in your query string
- You can define **except** in `allowedRequestExcept` and use the `except` key in your query string
- You can define **only** in `allowedRequestOnly` and use the `only` key in your query string

Mutability
--------------------------------------------------------------------------------------

Adding includes/excludes/only/except to a data object will only affect the data object (and its nested chain) once:

```
AlbumData::from(Album::first())->include('songs')->toArray(); // will include songs
AlbumData::from(Album::first())->toArray(); // will not include songs
```

If you want to add includes/excludes/only/except to a data object and its nested chain that will be used for all future transformations, you can define them in their respective \*properties methods:

```
class AlbumData extends Data
{
    /**
    * @param Lazy|Collection $songs
    */
    public function __construct(
        public string $title,
        public Lazy|Collection $songs,
    ) {
    }

    public function includeProperties(): array
    {
        return [
            'songs'
        ];
    }
}
```

Or use the 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');
```

When using a conditional includes/excludes/only/except, you can set the permanent flag:

```
AlbumData::from(Album::first())->includeWhen('songs', fn(AlbumData $data) => count($data->songs) > 0, permanent: true);
AlbumData::from(Album::first())->excludeWhen('songs', fn(AlbumData $data) => count($data->songs) > 0, permanent: true);
AlbumData::from(Album::first())->onlyWhen('songs', fn(AlbumData $data) => count($data->songs) > 0), permanent: true);
AlbumData::from(Album::first())->except('songs', fn(AlbumData $data) => count($data->songs) > 0, permanent: true);
```
