Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Tiskiel's avatar

Relation problem with DTO

Hi everyone,

I'm new to laravel two months ago and I work to project. I use DTO with Spatie laravel data and Spatie laravel permissions.

I don't understand one case. All my osers DTO work but this one no. I maked dd($role->permissions) inside fromModel function and I have permissions.

For me the problem come to Lazy::create but I don't understand why because my others DTO with same use case works.

Here my code :

<?php

namespace App\Data;

use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
use Spatie\LaravelData\Lazy;
use Spatie\LaravelData\Optional;
use Spatie\Permission\Models\Role;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript]
class RoleData extends Data
{
    public function __construct(
      public int $id,
      public string $name,
      /** @var DataCollection<PermissionData> */
      public Lazy|DataCollection|Optional $permissions,
      public string $created_at,
      public string $updated_at,
    ) {
    }

    public static function fromModel(Role $role): self
    {
        return new self(
            id: $role->id,
            name: $role->name,
            permissions: Lazy::create(fn () => PermissionData::collection($role->permissions)),
            created_at: $role->created_at->toDateTimeString(),
            updated_at: $role->updated_at->toDateTimeString(),
        );
    }
}

Here one of my others DTO who work :

<?php

namespace App\Data;

use App\Models\User;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
use Spatie\LaravelData\Lazy;
use Spatie\LaravelData\Optional;

/** @typescript  */
class UserData extends Data
{
    public function __construct(
        public int $id,
        public string $name,
        public string $username,
        public string $email,
        /** @var DataCollection<TeamData> $teams */
        public Lazy|DataCollection|Optional $teams,
        /** @var DataCollection<RoleData> $roles */
        public Lazy|DataCollection|Optional $roles,
        public string $created_at,
        public string $updated_at,
    ) {
    }

    public function fromModel(User $user): self
    {
        return new self(
            id: $user->id,
            name: $user->name,
            username: $user->username,
            email: $user->email,
            teams: Lazy::create(fn () => TeamData::collection($user->teams)),
            roles: Lazy::create(fn () => RoleData::collection($user->roles)),
            created_at: $user->created_at,
            updated_at: $user->updated_at,
        );
    }
}

Thank you in advanced for your help

0 likes
6 replies
Tiskiel's avatar

I've checked with fix proposition to Larry but not work.

Here the result of my dd($role->toArray()) before fromModel function return :

array:6 [▼ // app/Data/RoleData.php:27
  "id" => 2
  "name" => "admin"
  "guard_name" => "web"
  "created_at" => "2023-05-09T06:56:25.000000Z"
  "updated_at" => "2023-05-09T06:56:25.000000Z"
  "permissions" => array:3 [▼
    0 => array:6 [▼
      "id" => 1
      "name" => "create team"
      "guard_name" => "web"
      "created_at" => "2023-05-09T06:56:25.000000Z"
      "updated_at" => "2023-05-09T06:56:25.000000Z"
      "pivot" => array:2 [▶]
    ]
    1 => array:6 [▼
      "id" => 2
      "name" => "view team"
      "guard_name" => "web"
      "created_at" => "2023-05-09T06:56:25.000000Z"
      "updated_at" => "2023-05-09T06:56:25.000000Z"
      "pivot" => array:2 [▶]
    ]
    2 => array:6 [▼
      "id" => 3
      "name" => "update team"
      "guard_name" => "web"
      "created_at" => "2023-05-09T06:56:25.000000Z"
      "updated_at" => "2023-05-09T06:56:25.000000Z"
      "pivot" => array:2 [▶]
    ]
  ]
]

But after in my front I have undefined ( I forgot to tell I use Inertia React TS )

Tiskiel's avatar

@dacfabre Let me show you the way from my RoleController :

public function show(Role $role): InertiaResponse
    {
        $role->load('permissions');

        return Inertia::render('Roles/Show', new RolesShowViewModel(
            role: RoleData::from($role),
        ));
    }

Here my ViewModel used like super DTO :

<?php

namespace App\ViewModels\Roles;

use App\Data\RoleData;
use Spatie\LaravelData\Data;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript]
class RolesShowViewModel extends Data
{
    public function __construct(
        public RoleData $role
    ) {
    }
}

Here my RoleData :

<?php

namespace App\Data;

use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
use Spatie\LaravelData\Lazy;
use Spatie\LaravelData\Optional;
use Spatie\Permission\Models\Role;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript]
class RoleData extends Data
{
    public function __construct(
      public int $id,
      public string $name,
      /** @var DataCollection<PermissionData> */
      public Lazy|DataCollection|Optional $permissions,
      public string $created_at,
      public string $updated_at,
    ) {
    }

    public static function fromModel(Role $role): self
    {
        dd($role->toArray());

        return new self(
            id: $role->id,
            name: $role->name,
            permissions: Lazy::create(fn () => PermissionData::collection($role->permissions)),
            created_at: $role->created_at->toDateTimeString(),
            updated_at: $role->updated_at->toDateTimeString(),
        );
    }
}

And here my Show.tsx page :

import { RolesShowViewModel } from "@/types/generated";

export default function Show({ role }: RolesShowViewModel): JSX.Element {
    console.log(role.permissions);

    return (
        <div>
            <h1>Role {role.name}</h1>
            <h2>Permissions</h2>
            <ul>
                {role?.permissions?.map(permission => (
                    <li key={permission.id}>{permission.name}</li>
                ))}
            </ul>
        </div>
    );
}
Tiskiel's avatar

It's resolved.

It's because inside fromModel function I need to use :

Lazy::whenLoaded('permissions', $role, fn () => PermissionData::collection($role->permissions))

Please or to participate in this conversation.