> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/spatie/laravel-data/llms.txt
> Use this file to discover all available pages before exploring further.

# Abstract Data

> Work with abstract data classes and polymorphic data objects using the morph method.

It is possible to create an abstract data class with subclasses extending it:

```php theme={null}
abstract class Person extends Data
{
    public string $name;
}

class Singer extends Person
{
   public function __construct(
        public string $voice,
   ) {}
}

class Musician extends Person
{
   public function __construct(
        public string $instrument,
   ) {}
}
```

It is perfectly possible now to create individual instances as follows:

```php theme={null}
Singer::from(['name' => 'Rick Astley', 'voice' => 'tenor']);
Musician::from(['name' => 'Rick Astley', 'instrument' => 'guitar']);
```

## The Polymorphism Problem

But what if you want to use this abstract type in another data class like this:

```php theme={null}
class Contract extends Data
{
    public string $label;
    public Person $artist;
}
```

While the following may both be valid:

```php theme={null}
Contract::from(['label' => 'PIAS', 'artist' => ['name' => 'Rick Astley', 'voice' => 'tenor']]);
Contract::from(['label' => 'PIAS', 'artist' => ['name' => 'Rick Astley', 'instrument' => 'guitar']]);
```

The package can't decide which subclass to construct for the property.

## Using PropertyMorphableData

You can implement the `PropertyMorphableData` interface on the abstract class to solve this. This interface adds a `morph` method that will be used to determine which subclass to use. The `morph` method receives an array of properties limited to properties tagged by a `PropertyForMorph` attribute:

```php theme={null}
use Spatie\LaravelData\Attributes\PropertyForMorph;
use Spatie\LaravelData\Contracts\PropertyMorphableData;

abstract class Person extends Data implements PropertyMorphableData
{
    #[PropertyForMorph]
    public string $type;

    public string $name;
    
    public static function morph(array $properties): ?string
    {
        return match ($properties['type']) {
            'singer' => Singer::class,
            'musician' => Musician::class,
            default => null
        };
    }
}
```

The example above will work by adding this code, and the correct Data class will be constructed.

## Limitations

Since the morph functionality needs to run early within the data construction process, it bypasses the normal flow of constructing data objects so there are a few limitations:

<Steps>
  <Step title="Type restrictions">
    It is only allowed to use properties typed as string, int, or BackedEnum (int or string)
  </Step>

  <Step title="Enum handling">
    When a property is typed as an enum, the value passed to the morph method will be an enum
  </Step>

  <Step title="Pre-validation execution">
    It can be that the value of a property within the morph method is null or a different type than expected since it runs before validation
  </Step>

  <Step title="Mapped properties supported">
    Properties with mapped property names are still supported
  </Step>
</Steps>

## Abstract Data Classes as Collections

It is also possible to use abstract data classes as collections as such:

```php theme={null}
class Band extends Data
{
    public string $name;
    
    /**  @var array<Person> */
    public array $members;
}
```
