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(strings, ints, 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;
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 array 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.