@@ -29,16 +29,20 @@ trait LazyGhostTrait
29
29
* properties and closures should accept 4 arguments: the instance to
30
30
* initialize, the property to initialize, its write-scope, and its default
31
31
* value. Each closure should return the value of the corresponding property.
32
+ * The special "\0" key can be used to define a closure that returns all
33
+ * properties at once when full-initialization is needed.
32
34
*
33
35
* Properties should be indexed by their array-cast name, see
34
36
* https://php.net/manual/language.types.array#language.types.array.casting
35
37
*
36
- * @param \Closure(static):void|array<string, \Closure(static, string, ?string, mixed):mixed> $initializer
37
- * @param array<string, true> $skippedProperties An array indexed by the properties to skip, aka the ones
38
- * that the initializer doesn't set when its a closure
38
+ * @param \Closure(static):void|array<string, \Closure(static, string, ?string, mixed):mixed>|array{"\0": \Closure(static):array} $initializer
39
+ * @param array<string, true>|null $skippedProperties An array indexed by the properties to skip, aka the ones
40
+ * that the initializer doesn't set when its a closure
39
41
*/
40
- public static function createLazyGhost (\Closure |array $ initializer , array $ skippedProperties = [] , self $ instance = null ): static
42
+ public static function createLazyGhost (\Closure |array $ initializer , array $ skippedProperties = null , self $ instance = null ): static
41
43
{
44
+ $ onlyProperties = null === $ skippedProperties && \is_array ($ initializer ) ? $ initializer : null ;
45
+
42
46
if (self ::class !== $ class = $ instance ? $ instance ::class : static ::class) {
43
47
$ skippedProperties ["\0" .self ::class."\0lazyObjectId " ] = true ;
44
48
} elseif (\defined ($ class .'::LAZY_OBJECT_PROPERTY_SCOPES ' )) {
@@ -48,8 +52,7 @@ public static function createLazyGhost(\Closure|array $initializer, array $skipp
48
52
$ instance ??= (Registry::$ classReflectors [$ class ] ??= new \ReflectionClass ($ class ))->newInstanceWithoutConstructor ();
49
53
Registry::$ defaultProperties [$ class ] ??= (array ) $ instance ;
50
54
$ instance ->lazyObjectId = $ id = spl_object_id ($ instance );
51
- Registry::$ states [$ id ] = new LazyObjectState ($ initializer , $ skippedProperties );
52
- $ onlyProperties = \is_array ($ initializer ) ? $ initializer : null ;
55
+ Registry::$ states [$ id ] = new LazyObjectState ($ initializer , $ skippedProperties ??= []);
53
56
54
57
foreach (Registry::$ classResetters [$ class ] ??= Registry::getClassResetters ($ class ) as $ reset ) {
55
58
$ reset ($ instance , $ skippedProperties , $ onlyProperties );
@@ -61,7 +64,7 @@ public static function createLazyGhost(\Closure|array $initializer, array $skipp
61
64
/**
62
65
* Returns whether the object is initialized.
63
66
*/
64
- public function isLazyObjectInitialized (): bool
67
+ public function isLazyObjectInitialized (bool | string $ partial = false ): bool
65
68
{
66
69
if (!$ state = Registry::$ states [$ this ->lazyObjectId ?? '' ] ?? null ) {
67
70
return true ;
@@ -73,6 +76,21 @@ public function isLazyObjectInitialized(): bool
73
76
74
77
$ class = $ this ::class;
75
78
$ properties = (array ) $ this ;
79
+
80
+ if (\is_string ($ partial )) {
81
+ return \array_key_exists ($ partial , $ properties );
82
+ }
83
+
84
+ if ($ partial ) {
85
+ foreach ($ state ->initializer as $ key => $ initializer ) {
86
+ if (\array_key_exists ($ key , $ properties )) {
87
+ return true ;
88
+ }
89
+ }
90
+
91
+ return false ;
92
+ }
93
+
76
94
$ propertyScopes = Hydrator::$ propertyScopes [$ class ] ??= Hydrator::getPropertyScopes ($ class );
77
95
foreach ($ state ->initializer as $ key => $ initializer ) {
78
96
if (!\array_key_exists ($ key , $ properties ) && isset ($ propertyScopes [$ key ])) {
@@ -100,16 +118,34 @@ public function initializeLazyObject(): static
100
118
return $ this ;
101
119
}
102
120
121
+ $ values = isset ($ state ->initializer ["\0" ]) ? null : [];
122
+
103
123
$ class = $ this ::class;
104
124
$ properties = (array ) $ this ;
105
125
$ propertyScopes = Hydrator::$ propertyScopes [$ class ] ??= Hydrator::getPropertyScopes ($ class );
106
126
foreach ($ state ->initializer as $ key => $ initializer ) {
107
127
if (\array_key_exists ($ key , $ properties ) || ![$ scope , $ name , $ readonlyScope ] = $ propertyScopes [$ key ] ?? null ) {
108
128
continue ;
109
129
}
130
+ $ scope = $ readonlyScope ?? ('* ' !== $ scope ? $ scope : $ class );
110
131
111
- $ state ->initialize ($ this , $ name , $ readonlyScope ?? ('* ' !== $ scope ? $ scope : null ));
112
- $ properties = (array ) $ this ;
132
+ if (null === $ values ) {
133
+ if (!\is_array ($ values = ($ state ->initializer ["\0" ])($ this ))) {
134
+ throw new \TypeError (sprintf ('The lazy-initializer defined for instance of "%s" must return an array, got "%s". ' , $ class , get_debug_type ($ values )));
135
+ }
136
+
137
+ if (\array_key_exists ($ key , $ properties = (array ) $ this )) {
138
+ continue ;
139
+ }
140
+ }
141
+
142
+ if (\array_key_exists ($ key , $ values )) {
143
+ $ accessor = Registry::$ classAccessors [$ scope ] ??= Registry::getClassAccessors ($ scope );
144
+ $ accessor ['set ' ]($ this , $ name , $ properties [$ key ] = $ values [$ key ]);
145
+ } else {
146
+ $ state ->initialize ($ this , $ name , $ scope );
147
+ $ properties = (array ) $ this ;
148
+ }
113
149
}
114
150
115
151
return $ this ;
0 commit comments