Skip to content

Latest commit

 

History

History
164 lines (129 loc) · 4.44 KB

File metadata and controls

164 lines (129 loc) · 4.44 KB
title weight
Casts
4

We extend our example data object just a little bit:

class SongData extends Data
{
    public function __construct(
        public string $title,
        public string $artist,
        public DateTime $date,
        public Format $format,
    ) {
    }
}

The Format property here is an Enum and looks like this:

enum Format: string {
    case cd = 'cd';
    case vinyl = 'vinyl';
    case cassette = 'cassette';
}

When we now try to construct a data object like this:

SongData::from([
    'title' => 'Never gonna give you up',
    'artist' => 'Rick Astley',
    'date' => '27-07-1987',
    'format' => 'vinyl',
]);

And get an error because the first two properties are simple PHP types(string, int's, floats, booleans, arrays), but the following two properties are more complex types: DateTime and Enum, respectively.

These types cannot be automatically created. A cast is needed to construct them from a string.

There are two types of casts, local and global casts.

Local casts

Local casts are defined within the data object itself and can be added using attributes:

class SongData extends Data
{
    public function __construct(
        public string $title,
        public string $artist,
        #[WithCast(DateTimeInterfaceCast::class)]
        public DateTime $date,
        #[WithCast(EnumCast::class)]
        public Format $format,
    ) {
    }
}

Now it is possible to create a data object like this without exceptions:

SongData::from([
    'title' => 'Never gonna give you up',
    'artist' => 'Rick Astley',
    'date' => '27-07-1987',
    'format' => 'vinyl',
]);

It is possible to provide parameters to the casts like this:

#[WithCast(EnumCast::class, type: Format::class)]
public Format $format

Global casts

Global casts are not defined on the data object but in your data.php config file:

'casts' => [
    DateTimeInterface::class => Spatie\LaravelData\Casts\DateTimeInterfaceCast::class,
],

When the data object can find no local cast for the property, the package will look through the global casts and tries to find a suitable cast. You can define casts for:

  • a specific implementation (e.g. CarbonImmutable)
  • an interface (e.g. DateTimeInterface)
  • a base class (e.g. Enum)

As you can see, the package by default already provides a DateTimeInterface cast, this means we can update our data object like this:

class SongData extends Data
{
    public function __construct(
        public string $title,
        public string $artist,
        public DateTime $date,
        #[WithCast(EnumCast::class)]
        public Format $format,
    ) {
    }
}

Tip: we can also remove the EnumCast since the package will automatically cast enums because they're a native PHP type, but this made the example easy to understand.

Creating your own casts

It is possible to create your casts. You can read more about this in the advanced chapter.

Casting arrays or collections of non-data types

We've already seen how collections of data can be made of data objects, the same is true for all other types if correctly typed.

Let say we have an array of DateTime objects:

class ReleaseData extends Data
{
    public string $title;
    /** @var array<int, DateTime> */
    public array $releaseDates;
}

By enabling the cast_and_transform_iterables feature in the data config file (this feature will be enabled by default in laravel-data v5):

'features' => [
    'cast_and_transform_iterables' => true,
],

We now can create a ReleaseData object with an array of strings which will be cast into an array DateTime objects:

ReleaseData::from([
    'title' => 'Never Gonna Give You Up',
    'releaseDates' => [
        '1987-07-27T12:00:00Z',
        '1987-07-28T12:00:00Z',
        '1987-07-29T12:00:00Z',
    ],
]);

For this feature to work, a cast should not only implement the Cast interface but also the IterableItemCast. The signatures of the cast and castIterableItem methods are exactly the same, but they're called on different times. When casting a property like a DateTime from a string, the cast method will be used, when transforming an iterable property like an arrat or Laravel Collection where the iterable item is typed using an annotation, then each item of the provided iterable will trigger a call to the castIterableItem method.