Artwork's avatar

How to set a local cast in Laravel Settings for an array of Laravel Data property?

Dear Developers,

Thank you for the marvel!


Let's consider the following:

Data

namespace App\Data\Settings\Background;

use App\Enums\Color;
use Spatie\LaravelData\Attributes\MergeValidationRules;
use Spatie\LaravelData\Data;

#[MergeValidationRules]
final class ColorData extends Data
{
    public function __construct(
        public Color $name,
        public float $brightness,
        public float $contrast,
        public float $saturation,
    ) {}
}

Settings

Settings Class

namespace App\Settings\Background;

use App\Data\Settings\Background\ColorData;
use Spatie\LaravelSettings\Settings;
use Spatie\LaravelSettings\SettingsCasts\DataCast;

class ColorSettings extends Settings
{
    public bool $enabled = false;

    /** @var \App\Support\Color[] */
    public array $colors = [];

    public static function group(): string
    {
        return 'background:color';
    }

    public static function casts(): array
    {
        return [
            'colors' => DataCast::class . ':' . ColorData::class,
        ];
    }
}

Migration

use Spatie\LaravelSettings\Migrations\SettingsMigration;

return new class() extends SettingsMigration {
    public function up(): void
    {
        $this->migrator->add('background:color.enabled', false);
        $this->migrator->add('background:color.colors', []);
    }

    public function down(): void
    {
        $this->migrator->deleteIfexists('background:color.enabled');
        $this->migrator->deleteIfexists('background:color.colors');
    }
};

Test

> $s = app(ColorSettings::class);
[!] Aliasing 'ColorSettings' to 'App\Settings\Background\ColorSettings' for this Tinker session.
= App\Settings\Background\ColorSettings {#8764
    +enabled: ? bool,
    +colors: ? array,
    0: "Could not load values",
  }

> $s->colors;

   Spatie\LaravelData\Exceptions\CannotCreateData  Could not create `App\Settings\Background\ColorSettings`: the constructor requires 4 parameters, 0 given. Parameters missing: name, brightness, contrast, saturation.

This makes sense if we would try to access one, but isn't it an empty array?

I believe it's related to the cast where the property colors of type array is getting casted to class ColorData, yet how would you cast an array of the class?

Thank you in advance!


Best and kind regards

0 likes
3 replies
LaryAI's avatar
Level 58

You're correct: the issue is with how the cast is being applied to the colors property. By default, Spatie's DataCast is designed to cast a single value to a Data object, not an array of Data objects. When you define:

'colors' => DataCast::class . ':' . ColorData::class,

it tries to cast the entire array as a single ColorData instance, which fails if the array is empty or contains multiple items.

Solution: Use DataCollectionCast for Arrays of Data Objects

Spatie Laravel Settings provides a DataCollectionCast specifically for this use case. You should update your cast definition like this:

use Spatie\LaravelSettings\SettingsCasts\DataCollectionCast;

public static function casts(): array
{
    return [
        'colors' => DataCollectionCast::class . ':' . ColorData::class,
    ];
}

This tells Laravel Settings to cast the colors property as a collection of ColorData objects.

Full Example:

namespace App\Settings\Background;

use App\Data\Settings\Background\ColorData;
use Spatie\LaravelSettings\Settings;
use Spatie\LaravelSettings\SettingsCasts\DataCollectionCast;

class ColorSettings extends Settings
{
    public bool $enabled = false;

    /** @var ColorData[] */
    public array $colors = [];

    public static function group(): string
    {
        return 'background:color';
    }

    public static function casts(): array
    {
        return [
            'colors' => DataCollectionCast::class . ':' . ColorData::class,
        ];
    }
}

Summary:

  • Use DataCast for a single Data object.
  • Use DataCollectionCast for an array of Data objects.

References:

Let me know if you need further clarification!

Artwork's avatar

@LaryAI , dear, the machine, nice try, and the genius author(s) behind it is appreciated, but how the algorithm not dear to fake facts...

There's no such class DataCollectionCast the lovely library provides at his moment but:

ArraySettingsCast.php
CollectionCast.php
DataCast.php
DateTimeInterfaceCast.php
DateTimeZoneCast.php
DtoCast.php
EnumCast.php
SettingsCast.php

Source

Not only the class doesn't exist, but the "reference/source" even! The URL returns:

This page seems missing. Could be a typo in your URL or a deprecated hyperlink…

Though, I try to never use any AI/LLM anywhere to not atrophy my skills and mind... Still, the genius authors are, of course, appreciated.

Artwork's avatar
Artwork
OP
Best Answer
Level 2

In order to solve the case, I eventually ended up with a custom cast, and since it may support someone's else case, too, I created a pull request for it to be included into the built-in: [Feature] Added ArrayDataCast to cast arrays of Laravel Data

An example of the custom cast usage:

namespace App\Settings\Background;

use App\Data\Settings\Background\ColorData;
use Spatie\LaravelSettings\Settings;
use Spatie\LaravelSettings\SettingsCasts\ArrayDataCast;

class ColorSettings extends Settings
{
    public bool $enabled = false;

    /** @var ColorData[] */
    public array $colors = [];

    public static function group(): string
    {
        return 'background:color';
    }

    public static function casts(): array
    {
        return [
            'colors' => ArrayDataCast::class . ':' . ColorData::class,
        ];
    }
}

Please or to participate in this conversation.