#spatie/laravel-data Lazy|Collection relationship throws missing constructor parameter error

1 messages · Page 1 of 1 (latest)

glacial crystal
#

I'm using spatie/laravel-data module, but when I added a Lazy|Collection to my data class, trying to create from a model fails saying that the parameter is missing if the model doesn't pull in the relationship, but I don't see any difference between what I'm doing and what is described in the documentation. I assumed that Lazy would basically work like Optional in the scenario where I don't explicitly pull in the relationship.

Simplified for brevity, code looks something like this:

class FooData extends Data {
  public function __construct(
    public Optional|int $id,
    public Optional|string $name,
    public Lazy|Collection $bars,
    // ...
  ) {}

  public static function fromModel(Foo $foo): self
  {
    return new self(
      id: $foo->id,
      foo: $foo->name,
      bars: Lazy::create(fn() => BarData::collect($foo->bars)),
      // ...
    );
  }
}

class BarData extends Data {
  public Optional|int $id,
  public Optional|string $name,
}

class Foo extends Model {
  // ...
  public function bars() {
    return $this->hasMany(Bar::class);
  }
}

// All below fail with "the constructor requires n parameters, n-1 given."
FooData::from(Foo::first());
FooData::from(Foo::first())->include('bars');
FooData::from(Foo::with('bars')->first());

If I make the parameter Optional|Lazy|Collection $bars then it doesn't throw an error - but interestingly also DOES show a value for $foo->bars .

If I remove the Lazy part altogether (including the fromModel closure) - the value is also shown when loaded using Foo::with('bars') meaning it is fetched and passed to the underlying data object in some form (despite PHP's earlier protestations to the contrary).

What am I doing wrong? As far as I can tell I'm following the documentation as written here: https://spatie.be/docs/laravel-data/v4/as-a-resource/lazy-properties

ancient hazel
#

I can't spot any problems. Is it FooData or BarData that fails? And if those are just simplified examples, try showing a real world example.

glacial crystal
#

In this case it's FooData that fails on the constructor method with the exception that the $bars parameter is missing.

This is a simplified sample, but there's really not that much else to it except name changes.

#
/* Foo Equivalent */
class ProductData extends Data
{
    public function __construct(
        #[Ulid()]
        public Optional|string $id,
        public Optional|string $name,
        public Optional|string|null $slug,
        public Optional|string $sku,
        public Lazy|Collection $identifiers, /* Bar equivalent */
        #[Ulid()]
        public Optional|string|null $parent_id,
        public Optional|array $metafields,
        public ?CarbonImmutable $createdAt,
        public ?CarbonImmutable $updatedAt,
        #[Hidden]
        public ?CarbonImmutable $deletedAt,
    ) {
    }

    public static function fromModel(Product $product): self
    {
        return new self(
            id: $product->id,
            name: $product->name,
            slug: $product->slug,
            sku: $product->sku,
            identifiers: Lazy::create(fn() => ProductIdentifierData::collect($product->identifiers)),
            parent_id: $product->parent_id,
            metafields: $product->metafields,
            createdAt: $product->created_at ? CarbonImmutable::parse($product->created_at) : null,
            updatedAt: $product->updated_at ? CarbonImmutable::parse($product->updated_at) : null,
            deletedAt: $product->deleted_at ? CarbonImmutable::parse($product->deleted_at) : null,
        );
    }
}
/* Bar Equivalent */
class ProductIdentifierData extends Data
{
    public function __construct(
        #[Ulid()]
        public Optional|string $id,
        public Optional|string $identifier,
        public Optional|string|null $description,
        public CarbonImmutable|null $createdAt,
        public CarbonImmutable|null $updatedAt,
    ) {
    }
}
#
class Product extends Model {
    /* $fillable, etc... */
    public function identifiers()
    {
        return $this->hasMany(ProductIdentifier::class);
    }
}
/* Failing Call */
dump(ProductData::from(Product::first())->include('identifiers')->toArray());

Could not createApp\Data\ProductData: the constructor requires 10 parameters, 9 given. Parameters given: id, name, slug, sku, parent_id, metafields, createdAt, updatedAt, deletedAt. Parameters missing: identifiers.