Lazy Properties
Learn how to defer expensive operations until they’re actually needed using lazy properties.
What are Lazy Properties?
Section titled “What are 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 NOWBasic Usage
Section titled “Basic Usage”Using #[Lazy] Attribute
Section titled “Using #[Lazy] Attribute”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(),]);With Closures
Section titled “With Closures”Lazy Loading from Database
Section titled “Lazy Loading from Database”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 yetecho $dto->name; // No database query
// Posts are loaded NOW$posts = $dto->posts; // Database query executedLazy Expensive Calculations
Section titled “Lazy Expensive Calculations”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'), ],]);Combining with Other Features
Section titled “Combining with Other Features”Lazy + Conditional
Section titled “Lazy + Conditional”class UserDTO extends SimpleDTO{ public function __construct( public readonly string $name,
#[Lazy, WhenAuth] public readonly ?array $privateData = null, ) {}}Lazy + Computed
Section titled “Lazy + Computed”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 ?? []); }}Best Practices
Section titled “Best Practices”Use Lazy for Expensive Operations
Section titled “Use Lazy for Expensive Operations”// ✅ Good - lazy for expensive operations#[Lazy]public readonly ?array $statistics = null;
// ❌ Bad - eager loading expensive datapublic readonly array $statistics;Use Closures for Lazy Values
Section titled “Use Closures for Lazy Values”// ✅ Good - closure for lazy evaluationposts: fn() => $user->posts()->get()
// ❌ Bad - eager evaluationposts: $user->posts()->get()Document Lazy Properties
Section titled “Document Lazy Properties”/** * @property-read array|null $posts Lazy-loaded user posts * @property-read array|null $followers Lazy-loaded followers */class UserDTO extends SimpleDTO{ // ...}Code Examples
Section titled “Code Examples”The following working examples demonstrate this feature:
- Basic Lazy - Simple lazy properties
- Lazy Union Types - Lazy with union types
- Optional Lazy Combinations - Combining optional and lazy
All examples are fully tested and can be run directly.
Related Tests
Section titled “Related Tests”The functionality is thoroughly tested. Key test files:
- LazyPropertiesTest.php - Lazy property tests
Run the tests:
# Run teststask test:unit -- --filter=LazySee Also
Section titled “See Also”- Computed Properties - Calculate values on-the-fly
- Conditional Properties - Dynamic visibility
- Collections - Work with collections