Skip to content

Lazy Properties

Learn how to defer expensive operations until they’re actually needed using lazy properties.

Lazy properties are properties that are only evaluated when accessed, not when the DTO is created:

class UserDTO extends SimpleDTO
{
public function __construct(
public readonly string $name,
#[Lazy]
public readonly ?array $posts = null, // Only loaded when accessed
) {}
}
$dto = UserDTO::fromModel($user);
// Posts are NOT loaded yet
$posts = $dto->posts;
// Posts are loaded NOW
use Event4u\DataHelpers\SimpleDTO\Attributes\Lazy;
class UserDTO extends SimpleDTO
{
public function __construct(
public readonly string $name,
public readonly string $email,
#[Lazy]
public readonly ?array $posts = null,
#[Lazy]
public readonly ?array $comments = null,
) {}
}
$dto = UserDTO::fromArray([
'name' => 'John Doe',
'email' => 'john@example.com',
'posts' => fn() => Post::where('user_id', 1)->get(),
'comments' => fn() => Comment::where('user_id', 1)->get(),
]);
class UserDTO extends SimpleDTO
{
public function __construct(
public readonly int $userId,
public readonly string $name,
#[Lazy]
public readonly ?array $posts = null,
) {}
}
$dto = UserDTO::fromArray([
'userId' => 1,
'name' => 'John Doe',
'posts' => fn() => Post::where('user_id', 1)->get()->toArray(),
]);
// Posts are NOT loaded yet
echo $dto->name; // No database query
// Posts are loaded NOW
$posts = $dto->posts; // Database query executed
class ReportDTO extends SimpleDTO
{
public function __construct(
public readonly string $title,
#[Lazy]
public readonly ?array $statistics = null,
) {}
}
$dto = ReportDTO::fromArray([
'title' => 'Monthly Report',
'statistics' => fn() => [
'total' => Order::sum('total'),
'count' => Order::count(),
'average' => Order::avg('total'),
],
]);
class UserDTO extends SimpleDTO
{
public function __construct(
public readonly string $name,
#[Lazy, WhenAuth]
public readonly ?array $privateData = null,
) {}
}
class UserDTO extends SimpleDTO
{
public function __construct(
public readonly int $userId,
#[Lazy]
public readonly ?array $posts = null,
) {}
#[Computed, Lazy]
public function postCount(): int
{
return count($this->posts ?? []);
}
}
// ✅ Good - lazy for expensive operations
#[Lazy]
public readonly ?array $statistics = null;
// ❌ Bad - eager loading expensive data
public readonly array $statistics;
// ✅ Good - closure for lazy evaluation
posts: fn() => $user->posts()->get()
// ❌ Bad - eager evaluation
posts: $user->posts()->get()
/**
* @property-read array|null $posts Lazy-loaded user posts
* @property-read array|null $followers Lazy-loaded followers
*/
class UserDTO extends SimpleDTO
{
// ...
}

The following working examples demonstrate this feature:

All examples are fully tested and can be run directly.

The functionality is thoroughly tested. Key test files:

Run the tests:

Terminal window
# Run tests
task test:unit -- --filter=Lazy