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